ツール利用では提案を検証可能な実行記録へ変えます。在庫照会の最小ループから、結果の対応付け・停止状態・依存・書き込み結果不明の扱いへ進みます。提案、実行済み、タスク検証済みを区別できることが目標です。
固定されたプロセスからフィードバックに基づく意思決定へ
固定されたワークフローは、プログラムによって主要なステップが事前に計画される。例えば、注文の読み取り、フィールドの抽出、検証、データベースへの保存などである。モデルは特定のステップに関与できるが、主要なパスはコードによって制御される。一方、エージェントはモデルが観察に基づいて次のステップを動的に決定する。例えば、テストが失敗した場合、モデルは関連するファイルを自ら特定し、新しい修正行動を選択する。これら2つは組み合わせることができる。決定論的な外側のフローに、制限されたエージェントのサブタスクを含めることもできる。Building effective agents
ReActの研究は、推論と行動を交互に組織し、モデルが環境からのフィードバックに基づいて後続の処理を更新することを可能にする。ReAct これは「チャットインターフェースがあるかどうか」で製品を分類するものではない。チャットシステムもツールを呼び出すことができ、エージェントが1回の応答で簡単なリクエストを完了することもある。
モデルの推論は表現と出力を生成し、外部の行動は実行環境によって実現される。クライアント側のツールはアプリケーションによって実行され、サーバー側のツールはベンダーによって実行される可能性がある。したがって、権限と監査の境界は実際の実行コンポーネントに分散されており、すべての操作がローカルホスト上で行われると仮定することはできず、モデルが生成したコマンドテキストがすでに実行されたものとして扱うこともできない。
モデルの応答 テキストまたは構造化呼び出し 実行前の検証 名前、パラメータ、権限、予算 ツールの実行 ローカルまたはリモートサービス 結果とタスクの状態 呼び出しID、戻り値、証拠 モデルが行動を提案し、実行器が検証して実行し、結果が次の意思決定に入る モデルの応答終了、1回のツールの戻り値、ビジネス目標の達成は、3つの異なる完了の境界である。
完了の主張と根拠
完了は検証すべき主張です。未実行・未観測でも宣言できますが、受入層は独立して根拠を検査します。変更前の読み取りでは変更後の状態を証明できません。
図を準備しています 再読み込み
完了の主張と根拠 実行・観測・完了宣言は別物です。受入判定には実際の根拠が必要です。
{"id":"ai-loop-evidence","title":"完了の主張と根拠","summary":"実行・観測・完了宣言は別物です。受入判定には実際の根拠が必要です。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"execute\" data-i18n=\"execute\"></button><button id=\"observe\" data-i18n=\"observe\"></button><button id=\"claim\" data-i18n=\"claim\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}let actual=1,seen=0,claimed=false;function draw(){table('state',[[t('actual'),'v'+actual],[t('seen'),seen?'v'+seen:'—'],[t('claimed'),claimed?'✓':'—'],[t('accepted'),claimed&&seen===2?'✓':'×']]);set('explanation',t('explain'));document.body.dataset.accepted=claimed&&seen===2;}$('#execute').onclick=()=>{actual=2;draw()};$('#observe').onclick=()=>{seen=actual;draw()};$('#claim').onclick=()=>{claimed=true;draw()};$('#reset').onclick=()=>{actual=1;seen=0;claimed=false;draw()};draw();","audio":false,"strings":{"accepted":"根拠による受入","actual":"実際の版","claim":"完了を宣言","claimed":"完了宣言","execute":"変更を実行","explain":"先に完了を宣言しても受入は失敗します。変更前の v1 の観測は自動で v2 になりません。目標と一致する根拠が必要です。","heading":"完了の主張と根拠","intro":"実行・観測・完了宣言を操作し、システムの認識を比べます。","observe":"結果を読む","reset":"リセット","scope":"ローカルのファイル版を v1 から v2 に変えるモデルです。外部ツールは使いません。","scopeLabel":"モデルの範囲と前提","seen":"観測した版","state":"事実・観測・受入"}}
1回のツール呼び出しの完全なパス
ツールの定義はモデルのインターフェースであり、実行契約でもある
名前はモデルが行動を識別するのに役立ち、説明は用途と制限を説明し、スキーマはパラメータの構造を定義する。読み取り専用の在庫照会を例にとると、説明では、照会するのは販売可能数量であり、商品にどの識別子が使用されているか、結果の時間的またはバージョン的な意味合いを説明する必要がある。「在庫を取得する」という4文字だけでは、これらの境界を表現できない。
{
" name" : " lookup_stock" ,
" description" : " 商品SKUに基づいて現在販売可能な数量を照会する;読み取り専用で、在庫を予約しない。" ,
" input_schema" : {
" type" : " object" ,
" properties" : { " sku" : { " type" : " string" } } ,
" required" : [ " sku" ] ,
" additionalProperties" : false
}
}
これはClaudeスタイルのツール定義である。他のプロトコルではフィールド名が異なる可能性がある。スキーマは形状を制約するだけで、身元、商品が現在のテナントに属しているかどうか、または操作者が閲覧できるかどうかを自動的に検証するものではない。構造化された出力も実行側の検証に代わるものではない。実行側は、登録されたツールのみをディスパッチし、入力と権限を再検証しなければならない。Tool use with Claude
1回の照会は、以下の記録で追跡できる。
段階 例 保持すべき意味
モデルが行動を提案 呼び出しID c17、lookup_stock、sku=A1 これはリクエストであり、まだ実行されていない
実行前の検証 ツールが存在し、パラメータが有効で、主体に読み取り権限がある 実行が許可される範囲
ツールの実際の戻り値 available=3、在庫バージョン v83 これはどの読み取りの結果か
観察の伝達 呼び出しID c17が上記の結果に対応 結果の不一致を防ぐ
モデルの継続的な意思決定 販売可能数量を回答するか、次のステップを提案する 照会は在庫予約を意味しない
実行結果には、明確なステータス、必要なデータ、およびソースの位置指定を含めるのがよい。エラーも、商品なし、無効なパラメータ、権限不足、レート制限、または結果不明を区別するべきである。「失敗、再試行」という漠然とした返答は、意味のないループを誘発する。
メッセージプロトコルは完全でなければならない
Claudeのクライアントツールは、tool_use と一致する tool_result によって関連付けられる。1つの応答に複数の呼び出しを含めることができる。アプリケーションは、完全なアシスタント応答を保持し、プロトコルに従ってすべての対応する結果を整理する必要がある。思考、不透明な状態、および非テキストコンテンツは、「textのみを保存する」という単純化されたロジックによって失ってはならない。
エラー形式のフィードバックは後のモデルの行動に影響を与える可能性があるが、リクエストの中で「モデルの重みを密かに訓練している」わけではない。プロトコルの要件と推測的な行動解釈は分離され、具体的なツールメッセージの配置ルールは ツールインターフェースドキュメント に従う。
応答の停止とタスクの状態
モデルがツールを要求しなくなったことは、今回のモデル応答が停止したことを示すのみである。タスクが正しく完了した可能性もあれば、検証を漏らした、ユーザーからの情報補充が必要、切り捨てられた、または誤って成功を宣言した可能性もある。アプリケーションは、running、waiting、succeeded、failed、budget_exhausted などのタスク状態を独立して記録し、タスクの受入条件に基づいていつ succeeded とするかを決定する必要がある。
以下の表は、Claudeインターフェースにおける一般的な分岐であり、完全な列挙と後続のリクエスト形式は Stop reasons and fallback を参照のこと。
停止理由 意味 処理の方向
tool_useクライアントツールを要求 検証、実行、結果の伝達を完了
end_turnモデルが本轮を終了 タスクの受入条件を確認して終了
max_tokens出力が制限に達した 完全性を確認し、切り捨てられたパラメータを実行しない
pause_turnサーバー側の長時間ツールプロセスが一時停止 そのインターフェースの要件に従って再接続し、アプリケーションはループ制限を管理し続ける
refusalモデルが現在の生成を拒否 理由を記録し、ビジネスポリシーに従って終了するか、代替案を提供
ストリーミングは max_tokens を解除しない。ツールパラメータのJSONが半分しか受信できない状態でストリームが切断された場合、残りのフィールドを推測して実行してはならない。サーバー側のツールがまだ実行中の場合、pauseをクライアントが同じ副作用を再度実行するシグナルとして扱うこともできない。
「インポートされた重複コミットの修正」を例にとると、受入条件は、パッチが存在し、通常のテストが通過し、再起動後のリカバリテストが通過することかもしれない。モデルが「修正済み」と出力しても、通常のテストしか実行していない場合、タスクは未完了である。受入基準は最初に明確にし、実際のアーティファクトのバージョンに関連付けるべきである。
依存関係、再生、および副作用
並列実行の資格は依存関係による
2つの独立したドキュメントを同時に読み取ることは通常、並列実行可能である。「在庫を照会し、その結果に基づいて予約する」にはデータ依存関係があり、パラメータを推測して並列に送信することはできない。2つの行動の入力が独立していても、同じファイルまたは同じリソースを同時に変更する場合、競合が発生する可能性がある。
実行器は、データ依存関係、共有リソース、サービスの同時実行数の上限、および権限を同時に考慮する必要がある。並列実行が節約するのは、重複する待機時間のみであり、その代償にはピーク負荷と結果のマージが含まれる。モデルが1つのラウンドで複数の呼び出しを提案したからといって、無条件にすべてを並列実行してはならない。
呼び出しIDはビジネスの冪等キーではない
呼び出しIDは、応答を提案に関連付けるために使用される。モデルが再試行する場合、新しい呼び出しIDを生成する可能性があるが、同じビジネス行動を表現している。この場合、呼び出しIDのみで重複排除を行っても、注文の重複作成や通知の送信を防ぐことはできない。ビジネス操作キーは、アプリケーションによって明確なトランザクションセマンティクスに基づいて生成、保存され、冪等性をサポートするサービスによって、同じキーと同一のリクエストが一致するかどうかを検証する必要がある。
実行器 注文サービス データベース リクエスト:操作キー K42 送信完了 応答の喪失 × タイムアウト後の重要な問題:行動が失敗したのか、それとも結果が返ってこなか ったのか? タイムアウトを未実行と直接見なしてはならない。ビジネス操作キーで照会するか、サービスの冪等プロトコルに従って 再試行する。
失敗ステータス 再試行可能か 適切な処理
読み取りクエリのタイムアウト 通常可能だが、データが変更されている可能性がある 有限回の再試行を行い、読み取り時の時点情報を保持
パラメータ検証の失敗 そのまま再試行しても意味がない 修正可能な具体的なフィールドエラーを返す
権限なし 表現を変えて回避しない 認可の範囲内で調整するか、ブロックを報告
書き込み操作のタイムアウト、結果不明 未実行と仮定してはならない 操作キーで結果を照会するか、サービスの冪等プロトコルを使用
完了した行動、伝達喪失 再実行すると副作用が重複する可能性がある 保存された結果を再生し、ビジネス状態を確認
キャンセルもロールバックではない。モデルの生成を停止するか、ローカルの待機をキャンセルしても、リモートで開始された行動が取り消されることは保証されない。実行器は、「未実行の確認」、「完了」、「結果不明」を区別し、復元時には不明な状態を最初に解消する必要がある。
タイムアウト後、書き込みは何回か
ネットワーク結果と業務結果を分けます。応答なしは未書込を意味しません。サーバーの事実が見えても呼出側では不明であり、照会や同じキーの再試行で確認します。重複排除はサーバーの保証が必要です。
図を準備しています 再読み込み
タイムアウト後、書き込みは何回か 呼出 ID は試行、操作キーは業務操作を識別します。同じキーの副作用を防ぐのはサーバーの重複排除です。
{"id":"ai-unknown-write","title":"タイムアウト後、書き込みは何回か","summary":"呼出 ID は試行、操作キーは業務操作を識別します。同じキーの副作用を防ぐのはサーバーの重複排除です。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button><button id=\"same\" data-i18n=\"same\"></button><button id=\"fresh\" data-i18n=\"fresh\"></button><button id=\"query\" data-i18n=\"query\"></button><button id=\"cancel\" data-i18n=\"cancel\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"metrics\"><div><span data-i18n=\"effects\"></span><strong id=\"effects\"></strong></div><div><span data-i18n=\"known\"></span><strong id=\"known\"></strong></div></div><section class=\"panel\"><h3 data-i18n=\"history\"></h3><div id=\"history\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n.metrics strong{font-size:16px;min-height:4em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}let keys=new Set,history=[],known='none';function draw(){set('effects',keys.size);set('known',t(known));table('history',Array.from({length:4},(_,i)=>history[i]||['—','—','—']));$('#send').disabled=history.length>0;for(const id of['same','fresh'])$('#'+id).disabled=!history.length||history.length>=4;for(const id of['query','cancel'])$('#'+id).disabled=!history.length;set('explanation',t('explain'));document.body.dataset.effects=keys.size;document.body.dataset.known=known;}function send(key,lost=false){let duplicate=keys.has(key);keys.add(key);history.push(['call-'+(history.length+1),key,lost?'?':duplicate?'↩':'✓']);if(key==='op-A')known=lost?'unknown':'done';draw()}$('#send').onclick=()=>send('op-A',true);$('#same').onclick=()=>send('op-A');$('#fresh').onclick=()=>send('op-'+(history.length+1));$('#query').onclick=()=>{known=keys.has('op-A')?'done':'none';draw()};$('#cancel').onclick=draw;$('#reset').onclick=()=>{keys.clear();history=[];known='none';draw()};draw();","audio":false,"strings":{"cancel":"ローカル待機を中止","done":"確定済み","effects":"サーバーの書込回数","explain":"初回タイムアウト時にはサーバーで確定済みです。同じキーなら重複記録に一致し、新キーなら別操作になります。待機中止では確定を取り消せません。","fresh":"新しいキーで再試行","heading":"タイムアウト後、書き込みは何回か","history":"呼出 / 操作キー / 結果","intro":"応答を失う書き込みを送り、再試行や照会を選びます。","known":"元の操作の既知状態","none":"未送信","query":"元の操作を照会","reset":"リセット","same":"同じキーで再試行","scope":"メモリ内サーバーがキーを重複排除します。新キーごとに1増え、要求は最大4回。初回応答は失われます。実環境では保持期間と原子性も必要です。","scopeLabel":"モデルの範囲と前提","send":"書込・応答消失","unknown":"結果不明"}}
実行可能なローカル実行器の例
以下では、あらかじめ用意したモデル応答を使用して、1回の読み取り専用在庫照会をシミュレートする。これは、ツールのホワイトリスト、パラメータ、呼び出し結果の関連付け、およびリクエストの上限を検証する。モデルAPIを呼び出したり、実際のビジネス書き込みを実行したりすることはない。本番システムには、永続的な状態、身元と権限、タイムアウト、完全なプロトコル適応、および監査が必要である。
STOCK = { " A1" : 3 }
def execute ( call ) :
if call . get ( " name" ) != " lookup_stock" :
return { " ok" : False , " error" : " unknown_tool" }
args = call . get ( " input" )
if not isinstance ( args , dict ) or set ( args ) != { " sku" } :
return { " ok" : False , " error" : " invalid_arguments" }
if not isinstance ( args [ " sku" ] , str ) :
return { " ok" : False , " error" : " invalid_sku" }
if args [ " sku" ] not in STOCK :
return { " ok" : False , " error" : " not_found" }
return { " ok" : True , " available" : STOCK [ args [ " sku" ] ] }
def run ( model , max_requests = 3 ) :
observations , seen = [ ] , set ( )
for _ in range ( max_requests ) :
response = model ( list ( observations ) )
if response [ " kind" ] == " final" :
return { " state" : " model_ended" , " text" : response [ " text" ] ,
" observations" : observations }
if response [ " kind" ] != " tool" :
return { " state" : " invalid_response" }
call = response [ " call" ]
call_id = call . get ( " id" )
if not isinstance ( call_id , str ) or not call_id or call_id in seen :
return { " state" : " invalid_call_id" }
seen . add ( call_id )
observations . append ( { " call_id" : call_id , " result" : execute ( call ) } )
return { " state" : " budget_exhausted" , " observations" : observations }
def scripted ( observations ) :
if not observations :
return { " kind" : " tool" , " call" : {
" id" : " c17" , " name" : " lookup_stock" , " input" : { " sku" : " A1" } } }
assert observations [ 0 ] == {
" call_id" : " c17" , " result" : { " ok" : True , " available" : 3 } }
return { " kind" : " final" , " text" : " 現在、販売可能数量が3件照会されました。予約はされていません。" }
result = run ( scripted )
assert result [ " state" ] == " model_ended"
assert execute ( { " name" : " delete_stock" } ) [ " error" ] == " unknown_tool"
assert execute ( { " name" : " lookup_stock" , " input" : { " sku" : 1 } } ) [ " error" ] == " invalid_sku"
assert run ( scripted , max_requests = 1 ) [ " state" ] == " budget_exhausted"
print ( result [ " text" ] )
この例では、1回のツール呼び出し後、最終応答を取得する前にリクエスト予算が枯渇すると、明確に budget_exhausted を返す。これは、ループの終了をすべて「成功」とマッピングするよりも正確である。実際のアプリケーションでも、完了した呼び出しを永続的に保存し、プロセスの再起動後に副作用の記録が失われるのを防ぐべきである。
ツールの境界とループの収束
「最大10ラウンドで実行する」という最後の保険だけでは不十分である。各ラウンドで新しい証拠が得られたか、アーティファクトが変更されたか、エラーが解消されたかを観察するべきである。同じ失敗が連続して発生し、条件が変化しない場合、繰り返しの行動は通常、進展をもたらさない。無限に再送信するのではなく、より具体的なエラーを返す、検索範囲を変更する、または不足している条件を報告するべきである。
ツールの出力はサイズを制限し、読み続けられる位置を保持する必要がある。ログを切り捨てる場合は、切り捨てられた範囲、合計量、およびファイルの位置指定を明確に表示し、モデルが完全な証拠を取得したと誤解させないようにする。コンテキスト管理については コンテキストエンジニアリング を参照。
専用ツールは、パラメータとビジネスセマンティクスを実行器に公開し、検証、認可、記録を容易にする。Shellツールはより一般的な機能を提供するが、コマンドの許可リストだけでは、プログラムが行えるすべての操作を制限するには不十分である。実際の権限は、実行身份、ファイルおよびネットワークアクセスの範囲、隔離環境、およびリソース制限によって制御する必要がある。具体的な戦略は、すでに認可されたタスクに一致させるべきであり、各ステップの取り消し可能な操作を繰り返しの確認に退化させないこと。詳細は セキュリティと保護 を参照。
補足:環境シミュレータと実際の実行
エージェントのトレーニングと評価には、環境シミュレータを使用できる。ポリシーが行動を提案し、シミュレータが観察を生成し、ポリシーを継続する。言語世界モデルは、このような環境応答の学習を試みる。例えば、Qwen-AgentWorld モデルカード は、多様な対話環境を対象としたシミュレーション能力について記述している。これは、ファイルを直接変更したり、ビジネスサービスを呼び出したりする実際の実行パスとは異なる。
シミュレータは、一見合理的だが状態が矛盾した結果を生成する可能性がある。評価時には、単一の観察が本物のように見えるかどうかだけでなく、連続した行動の因果的一貫性、リソース制約、および長期状態が維持されているかも確認する必要がある。シミュレーション環境での高スコアが、実際のツール実行の成功率を直接証明するものではない。シミュレータまたはエージェントとして適しているかどうかは、それぞれ測定し、アクティベートされたパラメータの量だけで、ある種のロールが本質的に実行可能か不可能かを主張してはならない。
今後の MCP と Skills では、機能がどのように接続され、オンデマンドで提供されるかについて議論し、記憶と状態 では、長時間のタスクと障害復旧時に何を保存する必要があるかについて議論する。
次に読む:ツールインターフェースと MCP 。