まず 1 回の要求で、原文が示す事実、返してよい内容、確認が必要な項目を決めます。注文フォームの事前入力を例に、タスク定義・欠損値・構造制約・業務検証をつなぎます。有効な JSON でも、値の根拠と利用条件の確認が必要です。
プロンプトで目標、入力、境界を記述
「あなたは専門家です。正確に抽出してください」というだけでは、「正確」の定義が不明確です。検証可能なタスクとは、少なくとも以下を明確にするものです:入力は何か、どのフィールドが必要か、どの程度まで推論を許容するか、欠損や競合をどう処理するか、最終結果を誰が使うか。プロンプトエンジニアリングでも、変更が有効かどうかを比較するためには、まず成功基準が必要です。Prompt engineering overview
例えば、入力が「A1が欲しい。数量は後で確認」とする場合、タスクが sku と quantity のみを要求すると、モデルは 1 と推測する可能性があります。未知の数量は null と規定し、needs_input フラグを立てることで、「答えがないこと」を正当な結果として扱えます。
| 元の曖昧さ | 明確にするべき規約 |
| 数量が未記載の場合、デフォルトで1件とするか | 推測せず、null を返す |
| 複数の商品に言及しているが、フィールドは1つのみ対応可能 | 曖昧さをフラグ立てするか、配列に変更し、勝手に1つ選ばない |
| 「来週」の期間 | 基準日とタイムゾーンを指定するか、原文を保持 |
| 出典間で内容が矛盾している | 矛盾と出典を保持し、権威ある方を勝手に選ばない |
| 出力が直接注文をトリガーする | 抽出は候補であり、自動実行権限を与えない |
安定したルールは信頼性の高い指示層に配置できます。現在の資料では、出典が明確なデータを提供しています。メッセージのロールやコンテンツブロックはインターフェースの構造であり、単なるテキストの適当な連結として扱うべきではありません。XML タグ、見出し、または区切り文字はコンテンツを区別するのに役立ちますが、安全な分離を保証するものではありません。外部資料中の「以前のルールを無視せよ」といった指示も、データ処理として扱う必要があります。
教育的なプロンプトは以下のように構成できます:
タスク:ユーザー原文から商品 SKU と数量を1つ抽出し、人間による確認前のフォーム自動入力用とする。
ルール:原文に明示的に記載されている情報のみを使用する。欠損フィールドは null。
ステータス:2つのフィールドが明確で競合がない場合、complete。それ以外の場合、needs_input。
範囲:注文の作成、プロフィール情報の照会、支払い情報の推論を行わない。
出力:提供されたスキーマに従う。結果の前後に説明文を追加しない。
ユーザー原文:A1が欲しい。数量は後で確認。
プロンプト内の「範囲」はモデルに何を行うべきかを指示しますが、実際のツール権限は実行側で管理されます。操作権限を一文で記述するだけでは、Agent ループ における実行前の検証を代替できません。
具体例で実際の曖昧さを解消
few-shot の例は、具体的な入出力を通じて規約を説明します。null、列挙型、単位、競合など、抽象的なルールが誤解されやすい場面に適しています。必ずしも例が多いほど良いわけではなく、正例だけで境界条件の説明を代替できるわけでもありません。
| 入力 | 期待される核心結果 | 説明されるルール |
| A1を3個買う | A1、3、complete | 通常の抽出 |
| A1が欲しい。数量は後で確認 | A1、null、needs_input | 欠損は推測不可 |
| A1を3個、待って2個に変更 | 明確な修正ルールに従って処理 | 時間順序と修正のセマンティクス |
| A1かB2のどちらか。未決定 | SKU を勝手に選ばない | 曖昧さは表現する必要がある |
スキーマに曖昧さを表現する余地がない場合、例がどれだけ優れていても無理やり値を埋めることを避けられません。まずはデータモデルを調整し、次に表現を調整します。開発中のテストケースをすべて例として書き込み、それらの改善を汎化能力の向上と見なさないでください。
多様なケースが必要な場合は、各ケースが制約やトレードオフにおいて異なることを明示的に指定します。短い出力が必要な場合は、文字数とフィールドを直接規定します。temperature、effort、文字数制限はそれぞれ異なる問題を制御するものであり、temperature=0 → effort=low のような変換公式として記述すべきではありません。詳細なメカニズムについては、トークンとサンプリング および 推論と思考 を参照してください。
構造制約の適用範囲
プロンプトで JSON を要求するだけでは、モデルが文字通りの規約に従うように促すに過ぎません。制約付きデコーディングは、生成時に有効な出力の継続を制限し、完全な成功出力がサポートされるスキーマに適合するようにします。クライアント側での検証は生成後のチェックであり、サービスがサポートしない制約を追加できますが、生成済みのトークンを後から変更することはありません。
| メカニズム | 制約対象 | アプリケーション側での検証が必要 |
| JSON 形式要件またはスキーマ | 解析可能な JSON 構文(インターフェースにより能力は異なる) | フィールド、型、セマンティクス |
| スキーマによるレスポンス制約 | 最終出力の指定された構造 | 完全なレスポンス、証拠、ビジネス条件 |
| 厳格なツール引数 | ツールの選択および引数の有効な構造 | 権限、リソース状態、アクションのセマンティクス |
| クライアント側の型/ビジネス検証 | 受信したオブジェクト | 後続実行時の状態変化 |
Claude の output_config.format は JSON レスポンス用に使用され、ツール上の strict: true は厳格なツール呼び出し用に使用されます。これらを組み合わせることは可能ですが、利用可能なスキーマのサブセットや機能互換範囲は、対象モデルおよび SDK に照らして確認する必要があります。構造化出力
JSON Schema 自体の境界
properties はフィールドを記述しますが、これらのフィールドが必須であることを示すものではありません。必須かどうかは required で宣言します。additionalProperties: false は未定義のフィールドを拒否しますが、定義済みフィールドを自動的に必須にはしません。null は値であり、フィールドが存在しないこととは異なります。JSON Schema object
例えば、quantity フィールドは常に存在するが、不明な場合は null であるようにしたい場合、required と nullable 型の両方を指定する必要があります。これを integer として定義し required に設定すると、「原文に数量がない」場合、期待される表現(null)が得られなくなります。
ベンダーの構造化出力は、JSON Schema のサブセットのみをサポートする場合があります。一部の SDK は、完全なスキーマをサービスがサポートする簡略形式に変換し、クライアント側で元の制約に基づいて検証します。これは、サポートされていないフィールドを含む生の JSON リクエストを直接送信することとは異なります。「SDK が受け入れ可能」だからといって、「サーバー側で生成時にすべての制約が強制される」と解釈してはいけません。SDK スキーマ変換の説明
有効な JSON から利用可能な結果まで
数量を3から5にすると JSON と型は通っても原文の根拠が失われます。3のまま在庫を減らすと業務条件で失敗します。独立した判定から修正すべき層を特定できます。
図を準備しています
有効な JSON から利用可能な結果まで構文・schema・原文の根拠・業務条件は別の検査です。前段の合格は次段を保証しません。
{"id":"ai-output-contract","title":"有効な JSON から利用可能な結果まで","summary":"構文・schema・原文の根拠・業務条件は別の検査です。前段の合格は次段を保証しません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"valid\" data-i18n=\"valid\"></button><button data-mode=\"invent\" data-i18n=\"invent\"></button><button data-mode=\"missing\" data-i18n=\"missing\"></button><button data-mode=\"truncated\" data-i18n=\"truncated\"></button></div><div class=\"control\"><label for=\"stock\" data-i18n=\"stock\"></label><output for=\"stock\" id=\"stock-value\"></output><input id=\"stock\" type=\"range\" min=\"0\" max=\"8\" value=\"2\" step=\"1\"></div><section class=\"panel\"><h3 data-i18n=\"json\"></h3><div id=\"json\"></div></section><section class=\"panel\"><h3 data-i18n=\"layers\"></h3><div id=\"layers\"></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#json{min-height:7.2em;font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap}.table .tr>div{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 mode='valid';function draw(){pressed(mode);let src={valid:'{\"sku\":\"A1\",\"quantity\":3}',invent:'{\"sku\":\"A1\",\"quantity\":5}',missing:'{\"sku\":\"A1\"}',truncated:'{\"sku\":\"A1\",\"quantity\":'}[mode],parsed;try{parsed=JSON.parse(src)}catch{}let syntax=!!parsed,schema=syntax&&parsed.sku==='A1'&&Number.isInteger(parsed.quantity),source=schema&&parsed.quantity===3,business=schema&&parsed.quantity<=n('stock');set('stock-value',n('stock'));set('json',src);table('layers',[[t('syntax'),syntax?'✓':'×'],[t('schema'),syntax?(schema?'✓':'×'):'—'],[t('source'),schema?(source?'✓':'×'):'—'],[t('business'),schema?(business?'✓':'×'):'—']]);set('explanation',t('explain'));document.body.dataset.accepted=source&&business;}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;draw()});inputs(draw);","audio":false,"strings":{"business":"在庫が十分","explain":"原文との一致と十分な在庫が必要です。全部通っても注文を実行・許可したことにはなりません。","heading":"有効な JSON から利用可能な結果まで","intro":"候補出力と在庫を変え、検証の各層を見ます。","invent":"5個と推測","json":"候補 JSON","layers":"検証の層","missing":"数量欄なし","mode":"出力候補","reset":"リセット","schema":"構造が有効","scope":"原文を「A1 を3個購入」に固定し、sku と quantity を必須にして在庫を変えます。モデルや注文は実行しません。","scopeLabel":"モデルの範囲と前提","source":"原文の根拠","stock":"現在の在庫","syntax":"解析可能","truncated":"途中の JSON","valid":"原文と一致"}}
失敗時のパスに対する出力設計
構造化出力の保証は、インターフェースがサポートし、レスポンスが完全なパスに限定する必要があります。切り捨て、拒否、転送エラー、キャンセルは独立して処理する必要があり、最初の text ブロックを取得してデータベースに書き込むだけでは不十分です。
| 状況 | アプリケーションの処理 |
| 正常な完了 | 解析、スキーマ検証、その後ビジネス検証 |
| 出力の切り捨て | 未完成としてフラグ立て、理由を保持。中途半端なパラメータで実行しない |
| 情報欠落 | スキーマ内の未知の状態を受け入れ、タスクに従って読み込み継続または質問 |
| 原文の競合 | 位置特定可能な競合を返し、一貫した結論を勝手に作成しない |
| モデルによる拒否 | インターフェースを通じて拒否状態を読み取り、正常なビジネスデータとして偽装しない |
| ネットワーク断線またはタイムアウト | 生成未完成と外部操作の結果不明を区別 |
クライアント側では、有限のリトライや特定フィールドの修正要求が可能ですが、リトライには上限を設け、コストとして記録する必要があります。失敗が情報欠落に起因する場合、同じ入力を繰り返しても事実が自動的にに補完されるわけではありません。以前にツールの副作用が実行されていた場合、解析失敗を理由にタスク全体をリプレイしてはいけません。
構造化レポートの出典を保持するには、独自のスキーマにドキュメント ID、証拠の位置、バージョンを含め、アプリケーション側で検証します。ベンダー内蔵の citations が特定の構造化出力と同時に使用可能かどうかは、具体的な機能互換性の問題であり、「JSON と追跡可能性は共存できない」ことを意味するものではありません。検索証拠の完全性については、RAG を参照してください。
完全なローカル検証の例
以下のスキーマは、アプリケーション側での完全な検証用に使用され、対象サービスが其中的なすべてのキーワードをそのままサポートできることを保証するものではありません。この例は Python の jsonschema パッケージに依存し、正常な結果、欠損結果、余分なフィールド、誤った型、フィールド間の矛盾を検証します。モデルを呼び出すことも、注文を作成することもありません。
from jsonschema import Draft202012Validator, ValidationError
schema = {
"type": "object",
"properties": {
"sku": {"type": ["string", "null"], "minLength": 1},
"quantity": {"type": ["integer", "null"], "minimum": 1},
"status": {"type": "string", "enum": ["complete", "needs_input"]},
},
"required": ["sku", "quantity", "status"],
"additionalProperties": False,
}
Draft202012Validator.check_schema(schema)
validator = Draft202012Validator(schema)
def check(data):
validator.validate(data)
fields_present = data["sku"] is not None and data["quantity"] is not None
if (data["status"] == "complete") != fields_present:
raise ValueError("ステータスとフィールドの完全性に不一致がある")
return data
assert check({"sku": "A1", "quantity": 3, "status": "complete"})["quantity"] == 3
assert check({"sku": "A1", "quantity": None,
"status": "needs_input"})["quantity"] is None
bad = [
{"sku": "A1", "quantity": True, "status": "complete"},
{"sku": "A1", "quantity": 0, "status": "complete"},
{"sku": "A1", "quantity": None, "status": "complete"},
{"sku": "A1", "status": "needs_input"},
{"sku": "A1", "quantity": 3, "status": "complete", "extra": 1},
]
for item in bad:
try:
check(item)
except (ValidationError, ValueError):
pass
else:
raise AssertionError(f"無効な結果が受け入れられた: {item}")
print("正常/欠損結果は通過、5種類の無効な結果は拒否された")
この簡略化されたデータモデルは欠損のみを表現し、複数商品や競合は表現していません。実際のタスクでこれらの状態が必要な場合、対応するフィールドとルールを追加する必要があり、fields_present を完全性の判定基準として使い続けることはできません。この例では、SKU が存在するか、数量が原文から来ているか、在庫が十分かどうかも検証していません。これらは出典とビジネス検証の領域です。
アクションを実行する際には、リアルタイムの状態を再度検証する必要があります。例えば、抽出時には在庫が十分でも、提出時には売り切れている場合、スキーマではこの競合状態は解決できません。パラメータ検証、ビジネス制約、トランザクション処理は、対応するシステム内で完了させる必要があります。
プロンプトとスキーマのバージョン管理を一体化する
プロンプト、スキーマ、モデル、SDK、検証ロジックのバージョンを記録します。フィールドを nullable から required へ変更したり、列挙型に新しい値を追加したり、単一オブジェクトを配列で置き換えたりすると、ダウンストリームの動作が変わる可能性があります。モデルの出力だけでなく、旧来の消費者が新版の結果をどのように処理するかを検証する必要があります。
評価セットは、正常な入力、欠損、競合、ノイズ、長い入力、悪意のある材料、切り捨てをカバーする必要があります。構造の有効性、フィールドの正確性、未知値の処理の正しさ、そして実際のタスク成功率を個別に集計します。JSON がすべて合法でも事実が間違っている場合、構造制限を追加し続けるのは効果的ではないかもしれません。証拠、プロンプト、モデル能力の位置づけの問題に戻って検討する必要があります。
意味のある少量の例と明確な規約を保持し、互いに矛盾する重複する要求を削除します。変更のたびに、評価と観測可能性 のタスクレベル比較検証を行います。
次に読む:コンテキストエンジニアリング。