# ベンチマーク (`npm run bench`)

エージェントの **ツールコール能力 / 命令追従性 / 頭の良さ / チェックリスト使用 / 効率** を、1 つの統合タスクから多軸で計測するためのフレームワーク。

モデル変更・piece 改修・ツール追加などの前後で同じタスクを走らせ、品質回帰を検出する用途を想定している。

---

## 1. 前提

| 項目 | 必須 |
|------|------|
| `scripts/server.sh start`（またはそれ相当）でオーケストレータが起動していること | ✅ |
| `config.yaml` の `provider` が動作する LLM を指している（タスク実行 + judge の両方で使う） | ✅ |
| 初回のみ `npm run bench:fixtures` で `bench/fixtures/sales.xlsx` を生成 | ✅ |

ベンチランナーは外部ネットに依存しない。`fixtures/web/*` はランナー内蔵の **localhost HTTP サーバ** が配信する（起動時にランダムポートを取り、`{WEB_PORT}` トークンで prompt に注入）。

---

## 2. 使い方

```bash
# 全タスク
npm run bench

# 単一タスク
npm run bench -- --task=composite-mini-report

# 別ホスト/ポートのオーケストレータ向け
npm run bench -- --server=http://127.0.0.1:9876

# LLM judge を skip (axis D を 1.0 固定にして programmatic だけで採点)
BENCH_JUDGE=off npm run bench

# judge を別エンドポイント・別モデルにする
BENCH_JUDGE_ENDPOINT=https://api.example.com/v1 \
BENCH_JUDGE_MODEL=gpt-oss:20b \
npm run bench
```

実行が終わると `bench/results/<run_id>/` に書き出される（`run_id` は ISO タイムスタンプ）。

---

## 3. 出力の見方

```
bench/results/2026-05-01T03-22-11Z/
  summary.md                        # ← ここを最初に見る
  composite-mini-report/
    result.json                     # 完全な採点 + raw データ
    workspace/
      logs/activity.log             # エージェントが書いたログ
      output/report.md              # エージェントの成果物
```

`summary.md` の冒頭：

```
# Bench run @ 2026-05-01T03:22:11.000Z

**Overall: 73 / 100**

| Task                   | Status     | Total |   A |   B |   C |   D |
| ---------------------- | ---------- | ----: | --: | --: | --: | --: |
| composite-mini-report  | succeeded  |   73  | 90% | 100%| 70% | 60% |
```

各タスクの詳細セクションでは axis ごとに ✓/✗ 内訳とツールコールの全シーケンスが折りたたみで見られる。

`result.json` は CI から機械可読に扱える形式。

---

## 4. 採点軸（重み 100 点満点）

| 軸 | 重み | 何を見るか | 判定 |
|----|----:|-----------|------|
| **A. Tools** | 30 | `must_use_tools` を呼んだか / `forbidden_tools` を避けたか / `forbidden_tool_for_ext` (例: Read 禁止 .xlsx) | プログラム |
| **B. Checklist** | 15 | `CreateChecklist` / `CheckItem×N` / `GetChecklist` の使用 | プログラム |
| **C. Instructions** | 30 | 出力ファイル名・1 行目固定・セクション順・行数・文字数・禁止パターンなど | プログラム |
| **D. Reasoning** | 25 | 内容の妥当性・統合の質・「次アクション」の具体性など | LLM judge ルーブリック |
| _補助: Efficiency_ | – | duration / prompt tokens（summary に数値表示のみ） | – |

`Total = A×30 + B×15 + C×30 + D×25` を 0..100 に正規化。

`completion_status: [succeeded, waiting_human, failed, aborted, cancelled]` で受理する終了状態を指定。デフォルトは `[succeeded]` のみ。**failure でも grader は走り部分スコアが出る**。

---

## 5. 既存タスク

### `composite-mini-report`

3 ソース（Excel / Web / Markdown）を統合してミニレポートを書かせるタスク。1 本で全軸が動く。

- 必須ツール: `ReadExcel` / `WebFetch` / `Read` / `Write` / `CreateChecklist` / `CheckItem` (≥3 回) / `GetChecklist`
- 禁止: `.xlsx` を `Read` で開く（バイナリ混入防止 — issue #189 と同じ罠）
- 出力 `output/report.md` に厳格な形式制約（1 行目固定、セクション順、各セクション 5 行以内、「次アクション」3 件 40 字以内、画像・HTML 禁止）
- judge ルーブリック: `factual_grounding` / `actions_quality` / `synthesis`

`bench/tasks/composite-mini-report.yaml` を参考実装としてそのまま使える。

---

## 6. タスクを追加する

`bench/tasks/*.yaml` を作るだけで自動的に拾われる。スキーマは `src/bench/types.ts` の `BenchTask` を参照。最小例：

```yaml
id: my-task
title: 短いタスク説明
piece_hint: chat              # piece 名 (省略時は chat)
timeout_minutes: 5

fixtures:                      # 任意
  - source: fixtures/data.txt  # bench/ ルート相対
    dest: input/data.txt       # input/ に置けば attachments としてアップロード
  - source: fixtures/web/page.html
    dest: web/page.html        # web/ に置けば fixture HTTP server が配信

prompt_tokens:                 # 任意。prompt 内の {KEY} を実行時に置換
  CUSTOM_KEY: foo
prompt: |
  http://127.0.0.1:{WEB_PORT}/page.html を読み、… {CUSTOM_KEY} …

expected:
  must_use_tools: [WebFetch, Write]
  forbidden_tools: [Bash]
  forbidden_tool_for_ext:
    Read: ['.xlsx']
  must_produce_files: [output/answer.md]
  completion_status: [succeeded]

checklist:                     # 任意。指定すると軸 B が有効化される
  required_tools: [CreateChecklist, CheckItem, GetChecklist]
  min_check_item_calls: 3

grading:
  programmatic:
    constraints:
      - { type: file_first_line_equals, file: output/answer.md, line: '# Title' }
      - { type: file_must_contain_in_order, file: output/answer.md, sections: ['## A', '## B'] }
      - { type: file_section_max_lines, file: output/answer.md, section: A, max: 5 }
      - { type: file_line_starts_with, file: output/answer.md, prefix: '-', min_lines: 3, section: B }
      - { type: file_line_max_chars, file: output/answer.md, max: 40, section: B }
      - { type: file_no_pattern, file: output/answer.md, pattern: '!\[' }

  llm_judge:                   # 任意。指定しないと軸 D は 1.0 固定
    rubrics:
      - name: relevance
        prompt: 出力が prompt の意図と整合しているか
        max_score: 10
```

### プログラム制約の種類

| `type` | 意味 |
|--------|------|
| `file_first_line_equals` | ファイル 1 行目が完全一致するか |
| `file_must_contain_in_order` | 指定文字列が指定の順序で出現するか |
| `file_line_starts_with` | (任意セクション内で) 指定 prefix で始まる行が `min_lines` 以上あるか |
| `file_line_max_chars` | (任意セクション内で) 各行の文字数が `max` 以下か |
| `file_section_max_lines` | 指定セクションの非空行が `max` 以下か |
| `file_no_pattern` | 正規表現 (multiline) にマッチしないか |

`section` は `## ヘッダ` の `ヘッダ` 部分（`##` は付けない）。指定無しならファイル全体が対象。

---

## 7. 内部構造

```
bench/
  fixtures/
  tasks/
  results/                                 # gitignored
src/bench/
  types.ts          # BenchTask / BenchResult / 制約スキーマ
  fixture-server.ts # localhost HTTP fixture server
  runner.ts         # /api/local/tasks に投入 + ポーリング + ログ収集
  grader.ts         # 軸 A/B/C のプログラム採点
  judge.ts          # 軸 D の LLM judge 呼び出し + JSON parse
  summary.ts        # bench/results/<run_id>/summary.md 書き出し
  grader.test.ts
scripts/
  bench-run.ts             # CLI エントリ (`npm run bench`)
  build-bench-fixtures.ts  # sales.xlsx 生成 (`npm run bench:fixtures`)
```

ベンチランナーは既存の `/api/local/tasks` API を使うだけで、orchestrator 内部とは疎結合になっている。新しい piece やツールを追加してもベンチ側は基本変更不要。

---

## 8. トラブルシューティング

| 症状 | 原因 / 対処 |
|------|-------------|
| `runner failed for ...: fetch failed` | `scripts/server.sh start` が立っていない、または `--server=` で指定したポートが違う |
| 全タスクで axis D が 0.0 | judge LLM のレスポンスが JSON parse 失敗。`bench/results/<run>/summary.md` の reasoning details を確認。OS の事情で短い応答しか返ってこないモデルなら `BENCH_JUDGE_MODEL` を別モデルに切り替える |
| タイムアウトで status が固まる | タスク YAML の `timeout_minutes` を伸ばす。プロバイダ側 `provider.timeoutMinutes` も併せて確認 |
| 同じタスクで毎回スコアが揺れる | LLM judge が確率的なため。programmatic 軸だけ見る・複数回平均を取る運用が無難 |
| `bench/results/` がコミットに乗ってしまった | `.gitignore` 済みだが、過去に追跡されていた場合は `git rm -r --cached bench/results` |

---

## 9. 参考: 関連 issue / 機能

- #156 — このベンチマーク自体
- #189 — `Read` で xlsx を開かない仕様（composite-mini-report の `forbidden_tool_for_ext` 罠と直結）
- #190 — preflight ログ表示の整理（activity.log を grader が読みやすいことの恩恵）
