👄

TypeScriptを使ってChatGPT APIをアクセスしてみる

2023/03/03に公開

最近は、生成AI関連のニュースが非常に注目されていますね。連日のように画像生成または文章生成に関するニュースがあり、とても大きなニュースが毎週出てくるほどです。そして、2023/3/2(本国では3/1)の朝に筆者が目を覚ますと、タイムラインがChatGPT APIリリースの話題であふれていました。

この記事は、TypeScriptでChatGPT APIこと Chat completions API を使ってみるというものです。

ChatGPTを使うためには、OpenAIのアカウントが必要です。アカウントを持っている人は、$18のクレジットが付与されているため、その範囲内であれば3か月間は無料で利用できます。この記事を参考にして、ぜひAPIを実際に操作してみてください。APIは非常にシンプルでアクセスしやすいものになっています。世界の変革を自分自身で体験してみたい方は、まず手を動かすところから始めてみましょう。

最近書いているTypeScriptでGPT-3.5を使ってChatGPTクローンを作るシリーズの記事よりは軽めを目指して書きました。がっつり読みたい人は以下の記事もぜひ読んでみてください。

ChatGPT API とは

ChatGPT APIについて言及する前に改めてChatGPTについて軽く触れます。

OpenAIが提供するChatGPTは、世界中で大ブームとなっており、連日報道でも取り上げられているチャットアプリのサービスです。ChatGPT内では、大規模言語モデル(LLM)と呼ばれる仕組みが使用されており、実際にChatGPTのturboモードで使用されている言語モデルは gpt-3.5-turbo と呼ばれています。おそらく、NotionAIも同じ gpt-3.5-turbo を使用していると思われます。

GPT-3やGPT-3.5など色々あってよくわからないという人は、OpenAI API で提供されている GPT-3モデル まとめ|npaka|note の記事を読んでみることをお勧めします。この記事では、OpenAI APIで提供されているGPT-3モデルについて、歴史的な流れや違いなどがまとめられています。参考にしてみてください。

gpt-3.5-turbo は最近登場したモデルであり、それ以前のChatGPTは非公開の別のモデルを使用していました。これはGPT-3.5のモデルである text-davinci-003 とは異なるものであり、専用のファインチューニングや強化学習を施したモデルとされています。旧ChatGPTのLLMはtext-davinci-003 と同様に実行コストが非常に高いです。

今回登場した gpt-3.5-turbo は、旧世代のGPT-3.5シリーズに比べて圧倒的に高速であり、コストが削減されたものとなっています。

機械学習を用いた自然言語処理では、文字はトークンと呼ばれる単位に分割されます。text-davinci-003 モデルでは、トークン数1000個あたり$0.02のコストがかかっていましたが gpt-3.5-turbo ではなんとその1/10の価格でトークン数を処理できます。

なお、日本人には朗報です。text-davinci-003 で使用されていたトークン化の方法とは異なる方法が採用されており、これまで指摘されていた日本語が不利という状況が多少改善されたようです[1]

チャットアプリを作る場合は、リクエストを大量に投げることはあまりないかもしれませんが、より複雑なアプリを開発する場合は、大量のリクエストを投げることが必要となるかもしれません。しかし gpt-3.5-turbo の登場により、コストが大幅に削減され、開発者の財布にも優しくなりました。

ちなみに、筆者は1月に初めて text-davinci-003 を使い始めましたが、まだフリートライアルの$18を使い切っていません。gpt-3.5-turbo はそれよりもはるかにコストが安いと考えると、よりお得で安全に遊べると思いませんか?

Chat completion APIを叩く

では、実際にChatGPT API、つまりChat completion APIを叩いてみましょう。ライブラリを使用することもできますが、APIは非常にシンプルなため、直接アクセスする方が手っ取り早いと思われます。

APIリファレンス を読んでみてください。curlで投げられるサンプルがあるので YOUR_API_KEY に自分自身で発行したAPIキーを貼り付けて投げてみましょう。たったこれだけのシンプルなAPIです。

curl https://api.openai.com/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [{"role": "user", "content": "Hello!"}]
}'

このとき model には gpt-3.5-turbo-0301 といういかにもスナップショットみたいなモデルを指定できますが、普通は gpt-3.5-turbo を使います。将来的には少しずつ進化していく予定らしいです。

messages はいわゆるプロンプトをchat formatで記述したものですが、先にレスポンスを見てましょう。

{
  "id": "chatcmpl-6pf8TNlBcLbZr5zHx2kfZfgTFFIcD",
  "object": "chat.completion",
  "created": 1677770873,
  "model": "gpt-3.5-turbo-0301",
  "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 },
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "\n\nHello there! How may I assist you today?"
      },
      "finish_reason": "stop",
      "index": 0
    }
  ]
}

choices[0].messageroleassistantcontent に生成された文字列が入ってる形です。

メッセージ

筆者の過去の記事で使っていた Text completion API と大きく違うのは messages です。

[
  {"role": "system", "content": "You are a helpful assistant."},
  {"role": "user", "content": "Who won the world series in 2020?"},
  {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
  {"role": "user", "content": "Where was it played?"}
]

role には現時点では system user assistant のみが指定可能です。

筆者が別のロールを指定しようとするとエラーが返ってきました。

{
  error: {
    message: "'use' is not one of ['system', 'assistant', 'user'] - 'messages.1.role'",
    type: "invalid_request_error",
  }
}

先程も説明したように、ChatGPT APIから返されるメッセージは、assistant というロールを持っています。ユーザー入力は user ロールで表現されます。また、system ロールでは、会話全体に対する指示を与えることができるようです。ChatGPT職人たちは、これらの機能について、様々な実験を行っているため、それらの記事やツイートを読むことをお勧めします。

この仕組みをそのまま活用すれば、チャットアプリで必要となる会話の記録を表現することができます。ユーザーが投稿した言葉は user ロールで、ChatGPT APIから返ってきた言葉は assistant ロールで保持して、会話ごとに投稿することができます。

system: You are a helpful assistant.
user: Who won the world series in 2020?
assistant: The Los Angeles Dodgers won the World Series in 2020.
user: Where was it played?

こういうような会話がされてる感じです。

Text completion APIを使用する場合、これを実現するためには、プロンプトを文字列結合して表現する必要がありました。

ChatGPT APIをTypeScriptで叩く

TypeScriptで叩く場合はOpenAIの提供するnpmを使うよりはfecthで叩いた方が確実かもしれません。

まずはメッセージを型として定義してみましょう。

export type Message = {
  role: "user" | "system" | "assistant";
  content: string;
};

次にAPIを投げる関数を作ってみましょう。

export const chatCompletion = async (
  messages: Message[]
): Promise<Message | undefined> => {
  const body = JSON.stringify({
    messages,
    model: "gpt-3.5-turbo",
  });

  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body,
  });
  const data = await res.json();
  // console.log(data);
  const choice = 0;
  return data.choices[choice].message;
};

このとき apiKey にAPIキーを入れておきましょう。

あとはこれを実際に叩いてみましょう。

const messages: Message[] = [
  {
    role: "user",
    content: "JSで`0.1+0.1+0.1`の実行結果を教えて",
  },
];

const res = await completeChat(messages);
console.log(res?.content);

0.30000000000000004

となります。

JavaScriptでは、小数の計算に誤差が生じることがあります。この場合、正確に0.1を表現することができないため、計算結果に誤差が生じています。

素晴らしいですね。ChatGPTであれこれ遊んだのと同じようなことがAPI経由でできるようになりました。

脚注
  1. LLMを使う時に自動翻訳でいったん英語に翻訳した方が、効率も精度もいいという話もあります。これはトークンの問題だけではなくて、全世界において日本語(2〜3%)よりも英語(60%)の方がはるかに文章量が多いため、英語の方が「頭が良い」のです。 ↩︎

Discussion