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

無料レッスン

モデル呼び出しをトレースする方法

このレッスンのあとで: 私は、各呼び出しが何をしたかを記録して、失敗や遅れの原因を探せます。

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

遅い段階を見つける

モデル呼び出しのトレースとは、リクエストの各段階が何をして、どれだけ時間がかかったかを記録することです。モデルを使う機能は、処理の鎖の途中で失敗します。最初と最後にログを一行ずつ残しても、どこで失敗したかは分かりません。

ユーザーから「返事に十秒かかる」と連絡が来たとします。原因はプロンプトか、モデルか、再試行か、後の確認でしょうか。トレースがあれば、ひと目で分かります。

このレッスンのゴール

メモ

私は、各呼び出しが何をしたかを記録して、失敗や遅れの原因を探せます。

キーワード

ことばいみ
トレース一つのリクエストを始めから終わりまで記録したもの
スパントレースの中の、時間を測った一つの段階
親別のスパンを含んでいるスパン
属性スパンに付ける、名前の付いた情報
トレース ID一つのリクエストの全スパンが共有する ID
マスキング記録を保存する前に秘密情報を取り除くこと
自分の時間スパンの所要時間から子の所要時間を引いた値

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

トレースはスパンの木

トレースは一つのリクエストです。いくつものスパンでできています。スパンには名前、開始、終了、親があります。親とは、そのスパンを含むスパンのことです。親を持たないスパンは一つだけで、これがルートです。

一つのリクエストの全スパンは、同じトレース ID を持ちます。保存先はこれでスパンを集め直します。スパンには、ルートや結果などの属性も付きます。

例: 一つのリクエスト

スパン親開始終了所要時間
リクエストなし0856856 ms
プロンプト作成リクエスト044 ms
モデル呼び出しリクエスト4854850 ms
応答の確認リクエスト8548562 ms

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

トレースに入れてはいけないもの

トレースの保存先は多くの人が読み、何週間も残ります。ですから、スパンには秘密情報を入れません。API キー、トークン、パスワードです。個人情報も入れません。メールアドレス、氏名、電話番号です。

多くの場合、プロンプトや応答の本文も入れないほうが安全です。代わりに、長さ、件数、結果を残します。危険なキーは、スパンを保存する前に、一か所でまとめて取り除きます。この手順をマスキングと呼びます。

よくある間違い

注意

「ログは社内のものだから、プロンプトを全部書いても安全だ」と考える人が多くいます。しかし、プロンプトにはユーザーが打った内容が入り、そこには名前や秘密が混ざります。本文ではなく、長さと結果を記録しましょう。

モデル用の助っ人を書く(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\");' '' 'let now = 0; // 自分で進める時計。だれが動かしても同じ時間になる' 'const spans = [];' 'function tick(ms) { now = now + ms; }' 'function start(name, parent) {' '  const span = { id: spans.length + 1, name, parent, start: now, end: now, attrs: {} };' '  spans.push(span);' '  return span;' '}' 'function finish(span) { span.end = now; }' > trace1.js"

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

トレーサーを書く(2)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// トレースに入れてはいけないキー。秘密情報と個人情報。' 'const NEVER = [\"email\", \"api_key\", \"prompt\", \"name\"];' 'function note(span, key, value) {' '  span.attrs[key] = NEVER.includes(key) ? \"[除去済み]\" : value;' '}' >> trace1.js"

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

トレーサーを書く(3)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function show(parent, depth) {' '  for (const s of spans) {' '    if (s.parent === parent) {' '      console.log(\"  \".repeat(depth) + s.name + \" \" + (s.end - s.start) + \" ms \" + JSON.stringify(s.attrs));' '      show(s.id, depth + 1);' '    }' '  }' '}' >> trace1.js"

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

トレーサーを書く(4)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function modelStep(root) {' '  const call = start(\"モデル呼び出し\", root.id);' '  const messages = [{ role: \"user\", content: \"トレースの使い道を一文で一つ答えてください。\" }];' '  const reply = await ask(messages, \"トレースを見ると、リクエストのどの段階が遅かったか分かります。\", 40);' '  tick(850); // 実際の呼び出しにかかった時間にかかわらず、自分の時計を850ミリ秒進める' '  note(call, \"result\", reply.text.length > 0 ? \"ok\" : \"empty\");' '  note(call, \"prompt\", messages[0].content);' '  finish(call);' '}' >> trace1.js"

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

トレーサーを書く(5)

コマンド
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const root = start(\"リクエスト\", 0);' '  note(root, \"route\", \"/question\");' '  note(root, \"email\", \"[email protected]\");' '  const build = start(\"プロンプト作成\", root.id);' '  tick(4); finish(build);' '  await modelStep(root);' '  const check = start(\"応答の確認\", root.id);' '  tick(2); finish(check); finish(root);' '  console.log(\"トレース t-0001\");' '  show(0, 0);' '}' 'main();' >> trace1.js"

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

実行してトレースを表示する

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

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

練習用の代用品と、トレースの読み方

ラボにはトレースサービスがありません。そこで、スパンはリストに入れ、時計はスクリプトが進める数にしています。本物のシステムでは、スパンを本物のトレースサービスへ送り、本物の時計を読みます。

形は同じです。

遅い段階を探すには、自分の時間を使います。スパンの所要時間から、子の所要時間を引いた値です。親は子を含むので、いつも遅く見えるからです。

出力の例

行示していること
トレース t-0001トレース ID
リクエスト 856 msルート。ルートの情報があり、メールは除去されている
プロンプト作成 4 ms半角スペース二つ分下がった子
モデル呼び出し 850 ms遅い段階。プロンプトは除去されている
応答の確認 2 ms最後の子

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

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

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