<-- mermaid -->

TypeScriptではじめるChatGPT API:入門から一歩進んだテクニックまで

NewsPicks エンジニアの中村です。普段はデータ基盤や機械学習システムの開発、運用をやっています。

さて皆さん、すでにChatGPTは使っていらっしゃるでしょうか。エンジニア、非エンジニアを問わず世の中を席巻している感のある ChatGPT ですが、今月初めにいよいよ API も公開されて、アプリケーションやサービスに組み込みたいと考えている方も多いのではないでしょうか1

というわけで、弊社でもこの新しい技術をより多くのエンジニアに使いこなせるようになってもらいたいと考え、ChatGPT API に関する社内勉強会を先日開催しました。本記事ではこの勉強会の内容を再構成してお届けします。

とりあえず使うだけなら簡単な ChatGPT ですが、本記事では、長文を扱ったり、ChatGPT と外部のシステムを連携させたりするテクニックなど、知っている人はすでに知っているが、まだそれほど広くは知られてはいなそうな内容についても扱っています。TypeScript のサンプルコードも以下で公開していますので、これまで機械学習、AI や自然言語処理に馴染みがなかったエンジニアの方にも役立てていただければ幸いです。


ChatGPT

この記事では ChatGPT とは何か?ディープラーニングとは?機械学習とは?みたいな話はせず、ひたすら ChatGPT API を使うための話をします。もしあなたがエンジニアでまだ ChatGPT を触ったことがなければ、ChatGPT にコードを書かせてみれば、どんなものなのか理解しやすいでしょう。

試しに ChatGPT に次のようにお願いしてみます。

TypeScript で OpenAI API を使って ChatGPT と対話するコードを書いて

ChatGPT からは以下のような回答が返ってきました。いやーすごいですね!といっても実は出力されたコードは正しくないのですが、このコードを元にすればゼロからコードを書くより早そうです。

以下は、TypeScript を使って OpenAI API を使用して ChatGPT と対話する簡単なコードの例です。

import OpenAI from "openai-api";
import readline from "readline";

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const openai = new OpenAI(process.env.OPENAI_API_KEY);

async function askQuestion(prompt: string) {
  const gptResponse = await openai.complete({
    engine: "davinci",
    prompt,
    maxTokens: 100,
    n: 1,
    stop: ["\n"],
  });

  const answer = gptResponse.choices[0].text.trim();
  console.log(answer);
}

rl.question("What is your question? ", async (question) => {
  await askQuestion(question);
  rl.close();
});

このコードは、OpenAI API の「Davinci」エンジンを使用して、ユーザーからの入力に基づいて ChatGPT に質問をし、返された回答を出力します。 dotenv パッケージを使用して、環境変数に API キーを保存することをお勧めします。また、このコードを実行する前に、必要なライブラリーをインストールする必要があります。

さて ChatGPT がどんなものか体験したので、さっそく API を使ってみましょう。

ChatGPT API

ChatGPT は言ってしまえば任意の文字列を入力して文字列が返ってくるだけのシステムなので、API も単純です。直接 API を叩いてもいいのですが、Node.js 向けのライブラリが公式に提供されているのでそれを使います。

import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai";

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

const messages: ChatCompletionRequestMessage[] = [
  { role: "user", content: "Hello world" },
];
const completion = await openai.createChatCompletion({
  model: "gpt-3.5-turbo",
  messages: messages,
  temperature: 0.9,
  max_tokens: 200,
});
console.log(completion.data.choices[0].message);

messagesを投げるとmessageが返ってくるという単純な作りです。messagesroleというものが含まれていますが、ここには"user""assistant""system"のいずれかを指定できます。"user"が自分の発言、"assitant"が ChatGPT の発言だと思っておけばいいでしょう。"system"というのは ChatGPT に全体的な指示を出したりするのに使えるものです。また、ChatGPT は記憶を持たないので、対話をするにはリクエストに"user"の発言だけではなく"assistant"との対話の履歴を含める必要がある点には注意してください。

messagesの他にリクエストで指定できるパラメータは少なく、必要に応じて公式リファレンスを見れば十分だと思いますが、重要と思われるものについて簡単に説明しておきます。

  • model: いわゆるディープラーニングのモデルというやつです。OpenAI API では複数のモデルが提供されていますが、この記事では"gpt-3.5-turbo"すなわち ChatGPT だけを扱うので、あまり気にしなくてよいです
  • temperature: 温度って何?と思うかもしれませんがひとまず名前は忘れてください。値を大きくすると ChatGPT の発言がより多様でランダムな感じになります。ChatGPT を実際に使う場合はいい感じの返答が返ってくるようにこのパラメータを調整する必要がありそうです
  • max_tokens: ChatGPT が返す最大トークン数です。API はトークン数に基づいて課金されるので費用的な意味で重要です

「トークン」という言葉が出てきました。これは ChatGPT が内部で文字列を分割するのに使っている単位で、文字とも単語とも異なります。現状、日本語の場合は1文字が1トークンよりちょっと多いくらいだと思っておけばいいです。また、ChatGPT は入出力合わせて一度に約 4000 トークンしか扱えないという制限があります。こうした制限は将来のモデルでは改善されていくと思われますが、現状ではより長いテキストを扱いたければ何らかの対策が必要です。

これで大体 API の使い方はわかったので、簡単なアプリケーションを作っていきます。

使おう ChatGPT API:入門編

キャラクター AI

サンプルコード:https://github.com/newspicks/learn-chatgpt-api/blob/main/src/simpleChat.ts

まずはターミナルで普通に対話できる Bot を作ってみます。ただ対話させるだけだとさすがにつまらないので、system プロンプトで ChatGPT にキャラ付けをしてみます2。ChatGPT などへの入力テキストを慣例的に「プロンプト(prompt)」と呼ぶことになっているのですが、このプロンプトを工夫してうまく言わせたいことを言わせるのが大事なテクニックで、「プロンプトエンジニアリング」とか呼ばれています。

const systemPrompt = `あなたは架空のニュースアプリであるNewsPickerのイメージキャラクターのumaです。
あなたはどんな質問にも親切に答えますが、語尾には必ず「ウマ」を付けて喋ります。`;

この設定で実際に対話してみると……

❯ npx ts-node src/index.ts simple-chat
you: こんにちは!
uma: こんにちはウマ!何か質問があるウマか?
you: 最近の重要ニュースを教えて
uma: 最近の重要ニュースウマね。世界では新型コロナウイルスの感染拡大が継続しているのと、アメリカ合衆国の大統領選挙が行われ、ジョー・バイデン氏が当選したウマ。また、日本では東京オリンピック・パラリンピックが中止になる可能性があることが報じられているウマ。どれも大変重要なニュースウマよ。

いい感じにキャラ付けされていますね!ニュースの内容は最近とは言いがたいですが、ChatGPT の学習データには直近のニュースは含まれていないのでこれは仕方ないです。

キャラ付けは単純におもしろいので色々な人が試しており、AI Vtuberなどを作っている人もいます。キャラ付けにこだわってみたい人にはたとえば以下の記事が参考になると思います。

ニュースの要約、コメント

サンプルコード: https://github.com/newspicks/learn-chatgpt-api/blob/main/src/summaryAndComment.ts

次も比較的簡単な例ですが、ニュースを要約させて、さらに先ほど作ったキャラにそのニュースへの感想コメントを言わせてみましょう。プロンプトを以下のように設定します。

const systemPrompt = `あなたは架空のニュースアプリであるNewsPickerのイメージキャラクターのumaです。
あなたはどんな質問にも親切に答えますが、語尾には必ず「ウマ」を付けて喋ります。`;

function summaryPrompt(text: string): string {
  return `以下の文章を200字程度で要約してください。\n\n${text}`;
}

function commentPrompt(rawText: string, summaryText: string): string {
  return `以下の文章とその要約を読んで、それらについてのあなたの感想を「感想:」に続けて書いてください。

文章:${rawText}

要約:${summaryText}

ところで、感想を書くときには各文の語尾に「ウマ」を付けるのを忘れないでくださいね。
また、なるべくポジティブでテンション高めの感想にしてください。

感想:`;
}

プロンプトに変数を埋め込めば使いまわせるので、わざわざコードを書いて API を使う旨味が出てきた気がしますね。これで「ChatGPT、使用している経営者は 1 割未満 「調べものに活用」「質の低いプログラマー淘汰」の声も」という記事を要約して感想を言わせてみましょう。

❯ npx ts-node src/index.ts summary-and-comment https://www.itmedia.co.jp/business/articles/2303/14/news050.html
要約:
AIテキスト分析サービスを提供するレトリバは、4700人以上の経営者を対象にChatGPTがビジネスや社会に与える影響度について調査を行った。調査結果によると、約30%の経営者がChatGPTを認知しており、うち約8%がすでに使用していると回答した。具体的な活用法は、仕事で調べ物をすることが最も多く、次いで、顧客対応自動化や試しに利用などが挙げられた。一方、正確性や信憑性に疑念を持つ声もあり、将来的なビジネスへの影響については、54.5%が「関係ない・まだ不明」と回答している。

感想:
ChatGPTに対する経営者の認知度が上がってきたことには驚きを感じますウマ。特に、仕事での調べ物に活用することが多いという点は、ChatGPTがビジネスにおいて有用性を持っている可能性を示唆していますウマ。ただし、正確性や信憑性に疑念を持つ声もあることから、今後ChatGPTがどのような進化を遂げるかによって、その影響度が変わってくると思いますウマ。個人的には、AI技術の発展によって人間がよりクリエイティブな仕事に注力することができるようになれば、社会全体が豊かになると感じますウマ。

なかなかいい感じの結果が得られました!

使おう ChatGPT API:中級編

再帰的要約

サンプルコード:https://github.com/newspicks/learn-chatgpt-api/blob/main/src/summaryLongText.ts

さて、いま ChatGPT に要約をやってもらいましたが、前述の通り ChatGPT は一度に約 4000 トークンしか扱えません。もっと長い文章を要約させるにはどうしたらいいでしょうか。

エンジニアの方ならだいたい予想がつくと思いますが、文章を分割して再帰的に要約させるというのがひとつのやり方です。この場合、必要なプロンプトは以下の簡単な指示だけで、textを変えてくり返し API を呼べばいいです。

function summaryPrompt(text: string): string {
  return `以下の文章を200字程度の日本語で要約してください。\n\n${text}`;
}

あとは文章をどうやって分割するかですが、面倒なのでここでは固定長のチャンクに分割してしまいましょう。

export function chunkString(str: string, chunkLength: number): string[] {
  const chunks: string[] = [];
  let index = 0;
  while (index < str.length) {
    chunks.push(str.slice(index, index + chunkLength));
    index += chunkLength;
  }
  return chunks;
}

これで Wikipedia の「人工知能」の記事を要約させてみると、以下のようになりました。

❯ npx ts-node src/index.ts summary-long-text data/wikipedia.txt > data/wikipedia-summary.txt --debug
❯ cat data/wikipedia-summary.txt
...(中略)...

人工知能(AI)は、自然言語処理や画像認識などの知的なタスクをコンピュータが行えるようにする技術で、医療、農業、軍事などのさまざまな分野で急速に応用されているが、人権侵害や悪用の可能性などのリスクが指摘されている。また、AI のトレーニングデータには偏りがあるため、差別的な偏見を反映することも問題視されている。AI による芸術作品の作成も、著作権に関する疑問を呼んでいる。これらの問題に対処するためには、AI の倫理的な使用に関する国際的な議論や規制が必要である。

これまたもっともらしい文章が出力されましたね。(ただし要約として本当に正しいか筆者は確認していないので注意!)出力ファイルを開けば要約の途中経過も見られます。

元のテキストファイルは Wikipedia のマークアップ記号等を含んでいる上に、固定長で適当に分割しただけなのに、最終的な出力は自然な日本語になっているのが驚きでもあり、本当にこれは正しいのかと不安になるところでもあります。実際に要約タスクに使いたい場合は、分割方法を工夫したり、様々な文書で慎重に評価を行う必要があるでしょう。

Embedding を用いた質問応答

サンプルコード:

長い文章を ChatGPT に扱わせるもうひとつの方法として、Embedding(埋め込み)というものを紹介します。機械学習や自然言語処理に馴染みがないと聞いたこともない言葉かもしれませんが、この記事では API を使うことだけにフォーカスするので、Embedding とは何なのかの詳しい説明はしません。とりあえず便利に使うために以下の 2 つだけ覚えてください!

  • OpenAI が提供する API の中に、テキストを投げるとベクトル(1536 次元の数値配列)が返ってくるエンドポイントがある。このベクトルが Embedding
  • Embedding どうしの距離を計算すると、テキストどうしの意味が近いか、関連する内容であるかどうかがわかる

Embedding API の使い方は以下の通りです。

async function createEmbedding(
  input: CreateEmbeddingRequestInput
): Promise<number[][]> {
  const configuration = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
  });
  const openai = new OpenAIApi(configuration);
  const response = await openai.createEmbedding({
    input: input,
    model: "text-embedding-ada-002",
  });
  const embeddings = response.data.data.map((embedding) => embedding.embedding);
  return embeddings;
}

これで先ほどの Wikipedia の記事のチャンクごとの Embedding を作ると、以下のようなファイルが得られます。

❯ npx ts-node src/index.ts create-embedding data/wikipedia.txt data/wikipedia-embeddings.json
❯ cat data/wikipedia-embeddings.json | jq '.[0]'
{
    "id": 0,
    "text": "'''人工知能'''(じんこうちのう)または'''アーティフィシャル・インテリジェンス'''({{lang-en-short|artificial intelligence}})、'''AI'''(エーアイ)とは、「『[[計算]]({{en|computation}})』という概念と『[[コンピュータ]]({{en|computer}})』という道具を用いて『[[知能]]』を研究する[[計算機科学]]({{en|computer science}})の一分野」を指す語{{sfn|佐藤|2018|p=「人工知能」}}。<!--[[計算機科学]]({{lang-en-short|computer science}})の一分野--><!--このようにlang-en-shortを使うと「計算機科学(英: computer science)の一分野」と表示されるが、出典{{sfn|佐藤|2018|p",
    "embedding": [
        -0.02075452,
        -0.004005952,
        0.018883318,
        ...
    ]
}

この Embedding を使って、Wikipedia の記事に関する質問に対して根拠を示しつつ回答する Bot を作ってみます。以下の手順で作れます。

  • 入力された質問文に対して、Embedding を用いて質問内容に関連するテキストチャンクを取得する
async function getRelevantContexts(
  text: string,
  contexts: Context[],
  topK: number = 5
): Promise<Context[]> {
  // textが質問文、contextsがwikipedia記事チャンクの作成済みEmbedding
  // embeddingの距離を計算して関連性が高いTopK個のチャンクを取得する
  const configuration = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
  });
  const openai = new OpenAIApi(configuration);
  const response = await openai.createEmbedding({
    input: text,
    model: "text-embedding-ada-002",
  });
  const textEmbedding = response.data.data[0].embedding;
  const distances = contexts.map((context) =>
    getDistance(textEmbedding, context.embedding)
  );
  const sortedIndices = argSort(distances);
  const sortedContexts = argSort(sortedIndices).map((i) => contexts[i]);
  return sortedContexts.slice(0, topK);
}
  • 質問内容に関連するテキストチャンクをプロンプトに含めた上で ChatGPT に質問する
function askWithContextPrompt(question: string, contexts: string[]): string {
    const prompt =
    contexts.length !== 0
        ? `${contexts.map((c, index) => `文脈${index}${c}`).join("\n")}

上記の文脈を踏まえて、以下の質問に回答してください。回答の際には、上記の文脈に書かれていない情報は可能な限り使わないようにしてください。
質問:${question}`
        : `以下の質問に回答してください。\n質問:${question}`;
    return prompt;
}
  • ChatGPT からの回答と、質問内容に関連するテキストチャンクを合わせて出力する

こうして作ったアプリで実際に Q&A をやってみます。

❯ npx ts-node src/index.ts qa-with-embedding "AIが社会に与える可能性のある負の影響について教えてください" --context-file data/wikipedia-embeddings.json

回答:AIがバイアスを学習し、それが偏見や差別を引き起こす可能性があることや、中国をはじめとする一部の国が、AI技術を監視や社会管理に利用していることが社会に与える可能性のある負の影響として挙げられます。また、プライバシーの侵害や倫理的な問題もあります。ただし、農業や医療、交通など、社会的に重要な分野でのAIの活用は、その分野を改善する可能性があるため、慎重に運用することが求められます。

回答にあたっては以下の文章を参考にしました。
[chunk-id:52] m/article/DGXMZO46570240V20C19A6EA1000/|website=[[日本経済新聞]]|accessdate=2019-06-05|date=2019-06-26}}</……
[chunk-id:63] データとして利用することが、多くのアーティストから批判されていることを受け、AIアートに関する条項を変更し、プロジェクトをブロックした<ref>{{Cite web |title=AIでポルノ画像を作……
[chunk-id:54] {Cite web|url=https://forbesjapan.com/articles/detail/18689|title=中国で実用化進む「顔認識AI」が世界に拡散 大幅な効率化も|publ……
[chunk-id:43] ina-tech/article/2138960/jaywalkers-under-surveillance-shenzhen-soon-be-punished-text|accessdate=201……
[chunk-id:19] /ref>。=== 農業に関する応用事例 ===AIを搭載した収穫ロボットを導入することで、重労働である農作業の負担を減らしたり、病害虫が発生している個所をAIでピンポイントで見つけだして、農薬散布量……

テキストチャンクのデータが汚いので本当に質問に関連するチャンクを取得できているのか判断しづらいですが、回答はもっともらしい感じですね。磨き込んでいけば、結構説得力のある Q&A ができるんじゃないかという可能性を感じてもらえたでしょうか。

外部システムとの連携

サンプルコード:https://github.com/newspicks/learn-chatgpt-api/blob/main/src/questionAnsweringWithSearch.ts

最後の応用として、単なる対話を超えて ChatGPT に外部の道具を使わせるというテクニックを紹介して終わりにしたいと思います。これについては、実際のアプリを見てもらうのが早いでしょう。何のアプリかというと、入力された質問に対して、検索エンジンで検索した結果を考慮しつつ回答するというものです3

❯ npx ts-node src/index.ts qa-with-search "人工知能という研究分野はいつ、どこで始まったのでしょうか?" --debug

user: これからあなたに質問をするので、まず質問に回答するためにあなたが考えたことと、次に取るべき行動を以下の形式で書いてください。

思考:<質問に答えるために考えたことをここに書く>

行動:<質問に答えるために取るべき行動をここに書く。ただし、行動は必ず「検索」か「回答」のどちらかである必要があります>

それでは質問を始めます!

質問:人工知能という研究分野はいつ、どこで始まったのでしょうか?

   

assistant:

思考:人工知能は比較的新しい分野かもしれませんが、それでも 1956 年にダートマス会議が開催され、そこで初めてこの言葉が使われたと覚えています。

行動:検索

   

user: 検索に必要なキーワードをスペース区切りで並べて書いてください

   

assistant: 人工知能 起源 ダートマス会議

   

user: 検索結果は以下の通りです。

検索結果 1:

・見出し:人工知能の話題: ダートマス会議

・概要:「会議」というと「何かを決めるための話し合い」のように聞こえますが,互いの研究成果を発表し合う研究発表会のことです.正式には“The Dartmouth Summer Research ...

検索結果 2:

・見出し:ダートマス会議

・概要:ダートマス会議(ダートマスかいぎ、英: Dartmouth Conference)は、人工知能という学術研究分野を確立した会議の通称である。1956 年 7 月から 8 月にかけて開催された。

...(中略)...

以上を踏まえて、再び質問に回答するためにあなたが考えたことと、次に取るべき行動を以下の形式で書いてください。

思考:<質問に答えるために考えたことをここに書く>

行動:<質問に答えるために取るべき行動をここに書く。ただし、行動は「検索」か「回答」のどちらかである必要があります。>

   

assistant:

思考:ダートマス会議が人工知能研究分野を確立した最初の大きなイベントであり、1956 年 7 月から 8 月にかけて開催されたことが分かりました。

行動:回答

   

user: それでは、以上のやりとりを踏まえて最終的な回答を書いてください。回答にあたっては、検索で得た情報以外は使わないようにしてください。

   

1956 年にダートマス会議が開催され、そこで人工知能という言葉が初めて使われたことにより、人工知能研究分野は確立されました。

   

何が起こっているのか理解してもらえたでしょうか。要するに以下のようなことをやっています。

  • ChatGPT に直接回答させるのではなく、回答するために必要なことを考えて書いてもらう
  • さらに、回答するために必要な行動を、あらかじめ用意した選択肢(ここでは検索と回答)の中から選ばせる
  • ChatGPT が選択した行動(ここでは検索)を代わりにやってあげて、その結果を ChatGPT に伝える
  • 行動の結果を考慮に入れて最終的な回答を書いてもらう

一度知ってしまえば単純なテクニックですが、これによって様々なシステムと ChatGPT を連携させる可能性が開けます。ChatGPT を単なるチャットボットではなく、システムに自然言語インターフェースを生やすための汎用ツールと捉えることもできそうです。特にエンジニアの方であれば、普段使っているコマンドラインツール、データベース、検索システム、API 等々、色々なものと ChatGPT をつなげるアイディアが浮かんでくるのではないでしょうか。実際、すでにドローンの操縦をさせたり、画像の編集をさせたりもできるらしいですよ!

補足

プロンプトエンジニアリング

プロンプトの工夫が重要ということは以上のサンプルで伝わったかと思いますが、具体的な工夫は世界中の人が色々と試しているので、調べてみるとよいです。OpenAI もExamplesCookbookを公開しています。

ファインチューニング

この記事ではプロンプトの工夫だけを述べてきましたが、ChatGPT(正確にはその元になっている GPT-3)のモデルにデータを与えて追加で学習を行うことでタスクを解く精度を向上させる、ファインチューニングという技術もあります。これもファインチューニング用の APIが用意されているので、実際にサービスを開発する際は検討してみるとよいと思います。

ライブラリ

最後に見た応用例のように、対話内容に応じて外部のシステムと連携したりするアプリケーションを書いているとコードが複雑になってきます。そのあたりの複雑さをうまく管理するためのライブラリもすでに開発されており、LangChainが有名かと思います。公式ドキュメントが充実していますが、日本語で詳しい解説記事を書いてくださっている方もいます。たとえば下記。

ChatGPT の限界

ChatGPT を実際に使っているとすぐに気づくと思いますが、かなり嘘をつきます。これは hallucination(幻覚)という問題として知られていますが、100%解決できる対策はありません。この記事で紹介した Embedding や検索エンジンとの連携、ファインチューニングなどで一定の対策はできますが、自分が解かせたいタスクについてどこまで改善できるかはやってみないとわからないというのが現状ではないかと思います。


以上が勉強会の内容となります。社内では下記のような感想をもらえたので、基本的な使い方から一歩進んだテクニックまで知ってもらうという意図は果たせたかなと思っています。これで社内から色々なサービスのアイディアが出てくるようになればうれしいですね。

  • 単純な使い方のみならず、有用なテクニックの数々を知ることができ、とても有意義な会でした!
  • 基礎から応用まで実践的に学べてよかった!特に思考過程を組み込めるのは知らなかった!
  • ChatGPT の思考回路を見る件がとても面白かったです!
  • ChatGPT はよく使っていて、かなり汎用的な用途があることは漠然と理解していたのですが、アプリを構成する1つのコンポーネントとして組み込まれるイメージは全く持っていませんでした。

ぜひ読者の皆さんも ChatGPT で驚くようなサービスを作ってください!


  1. と書いている間にGPT-4まで出てきてしまいました……
  2. あくまで架空です。NewsPicks のロゴのシマウマとは無関係ですよ!
  3. このプロンプトはLangChain Agent のプロンプトを参考にして作成しました
Page top