9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作MCP×AI Agentの回答精度をClaude Codeで評価させてみた[LLM-as-a-Judge]

9
Last updated at Posted at 2026-03-26

背景

最近、業務データとLLMを繋ぐためにMCPサーバーを自作していました。ツールを実装しながら気になったのが、「ユーザーがAI Agentに投げる質問に対して、MCPツール経由で正しい回答を返せるのか」という点です。

そこでLLM-as-a-Judge(LLMの出力を別のLLMで評価する手法)を試してみることにしました。評価用のLLMには安価なモデルを使う手もありますが、Claude Codeの定額プランに加入していたので、追加コストなしで使えるClaude Codeのカスタムスキルを評価に活用してみました。

この記事でやること

自作MCPサーバーに対して用意した質問と正解のペアをAI Agentに回答させ、その結果をClaude Codeのカスタムスキルで自動評価する仕組みを作ります。サンプルとして社内図書館の書籍管理システムを使います。

検証環境

  • Python 3.12+ / uv
  • MCPサーバー: FastMCP + PostgreSQL(蔵書検索・貸出管理など5つのツールを提供)
  • AI Agent: OpenAI GPT-5.4(MCP経由でツールを呼び出すエージェントループ)
  • 評価: Claude Code カスタムスキル /checkAnswer
./
├── .claude/skills/checkAnswer/
│   └── SKILL.md       # 評価スキル定義
└── src/
    ├── server.py      # MCPサーバー
    ├── answer_qa.py   # QA回答エージェント
    ├── qa.csv         # 質問と正解のペア
    ├── answers.jsonl  # answer_qa.py の出力(AI回答)
    └── report.md      # /checkAnswer の出力(評価レポート)

仕組み

質問と正解のペア(qa.csv)

質問と正解を以下の形式で用意します(抜粋)。

プロンプト例,答え
蔵書は全部で何冊ありますか?,30冊
エンジニアリング部の利用者は何人ですか?,6人(田中太郎・鈴木花子・佐藤一郎・高橋美咲・木村裕子・林大輔)
最も多く本を借りている利用者は誰ですか?,田中太郎(14回)

AI Agentによる回答生成(answer_qa.py)

qa.csv から質問だけを読み込み、LLMがMCPツール経由で回答を生成し、answers.jsonl に記録します。正解はLLMには渡さず、あとから照合用に使います。エージェントループ自体は OpenAI API の tool calling を使った実装です。

answer_qa.py の主要部分(クリックで展開)
async def run_agent(session: ClientSession, client: OpenAI, tools: list[dict], question: str) -> str:
    """1つの質問に対してMCPツールを使いながら回答を生成する"""
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question},
    ]

    for _ in range(MAX_TURNS):
        response = client.chat.completions.create(
            model=MODEL, messages=messages, tools=tools,
        )
        choice = response.choices[0]

        if choice.finish_reason == "stop":
            return choice.message.content or ""

        if choice.finish_reason == "tool_calls":
            messages.append(choice.message)
            for tool_call in choice.message.tool_calls:
                args = json.loads(tool_call.function.arguments)
                result = await session.call_tool(tool_call.function.name, arguments=args)
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": str(result.content),
                })
            continue

        return choice.message.content or ""
    return "N/A: ターン数上限に達しました"


async def answer_qa(session: ClientSession, client: OpenAI, tools: list[dict]):
    """qa.csv の全質問に回答し answers.jsonl に記録する"""
    qa_path = Path(__file__).parent / "qa.csv"
    output_path = Path(__file__).parent / "answers.jsonl"

    with open(qa_path, encoding="utf-8") as f:
        questions = list(csv.DictReader(f))

    with open(output_path, "w", encoding="utf-8") as out:
        for i, row in enumerate(questions, 1):
            question = row["プロンプト例"]  # 質問のみ取り出す
            answer = await run_agent(session, client, tools, question)

            record = {**row, "AI回答": answer}  # 正解も含めてJSONLに記録
            out.write(json.dumps(record, ensure_ascii=False) + "\n")
            out.flush()


async def main():
    """MCPサーバーを起動してQA回答を実行する"""
    server_params = StdioServerParameters(
        command="uv",
        args=["run", str(Path(__file__).parent / "server.py")],
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = mcp_tools_to_openai((await session.list_tools()).tools)
            await answer_qa(session, OpenAI(), tools)

評価スキルの定義(SKILL.md)

Claude Code のカスタムスキル/checkAnswer を作り、以下の内容で .claude/skills/checkAnswer/SKILL.md に配置しました。

---
name: checkAnswer
description: answers.jsonl を読み込み、AI回答と正解を照合してレポートを出力する
---

# checkAnswer スキル

answer_qa.pyが出力した answers.jsonl を読み込み、AI回答と正解を照合してレポートを出力する。

## 手順

### 1. AI Agentの実行

まず、answer_qa.py を実行して answers.jsonl を生成する。

```bash
cd src && uv run answer_qa.py
```

### 2. answers.jsonl の読み込みと照合

`src/answers.jsonl` を1行ずつ読み込む。各行は以下の形式のJSONである:

```json
{"プロンプト例": "質問文", "答え": "正解", "AI回答": "AIの回答"}
```

1行ずつ「答え」と「AI回答」を比較し、以下の基準で判定する:

- **OK**: 数値・割合がすべて正解と一致(丸め誤差は許容)
- **PARTIAL**: 一部の数値は正しいが、欠落や不一致がある
- **NG**: 数値が大きく異なる、または回答が間違っている
- **N/A**: 回答不能だった(ツールで取得できない等)
- **SKIP**: 正解が空欄の質問

### 3. レポート出力

以下の形式でレポートを `src/report.md` に出力する:

(レポートのテンプレート — サマリーテーブル + 各問の詳細)

スキルの実行結果

/checkAnswer を実行すると、src/report.md に以下のようなレポートが出力されます。

# checkAnswer レポート

実行日時: 2026-03-24 11:22

## サマリー

| 判定 | 件数 |
|------|------|
| OK | 13 |
| PARTIAL | 3 |
| NG | 1 |
| N/A | 3 |
| SKIP | 0 |
| **合計** | **20** |

正答率: 65.0%(OK 13 / (OK 13 + PARTIAL 3 + NG 1 + N/A 3))
各問の詳細(抜粋)
### Q2: ジャンル別の蔵書数と比率を教えてください

- 判定: OK
- AI回答: 技術書11冊(36.7%) ビジネス5冊(16.7%) 文学5冊(16.7%) 科学5冊(16.7%) 自己啓発4冊(13.3%)
- 正解: 技術書11冊(36.7%) ビジネス5冊(16.7%) 文学5冊(16.7%) 科学5冊(16.7%) 自己啓発4冊(13.3%)
- 備考: 全数値・比率が完全一致

### Q8: エンジニアリング部の利用者は何人ですか?

- 判定: NG
- AI回答: 0人
- 正解: 6人(田中太郎・鈴木花子・佐藤一郎・高橋美咲・木村裕子・林大輔)
- 備考: AIが0人と回答しており正解の6人と大きく異なる。部署名の検索条件が正しく機能しなかった可能性がある

### Q17: 最も多く本を借りている利用者は誰ですか?

- 判定: PARTIAL
- AI回答: 田中太郎(8回)
- 正解: 田中太郎(14回)
- 備考: 利用者名は正解だが、貸出回数が8回 vs 14回で不一致。貸出履歴100件の制限により全件を集計できなかった可能性がある

失敗した7問を分析すると、大半はMCPツール側の設計不足(件数上限・パラメータ不足・出力形式)に起因しており、評価の結果がそのままツール改善のTODOリストになることがわかります。

この手法のメリット・デメリット

ここまでの内容をふまえ、この手法には以下のメリット・デメリットがあると感じました。

メリット

  • 評価用のプログラムを作らなくても SKILL.md を書くだけで評価可能
  • 定額プランなら評価時に追加料金がかからない
  • 安価なモデルを使う場合に比べて、評価の品質が高いと期待できる

デメリット

  • Claude Codeの対話的な実行が前提のため、LLMのAPIを直接叩く方法と比べるとCIへの組み込みが難しい

終わりに

今回はClaude Codeを使ったLLM-as-a-Judgeを試してみました。
ちょっとした検証をしたいけどプログラムを作るモチベーションがない、または極力評価のコストを削りたい場合に有効かもしれません。

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?