Open26

OpenAI API で開発してての学び

seyaseya

トークン量について

seyaseya

私たちのモデルは、テキストをトークンに分解して理解し処理します。トークンは、単語や文字の塊になります。例えば、「hamburger」という単語は、「ham」、「bur」、「ger」というトークンに分解されますが、「pear」という短くて一般的な単語は1つのトークンです。多くのトークンは、例えば「こんにちは」と「さようなら」のように空白から始まります。

与えられたAPIリクエストで処理されるトークンの数は、入力と出力の長さによって異なります。大まかな目安として、英語のテキストでは1トークンは約4文字または0.75語です。考慮すべき制限の1つとして、テキストのプロンプトと生成された補完がモデルの最大コンテキスト長を超えないようにしなければなりません(ほとんどのモデルでは2048トークン、または約1500語です)。

https://platform.openai.com/docs/introduction/key-concepts

GPT-3.5 だと大体 4096 トークンです。

LATEST MODEL DESCRIPTION MAX TOKENS TRAINING DATA
gpt-3.5-turbo Most capable GPT-3.5 model and optimized for chat at 1/10th the cost of text-davinci-003. Will be updated with our latest model iteration. 4,096 tokens Up to Sep 2021
gpt-3.5-turbo-0301 Snapshot of gpt-3.5-turbo from March 1st 2023. Unlike gpt-3.5-turbo, this model will not receive updates, and will only be supported for a three month period ending on June 1st 2023. 4,096 tokens Up to Sep 2021
text-davinci-003 Can do any language task with better quality, longer output, and consistent instruction-following than the curie, babbage, or ada models. Also supports inserting completions within text. 4,097 tokens Up to Jun 2021
text-davinci-002 Similar capabilities to text-davinci-003 but trained with supervised fine-tuning instead of reinforcement learning 4,097 tokens Up to Jun 2021
code-davinci-002 Optimized for code-completion tasks 8,001 tokens Up to Jun 2021

https://platform.openai.com/docs/models/gpt-3-5

GPT-4 だと 8192 トークンに伸びてます。嬉しい!

LATEST MODEL DESCRIPTION MAX TOKENS TRAINING DATA
gpt-4 More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. Will be updated with our latest model iteration. 8,192 tokens Up to Sep 2021
gpt-4-0314 Snapshot of gpt-4 from March 14th 2023. Unlike gpt-4, this model will not receive updates, and will only be supported for a three month period ending on June 14th 2023. 8,192 tokens Up to Sep 2021
gpt-4-32k Same capabilities as the base gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration. 32,768 tokens Up to Sep 2021
gpt-4-32k-0314 Snapshot of gpt-4-32 from March 14th 2023. Unlike gpt-4-32k, this model will not receive updates, and will only be supported for a three month period ending on June 14th 2023. 32,768 tokens Up to Sep 2021

https://platform.openai.com/docs/models/gpt-4

seyaseya

トークン量の確認

プロンプトがどのくらいのトークン量なのかはこちらからテキストエリアにペタッと貼れば確認することができます。
https://platform.openai.com/tokenizer

ただし、GPT-3 の場合なので、GPT-4 などを使う場合はもう少し減るかもしれません。

プログラマブルに確認する方法として一つにリクエストのレスポンスを見るという方法があります。
API のレスポンスにはこんな感じで usage というオブジェクトが存在します。

ただ、リクエストを送る前に判定したいこともあるでしょう、私はありました。
すごいざっくりでいいのであれば、英語の場合は 1 Token = 4 characters ぐらいみたいなので、文字列の length を 4 で割ったらそこまでズレていないトークン量は手に入ると思います。

それに関してはこちらの Tiktoken というライブラリを活用すればできるみたいです。
https://github.com/openai/tiktoken

有志の npm パッケージもあったりしました。

https://www.npmjs.com/package/@dqbd/tiktoken

seyaseya

節約法

そんなトークンですが節約する方法がいくつかあります。

英語

残念ながら日本語(というか英語以外?)はトークン量的には大分不利なようです。
今めっちゃテキトーに文章を書いてみましたが、同じ意味の文章でも2倍ぐらいのトークン量となっています。


なのでとりあえずプロンプトは英語で書くのが基本となるでしょう。使いたいデータが日本語の場合はその限りではないかもしれないですが...。

要約する

長いなら要約してしまえばいいじゃないというシンプルな発想ですが、自分で頑張って要約してもいいですし、これも ChatGPT さんにお願いしてもらうのはアリだと思います。

また、Langchain というツールの中に ConversationSummaryMemory という正に要約のためのものがあるのでこれを活用してみるのもいいと思います。
https://www.pinecone.io/learn/langchain-conversational-memory/

絵文字で圧縮

多少できる模様です。時代は象形文字。しかし失われた情報もそれなりにあるもよう。

https://twitter.com/Developer65537/status/1644909849304776704?s=20

こちらは React コードが Lossless で圧縮できた事例です。

https://twitter.com/mckaywrigley/status/1643592353817694218?s=20

やってほしいことを分割する

これもそらそうだろという話ではありますが、一度のプロンプトでなんとかしようとするのではなく、独立したタスクが複数あるのなら分割するのも手です。

あとこれはトークン量というよりお金的な話なのですが、分割した結果 GPT-4 じゃなくて 3.5-turbo でも十分だな、という処理は 3.5-turbo にしておくと料金が大分安くなります。

seyaseya

回答を安定させたい

いざシステムに組み込んで使おうという時に、毎回微妙に違う回答をされてこいつぅ!となることがよくあります。
正直まだ安定させる方法やテストする方法全然分かってないのですが、今の所の工夫

seyaseya

リクエストのパラメータ

temperature と top_p というパラメータをいじれば安定させることができます。
https://platform.openai.com/docs/api-reference/chat/create

temperature は高い値ほど回答のばらつきが大きくなります。安定させたい場合はとりあえず 0 にしておけばいいです。

「0」と「2」の間でどのサンプリング温度を使用するか決めてください。0.8のような高い値は、出力をよりランダムにします。一方、0.2のような低い値は、出力をより焦点を絞った、決定論的なものにします。

top_p はサンプリングする対象を絞ることにより安定性を増すことができるみたいです。

温度によるサンプリングの代わりに、核サンプリングと呼ばれる手法があります。この方法では、モデルはtop_p確率の質量を持つトークンの結果を考慮します。つまり、0.1とすると、上位10%の確率質量を持つトークンだけが考慮されます。

それぞれ安定性を増すために有効なパラメータですが、両方を同時にいじることは推奨されていないようです。(なんでだろう)

We generally recommend altering this or temperature but not both.

seyaseya

出力形式を指定する

回答の形式をしっかり指定するとあまりぶれない印象があります。
例えば下記のような形で JSON にして返してくれよ、と伝えると、とりあえず30回ぐらいはこのプロンプト実行したと思いますが、これ以外のフォーマットで返したことはないです。

## constraints
out put as JSON in following property names, do not include new line since this string is going to be parsed with JSON.parse

{ "branchName": "branch name", "commitMessage": "commit message", "prTitle": "PR title", "componentName": "component name" }

あとは月並みな話にはなりますが、プロンプトを実行して「ここはいつも同じ感じにしてほしいなぁ」と思ったところをしっかり言語化してプロンプトに含めるというのを繰り返すという地道なプロセスを辿る必要があると思います。

seyaseya

【TypeScript ユーザ向け】ライブラリを使う

OpenAI が専用の API クライアント作ってくれているのでこれを使いましょう。
https://github.com/openai/openai-node

seyaseya

...しかし Origin が null になってしまうような環境(a.k.a Figma プラグイン)だとエラーが出てしまいますのでスニペットをここに記しておきます。(私が書いた訳でないのですが元となった stackoverflow のページが見つからず引用元が貼れないすまない)

createChatCompletion.ts
type Chat = { role: 'user' | 'assistant'; content: string };

async function fetchStream(stream: any) {
  const reader = stream.getReader();
  let charsReceived = 0;

  let chunks: Uint8Array = new Uint8Array([]);

  await reader
    .read()
    .then(function processText({
      done,
      value,
    }: {
      done: boolean;
      value: Uint8Array;
    }) {
      if (done) {
        console.log('Stream complete');
        return chunks;
      }
      charsReceived += value.length;
      const chunk = value;
      console.log(
        `Received ${charsReceived} characters so far. Current chunk = ${chunk}`
      );

      chunks = new Uint8Array([...chunks, ...chunk]);
      return reader.read().then(processText);
    });
  const decorder = new TextDecoder();
  const resultString = decorder.decode(chunks);

  const response = JSON.parse(resultString);
  return response;
}

export async function createChatCompletion(
  chat: string,
  previousChats: Chat[],
  use35turbo?: boolean
) {
  const DEFAULT_PARAMS = {
    model: use35turbo ? 'gpt-3.5-turbo' : 'gpt-4',
    messages: [...previousChats, { role: 'user', content: chat }],
    temperature: 0,
  };
  const params_ = { ...DEFAULT_PARAMS };
  const result = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: 'Bearer ' + String(process.env.OPENAI_API_KEY),
    },
    body: JSON.stringify(params_),
  });
  const stream = result.body;
  const output = await fetchStream(stream);

  return output.choices[0].message.content;
}
seyaseya

独自データを元に回答してもらう

  • embedding
  • fine-tuning
seyaseya
  • embedding と fine-tuning について
    • embedding は主にサーチには優秀
    • ただどうもその内容を構造化して使うというところはやってくれていないように見える
      • 例: コンポーネントの Variants を JSON として Vectorstore に保存して、Buttonコンポーネントの Props を教えて、のような質問を投げたが Typical な答えしか返してくれなかった
    • なので曖昧検索がうまくできるぐらいの期待値なのかも

https://community.openai.com/t/fine-tune-vs-embedding/54627

なので fine-tuning をしてみたいのですが、現状 OpenAI の場合は GPT-3.5 はおろか GPT-3 までしかできません。

https://platform.openai.com/docs/guides/fine-tuning/advanced-usage

それでも与えた情報に対してだけならいい感じの回答してくれるようになるのかな?