プロンプトに入れる量には限りがある
コンテキストの予算とは、プロンプトの各部分が使えるトークンの数です。モデルは一度に読める量が決まっているので、部分を足して多すぎるときは、何かを諦めます。
コスト、トレース、信頼性のモジュールでは、お金と呼び出しの予算を扱いました。このレッスンは、窓そのものを予算立てし、さらに、何を先に捨てるか、何が抜けたかを利用者にどう伝えるかまで進みます。
このレッスンのゴール
メモ
窓に何が入り、何を先に捨てるかを、決めることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| トークン | モデルが数える、文章の小さな単位 | 日本語では、ここで 2 文字を 1 と数える |
| 窓 | モデルが一回で読み書きできる最大のトークン数 | |
| 予算 | プロンプトの一部分が使えるトークン数 | |
| 優先度 | 部分の大切さ。0 は決して捨てない | |
| 切り詰める | 収まるように短くすること | |
| 余地 | 返答のために空けておく分 |
学生が入力するセルは、このプレビューでは空白です。
返答にも、余地がいる
窓は、プロンプトと返答で分け合います。プロンプトが窓をいっぱいにすると、返答が途中で切れるか、呼び出しが失敗します。
そこで、まず返答の余地を空けます。次に、数え方は目安なので、少し余裕を引きます。残りがプロンプトの予算です。
例: プロンプトの予算
| 手順 | トークン | 理由 |
|---|---|---|
| 窓 | 4000 | モデルが扱える最大 |
| 返答の余地 | 500 | 返答にも場所がいる |
| 余裕 5 パーセント | 200 | 4000 の 5 パーセント。数え違いに備える |
| プロンプトの予算 | 3300 | 4000 引く 500 引く 200 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
窓はプロンプトのためだけにあると考える人が多いです。実は返答もその窓に入ります。プロンプトが窓をすべて使うと、答える場所が残りません。
どの部分を先に残すか
各部分に優先度をつけます。規則と利用者の質問は優先度 0 で、決して捨てません。これが収まらないなら、プロンプトは壊れているので、そう伝えます。
ほかは大切な順に取ります。収まれば残し、収まらなければ捨てます。ただし、あとに続く小さい部分は、入ることがあります。
例: 予算 3300 の場合
| 部分 | トークン | 優先度 | ここまでの合計 | 結果 |
|---|---|---|---|---|
| 規則 | 300 | 0 | 300 | 残す |
| 質問 | 100 | 0 | 400 | 残す |
| 資料A | 900 | 1 | 1300 | 残す |
| 資料B | 800 | 2 | 2100 | 残す |
| 資料C | 700 | 2 | 2800 | 残す |
| 履歴 | 1400 | 3 | 4200 で超える | 捨てる |
学生が入力するセルは、このプレビューでは空白です。
履歴は、全部ではなく切り詰める
履歴を丸ごと捨てると、会話がすべて消えます。よい方法は、収まる新しい発言だけを残すことです。
新しい発言から古いほうへ進み、収まらない最初の発言で止まります。会話の真ん中に穴があくと、意味が通らなくなるからです。
例: 残りが 500 トークンのとき
| 発言 | トークン | 合計 | 結果 |
|---|---|---|---|
| いちばん新しい | 200 | 200 | 残す |
| その前 | 250 | 450 | 残す |
| さらに前 | 300 | 750 で超える | ここで止める |
| いちばん古い | 40 | 見ない | 450 に足して 490 なら入る大きさでも、捨てる |
学生が入力するセルは、このプレビューでは空白です。
助っ人 model.js を書く(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"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
助っ人 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 data = await res.json();' ' const text = data.choices[0].message.content;' ' return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"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\");' 'function tokens(text) { return Math.ceil(text.length / 2); }' 'const parts = [' ' { name: \"規則\", tokens: 60, priority: 0 },' ' { name: \"質問\", tokens: 25, priority: 0 },' ' { name: \"資料A\", tokens: 120, priority: 1 },' ' { name: \"資料B\", tokens: 90, priority: 2 },' ' { name: \"履歴\", tokens: 150, priority: 3 },' ' { name: \"資料C\", tokens: 80, priority: 2 },' '];' > budget.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算のスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function assemble(parts, budget) {' ' const order = parts.slice().sort(function (a, b) { return a.priority - b.priority; });' ' const kept = [];' ' let used = 0;' ' for (const p of order) {' ' if (p.priority === 0 || used + p.tokens <= budget) { kept.push(p.name); used = used + p.tokens; }' ' }' ' return { kept: kept, used: used };' '}' >> budget.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算のスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const names = parts.map(function (p) { return p.name; });' ' for (const budget of [350, 600]) {' ' const r = assemble(parts, budget);' ' const lost = names.filter(function (n) { return r.kept.includes(n) === false; });' ' console.log(\"予算 \" + budget + \": 残した \" + names.filter(function (n) { return r.kept.includes(n); }).join(\", \"));' ' console.log(\" 捨てた \" + (lost.join(\", \") || \"なし\") + \"、使用 \" + r.used);' ' }' ' const q = \"短く一文で答えてください。長すぎるプロンプトは、まず何を捨てますか。\";' ' const sample = \"古い履歴や役に立たない部分から先に捨て、規則と質問は残します。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 80);' ' console.log(\"モデル: \" + reply.text);' ' console.log(\"私の数 \" + tokens(q) + \"、モデルの数 \" + (reply.inTokens === null ? \"報告なし\" : reply.inTokens));' '}' 'main();' >> budget.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
予算を動かす
docker exec -w /home/node/project agent node budget.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| 予算 350: 残した 規則, 質問, 資料A, 資料B | 収まった部分を、元の順で表示 |
| 捨てた 履歴, 資料C、使用 295 | 捨てた部分と、使ったトークン |
| 予算 600: ... 捨てた なし | 余裕があれば、全部入る |
| モデル: ... | ラボからの一文。モデルが答えなければ記録済みサンプル |
| 私の数 17、モデルの数 ... | 自分の数え方と、モデルの数。なければ報告なし |
学生が入力するセルは、このプレビューでは空白です。