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

無料レッスン

モデルの応答をキャッシュする方法

このレッスンのあとで: 私は、モデルの応答を安全にキャッシュし、何がヒットを決めるのかを説明できます。

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

同じ答えに二度払わない

モデルの応答をキャッシュするとは、応答を保存しておき、同じリクエストが来たときにそれを返すことです。呼び出しにはお金と時間がかかり、同じリクエストは何度も来るので、効果があります。

ヘルプページが、訪れた人ごとに同じ返品ルールの要約をモデルに頼むとします。千回呼べば、一つの答えに千回払うことになります。一回の呼び出しと九百九十九回のヒットなら、そうはなりません。

このレッスンのゴール

メモ

私は、モデルの応答を安全にキャッシュし、何がヒットを決めるのかを説明できます。

キーワード

ことばいみ
キャッシュ払い済みの応答をしまっておく場所
キャッシュキー二つのリクエストが同じかを決める値
ヒットキャッシュから答えられたリクエスト
ミスモデルを呼ぶ必要があったリクエスト
有効期限エントリーを捨てる時点
個人情報一人の人を特定できる情報すべて

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

キーはリクエスト全体から作る

二つのリクエストが答えを共有してよいのは、モデルが同じものを見る場合だけです。そのため、キャッシュキーは、応答を変えるものすべてを含めます。モデル、役割付きの各メッセージ、そして温度や応答の上限などのパラメーターです。

一つでも抜くと、間違った答えを返してしまいます。このテキストのハッシュを使うと、キーを短く保てます。

例: キーを変えるもの

変更同じキーか理由
モデル、メッセージ、パラメーターが同じはいモデルが同じリクエストを見る
メッセージの一語が違ういいえメッセージはキーの一部
温度 0 のあとに 1いいえ応答が変わりうる
別のモデルいいえ別のモデルは別の答えを返す

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

エントリーは期限で消え、個人情報は入れない

答えは古くなります。各エントリーに有効期限、つまり秒単位の寿命を付けましょう。期限が来たらエントリーを捨て、次のリクエストはミスになります。

個人情報は決してキャッシュしません。ある人のアドレスや注文から作った答えが、同じキーのほかの人に返るかもしれないからです。検索の前にリクエストを調べ、該当するものはキャッシュを使いません。

よくある間違い

注意

「答えはただの文字だから、キャッシュはいつも安全だ」と考える人が多くいます。ある人の情報を含む答えが保存されると、別の人に見えてしまうことがあります。最初のエントリーを保存する前に、何を保存してよいかを決めましょう。

モデル用の助っ人を書く(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 crypto = require(\"crypto\");' 'const { ask } = require(\"./model\");' '' 'let now = 0; // 秒。自分で進める時計' 'const TTL = 60; // エントリーの寿命は60秒' 'const cache = new Map();' > cache2.js"

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

キャッシュを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// キーはリクエスト全体から作る。モデル、メッセージ、パラメーター。' 'function keyOf(request) {' '  const whole = JSON.stringify([request.model, request.messages, request.temperature, request.max_tokens]);' '  return crypto.createHash(\"sha256\").update(whole).digest(\"hex\").slice(0, 12);' '}' '// 個人情報は決してキャッシュしない。ここでは、メールアドレスか長い数字。' 'function personal(request) {' '  return /@|[0-9]{7,}/.test(JSON.stringify(request.messages));' '}' >> cache2.js"

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

キャッシュを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function answer(request, sample) {' '  if (personal(request)) return \"スキップ(個人情報)\";' '  const key = keyOf(request);' '  const found = cache.get(key);' '  if (found && found.expires > now) return \"ヒット\";' '  const reply = await ask(request.messages, sample, request.max_tokens);' '  cache.set(key, { text: reply.text, expires: now + TTL });' '  return found ? \"期限切れ、ミス\" : \"ミス\";' '}' >> cache2.js"

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

キャッシュを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const tip = [{ role: \"user\", content: \"キャッシュのコツを一文で教えてください。\" }];' '  const base = { model: \"lab\", temperature: 0, max_tokens: 40, messages: tip };' '  const other = { ...base, temperature: 1 };' '  const secret = { ...base, messages: [{ role: \"user\", content: \"[email protected] にコツを送ってください。\" }] };' '  const sample = \"全員に同じ内容になるものだけを保存しましょう。\";' '  for (const at of [0, 30, 90]) {' '    now = at;' '    console.log(\"t=\" + at + \" s: \" + (await answer(base, sample)));' '  }' '  console.log(\"温度を変えても同じキーか: \" + (keyOf(base) === keyOf(other)));' '  console.log(\"個人情報を含むリクエスト: \" + (await answer(secret, sample)));' '}' 'main();' >> cache2.js"

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

キャッシュを実行する

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

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

これは練習用の代用品です

ラボにはキャッシュサーバーがありません。そこで、キャッシュはスクリプトの中の Map で、時計はスクリプトが決める数にしています。本物のシステムでは、すべてのサーバーが共有する本物のキャッシュサービスを使います。

決まりは同じです。リクエスト全体から作るキー、有効期限、そして個人情報の確認です。

出力の例

行示していること
t=0 s: ミス最初のリクエストはモデルを呼ぶ
t=30 s: ヒット同じリクエストをキャッシュから答える
t=90 s: 期限切れ、ミスエントリーが60秒たったので、モデルをもう一度呼ぶ
温度を変えても同じキーか: falseパラメーターが違えば別のキー
個人情報を含むリクエスト: スキップ(個人情報)キャッシュには一度も触れていない

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

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

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