Files
maestro/docs/bench.md
T
oss-sync 57702b4f34
CI / build-and-test (push) Canceled after 0s
sync: update from private repo (2336949a)
2026-09-17 11:29:31 +00:00

242 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ベンチマーク (`npm run bench`)
エージェントの **ツールコール能力 / 命令追従性 / 頭の良さ / チェックリスト使用 / 効率** を、1 つの統合タスクから多軸で計測するためのフレームワーク。
モデル変更・piece 改修・ツール追加などの前後で同じタスクを走らせ、品質回帰を検出する用途を想定している。
---
## 1. 前提
| 項目 | 必須 |
|------|------|
| `scripts/server.sh start`(またはそれ相当)でオーケストレータが起動していること | ✅ |
| `config.yaml` の `provider` が動作する LLM を指している(タスク実行 + judge の両方で使う) | ✅ |
| 初回のみ `npm run bench:fixtures` で `bench/fixtures/sales.xlsx` を生成 | ✅ |
テスト用の Web 素材はローカルで配信する。タスク実行とモデルによる採点は、設定したサーバーへ通信する。`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 は「採点していない」扱いで総合点の計算から外れる)
BENCH_JUDGE=off npm run bench
# judge を別エンドポイント・別モデルにする
BENCH_JUDGE_ENDPOINT=https://api.example.com/v1 \
BENCH_JUDGE_MODEL=gpt-oss:20b \
npm run bench
```
### 判定用モデルの設定と認証
既定では `provider.workers` の先頭から接続先・モデル・API キーを取得する。ワーカーが無い場合は旧形式の `provider.baseUrl` / `provider.model` を使う。
`BENCH_JUDGE_ENDPOINT`、`BENCH_JUDGE_MODEL`、`BENCH_JUDGE_API_KEY` は、それぞれワーカー設定より優先される。接続先を別の URL に変更した場合は元のワーカーのキーを引き継がないため、認証が必要なら `BENCH_JUDGE_API_KEY` を設定する。同じ接続先の末尾スラッシュの有無は区別しない。キーを明示的に空文字にすると、認証ヘッダーを送らない。
実行が終わると `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: 77 / 100** (avg of 2 scored tasks of 3, scored weight 75-100 of 100)
**Failed runs: 1** (kept in the table below, excluded from the average)
| Task | Outcome | Status | Total | Scored weight | A | B | C | D |
| ---------------------- | ------- | --------- | ----: | ------------: | --- | ---- | --- | ------- |
| composite-mini-report | graded | succeeded | 73 | 100/100| 90% | 100% | 70% | 60% |
| judge-off-task | graded | succeeded | 80 | 75/100| 100%| 100% | 50% | skipped |
| exploded-task | failed | – | failed| – | – | – | – | – |
```
総合行の `scored weight 75-100 of 100` は、平均に入れた 2 件が別々の重みで割り直されていることを示す。全件が同じ重みなら `scored weight 100 of 100` と 1 つの数字になる。
各タスクの詳細セクションでは axis ごとに ✓/✗ 内訳とツールコールの全シーケンスが折りたたみで見られる。
**Scored weight 列を必ず一緒に見ること。** 採点した軸の顔ぶれが違う総合点どうしは、同じ 100 点満点でも別の試験の点数になる。
`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 に数値表示のみ) | – |
### 採点した軸だけで割り直す
各軸は `scored` / `skipped` / `failed` の 3 状態を持つ。
| 状態 | 意味 | 総合点への扱い |
|------|------|----------------|
| `scored` | 実際に評価した。0..1 の点がつく | 重みぶん加算する |
| `skipped` | 測っていない(その軸の期待値をタスクが書いていない、judge が OFF など) | 分子からも分母からも外す |
| `failed` | 採点そのものが壊れた(judge の通信失敗・応答が JSON でない・結果が空) | 分子からも分母からも外す |
総合点は `採点できた軸の (score × 重み) の合計 ÷ 採点できた軸の重みの合計 × 100`。
`skipped` を満点、`failed` を 0 点として混ぜない。前者は「測っていないもの」を「満点だった」に見せかけ、後者は「採点機が壊れた」を「中身が悪かった」に見せかけるため。
採点できた軸が 1 つも無いときは総合点を出さず `not scored` と書く。`result.json` の `scoring` に `scoredAxes` / `scoredWeight` / `uncountedAxes`(状態と理由つき)が入るので、後から run どうしを比べるときに顔ぶれの違いが分かる。
タスクの実行そのものが落ちた場合は `outcome: "failed"` の結果として一覧に残り、平均からは除いたうえで件数を表示する。落ちた課題を黙って捨てない。**1 件でも落ちたら `npm run bench` の終了コードは 1 になる**(点が低いだけなら 0 のまま。走れなかったことだけを信号にする)。
`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 が読みやすいことの恩恵)