広告

「エージェントが間違った判断をした」のではない。指示が矛盾していた——そして誰もそれに気づかなかった。

Dev.to / 2026/3/28

💬 オピニオンDeveloper Stack & InfrastructureIdeas & Deep AnalysisTools & Practical Usage

要点

  • 記事では、AIエージェントが「間違った」選択をしたように見える場合、その根本原因は「エージェントが誤って判断したこと」ではなく、しばしば矛盾したり曖昧だったりする指示にあると主張している。
  • 現在のv0.1版「Agent Forensics」では、どのツールが呼び出され、どの結果が使われたかという意思決定の“トレイル”は提供するが、その判断に影響した文脈(コンテキスト)に関する可視性はないことを説明している。
  • 重要な見落とし(ブラインドスポット)を特定している。具体的には、外部文脈(RAGドキュメント、メモリ、顧客プロファイルなど)が結果にどう影響したか、会話の途中で指示が変わったかどうか、そしてシステムプロンプト内に矛盾した優先順位が含まれていないか、などである。
  • 著者は、これらの課題に対処するための新しい能力として2つを予告している。すなわち、Context Injection Tracking(エージェントが依拠した情報が何かを示す)と、Prompt Drift Detection(会話中に優先順位を変えてしまう変更/上書きを検出する)である。
  • 全体として、この投稿はフォレンジック(事後調査)のログがそれ自体では不十分であることを再定義し、曖昧さに遭遇した際の事前検知や、黙って行われる選好(優先順位)の解決を重視すべきだと強調している。

先週、RedditにAgent Forensicsを投稿しました。これはオープンソースのブラックボックスで、AIエージェントが下すすべての判断を記録し、うまくいかなかったときにはフォレンジックレポートを生成します。

反応はとても良かったです。ですが、あるコメントが私を止めました:

「“マジックマウス”の例は完璧です。なぜなら、本当の問題が“エージェントが間違った判断をした”ことではなく、システムプロンプトの優先順位が衝突していたことだと示しているからです。“ユーザーが求めたものを買う”のと“最良の取引を見つける”の衝突です。そしてモデルは曖昧さを黙って解決しました。」

そして、特に刺さったのがこの部分:

「意思決定のログは事後にそうした点を見つけるのに役立ちますが、難しい問題はそれを防ぐことです。最も有用な洞察は“何がうまくいかなかったか”ではなく、“どこでモデルが曖昧さに遭遇し、フラグを立てずに解釈を1つ選んだのか”です。」

彼らの言う通りでした。そして私のツールではまだそれができませんでした。

The Blind Spot

何が欠けていたのか、正確にお見せします。

v0.1では、買い物エージェントがApple Magic Mouseの代わりにLogitechを購入したとき、フォレンジックの痕跡は次のように示されました:

[DECISION] search_products("Apple Magic Mouse")
  → [TOOL] search_api → ERROR: not found

[DECISION] search_products("Apple wireless mouse")
  → [TOOL] search_api → OK: 3 products found

[DECISION] compare_prices → Logitech M750 cheapest

[DECISION] purchase("Logitech M750") → SUCCESS

[FINAL] "Purchased Logitech M750 for $45"

何が起きたかは分かります。ですが、次のことは分かりませんでした:

  1. どんな文脈が判断に影響したのか? 「常に価格を優先する」と書いたRAG文書はありましたか? エージェントの振る舞いを変える顧客プロファイルはありましたか?
  2. 指示は会話の途中で変わったのか? ユーザーの元の依頼を上書きするような、動的に注入されたルールはありましたか?
  3. プロンプトのどこに、優先順位の衝突があったのか? システムプロンプトには「一番安いものを買え」という指示と「ユーザーの要望に従え」という指示が同時にありました。エージェントは黙って、一方を他方より優先して選んだのです。

意思決定ログは、エージェントが何をしたかを教えてくれます。ですが、エージェントが判断したときに何を見ていたのかは教えてくれません。

Two New Features: Context Injection Tracking + Prompt Drift Detection

Context Injection: "What Was the Agent Looking At?"

AIエージェントは、真空の中で動いているわけではありません。ベクターデータベース、メモリーストア、顧客プロファイル、取得した文書などから文脈(コンテキスト)を受け取ります。この文脈は判断に直接影響します。しかしv0.1では、それが見えませんでした。

これで追跡できます:

from agent_forensics import Forensics

f = Forensics(session="support-ticket-7831")

# Agent looks up customer info
f.decision("lookup_customer", input={"id": "C-9921"},
           reasoning="Customer asked about refund policy")

# RAG context injected — this is new
f.context_injection("vector_db", content={
    "document": "refund_policy_v2.md",
    "chunk": "Refunds available within 30 days for all items except digital goods.",
    "similarity_score": 0.92,
}, reasoning="Retrieved refund policy from vector store")

# Customer history injected from a different source
f.context_injection("memory_store", content={
    "customer_history": "3 previous refund requests in last 6 months",
    "risk_flag": "high_refund_frequency",
}, reasoning="Customer flagged as high refund frequency")

フォレンジックレポートでは、各判断に影響した「どの文書」や「どのデータソースか」が、今度は正確に表示されます:

[DECISION] lookup_customer
  Reasoning: Customer asked about refund policy

[CONTEXT] vector_db
  Injected: refund_policy_v2.md (similarity: 0.92)

[CONTEXT] memory_store
  Injected: customer flagged as high_refund_frequency

判断がうまくいかなかった場合、次のように答えられるようになります。「この判断は、この特定のソースからこの特定の文書として取得された内容、そしてこの信頼度スコアの影響を受けています。」

Prompt Drift Detection: "Did the Rules Change Mid-Game?"

これは、Redditのコメントが本当に問題にしていたものです。

複数ステップのエージェントワークフローでは、ステップ間でシステムプロンプトが変わることがあります。新しい指示が注入されます。ガードレールが追加されます。ポリシールールが文脈に基づいて変更されます。これはプロンプトドリフトであり、しかも静かに起こります。

# Step 1: Agent starts with this system prompt
f.prompt_state(
    "You are a customer support agent. Follow company policy. Be polite.")

返却形式: {"translated": "翻訳されたHTML"}# ... agent は何か作業をする ...

# 手順5: システムプロンプトが変更された — 新しいルールが注入された
f.prompt_state(
    "あなたはカスタマーサポートのエージェントです。会社のポリシーに従ってください。丁寧に対応してください。 "
    "注: 払い戻し回数が多い(高リファンド頻度)とフラグ付けされた顧客については、"
    "払い戻しを処理する前にマネージャーの承認を要します。"
)

Agent Forensics は自動的にこの変更を検出し、フラグを立てます:

[*** PROMPT DRIFT ***]
  + 追加: 「注: 払い戻し回数が多い(高リファンド頻度)とフラグ付けされた顧客については、
    払い戻しを処理する前にマネージャーの承認を要します。」
  - 削除: (NOTE のない元のプロンプト)

[決定] deny_refund
  理由: 顧客は高リファンド頻度で、マネージャーの承認が必要

因果の連鎖はこれで完成です: 顧客の履歴が取得される → 顧客がフラグ付けされる → 新しい指示がプロンプトに注入される → エージェントが払い戻しを拒否する。すべてのつながりが見えています。

LangChain および OpenAI Agents SDK のユーザー向けには、プロンプトドリフト検出は 自動 です — 手動の呼び出しは不要です。統合のフックが、各 LLM 呼び出しごとにシステムプロンプトを追跡し、変更があればフラグを立てます。

全体像: 後と前

同じインシデントを、v0.1 と v0.2 で見てみましょう:

v0.1 — 何が起きたか分かる:

[決定] lookup_customer
[ツール] policy_lookup → 「30日間の払い戻しポリシー」
[決定] deny_refund
[最終] 「返金にはマネージャーの承認が必要です」

質問: ポリシーでは30日間の払い戻しが利用可能なのに、なぜエージェントは払い戻しを拒否したのですか?
回答: わかりません。ポリシー検索と拒否の間で何かが起きました。

v0.2 — なぜ起きたか分かる:

[決定] lookup_customer
  理由: 顧客が払い戻しポリシーについて質問した

[コンテキスト] vector_db
  注入: refund_policy_v2.md — 「すべての商品に対する30日間の払い戻し」

[ツール] policy_lookup → 「30日間の払い戻しポリシー」

[コンテキスト] memory_store
  注入: high_refund_frequency としてフラグ付けされた顧客

[*** PROMPT DRIFT ***]
  + 追加: 「高リファンド頻度の顧客にはマネージャーの承認を求める」

[決定] deny_refund
  理由: 新しい指示により、この顧客にはマネージャーの承認が必要

[最終] 「返金にはマネージャーの承認が必要です」

質問: ポリシーでは30日間の払い戻しが利用可能なのに、なぜエージェントは払い戻しを拒否したのですか?
回答: 顧客リスクのフラグが、マネージャー承認の要件を追加する動的な指示注入を引き起こしたためです。払い戻しポリシーは、手順5でのプロンプト変更によって上書きされました。

これが、意思決定ログと実際のフォレンジックの違いです。

次にやること: 曖昧さ検出

Reddit の投稿者が言っている本質的な点は、まだ解決されていません:

「最も役に立つ洞察は、『何がうまくいかなかったか』ではなく、『モデルがどこで曖昧さに遭遇し、フラグを立てずに解釈を1つ選んだのか』だ。」

コンテキスト注入とプロンプトドリフトは、追跡可能性(traceability) の問題を解決します — これで、事後に完全な連鎖を再構築できるようになりました。ですが、エージェントが行動する前に、リアルタイムで曖昧さを検出することは、次のフロンティアです。

例えば、次のようにフラグを立てる仕組みを想像してください: 「この意思決定は競合する優先事項を解決しました — 『ユーザーが求めたものを買う』 vs 『最も安い選択肢を見つける』 — しかし、明示的な優先順位付けがありません。選択された解釈に対する自信: 62%。」

私はここから先に進めたいと思っています。曖昧さ検出の共同開発に興味があるなら、issue を作成してください、または連絡してください。

v0.2 を試す

pip install --upgrade agent-forensics
from agent_forensics import Forensics

f = Forensics(session="order-123")

# コンテキスト注入を記録
f.context_injection("vector_db", content={...})

# プロンプト状態を追跡(自動ドリフト検出)
f.prompt_state("あなたのシステムプロンプトはこちら...")

# LangChain/OpenAI Agents SDK: ドリフト検出は自動
agent.invoke(..., config={"callbacks": [f.langchain()]})

# コンテキスト + ドリフト分析を含む完全なレポート
print(f.report())

最高のオープンソースプロジェクトは、そのコミュニティによって形作られます。Reddit のあるコメントがきっかけで、新しい2つの機能が24時間で出荷されました。引き続きフィードバックをお寄せください。

広告