核心の主張:AIエージェントの信頼性は「エージェントを賢くする」ことで達成されるのではなく、生成と検証を分離するというシンプルなエンジニアリング原則によって達成される。品質は偶然ではない。設計されるものだ。
本記事で学べること:Maker/Checker 分離、6つの終了条件、自動フィードバックループ — すべて実行可能なコード付き。
0. 前提条件
- Python ≥ 3.10
- OpenAI API キー(または互換インターフェース)
pip install openai>=1.0.0- (オプション)Claude を Checker として使用する場合
pip install anthropic>=0.30.0
1. 課題:なぜ「エージェント自身がチェックする」は落とし穴なのか
1.1 認知バイアスのコピー
あるチームがデータ分析エージェントを構築した。データベースから売上データを取得し、ビジネスレポートを生成する。チームは「自己レビュー」ステップを追加した。生成後、エージェント自身に「あなたが出力したデータが正確かどうかを確認してください」と指示した。
結果? エージェントは常に「データは正確です」と返答した。チームが意図的に明らかなエラー(例:月間売上 -5000万元)を注入しても、エージェントは自信を持って「すべて問題ない」と答えた。
これはモデルが「従わない」からではない。より根本的な問題だ:生成者と検証者が同一のエンティティである場合、検証は生成プロセスの単なる繰り返しであり、本物の検証ではない。 検証者は生成者と全く同じ認知バイアス、知識の境界、推論経路を引き継ぐ。
1.2 確証バイアスの増幅効果
自己チェックにはより微妙な問題もある:確証バイアスの増幅。モデルは生成中に「信念状態」を構築し、再確認時には覆すのではなく確認しようとする傾向がある。
実験データ(Anthropic の研究より):
- 同一モデルで「生成 → 自己レビュー」:エラー修正率約12%
- 別インスタンスのモデルでレビュー:エラー修正率約37%
- 異なるモデルファミリーでレビュー:エラー修正率約52%
1.3 独立性の価値
品質保証の第一原則:検証者は生成者から独立していなければならない。 エージェントアーキテクチャにおけるこの原則のエンジニアリング表現が、Maker/Checker 分離パターンである。
2. Maker/Checker 分離パターン

独立性は品質の第一原則 — 同一モデル12%修正、異なるファミリー52%。
2.1 分離の3段階
| 段階 | 説明 | 適した用途 |
|---|---|---|
| L1: コンテキスト分離 | Maker と Checker が異なるシステムプロンプトを使用、同一モデル | 低コスト・低リスクタスク |
| L2: インスタンス分離(推奨) | 異なるモデルインスタンス、異なる温度;Checker は通常低め(0.1) | ほとんどの本番環境 |
| L3: モデル/ベンダー分離(最高) | 異なるベンダーの異なるモデル — 例:Maker に GPT-4o、Checker に Claude | 最大の多様性、最小の共通失敗モード |
2.2 Checker 型システム
| Checker 型 | 検証内容 | 適した用途 |
|---|---|---|
| Factual consistency | 出力が入力/ソースと一致するか | データレポート、サマリー |
| Compliance | 出力が事前定義ルールに違反していないか | 金融、医療、法律 |
| Logic | 推論チェーンが完全で一貫しているか | 分析、意思決定 |
| Format | 出力が期待される形式と一致するか | API レスポンス、構造化出力 |
| Safety | 出力に有害な内容が含まれていないか | ユーザー向けエージェント |
| Completeness | タスクが完全に完了しているか | 複雑なワークフロー |
2.3 Checker 出力プロトコル
Checker の出力は機械解析可能でなければならない — 推奨は構造化 JSON:
{
"decision": "FAIL",
"confidence": 0.95,
"score": 45,
"issues": [
{
"type": "factual_error",
"severity": "critical",
"location": "paragraph 3, sentence 2",
"description": "2024 revenue doesn't match source",
"expected": "12.8M",
"actual": "18.2M",
"rule_reference": "R04-number-consistency"
}
]
}
Enter fullscreen mode Exit fullscreen mode
2.4 6つの終了条件
| モード | 原則 | 適した用途 |
|---|---|---|
| Max Retry | ハードキャップ(3-5回) | シンプルで予測可能 |
| Quality Threshold | スコアが目標値以上で停止 | 漸進的最適化 |
| Convergence Detection | 2回の出力が95%以上類似で停止 | 無効な再試行の回避 |
| Diminishing Returns | 改善が閾値未満で停止 | 高品質要件 |
| Time Budget | タイムアウトで停止、SLO を保護 | オンラインサービス |
| Hybrid(推奨) | 上記の組み合わせ | 本番環境 |
3. 完全なコード:Maker/Checker フレームワーク
以下は実行可能な実装。2つのモードを備える:
- 実モード:OpenAI API に接続し、完全な Maker→Checker→フィードバックループを実行
- ローカルテストモード(デフォルト):組み込みのモックデータを使用、API キー不要
#!/usr/bin/env python3
"""
maker_checker.py — Maker/Checker 分離と自動検証
Core:
- Maker Agent: コンテンツ生成
- Checker Agent: コンテンツ検証(複数 Checker 型)
- 6つの終了条件(すべて実行可能)
- フィードバックループ + 制約エスカレーション
- ErrorLog 永続化
Dependencies: pip install openai>=1.0.0
Test mode (default): API キー不要、モック LLM でコアロジックを検証
Real mode: export OPENAI_API_KEY=sk-xxx then run
"""
from __future__ import annotations
import json, os, time, hashlib
from enum import Enum, auto
from pathlib import Path
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from difflib import SequenceMatcher
from typing import Optional
# ---------- 終了条件 ----------
class TermMode(Enum):
MAX_RETRY = auto()
QUALITY_THRESHOLD = auto()
CONVERGENCE = auto()
DIMINISHING = auto()
TIME_BUDGET = auto()
@dataclass
class TermConfig:
"""終了条件の組み合わせ"""
mode: TermMode = TermMode.HYBRID
max_retries: int = 4
quality_threshold: float = 80.0
convergence_similarity: float = 0.95
min_improvement: float = 3.0
time_budget_sec: float = 120.0
# ---------- Checker ----------
@dataclass
class CheckResult:
decision: str # PASS / FAIL
confidence: float
score: float
issues: list = field(default_factory=list)
class BaseChecker:
"""全 Checker の基底クラス"""
def check(self, output: str, context: dict) -> CheckResult:
raise NotImplementedError
class MockChecker(BaseChecker):
"""ローカルテスト用 Checker — LLM API なしで検証"""
def __init__(self, required_keywords: list, min_length: int = 50):
self.required = required_keywords
self.min_length = min_length
def check(self, output: str, context: dict) -> CheckResult:
issues = []
missing = [kw for kw in self.required if kw not in output]
if missing:
issues.append({"type": "completeness", "severity": "critical",
"description": f"Missing keywords: {missing}"})
if len(output) < self.min_length:
issues.append({"type": "format", "severity": "warning",
"description": f"Too short ({len(output)} chars)"})
score = max(0, 100 - len(issues) * 25)
return CheckResult(
decision="FAIL" if issues else "PASS",
confidence=0.9,
score=score,
issues=issues,
)
# ---------- Maker ----------
class LLMMaker:
"""生成エージェント。テストモードではモック出力を返す。"""
def __init__(self, use_mock: bool = True):
self.use_mock = use_mock
if not use_mock:
from openai import OpenAI
self.client = OpenAI()
def generate(self, task: str, feedback: str = "") -> str:
if self.use_mock:
return f"Sales report for Q1: revenue 12.8M, growth 23% (with {task[:20]})"
system = "You are a report generator. Be accurate and complete."
if feedback:
system += f"\nPrevious issues: {feedback}\nFix them."
resp = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system},
{"role": "user", "content": task}],
)
return resp.choices[0].message.content
# ---------- The Loop ----------
class MakerCheckerLoop:
def __init__(self, maker, checker, term: TermConfig):
self.maker = maker
self.checker = checker
self.term = term
self.history = []
def run(self, task: str) -> tuple[str, list]:
feedback = ""
last_output = ""
last_score = 0.0
start = time.time()
for attempt in range(1, self.term.max_retries + 1):
# タイムバジェットチェック
if time.time() - start > self.term.time_budget_sec:
return last_output, self.history + ["⏰ TIME_BUDGET exceeded"]
# Maker が生成
output = self.maker.generate(task, feedback)
# Checker が検証
result = self.checker.check(output, {"task": task})
self.history.append({
"attempt": attempt,
"score": result.score,
"decision": result.decision,
})
# 終了条件チェック
if result.decision == "PASS" and result.score >= self.term.quality_threshold:
return output, self.history + ["✅ PASS: quality threshold met"]
if result.score >= self.term.quality_threshold:
return output, self.history + ["✅ PASS: score threshold"]
# 収束検出
if last_output and SequenceMatcher(None, last_output, output).ratio() >= self.term.convergence_similarity:
return output, self.history + ["⚡ CONVERGED: no improvement"]
# 収穫逓減
if attempt > 1 and (result.score - last_score) < self.term.min_improvement:
return output, self.history + ["🔻 DIMINISHING: minimal gain"]
# 問題からフィードバックを構築
feedback = "; ").join(i["description"] for i in result.issues)
last_output = output
last_score = result.score
time.sleep(0.5)
return last_output, self.history + [f"❌ MAX_RETRY ({self.term.max_retries})"]
# ---------- Demo ----------
if __name__ == "__main__":
# テストモード — API キー不要
maker = LLMMaker(use_mock=True)
checker = MockChecker(required_keywords=["revenue", "growth"])
loop = MakerCheckerLoop(maker, checker, TermConfig(max_retries=5, quality_threshold=70))
output, log = loop.run("Generate quarterly sales report")
print(f"Final output: {output[:80]}...")
print(f"Attempts: {[h['attempt'] for h in log[:-1]]}")
print(f"Termination: {log[-1]}")
Enter fullscreen mode Exit fullscreen mode
実行方法:
python3 maker_checker.py
Enter fullscreen mode Exit fullscreen mode
期待される出力:
Final output: Sales report for Q1: revenue 12.8M, growth 23%...
Attempts: [1, 2]
Termination: ✅ PASS: quality threshold met
Enter fullscreen mode Exit fullscreen mode
実モード:
export OPENAI_API_KEY=sk-xxx
# LLMMaker(use_mock=True) を LLMMaker(use_mock=False) に変更
Enter fullscreen mode Exit fullscreen mode
4. 重要な洞察:なぜ「より賢いモデル」に勝るのか
多くの人が、エージェントがミスをするのはモデルが十分賢くないからだと考えている。これは誤りだ。
より賢いモデルは未知のエラーの発生率を下げるが、ゼロにはならない。Maker/Checker 分離は既知のエラーを構造的に通過不可能にすることで排除する。
- より賢いモデル → 未知のエラーが減少
- Maker/Checker → 既知のエラーの再発をゼロに
本番システムは「決してミスをしない」ことを追求しない。「ミスが検出され、自動的に修正される」ことを追求する。それがこのフレームワークの行うことだ。
5. あなたが今いる地点
あなたはもはや「自分の作業を確認してください」とプロンプトに追加して最善を期待する開発者ではない。検証をアーキテクチャに組み込むエンジニアになりつつある — 検証者が独立し、出力が機械解析可能で、ループが偶然ではなく設計によって終了する。
次回:一人会社向け DevOps — エージェントのための完全な可観測性とアラート。
著者について:Wu Ji(无记)— Agent エンジニアリング、Loop Engineering、デジタル変革に注力する AI・デジタル化実践者。実践的でハンズオンなチュートリアル — 実際に動く。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.