リクエストの形式
リクエストはJSONで送ります。
最低限必要なのは、使用するモデル名(model)と会話の内容(messages)です。
{
"model": "モデル名",
"messages": [
{ "role": "system", "content": "あなたはMSXに詳しいアシスタントです。" },
{ "role": "user", "content": "MSX2の画面モードを教えてください。" }
],
"temperature": 0.7
}
messagesは会話の履歴を配列で並べたもので、roleで誰の発言かを指定します。
| role |
意味 |
| system |
AIへの前提条件や役割の指示 |
| user |
ユーザーの発言 |
| assistant |
AIの過去の返答 |
ここで大事なのは、''APIは会話を覚えていない''ということです。
2回目以降の質問では、それまでのuserとassistantのやり取りを全部messagesに入れて送り直します。
チャットアプリが会話の流れを覚えているように見えるのは、アプリ側が毎回履歴をまとめて送っているからです。
例えば、1回目に「MSX2の画面モードを教えてください」と聞き、AIが答えた後に「その中でスプライトが使えるのはどれ?」と続けて質問する場合は、次のように送ります。
{
"model": "モデル名",
"messages": [
{ "role": "system", "content": "あなたはMSXに詳しいアシスタントです。" },
{ "role": "user", "content": "MSX2の画面モードを教えてください。" },
{ "role": "assistant", "content": "MSX2ではSCREEN 0〜8が使えます。SCREEN 0は..." },
{ "role": "user", "content": "その中でスプライトが使えるのはどれ?" }
]
}
1回目の質問、1回目のAIの返答(assistant)、2回目の質問を順番に並べています。
「その中で」が何を指しているのか、AIは送られてきた履歴を読んで判断しています。
もしassistantの行を入れずに2回目の質問だけを送ると、AIは何の話かわからなくなります。
3回目、4回目と続く場合も同じで、それまでのやり取りの後ろに新しい質問を追加していきます。
例えば、2回目の返答の後に「SCREEN 5でスプライトを表示するサンプルを書いて」と頼み、さらに「スプライトをカーソルキーで動かせるようにして」と続ける場合、4回目の質問では次のようになります。
{
"model": "モデル名",
"messages": [
{ "role": "system", "content": "あなたはMSXに詳しいアシスタントです。" },
{ "role": "user", "content": "MSX2の画面モードを教えてください。" },
{ "role": "assistant", "content": "MSX2ではSCREEN 0〜8が使えます。SCREEN 0は..." },
{ "role": "user", "content": "その中でスプライトが使えるのはどれ?" },
{ "role": "assistant", "content": "スプライトが使えるのはSCREEN 1〜8です。..." },
{ "role": "user", "content": "SCREEN 5でスプライトを表示するサンプルを書いて。" },
{ "role": "assistant", "content": "10 SCREEN 5,2\n20 SPRITE$(0)=...\n..." },
{ "role": "user", "content": "スプライトをカーソルキーで動かせるようにして。" }
]
}
userとassistantが交互に並び、最後が今回の質問になっています。
4回目の「スプライトを」がどのプログラムのことなのか、AIは3回目の返答に入っているサンプルプログラムを読んで判断します。
systemの指示は先頭に1回書いておけば、会話が続いても毎回同じものを送り続けます。
このように、やり取りが増えるごとにmessagesがどんどん長くなっていきます。
そのため、会話が長くなるほど送るデータ量と処理時間が増え、モデルが扱える長さ(コンテキスト長)を超えるとエラーになったり、古い部分を忘れたりします。
チャットアプリの中には、古い履歴を削ったり要約したりして送る工夫をしているものもあります。
temperatureは返答のランダムさで、低いほど毎回同じような答えに、高いほどバリエーションのある答えになります。
プログラムを生成させる場合は低めにすることが多いです。
レスポンスの形式
レスポンスもJSONで返ってきます。
{
"id": "chatcmpl-xxxx",
"object": "chat.completion",
"model": "モデル名",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "MSX2の画面モードは..." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 35, "completion_tokens": 120, "total_tokens": 155 }
}
AIの返答は「choices[0].message.content」に入っています。
usageには使用したトークン数が入っていて、クラウドサービスではこれが料金の計算に使われます。
ストリーミング
ChatGPTのように文字が少しずつ表示されるのは、ストリーミングという方式を使っているからです。
リクエストに「"stream": true」を付けると、返答が一度にではなく、少しずつ送られてきます。
{
"model": "モデル名",
"messages": [
{ "role": "user", "content": "MSX2の画面モードを教えてください。" }
],
"stream": true
}
すると、次のようなデータが順番に届きます。
data: {"choices":[{"delta":{"content":"MSX"}}]}
data: {"choices":[{"delta":{"content":"2の"}}]}
data: {"choices":[{"delta":{"content":"画面"}}]}
data: [DONE]
これはServer-Sent Events(SSE)という形式で、「data: 」で始まる行ごとに続きの文字がdeltaとして届きます。
最後に「data: [DONE]」が来たら終了です。
ローカルLLMは生成に時間がかかることが多いので、ストリーミングで少しずつ表示すると体感的な待ち時間がかなり短くなります。