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

無料レッスン

LLM エージェントのコンテキスト設計

このレッスンのあとで: トークンの予算の中で、エージェントのウィンドウに何を入れるかを選べる。

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

モデルに見せるものを選ぶ

コンテキスト設計とは、エージェントの実行の各段階で、モデルのウィンドウに何を入れるかを決めることです。ウィンドウには限りがあるので、どの部分も選択の結果です。

すべてのファイルとツールの結果を残すエージェントは、数段階でウィンドウが一杯になることがあります。大事な指示が、誰も要らない出力の下に埋もれてしまいます。

このレッスンのゴール

メモ

トークンの予算の中で、エージェントのウィンドウに何を入れるかを選べる。

キーワード

ことばいみ
コンテキストウィンドウモデルが一回の呼び出しで読むものすべて
予算ウィンドウに入れられるトークン(言葉の小さな切れはし)数の上限
ピン留め決して削除しない部分
ツールの結果ツールが出力して、モデルに返すもの
メモリ実行をまたいで残し、開始時に読み込むメモ
削除の順番ウィンドウが一杯のとき、先に削る部分の順

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

ウィンドウは部分でできている

呼び出しのたびに、決まった順番で部分を送ります。指示は、エージェントが何者で何をしてよいかを伝えます。メモリは、長く残るメモです。ファイルは、作業の対象です。ツールの結果は、ツールが出力したものです。履歴は、ここまでの会話です。

メモリの保存方法は、前のモジュール「エージェントを作る」で学びました。ここで問うのは、どれだけ入るかだけです。

最初に何を削るか

ウィンドウ全体に予算を決め、収まるまで部分を削ります。作り直しの費用が小さいものから削ります。古いツールの結果は、もう一度頼めます。ファイルは、もう一度読めます。メモリは、作り直すのに時間がかかります。

指示は決して削りません。編集中のファイルはピン留めします。収まったらすぐ止めます。余分に削るたびに、何かが失われるからです。

コーディングエージェントの削除順

部分削る順番理由
ツールの結果最初。古いものからツールをもう一度動かせる
ファイル二番目。ピン留めを除くファイルをもう一度読める
メモリ三番目メモは作り直しに時間がかかる
履歴最後依頼の内容が入っている
指示削らない指示がなければ、規則がなくなる

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

よくある間違い

注意

多くの人は、内容に関わらず古いメッセージから削ります。すると、指示が最初に消えます。部分の種類で削り、古さでは削りません。

モデル用の補助を書く(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 は残る
モデル: ...一文です。言葉は実行のたびに変わる

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

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

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