クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

コマンドラインのチャットボットを作る

このレッスンのあとで: モデルの呼び出しをまとめて、人が使えるプログラムにできる。

75分のレッスンコマンド5件AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

チャットボットは呼び出しのループ

コマンドラインのアシスタントは、打った一行を読み、言語モデルに送り、返答を表示して、終わるまで繰り返すプログラムです。モデルの上に作る、最も小さな役に立つプログラムです。

これが大切なのは、ここまでのすべてを使うからです。メッセージ、履歴、ストリーム、そしてチャットを壊してはいけない失敗した呼び出しです。

この課のゴール

メモ

モデルの呼び出しをまとめて、人が使えるプログラムにできる。

キーワード

ことばいみれい
ループ止めるよう言われるまで繰り返すコード
コマンドモデルではなくプログラムを操作する、打った行/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 件保持した
/history4 件を保持と表示した
/clear履歴を空にした
/quitさようならと表示して終わった

学生が入力するセルは、このプレビューでは空白です。

自分でもチャットしてみる

上の台本は、あなたのために打ち込んだものです。手でチャットするには、ラボでターミナルを開き、次の行を打ちます。docker exec -it -w /home/node/project agent node assistant.js。続けて質問を打ち、/quit で終えます。

入力を待つので、ボタンにはできません。パスワードや個人的なことは打たないでください。打ったものはモデルに送られます。

さらに:アシスタントを変える

その1の system の指示を変えて、アシスタントに個性を与え、LIMIT を 4 にします。六回チャットしてください。

アシスタントが何をいつ忘れたかを書きましょう。長い履歴を持つ費用と、それへの対策も言ってください。

このラボとソースについて: AI コーディングエージェントのラボ:opencode でコードを説明・修正し、その結果を確かめる

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。