モデルに見せるものを選ぶ
コンテキスト設計とは、エージェントの実行の各段階で、モデルのウィンドウに何を入れるかを決めることです。ウィンドウには限りがあるので、どの部分も選択の結果です。
すべてのファイルとツールの結果を残すエージェントは、数段階でウィンドウが一杯になることがあります。大事な指示が、誰も要らない出力の下に埋もれてしまいます。
このレッスンのゴール
メモ
トークンの予算の中で、エージェントのウィンドウに何を入れるかを選べる。
キーワード
| ことば | いみ |
|---|---|
| コンテキストウィンドウ | モデルが一回の呼び出しで読むものすべて |
| 予算 | ウィンドウに入れられるトークン(言葉の小さな切れはし)数の上限 |
| ピン留め | 決して削除しない部分 |
| ツールの結果 | ツールが出力して、モデルに返すもの |
| メモリ | 実行をまたいで残し、開始時に読み込むメモ |
| 削除の順番 | ウィンドウが一杯のとき、先に削る部分の順 |
学生が入力するセルは、このプレビューでは空白です。
ウィンドウは部分でできている
呼び出しのたびに、決まった順番で部分を送ります。指示は、エージェントが何者で何をしてよいかを伝えます。メモリは、長く残るメモです。ファイルは、作業の対象です。ツールの結果は、ツールが出力したものです。履歴は、ここまでの会話です。
メモリの保存方法は、前のモジュール「エージェントを作る」で学びました。ここで問うのは、どれだけ入るかだけです。
最初に何を削るか
ウィンドウ全体に予算を決め、収まるまで部分を削ります。作り直しの費用が小さいものから削ります。古いツールの結果は、もう一度頼めます。ファイルは、もう一度読めます。メモリは、作り直すのに時間がかかります。
指示は決して削りません。編集中のファイルはピン留めします。収まったらすぐ止めます。余分に削るたびに、何かが失われるからです。
コーディングエージェントの削除順
| 部分 | 削る順番 | 理由 |
|---|---|---|
| ツールの結果 | 最初。古いものから | ツールをもう一度動かせる |
| ファイル | 二番目。ピン留めを除く | ファイルをもう一度読める |
| メモリ | 三番目 | メモは作り直しに時間がかかる |
| 履歴 | 最後 | 依頼の内容が入っている |
| 指示 | 削らない | 指示がなければ、規則がなくなる |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
多くの人は、内容に関わらず古いメッセージから削ります。すると、指示が最初に消えます。部分の種類で削り、古さでは削りません。
モデル用の補助を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答に含まれるトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
モデル用の補助を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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 || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ウィンドウの組み立てを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const BUDGET = 180;' '// ここでは 1 文字を 1 トークンと数える。本物のトークナイザーは数え方が違う。' 'function size(text) { return Array.from(text).length; }' 'const parts = [' ' { kind: \"instructions\", name: \"rules\", text: \"あなたはコードの助手です。見せられたファイルだけを編集します。変更した内容を伝えます。\" },' ' { kind: \"memory\", name: \"notes\", text: \"このプロジェクトはタブを使います。テストは tests にあります。コミットの前に実行します。\" },' ' { kind: \"file\", name: \"app.py\", pinned: true, text: \"def total(items): return sum(items) # かごの値段を合計する\" },' '];' > context1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ウィンドウの組み立てを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'parts.push({ kind: \"tool_result\", name: \"run 1\", text: \"3 件成功 1 件失敗 test_total 期待 12 結果 10 tests の test_app\" });' 'parts.push({ kind: \"tool_result\", name: \"run 2\", text: \"4 件成功 0 件失敗\" });' 'parts.push({ kind: \"file\", name: \"readme\", text: \"かごの値段を計算して、レシートを印刷する小さな店のスクリプトです。\" });' 'const DROP = [\"tool_result\", \"file\", \"memory\"];' 'function total(list) {' ' let sum = 0;' ' for (const p of list) sum = sum + size(p.text);' ' return sum;' '}' >> context1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ウィンドウの組み立てを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function assemble(all, budget) {' ' const kept = all.slice();' ' const dropped = [];' ' for (const kind of DROP) {' ' for (const p of all) {' ' if (total(kept) <= budget) break;' ' if (p.kind === kind && p.pinned === undefined) {' ' kept.splice(kept.indexOf(p), 1);' ' dropped.push(p.name);' ' }' ' }' ' }' ' return { kept: kept, dropped: dropped, used: total(kept) };' '}' >> context1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ウィンドウの組み立てを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const w = assemble(parts, BUDGET);' ' console.log(\"予算 \" + BUDGET + \"、使用 \" + w.used);' ' for (const p of parts) {' ' console.log((w.kept.includes(p) ? \"保持 \" : \"削除 \") + p.kind + \" \" + p.name + \" \" + size(p.text));' ' }' ' const body = w.kept.slice(1).map(function (p) { return p.name + \": \" + p.text; }).join(\" | \");' ' const question = body + \" | 最初に編集するファイルはどれですか。一文で答えてください。\";' ' const messages = [{ role: \"system\", content: w.kept[0].text }, { role: \"user\", content: question }];' ' const reply = await ask(messages, \"最初に app.py を編集します。失敗したテストが total に関するものだからです。\", 60);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> context1.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して削られたものを見る
docker exec -w /home/node/project agent node context1.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
文字がトークンの代わり
本物のウィンドウは、モデルのトークナイザーでトークンを数えます。ここでは 1 文字を 1 トークンと数えるので、だれが実行しても同じ大きさが出ます。考え方は同じで、部分を足して上限と比べます。
Run は、残したもので短い呼び出しを一回行います。モデルがなければ、記録済みサンプルを表示します。
出力の一例
| 行 | 示すこと |
|---|---|
| 予算 180、使用 171 | これでウィンドウに収まる |
| 保持 instructions rules 43 | 指示は決して削られない |
| 保持 memory notes 47 | メモリは、ここでは削られない |
| 保持 file app.py 48 | ピン留めしたファイルは削られない |
| 削除 tool_result run 1 51 | 古いツールの結果が最初に消える |
| 削除 tool_result run 2 11 | 収まった時点で削除が止まる |
| 保持 file readme 33 | ファイルは削られず、readme は残る |
| モデル: ... | 一文です。言葉は実行のたびに変わる |
学生が入力するセルは、このプレビューでは空白です。