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

無料レッスン

チャットの履歴を切り詰める方法

このレッスンのあとで: 私は、会話をモデルの上限の内に収められます。

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

会話が長くなりすぎたとき

チャット履歴の切り詰めとは、モデルに送るメッセージを減らして、リストを上限に収めることです。長いチャットはターンごとに大きくなり、メッセージ1件ごとに時間とクレジットがかかるからです。

旅行案内のチャットボットと1時間話したあとは、最初の質問はもう要りません。外せば、チャットは速く、安く保てます。

このレッスンのゴール

メモ

私は、会話をモデルの上限の内に収められます。

キーワード

ことばいみ
トークンモデルが数える、文章の小さな単位
コンテキストの上限モデルが1回の呼び出しで受け取れる最大のトークン数
予算送ると自分で決めた、最大のトークン数
切り詰める収まるまで、古いメッセージを外すこと
要約外したメッセージの代わりになる、短い文章

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

送る前にトークンを数える

モデルは文章をトークンで数え、1回の呼び出しで受け取れる数に上限があります。本当の数は、モデルごとの数え方で決まります。おおまかな目安は、4文字で1トークンです。日本語では、実際の数が目安より多くなりがちです。

このレッスンでは、役割や包みの分として、メッセージごとに4を足します。全部を足して、予算と比べます。

例: 6件のメッセージを数える

メッセージ文字数トークン
system: あなたは電車の旅の案内役です。短く答えてください。2511
user: 新宿から横浜まで、電車で何分かかりますか。2110
assistant: およそ30分です。97
user: 切符はいくらですか。107
assistant: 時間帯によって変わります。138
user: 静かな車両はありますか。127
合計9050

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

古いものを外し、規則は残す

切り詰めるときは、合計が予算に収まるまで、古いメッセージから外します。予算がどうでも、2件は残します。先頭の system メッセージと、最後の最新のメッセージです。

system メッセージには規則が入っています。最新のメッセージは、いま聞いている質問です。

要約が外した分の代わりになる

メッセージを外すと、事実が失われます。要約なら、主な事実を少ない言葉で残せます。外したメッセージをモデルに要約させ、その要約を system メッセージに足します。

切り詰めるたびに、呼び出しが1回増えます。短いチャットなら、ただ外すだけで足りることが多いです。

よくある間違い

注意

「一番古いメッセージは、いつも一番大事でない」と考える人が多いです。実際は、規則の入った system メッセージや、チャットが頼りにする唯一の事実であることもあります。system メッセージは残し、大事なものは外す前に要約しましょう。

モデルの補助を書く(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 { ask } = require(\"./model\");' 'const BUDGET = 35; // 切り詰めが起きるように、小さな予算にする' '' 'function tokens(message) { return Math.ceil(message.content.length / 4) + 4; }' 'function total(list) {' '  return list.reduce(function (sum, m) { return sum + tokens(m); }, 0);' '}' > trim2.js"

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

切り詰めのスクリプトを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function trim(list, budget) {' '  const kept = list.slice();' '  const dropped = [];' '  while (total(kept) > budget && kept.length > 2) {' '    const at = kept[0].role === \"system\" ? 1 : 0;' '    dropped.push(kept.splice(at, 1)[0]);' '  }' '  return { kept: kept, dropped: dropped };' '}' >> trim2.js"

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

切り詰めのスクリプトを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const history = [' '  { role: \"system\", content: \"あなたは電車の旅の案内役です。短く答えてください。\" },' '  { role: \"user\", content: \"新宿から横浜まで、電車で何分かかりますか。\" },' '  { role: \"assistant\", content: \"およそ30分です。\" },' '  { role: \"user\", content: \"切符はいくらですか。\" },' '  { role: \"assistant\", content: \"時間帯によって変わります。\" },' '  { role: \"user\", content: \"静かな車両はありますか。\" },' '];' >> trim2.js"

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

切り詰めのスクリプトを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const result = trim(history, BUDGET);' '  console.log(\"トークン 前: \" + total(history) + \"、後: \" + total(result.kept) + \"、捨てた数: \" + result.dropped.length);' >> trim2.js"

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

切り詰めのスクリプトを書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  const old = result.dropped.map(function (m) { return m.role + \": \" + m.content; }).join(\" \");' '  // 記録済みのサンプル。モデルが答えないときだけ使う。' '  const first = \"利用者は新宿から横浜までの時間を聞き、およそ30分と教えられました。\";' '  const summary = await ask([{ role: \"user\", content: \"次の会話を短い一文で要約してください: \" + old }], first, 50);' '  console.log(\"要約:\", summary);' '  const prompt = result.kept.slice();' '  prompt[0] = { role: \"system\", content: prompt[0].content + \" このチャットの前の部分: \" + summary };' '  console.log(\"返事:\", await ask(prompt, \"はい、たいていの電車には静かな車両があります。\", 50));' '}' 'main();' >> trim2.js"

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

切り詰めて要約する

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

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

出力の一例

行表示されるかもしれないもの
トークン前: 50、後: 33、捨てた数: 2(いつも同じ。推測はしません)
要約外した、所要時間についての一文
返事静かな車両についての、短い答え

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

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

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