チャットボットは呼び出しのループ
コマンドラインのアシスタントは、打った一行を読み、言語モデルに送り、返答を表示して、終わるまで繰り返すプログラムです。モデルの上に作る、最も小さな役に立つプログラムです。
これが大切なのは、ここまでのすべてを使うからです。メッセージ、履歴、ストリーム、そしてチャットを壊してはいけない失敗した呼び出しです。
この課のゴール
メモ
モデルの呼び出しをまとめて、人が使えるプログラムにできる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| ループ | 止めるよう言われるまで繰り返すコード | |
| コマンド | モデルではなくプログラムを操作する、打った行 | /quit |
| 履歴 | 呼び出しのたびに送り直す、前のメッセージ | |
| 切り詰め | 古いメッセージを取り除き、履歴を短く保つこと | |
| きれいな終了 | エラーも失うものもなく、自分の意思で終えること |
学生が入力するセルは、このプレビューでは空白です。
プログラムはリストを持つループ
毎回、同じ手順を行います。一行を読む。それを user メッセージとして履歴に足す。メッセージを送る。返答を表示する。返答を assistant メッセージとして履歴に足す。
リストは、プログラムの記憶のすべてです。リストが間違っていれば、あとの答えもすべて間違います。
その1を書く:履歴とその上限
docker exec -w /home/node/project agent node -e 'require("fs").writeFileSync("assistant.js", ["// assistant.js その1:履歴と、その長さの上限。", "const lab = require(\"./lab.js\");", "const readline = require(\"readline\");", "const system = { role: \"system\", content: \"あなたは役に立つアシスタントです。一、二文の短い文で答えてください。\" };", "const history = [];", "const LIMIT = 6;", "", "// 新しいメッセージだけを残し、assistant のメッセージで始まらないようにします。", "function trim() {", " while (history.length > LIMIT) history.shift();", " while (history.length > 0 && history[0].role === \"assistant\") history.shift();", "}"].join("\n") + "\n")'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
履歴を切り詰めて短く保つ
呼び出しのたびに履歴全体を送るので、長いチャットは回ごとに費用が増え、モデルが読める大きさを超えることもあります。切り詰めましょう。新しいメッセージだけを残します。
切り詰めたあとのリストは、assistant のメッセージで始まってはいけません。最初の課で見たとおり、チャットは user で始まります。
その2を足す:返答のストリーム
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// その2:履歴を送り、届いたそばからストリームを表示します。", "const { piecesOf } = require(\"./stream.js\");", "async function streamReply() {", " let reply = \"\";", " for await (const piece of piecesOf({ max_tokens: 120, messages: [system, ...history] })) {", " reply += piece;", " process.stdout.write(piece);", " }", " if (reply === \"\") throw new Error(\"言葉が一つも届きませんでした\");", " return reply;", "}"].join("\n") + "\n")'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
その3を足す:履歴の順序を保つ
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// その3:履歴の順序を保ちます。呼び出しが失敗したら、質問を取り除きます。", "async function answer(text) {", " history.push({ role: \"user\", content: text });", " try {", " const reply = await streamReply();", " process.stdout.write(\"\\n\");", " history.push({ role: \"assistant\", content: reply });", " trim();", " } catch (error) {", " history.pop();", " console.log(\"\\n記録済みのサンプル:今はモデルに届かないので、記録済みの答えです。(\" + error.message + \")\");", " }", "}"].join("\n") + "\n")'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
呼び出しが失敗したとき、質問を履歴に残してしまう人が多くいます。すると次の回に user メッセージが二つ続き、順序が崩れます。呼び出しが失敗したら、質問を取り除き、そう知らせ、もう一度試してもらいます。
コマンドときれいな終了
行によっては、モデルではなくプログラムに向けたものがあります。/ で始まる行はコマンドです。/quit は終了、/clear は履歴を空にする、/history は数を数えます。
モデルを呼ぶ前に、コマンドかどうかを調べます。そうすれば、モデルが何をしていても、人はいつでも終えられます。
その4を足す:ループ
docker exec -w /home/node/project agent node -e 'require("fs").appendFileSync("assistant.js", ["", "// その4:ループ。/ で始まる行はコマンドです。", "const lines = readline.createInterface({ input: process.stdin });", "const prompt = () => process.stdout.write(\"あなた> \");", "(async () => {", " console.log(\"アシスタント準備完了。コマンド:/quit、/clear、/history\");", " prompt();", " for await (const line of lines) {", " const text = line.trim();", " if (!process.stdin.isTTY) console.log(text);", " if (text === \"/quit\") break;", " if (text === \"/clear\") { history.length = 0; console.log(\"履歴を消しました。\"); }", " else if (text === \"/history\") console.log(history.length + \" 件のメッセージを保持しています。\");", " else if (text !== \"\") { process.stdout.write(\"アシスタント> \"); await answer(text); }", " prompt();", " }", " console.log(\"さようなら。\");", " process.exit(0);", "})();"].join("\n") + "\n")'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
打ち込み済みの台本でチャットする
printf "私の名前は健です。\n私の名前は何ですか。\n/history\n/clear\n/history\n/quit\n" | docker exec -i -w /home/node/project agent node assistant.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
例:台本がしたこと
| 打ったもの | プログラムがしたこと |
|---|---|
| 私の名前は健です。 | system を含めて 2 件送り、返答を表示し、2 件保持した |
| 私の名前は何ですか。 | system を含めて 4 件送り、返答を表示し、4 件保持した |
| /history | 4 件を保持と表示した |
| /clear | 履歴を空にした |
| /quit | さようならと表示して終わった |
学生が入力するセルは、このプレビューでは空白です。
自分でもチャットしてみる
上の台本は、あなたのために打ち込んだものです。手でチャットするには、ラボでターミナルを開き、次の行を打ちます。docker exec -it -w /home/node/project agent node assistant.js。続けて質問を打ち、/quit で終えます。
入力を待つので、ボタンにはできません。パスワードや個人的なことは打たないでください。打ったものはモデルに送られます。
さらに:アシスタントを変える
その1の system の指示を変えて、アシスタントに個性を与え、LIMIT を 4 にします。六回チャットしてください。
アシスタントが何をいつ忘れたかを書きましょう。長い履歴を持つ費用と、それへの対策も言ってください。