
【CCA Foundations対策 / Claude API編 #14】コンテキスト最適化とprovenance管理——マルチエージェントの信頼性設計
CCA Foundations試験ガイドおよびAnthropicの公式ドキュメントをもとに解説しています。
マルチエージェントシステムで「重要な情報が無視された」「ソースが不明な主張が含まれた」「部分的な失敗で全体が止まった」という問題が起きる。今回はこれらを設計レベルで防ぐ2つのパターン——コンテキスト最適化とprovenance管理——を整理する。
この記事でわかること:
- lost-in-the-middle現象とprimacy effectを使った対策
- 上流エージェントのverbose outputを削減する設計
- 矛盾する情報源をconflict annotationで扱う方法
- カバレッジ注釈で部分的な成功を透明に伝える
- エラーを最低レイヤーで解決して構造化して伝播させる
lost-in-the-middle問題
大量のテキストを入力すると、Claudeは先頭と末尾の情報は確実に参照するが、中間の情報を見落とす傾向がある。これをlost-in-the-middle現象と呼ぶ。
マルチエージェントシステムでは、複数のサブエージェントの出力を集約して合成エージェントに渡す場面でこの問題が起きやすい。
Web検索エージェント: 15,000トークンの結果 ← 先頭なので参照される
文書分析エージェント: 50,000トークンの結果 ← 中間なので見落とされる
レポート生成エージェント: 10,000トークンの結果 ← 末尾なので参照される
対策①:重要情報を先頭に置く(primacy effect)
合計75,000トークンの集約入力を渡す前に、重要な発見のサマリーを冒頭に追加する。
def build_synthesis_input(search_results, doc_results, report_results):
# 重要な発見を先頭に要約(primacy effectを活用)
key_findings_summary = extract_key_findings([
search_results, doc_results, report_results
])
synthesis_input = f"""## 主要な発見(サマリー)
{key_findings_summary}
---
## Web検索エージェントの詳細結果
{search_results}
## 文書分析エージェントの詳細結果
{doc_results}
## レポート生成エージェントの詳細結果
{report_results}
"""
return synthesis_input
先頭にサマリーを置くことで、合成エージェントが全体像を把握した上で詳細を読める。セクション見出し(##)を使うことで、中間のコンテンツもナビゲートしやすくなる。
対策②:上流エージェントのverbose outputを削減する
85,000トークンのWeb検索結果・70,000トークンの文書分析結果を丸ごと渡すのが問題の根本。上流エージェントが返すデータ量をソースで減らすのが最も効果的な解決策。
# NG:ページの全コンテンツをそのまま返す
def search_agent_output(results):
return {
"raw_pages": [page.full_content for page in results], # 85,000トークン
}
# OK:主要な事実・引用・関連スコアを構造化して返す
def search_agent_output(results):
return {
"findings": [
{
"claim": finding.key_claim, # 主張
"evidence": finding.excerpt, # 証拠(抜粋)
"source": finding.url, # ソース
"relevance_score": finding.score, # 関連スコア
"date": finding.published_date, # 公開日
}
for finding in results.top_findings
]
} # → 数千トークンに削減
要約エージェントを中間に追加するのではなく、サブエージェント自身が構造化されたデータを返す設計にする。要約エージェントを追加すると、そのエージェントも全コンテンツを処理する必要があり、問題を別の場所に移すだけになる。
provenance管理:情報の出所を追跡する
合成エージェントが複数ソースの情報を組み合わせるとき、「この主張はどこから来たのか」を追跡できる設計が信頼性の要になる。
構造化された出力形式
各発見に出所情報を付ける。
finding_schema = {
"claim": str, # 主張の内容
"evidence": str, # 根拠となる引用
"source": str, # 出典(URL・文書名)
"published_date": str, # 情報の鮮度
"confidence": float, # 信頼スコア(0.0〜1.0)
}
合成エージェントはこの構造化データを受け取り、最終レポートでも出典を保持したまま回答を生成する。
conflict annotation:矛盾を解決せずに伝える
信頼できる2つのソースが矛盾する数値を示している場合——文書分析エージェントはどうすべきか。
やってはいけないこと:
- どちらかを選んで処理を続ける(ヒューリスティックで選択)
- 矛盾に気づかずそのまま渡す
正しい設計:両方の値を出典付きでフラグを立て、調整の判断をコーディネーターに委ねる。
def handle_conflicting_sources(source_a, source_b, metric):
return {
"type": "conflict",
"metric": metric,
"values": [
{
"value": source_a.value,
"source": source_a.name,
"date": source_a.date,
},
{
"value": source_b.value,
"source": source_b.name,
"date": source_b.date,
},
],
"conflict_note": (
f"{source_a.name}は{source_a.value}、"
f"{source_b.name}は{source_b.value}と相反するデータを示している。"
"どちらの値を採用するかはコーディネーターが判断する。"
),
}
文書分析エージェントは分析タスクを完了させつつ、矛盾点を構造化して上位に伝える。コーディネーターはより広いコンテキスト(調査全体の目的・依頼者の優先事項)をもとに判断できる。
coverage annotation:部分的な成功を透明に伝える
サブエージェントがタイムアウト・接続エラーで一部のデータを取得できなかった場合、合成エージェントはどう対応すべきか。
やってはいけないこと:
- 取得できたデータだけで何も言わず合成を完了する
- 部分的な成功を「失敗」として全体を止める
正しい設計:取得できた部分で処理を続け、不足している部分を注釈で明示する。
def synthesize_with_coverage(available_data, missing_sources):
synthesis = generate_synthesis(available_data)
coverage_annotations = {
"well_supported_topics": [
topic for topic in synthesis.topics
if topic.source_count >= 2
],
"coverage_gaps": [
{
"topic": source.expected_topic,
"missing_source": source.name,
"reason": source.failure_reason, # "timeout" / "no_results" / "access_denied"
}
for source in missing_sources
],
}
return {
"synthesis": synthesis.content,
"coverage": coverage_annotations,
}
この設計により、レポートの利用者は「どの発見が十分に裏付けられているか」「どの領域の情報が不足しているか」を把握できる。不完全なデータでも価値ある情報を届けつつ、その限界を透明に伝える。
📋 試験ガイドより
公式試験ガイドのIn-Scope Topicsに「Information provenance: Claim-source mappings, temporal data handling, conflict annotation, coverage gap reporting」および「Context window optimization: Trimming verbose tool outputs, structured fact extraction, position-aware input ordering」が明記されている。provenance管理は出典の追跡だけでなく、矛盾・ギャップ・不確実性を下流に正確に伝える設計全体を指す。
エラー伝播:最低レイヤーで解決して構造化して伝える
マルチエージェントシステムのエラー処理には一つの原則がある。
解決できる最も低いレイヤーで処理し、解決できないものだけを上位に伝える。
サブエージェントはエラーを3種類に分類して対応する。
| エラー種別 | 対応 |
|---|---|
| 一時的な障害(タイムアウト・ネットワーク) | サブエージェントが内部でリトライ |
| 構造的な失敗(破損ファイル・アクセス拒否) | 構造化エラーをコーディネーターに返す |
| 有効な空の結果(「0件」) | 成功として返す(タイムアウトと区別する) |
タイムアウトと空の結果は別物:「0件の結果」はクエリが成功して情報がないことを示す。「タイムアウト」はアクセス自体が失敗したことを示す。コーディネーターは前者を受け入れ、後者だけリトライを判断できる。
def handle_search_failure(error, query, partial_results):
return {
"is_error": True,
"error_category": classify_error(error), # "transient" / "structural" / "access"
"attempted_query": query,
"partial_results": partial_results, # 取得できた分
"alternative_approaches": suggest_alternatives(query, error),
"is_retryable": error.category == "transient",
}
構造化エラーにはコーディネーターが意思決定に必要な情報をすべて含める:エラーの種類・試みたクエリ・部分的な結果・代替アプローチ。
よくある誤解まとめ
| 誤解 | 実際 |
|---|---|
| コンテキストウィンドウが大きければlost-in-the-middleは起きない | コンテキストサイズの問題ではなく注意の質の問題。サマリーを先頭に置く設計が必要 |
| 中間要約エージェントを追加すれば解決する | 要約エージェント自体が全コンテンツを処理する必要がある。上流でverbose outputを削減するほうが根本解決 |
| 矛盾するデータはどちらか選んで処理を続けるべき | 両方を出典付きでフラグを立てコーディネーターに判断を委ねる |
| 部分的な失敗は全体の失敗として扱う | 完了した部分でcoverage注釈付きで合成する。利用者に透明に伝えつつ価値を届ける |
| タイムアウトと0件の結果は同じエラーとして扱う | 前者はアクセス失敗(リトライ対象)、後者は有効な結果(リトライ不要)。区別して返す |
設計の判断基準
| 場面 | やりがちな選択 | 正しい選択 | 判断の根拠 |
|---|---|---|---|
| 複数サブエージェントの出力を合成エージェントに渡す | 全出力を結合してそのまま渡す | 主要な発見のサマリーを先頭に追加し、セクション見出しで整理してから渡す | primacy effectを活用し、lost-in-the-middleを防ぐ |
| サブエージェントの出力が大きすぎる | 中間要約エージェントを追加する | サブエージェント自身が構造化データ(主張・証拠・ソース・スコア)を返す設計にする | 要約エージェントも全コンテンツを処理する必要があり、問題を移動させるだけ |
| 信頼できるソースが矛盾する数値を示している | ヒューリスティックで一方を選んで処理を続ける | 両方を出典付きでconflict annotationとして返し、コーディネーターに判断を委ねる | 文書分析エージェントは分析が役割。判断はコンテキストを持つコーディネーターが行う |
| サブエージェントが一部のデータを取得できなかった | エラーとして全体を止める | 取得できた部分でcoverage注釈付きで合成を完了する | 完了した作業の価値を捨てずに、不足を透明に伝えられる |
まとめ
- lost-in-the-middleはコンテキスト先頭に重要情報のサマリーを置き、セクション見出しで中間をナビゲートすることで対策する
- verbose outputの根本解決は中間要約エージェントではなく、上流エージェントが構造化データを返す設計にすること
- 矛盾するソースはどちらかを選ばずにconflict annotationとして上位に委ねる
- 部分的な失敗はcoverage注釈付きで合成を完了し、不足を透明に伝える
- エラーは最低レイヤーで解決し、解決できないものだけを構造化して上位に伝播させる