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

無料レッスン

チャットボットが会話を覚える仕組み

このレッスンのあとで: 私は、リクエストをまたいで会話を保てます。

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

なぜ名前を忘れてしまうのか

チャットボットは、会話のメッセージを保管し、新しい質問のたびにもう一度送ります。言語モデルの呼び出しには、それ自身の記憶がないからです。

お客さんがバス会社のチャットボットに、切符をなくしたと伝えます。そのあと「弟の分はどうですか」と聞きます。前のメッセージが一緒に届いて、はじめて答えられます。

このレッスンのゴール

メモ

私は、リクエストをまたいで会話を保てます。

キーワード

ことばいみれい
セッション一人の人とチャットボットの会話
セッション ID一つのセッションを指す札sakura
状態リクエストの間も、プログラムが覚えていること
保管場所すべてのセッションをしまっておく所
メッセージのリスト呼び出しのたびに送るメッセージ

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

呼び出しは毎回ゼロから始まる

モデルの呼び出しは、毎回まっさらな状態で始まります。モデルが知っているのは、その呼び出しで送ったメッセージだけです。

会話にするには、プログラムがすべてのメッセージを保管します。そして毎回、リスト全体を送り、新しい質問を最後に置きます。このリストが、チャットの状態です。

例: 3回のリクエスト

リクエスト送るメッセージモデルに見えるもの
1回目system, user規則と「私の名前はさくらです。」だけ
2回目system, user, assistant, user最初のやり取りと「私の名前は何ですか。」
3回目system, user, assistant, user, assistant, userここまでのすべてと、最新の質問

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

人ごとに別のセッションを持つ

チャットボットは、たくさんの人と同時に話します。そこで保管場所は、セッション ID ごとに分けます。会話一つにつき、リスト一つです。新しい ID なら新しいリストを作り、知っている ID ならそのリストに足します。

保管場所は、プログラムが動いている間は変数で足ります。止めたあとも残すには、プロジェクトのフォルダーに小さな JSON ファイルを置きます。

状態は誰のものか

状態は、モデルではなくプログラムのものです。だから、何をどれだけの間保管するかは、自分で決めます。

パスワードやカード番号など、チャットボットに要らないものは保管しません。セッションを消す方法も用意します。セッションを消すと、リストが丸ごとなくなります。

よくある間違い

注意

「モデルは、誰が話しているかを覚えている」と考える人が多いです。そうではありません。二人が一つのリストを共有すると、お互いの言葉が見えてしまいます。セッション ID ごとに、別のリストを持ちましょう。

モデルの補助を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' '  try {' '    const res = await fetch(base + \"/v1/chat/completions\", {' '      method: \"POST\",' '      headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' '      body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    return (await res.json()).choices[0].message.content;' > model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

モデルの補助を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  } catch (error) {' '    console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' '    const list = [].concat(sample);' '    const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' '    return list[Math.min(turn, list.length - 1)];' '  }' '}' 'module.exports = { ask };' >> model.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

セッションの保管場所を書く(1)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const fs = require(\"fs\");' 'const { ask } = require(\"./model\");' 'const FILE = \"sessions.json\"; // 保管場所は、プロジェクトのフォルダーに置く' 'const RULE = \"あなたは親切なアシスタントです。短い一文で答えてください。\";' '' 'function load() {' '  try { return JSON.parse(fs.readFileSync(FILE, \"utf8\")); } catch (error) { return {}; }' '}' 'function save(store) { fs.writeFileSync(FILE, JSON.stringify(store, null, 1)); }' > chat1.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

セッションの保管場所を書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function say(store, id, text, samples) {' '  if (store[id] === undefined) store[id] = [{ role: \"system\", content: RULE }];' '  store[id].push({ role: \"user\", content: text });' '  const answer = await ask(store[id], samples, 40);' '  store[id].push({ role: \"assistant\", content: answer });' '  save(store);' '  return answer;' '}' >> chat1.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

セッションの保管場所を書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const store = {};' '  // 記録済みのサンプル。モデルが答えないときだけ使う。' '  const sakuraSamples = [\"はじめまして、さくらさん。\", \"あなたの名前は、さくらさんです。\"];' '  const kentaSamples = [\"あなたの名前は、まだ知りません。\"];' '  console.log(\"さくら:\", await say(store, \"sakura\", \"私の名前はさくらです。覚えておいてください。\", sakuraSamples));' '  console.log(\"さくら:\", await say(store, \"sakura\", \"私の名前は何ですか。\", sakuraSamples));' '  console.log(\"健太:\", await say(store, \"kenta\", \"私の名前は何ですか。\", kentaSamples));' '  const saved = load();' '  console.log(\"保存済み: sakura は \" + saved.sakura.length + \" 件、kenta は \" + saved.kenta.length + \" 件\");' '}' 'main();' >> chat1.js"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

二つの会話を実行する

コマンド
docker exec -w /home/node/project agent node chat1.js

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

出力の一例

行表示されるかもしれないもの
さくら(1回目)名前を使うかもしれない、短いあいさつ
さくら(2回目)前のメッセージを送ったので、名前を言えるかもしれない答え
健太健太は別のリストなので、さくらさんの名前を知らない答え
保存済みsakura は 5 件、kenta は 3 件

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

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

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