LLM AGENT EVAL HARNESS

解説Q&A

この評価基盤を作る過程で出た疑問と、それに対する解説。設計判断の背景はここに集約している。

Q1OpenTelemetry準拠とは何か

OpenTelemetry(OTel)は、システムの動作記録の取り方を定めた業界標準規格です。もともとは複数のサービスにまたがる処理を追跡するために作られたもので、CNCF(Cloud Native Computing Foundation)が管理しています。エージェント評価の分野では、これがそのままトレース記録の共通語として使われています。

規格の中心にあるのがspan(スパン)という単位です。1つのspanは「ある処理の1区間」を表します。処理の開始時に1つ起こし、終了時に閉じる、実行の記録票のようなものです。中身は決まった項目でできています。

1つのspanに残るもの
名前 tool:run_sql 時刻 12:04:31.208 → 12:04:31.244 (36ms) 入力 SELECT c.name, SUM(oi.amount) FROM orders o ... 出力 12行 エラー なし 属性 row_count = 12

記録する側は、処理を with で囲うだけです。囲った範囲がそのまま1つのspanになります。

spanを1つ作るコード
with tracer.start_as_current_span("tool:run_sql") as span: span.set_attribute("db.statement", sql) rows = conn.execute(sql).fetchall() span.set_attribute("row_count", len(rows))

囲む対象は何でもかまいません。SQL実行、HTTP呼び出し、ファイル読み込み、いずれも同じ形のspanが1つ残ります。上の例でSQL文が見えているのは属性の1項目に入った値で、spanの形自体はどの処理でも共通です。

spanは親子関係を持てるので、1回の実行が木構造として残ります。本プロジェクトでは、1つの質問への応答が次のような木になります。

span記録される内容
agent-run(根)質問文・最終回答・総所要時間
├ tool:get_schemaツールの引数と返り値
├ llm:generate_sqlプロンプト・生成されたSQL・トークン数
├ tool:run_sql実行したSQL・結果 or エラー
└ llm:write_answer実行結果から回答文を作る過程
実際に記録されるspan(「8月の売上を教えて」の実行より抜粋)
span llm:generate_sql#1 属性 model = grok-4-fast-non-reasoning duration_ms = 1842 tokens = 731 出力 SELECT SUM(oi.amount) FROM orders o JOIN order_items oi ON o.order_id = oi.order_id WHERE strftime('%Y-%m', o.ordered_at) = '2024-08' ↑ 誤り。データは2026年しかないので該当0件になる span tool:run_sql#1 出力 {"columns": ["SUM(oi.amount)"], "rows": [[null]]} ← 該当0件 span llm:write_answer 出力 8月の売上は0円です。 ← nullを0と断定

この記録があるから、評価器は「回答文だけを見て推理する」必要がありません。年の誤りはllm:generate_sqlのspanから、断定はtool:run_sqlの結果とllm:write_answerの突き合わせから、それぞれ機械的に検出できます。

「準拠」にする理由は、記録が特定の製品に縛られなくなることです。OTel形式で出力しておけば、可視化ツール(Phoenix / Langfuse / LangSmith など)は受け取り側を差し替えるだけで乗り換えられます。独自形式で記録すると、ツールを変えるたびに記録の作り直しが必要になり、過去のトレースも読めなくなる。評価基盤は数年単位で使うものなので、この移植性は実利があります。

実装としては、OTelのSDKでspanを作り、HTTPで受信側(ローカルのPhoenixなど)へ送るだけです。本プロジェクトでは、受信側が起動していなくても動くようJSON Lines形式でも並行保存しており、可視化ツールなしでも評価パイプラインは完結します。

【2026年時点の標準化の現在地】 この構成は「枯れた古い手法」ではなく、いま標準になりつつある層です。OpenTelemetry自体はCNCF卒業プロジェクトで安定していますが、LLM/エージェント向けの属性規約(GenAI Semantic Conventions、gen_ai.*)は2026年時点でまだ実験的(experimental)ステータスで1.0に達していません(2026年6月のv1.42.0で専用リポジトリへ分離)。一方で採用は加速しており、DatadogはAgent Observabilityでv1.37以降ネイティブ対応、GrafanaもLLMトレースの収集を開始、LangfuseもOTel統合を公表しています。「規約はまだ固まりきっていないが、主要ベンダーが揃って寄せてきている」段階です。

本プロジェクトが使っているのはOTelのspan構造そのもの(安定した基盤側)で、gen_ai.* の細かい属性命名までは踏み込んでいません(実装ではPhoenix系の openinference.span.kind を使用)。規約が安定版になったときの移行負担は小さいはずです——span構造は変わらず、属性名の対応付けだけで済み、OTel側にも移行期に新旧の属性名を併記する仕組み(OTEL_SEMCONV_STABILITY_OPT_IN)が用意されています。

関連: Q3 judgeの位置(評価器はこのトレースを読む) / Q6 オンライン評価(本番でもこの記録が判断材料になる)

目次へ戻る

Q2Phoenixとは何か

Arize Phoenixは、記録したトレースを画面で見るためのオープンソースのツールです。pip install arize-phoenix で入れてローカルで起動すると、ブラウザ(既定で localhost:6006)からトレースを木構造で閲覧できます。外部サービスへの登録もデータの送信も不要で、すべて手元で完結します。

役割を一言でいうと「トレースの受信側」です。Q1のOpenTelemetry準拠で記録したspanをHTTPで受け取り、1回の実行を折りたためる木として表示します。各spanをクリックすると、そのステップの入出力・所要時間・トークン数・エラーが見える。失敗したときに「どのステップで壊れたか」を目で特定するための道具で、これがないとJSONを手で追うことになります。

Phoenixの画面で見えるもの(失敗した実行を開いたとき)
agent-run 1.9s 「8月の売上を教えて」 tool:get_schema 3ms 8 tables llm:generate_sql#1 1.8s 731 tokens ← クリックでSQL全文 tool:run_sql#1 2ms rows: [[null]] ← ここで異変に気づく llm:write_answer 0.4s 「8月の売上は0円です」

横に並ぶ所要時間で遅い区間が一目で分かり、run_sqlの結果が null なのに最終回答が「0円」だと分かる。JSONを開いて目で追う代わりに、木をたどって数クリックで原因に着く——これがPhoenixを使う実利です。

Phoenixがやることやらないこと
トレースの受信・保存・木構造での表示エージェントの実行(それは自前のコード)
spanごとの入出力・遅延・トークン数の確認合否の判定(それは評価パイプライン)
実行間の比較、評価スコアのspanへの紐付け本番の常時監視(規模が要るなら別構成)

同種のツールにLangfuse、LangSmith、Arizeの商用版などがあります。本プロジェクトがどれか1つに依存していないのは、記録がOpenTelemetry準拠だからで、受信先を変えるだけで乗り換えられます。Phoenixを選んだ理由は、オープンソースでローカル起動でき、アカウント登録なしに始められる点です。

なお本プロジェクトでは、Phoenixが起動していなくてもエージェントは動きます。トレースはJSON Lines形式でも並行保存しており、評価パイプライン(run_eval.py)はそちらを読むため、可視化ツールなしでも採点は完結します。Phoenixは人間がトレースを読むとき専用の任意の追加要素という位置づけです。

関連: Q1 OpenTelemetry準拠(何を記録しているか) / Q3 judgeの位置(記録を機械が読む側)

目次へ戻る

Q3MLflowとは何か、Langfuseと何が違うのか

MLflowは、機械学習の実験と成果物を管理するオープンソースの基盤です。2018年にDatabricksが公開し、現在はLinux Foundation管理下で、LLM以前の機械学習の時代から実験管理の事実上の標準として使われてきました。中核は「実験管理(experiment tracking)」——学習や評価の1回の実行をrunとして記録し、そのときの設定(パラメータ)と結果(メトリクス)を紐付けて蓄積、後から並べて比較する仕組みです。生成AIの普及後は、この土台の上にトレース記録(OpenTelemetry準拠)・LLM評価・プロンプトの版管理が追加されました。DatabricksのManaged MLflowとして提供されるため、Databricks案件では既定の選択肢になります。

Langfuseは逆の出自です。2023年創業のLLM専業で、最初からLLMアプリのトレース観測のために作られ、そこから評価・プロンプト管理へ広がりました(2026年1月にClickHouseが買収)。つまり2つは同じ機能群へ反対側から到達しています。MLflowは「実験管理から出発してLLM観測を後付け」、Langfuseは「LLM観測から出発して実験管理的な機能を後付け」。現在の機能表はかなり重なりますが、得意分野は出自の側に残っています。

 MLflowLangfuse
出自2018年・機械学習全般の実験管理2023年・LLMアプリのトレース観測
中核の強みrun履歴の比較。設定と成績を紐付けて蓄積し、変更前後を並べる本番トレースの観測。トークン消費・コスト・遅延の集計画面
トレース記録対応(OTLP受信あり)対応(OTelネイティブ。こちらが本業)
評価・プロンプト管理あり(評価はカスタム採点器の登録が前提の作り)あり(LLM-as-judge・人手アノテーションの画面が充実)
運用形態自己ホスト or Databricks Managed自己ホスト or クラウド版
向く状況Databricksスタックの案件。モデル/プロンプトの変更を成績で管理したい場合スタック非依存で本番観測を軽く始めたい場合

使い分けの実務的な目安は「どちらの台帳を正とするか」です。実験の比較(この変更で点が上がったか)を軸に運用するならMLflow、本番の観測(いまコストと品質はどうか)を軸にするならLangfuse。両方をOTelで併用する構成も普通にあり、トレースの送り先を2つにするだけで成立します。

本プロジェクトでは段階6でMLflowを採用しました。理由は実験管理の側が欲しかったからです。scoreboard.pyは「現在」の成績しか出せず、プロンプトや採点器を変えた前後の比較履歴が残らない。評価の1実行をrunとして記録し、モデル・設定と成績を紐付けて蓄積する部分をMLflowに任せ、採点ロジック(grader・トラジェクトリ検査)は自作のまま登録する分担です。トレースの送信先も環境変数でPhoenix/MLflowを切り替えられるようにし、同じ送信コードのままMLflowの/v1/tracesで受信されることを確認済み——Q1で述べたOTel準拠の移植性の実証になっています。Phoenix・LangSmith・Langfuseの3ツール比較はツール比較のページを参照。

目次へ戻る

Q4LLM-as-judgeはワークフローのどこにいるのか

結論から言うと、エージェントのワークフローの中にはいません。エージェントが回答を出し終えた後、記録されたトレースと回答文を入力として動く別のプロセスです。審査員は答案を作る側と同席しない、と考えると分かりやすい。

実行時に割り込む唯一の防御は run_sql の直前に走る禁止操作のガードレールだけで、これは決定論のコードです。judgeが不合格と判定してもエージェントの回答は書き換わらず、採点結果が記録されるだけ——これが「ワークフローの中にいない」の意味です(オフライン評価は traces.jsonl を読む独立したスクリプト、オンライン評価はエージェント実行直後の同一スクリプト内ですが、どちらもjudgeの入力は完成した実行記録であり、エージェントの出力には影響しません)。judgeを実行時に挟む構成(回答を出す前にjudgeが検閲する)も実務には存在しますが、遅延と費用が倍増するため本プロジェクトでは採用していません。

この構図の前提として重要なのが、評価は回答文だけを読んでいるのではないという点です。エージェントの動作中に、スキーマ調査・生成したSQL・実行結果・再試行・最終回答のすべてがトレースとして自動記録されています。評価器はこの記録を読むので、回答文から手順を推理する必要がありません。

関連: 概要ページの全体構成図(実行時レーンと実行後レーンの2段構成)

目次へ戻る

Q5評価結果が実行のたびに変動するのは、LLMのせいか評価器のせいか

両方が関与していますが、役割が違います。

評価器そのものはブレていません。Execution Accuracyの判定はgrader(純粋なコード)が行うので、同じ入力には100%同じ判定を返します。自己テストで守られており、実行ごとに判定基準が変わることはありません。

変動の根本原因はLLM側の出力の揺れです。temperature=0でも、3回の実行で15問中7問のSQLが変わりました。推論サーバー側の要因(リクエストのバッチ構成によって浮動小数点演算の順序が変わり、僅差のトークン候補が入れ替わる)による非決定性で、利用者側の設定では消せません。

ただし、揺れが合否の変動に化けた箇所には評価器の欠陥が絡んでいました。7問でSQLが変わったのに判定が揺れたのは1問だけ。残り6問は「SQLは違うが結果は同じ・判定も同じ」でした。揺れた1問の中身は、月ラベルが「2026-01」形式か「01」形式かという意味的には無害な違いで、数値は3回とも正しかった。それを×にしたのは、graderがラベルの書式に敏感だったからです。

実際に揺れた1問(月別売上)。数値は3回とも正しく、月ラベルの書式だけが違う
1回目・3回目 SELECT substr(o.ordered_at, 1, 7) AS m, SUM(...) FROM orders o JOIN order_items oi ON o.order_id = oi.order_id WHERE o.status = 'paid' GROUP BY m → [['2026-01', 3378400], ['2026-02', 3538100], ...] 2回目 SELECT strftime('%m', o.ordered_at) AS month, SUM(...) FROM orders o JOIN order_items oi ON o.order_id = oi.order_id WHERE o.status = 'paid' GROUP BY month → [['01', 3378400], ['02', 3538100], ...] × ↑ 金額は完全に一致。ゴールドが '2026-01' 形式のため文字列比較で不一致

つまり因果の連鎖は「LLMの無害な揺れ → 評価器の書式感受性がそれを増幅 → 合格数の変動として表面化」という2段構造。根本原因はLLMの非決定性、増幅器は評価器の設計です。graderに書式の正規化を入れた後は、3回とも同じ判定で安定しました。LLMの揺れは消せないが、揺れを合否の変動に変換しない評価器は作れる——これがこの件の要点です。

目次へ戻る

Q6検査はコードで書くべきか、LLM-as-judgeに任せてよいか

2つの軸で線引きします。

第1軸: 構造化された記録(SQL文字列・結果行・回数)の上でコードとして書けるか。書けるなら決定論、書けない(自然言語の読解や意味比較が要る)ならjudge。

第2軸: その規則は汎用的な不変条件か、一回きりの事例パッチか。決定論に書いてよいのは前者だけです——データ期間(スキーマのメタ情報から導ける)、業務定義(セマンティックレイヤー由来の有限の規則)、禁止操作、スキーマに存在するテーブルのみ参照、のような「システムの仕様として安定している条件」。事例ごとの継ぎ接ぎ規則を決定論側に積み上げると、ニッチな正規表現の博物館ができあがり保守不能になります。汎用化できない検査は、judgeの汎用検査に委ねるのが正しい。

この基準で本プロジェクトの検査を分類すると、期間外の年・税抜の算出経路は汎用側(データ期間と業務定義という安定した不変条件)なので決定論が正当。judgeの正当な守備範囲は「断定」「言語化」「忠実性」の3検査に集約され、いずれも自然言語の読解が本質的に必要で、かつ特定の題材に依存しない汎用検査です。

なお、事前に書ける規則(禁止操作・試行回数の上限)と、失敗を観測して初めて書ける規則(期間外の年など)は区別が必要です。前者は初日に書くべきで、後者は認識の限界であって怠慢ではありません。観測した失敗は「コードで表現できるか」「汎用化できるか」を問い、両方満たすときだけコード化します。

目次へ戻る

Q7オフライン評価とオンライン評価は何が違うのか

オフライン評価・オンライン評価は、このプロジェクトの造語ではなく機械学習の運用で昔から使われてきた業界標準の区分です。検索や推薦システムの分野で「手元のデータで測る評価」と「本番投入後にユーザーの反応で測る評価」を呼び分けるために定着し、LLMの評価にもそのまま持ち込まれました。LangSmith・Langfuse・Phoenixといった評価ツールのドキュメントでも、この2語が節の見出しになっています。つまり新しい概念ではなく、LLM以前からある区分に、LLM特有の中身が入ったものです。

区分の軸は場面です。本番に出す前の評価がオフライン評価、出したあとの評価がオンライン評価と呼ばれます。

オフライン評価は、あらかじめ用意した試験問題を解かせて採点することです。学校の期末試験に近く、問題と模範解答が先に揃っていて、同じ問題を何度でも解かせられます。本プロジェクトでは15問を用意し、コードやプロンプトを変えるたびにこれを解かせて点数を出しています。ここで答えたい問いは「この変更を本番に出してよいか」です。

オンライン評価は、本番で実際に来た質問への応答を、その場で採点し続けることです。試験ではなく、現場での抜き打ち検査に近い。問題は選べませんし、模範解答もありません。ここで答えたい問いは「いま壊れていないか」で、おかしな兆候が出たら警報を上げます。

名前は接続の有無とは関係がありません。本番の通信の外側で回すのがオフライン、内側で回すのがオンライン、という区切りです。どちらもネットワークにはつながっていますし、どちらもLLMのAPIを呼びます。

2つの性質を並べると次のようになります。

 オフライン評価オンライン評価
いつ動くか変更を取り込む前(CI)本番の稼働中、実行のたび
採点する対象固定した試験問題(本プロジェクトは15問)実際に来たユーザーの質問
正解の有無人間が事前に用意済みない
何を決めるかこの変更を出してよいか(合否)いま壊れていないか(警報)
数値の読み方前回と直接比べられる推移で見る

ここで効いてくるのが問題が固定かどうかです。オフラインは毎回同じ15問を解かせるので、点が下がったらそれは変更のせいだと言い切れます。オンラインは来る質問が毎回違うため、今日の点が昨日より低くても、変更のせいなのか難しい質問が多かっただけなのか切り分けられません。だから警報は1回の値ではなく推移で判断します。

分かれ目は1点だけです: その評価器が「事前に用意した正解(ゴールド)」を参照するかどうか

オフラインでしか使えない評価器は、Execution Accuracy(ゴールデンデータセット照合)です。これは「エージェントの出力」と「人間が事前に確定した正解値」の突き合わせなので、正解が存在しない場所では原理的に動きません。そして本番の質問に正解が用意できない理由は、質問が事前に列挙できないからです。ユーザーは何を聞いてくるか分からず、「8月の売上を教えて」に対する正解SQLと正解値を先回りして人間が作っておくことは不可能。正解作りは人間の労働なので、有限の試験問題(本プロジェクトでは15問)にしか投資できない——これがオフライン評価がオフラインに閉じる理由です。

オンラインでも使える評価器は、実行エラー・試行回数・トラジェクトリ検査・judgeの4つ。共通点は正解の代わりに「答えが何であれ成り立つべき条件」を検査していることです。参照点が外部の正解ではなく、システムの不変条件と実行記録の内部整合に置かれている。

評価器参照している基準
実行エラー・試行回数正常な動作の形(例外を出さない・上限回数内)。正解を知らなくても異常と分かる
トラジェクトリ検査不変条件(どんな質問でも2024年で集計するのは誤り・廃止テーブルは触らない)。答えの中身と無関係に判定できる
LLM-as-judge手元の3点(質問・実行結果・回答文)の間の矛盾。外部の正解が不要
同じ失敗を、オフラインとオンラインでどう捕まえるか
質問 「8月の売上を教えて」 (データは7月まで) 回答 「8月の売上は0円です」 オフライン評価 正解値 = 事前に用意した「該当データなし」 Execution Accuracy × 正解と不一致 → 直接「間違い」と言える オンライン評価 正解値 = 存在しない Execution Accuracy 照合対象がなく測定不可 トラジェクトリ検査 × SQLに2024年 → 不変条件違反(正解を知らずに判定) LLM-as-judge × 結果はnullなのに「0円」と断定 → 内部矛盾

オンライン側は「間違い」とは言えていません。言えているのは「守るべき条件を破っている」「回答が実行結果と矛盾している」だけ。それでも同じ失敗を捕まえられています。

まとめると、オフライン評価は「答えが正しいか」を外部の真実と比べて測り、オンライン評価は「答えが自分自身と矛盾していないか・守るべき条件を破っていないか」しか測れない。だから本番で「正しさ」そのものは直接監視できず、矛盾と違反の検出で代用する——これが評価を二層で運用する理由の全体です。

補足として、この2つは実務では循環でつながっています。オンラインで見つかった失敗(例: 在庫のすり替え)に人間が事後に正解ラベルを付ければ、それは新しいゴールド事例としてオフラインの評価セットやjudgeの較正セットに追加できる。業界の運用で本番出力の数%を人手レビューするのは、まさにこの「オンラインの事件をオフラインの資産に変換する」工程です。

目次へ戻る

Q8ゴールデンデータセット照合とオフライン評価は同じものか

指しているものはほぼ重なりますが、語としては別の軸に属します。オフライン評価は「いつ・どこで回すか」という場面の名前、ゴールデンデータセット照合は「何と比べて採点するか」という手法の名前です。どちらも機械学習の評価で定着している標準語で、対立する2つの流儀があるわけではありません。本プロジェクトでは、オフライン評価という場面で、ゴールデンデータセット(15問の質問と確定済みの正解値)と照合する手法を使っています。

2語が別々に存在する理由は、オフライン評価にはゴールド照合以外の評価器も同居できるからです。このプロジェクトのCIでも、同じ15問に対してゴールド照合(Execution Accuracy)と、ゴールドを使わないトラジェクトリ検査・judgeを一緒に走らせています。逆にゴールド照合だけはオンラインへ持ち出せません。本番の質問には正解が用意できないからです(Q7)。

手法の軸で本当に使い分けが問われるのは、ゴールド照合と、ゴールド不要の評価(judge・不変条件検査)のどちらを使うかです。ここには業界でほぼ定着した分業があります。

状況使うもの理由
リリース判定・回帰テスト(CI)ゴールド照合が主力合否を言い切るには外部の正解が要る。judgeの点数だけでデプロイを止める運用は揺らぎが大きく信用されない
本番監視ゴールド不要の評価のみ正解が原理的に用意できず、選択肢がない
正解が一意に定まらないタスク(要約・自由記述)judge中心+少量の人手ラベルで較正正解値との一致という判定自体が成立しない

どちらかが廃れる方向の潮流はなく、むしろ「judgeを人手ラベルで較正する」という形で両者の接続が密になっています。本プロジェクトの段階5(judge較正)がその実装です。

関連して、評価ツール(Phoenix / LangSmithなど)は乗り換えが利きますが、自社タスクのゴールデンデータセットは作った分だけ残る資産です。評価基盤の仕事で長く価値が落ちないのは、ツールの操作よりこのデータセットの設計と維持のほうです。

目次へ戻る

Q9Execution Accuracyはどう計算しているのか

Execution Accuracyは、text-to-SQL評価の標準指標です。かつては生成SQLの文字列を正解SQLと直接比べるExact Matchという指標が使われていましたが、同じ質問に正しいSQLは何通りも書けるため、正しい答えを大量に×にしてしまう。そこでSQL文ではなく実行結果を比べる方式へ移り、SpiderやBIRDなどの公開ベンチマークでも主指標になっています。ゴールデンデータセット照合という分類(Q8)の中の、text-to-SQL向けの指標名という位置づけです。

計算式は単純で、LLMは一切関与しません。

計算式
Execution Accuracy = 一致した質問数 ÷ 全質問数 # 1問ごとの判定は0か1。生成SQLを実際に実行し、 # 結果表が正解と一致すれば1、しなければ0。本プロジェクトは 13/15

採点側がただの照合コードなので、同じトレースを何度採点しても必ず同じ数値が出ます。オフライン評価の点数をCIの合否に使えるのはこの再現性があるからで、judgeの点数だけでデプロイを止められないのと対になっています(Q6)。

ただし式は単純でも、「一致」の定義が設計の本体です。本プロジェクトのgrader.pyでは、型の違い(4040.0)・余分な列・日付ラベルの書式差(2026-0101)を吸収し、順序はランキング系の質問だけ一致条件に含めています。この吸収を実装する前は、正しい答えを5件×にして8/15と出ていました。指標の式ではなく照合の緩さ/厳しさが数値を決めるため、grader.py自体に自己テストを15件持たせて、照合コードが壊れていないことを先に検査しています。

目次へ戻る

Q10ゴールデンデータセットは誰が・いつ作るのか

まず中身から。ゴールデンデータセットで自然言語なのは質問だけで、正解側は文章ではなく構造化された値を持ちます。

stage0/questions.json の実物(1問分)
{ "question": "商品は全部で何種類ありますか?", ← 自然言語 "gold_sql": "SELECT COUNT(*) FROM products", ← 正解SQL(根拠として保存) "gold_answer": 40 ← 正解値 }

正解SQLを実行するのはゴールド生成時の1回だけで、出た値をgold_answerとして確定します。採点時に読むのは値だけで、gold_sqlは「正解値がどう導かれたか」を人間が検証するための根拠です。ルールベースの照合が成立するのは、比較の両側が数値と文字列の表だから。正解側が「40種類です」のような文章だったら文字列一致では比べられず、この方式は崩れます。

誰が作るかは分業です。正解値の計算は機械にしかできません(40件か41件かを人間が数えるのは無理)が、「何を正解とするか」の決定は人間にしかできない——「売上とはpaidのみ・税抜・クーポン除外」といった定義の判断がそれで、ここを間違えるとゴールド全体が汚染されます。業界の作り方は3通りあります。

作り方実態注意点
ドメイン専門家が手で作るBIRDやSpiderはこれ。品質は最高コストが高く、量が作れない
LLMに下書きさせて人がレビュー実務で最も多い生成側と採点側が同じモデルだと、モデル固有の誤解がゴールドに入り込み評価が素通しする
本番ログに人が事後ラベルを付ける運用が回り始めた組織の主軸オンラインで見つけた失敗が資産に変わる(Q7の循環)

いつ作るかは設計フェーズです。ゴールドセットは実質的に受入条件そのもので、15問を作る作業は「売上とは何か」を確定する作業になります。実装後に作ると、実装の出力を見てから正解を決めることになり、実装のバグを追認するゴールドができる——実装を読んでからテストを書くとバグごと通ってしまうのと同じ構造です。ただし設計時に全量は作れないので、設計時に代表的な質問と罠を10〜30問、実装中に失敗を見つけるたびに追加、本番後はオンラインの事件にラベルを付けて追加、と育てます。

受託開発では、ゴールドセット作りはそのまま要件定義の成果物になります。「何ができれば完成か」を顧客と握る手段として、抽象的な仕様書より具体的な質問と正解の表のほうが議論が早く、納品後の合意の根拠にもなります。

目次へ戻る

Q11SQLで解けない質問はゴールドセットに入れられるのか

入れます。むしろ除外すると、答えられない質問にどう振る舞うかを一度も測らないまま出荷することになります。エージェントは必ず何か答えを返そうとするので、テストされていない領域では平然と捏造します。本プロジェクトのオンライン評価でも、存在しない在庫テーブルについて聞かれたエージェントが、売上の少ない商品を在庫のように言い換えて返す挙動が出ました。

この考え方の出自は質問応答分野のSQuAD 2.0(2018年)です。回答不能な問いを意図的に混ぜたのが転換点で、それ以前は「答えられる問いにどれだけ正確に答えるか」だけを測っており、モデルは常に何か答えるほうが得だった。不能問題を入れて初めて「答えない」という判断が能力として測れるようになりました。τ-benchが「ポリシー違反をしないこと」を合格条件に含めるのも同じ系譜です。

入れ方は、正解の形を変えます。値ではなく期待する振る舞いのラベルをゴールドとして持ちます。

正解の型が変わる(概念例。本プロジェクトは値の型のみ実装済み)
# SQLで解ける質問 { "question": "商品は全部で何種類?", "expected": { "type": "value", "answer": 40 } } # SQLで解けない質問(正解値は存在しない) { "question": "在庫が少ない商品を教えて", "expected": { "type": "refuse", "reason": "在庫データは存在しない" } }

採点は大部分ルールで書けます。refuse型なら「回答文に数値や商品名を出していないか(出していたら捏造)」「データが存在しない旨の言明があるか」は正規表現と文字列検査で判定できる。正解値との照合はできなくても、「この振る舞いをしたか」の判定はルールベースで可能です。judgeに残るのは「明示せず別の量へすり替えた」のような読解が要る部分だけで、値のある質問と同じ分担(Q6)になります。

ここで効いているのは検査は解くより常に簡単という非対称性です。「在庫データがないと正しく断れるか」を検査するコードは数行ですが、それを解くコードは書けません(書けるならLLMが不要です)。評価基盤が成立するのはこの非対称性のおかげです。実務ではゴールドセットの2〜3割を答えられない・曖昧・範囲外の問いに充てるのが目安で、設計フェーズでこれを決めると「対応範囲外の質問にどう応じるか」という要件が自然に確定します。なお本プロジェクトの15問はすべて値の型で、振る舞い型はオンライン評価の10問に混ぜただけでゴールド化していません。これは現状の設計の穴です。

目次へ戻る

Q12ガードレールと評価器は何が違うのか

「ガードレール」は一般のソフトウェア開発では広い比喩で、CIの必須チェックやlintを指して使っても自然に通じます。ただしLLM評価の文脈では、実行時の防御に意味が狭まって定着しつつあります。Guardrails AIやNVIDIA NeMo Guardrailsといった製品名の影響が大きく、この分野で「ガードレールを入れる」と言うと、本番の入出力を実行時に検査してブロックする層を指すのが既定です。

評価器との区別は、動くタイミングと目的で引けます。

 動くタイミング目的失敗時の動作
ガードレール実行の途中(出力がユーザーへ届く前)事故を止めるブロック・差し戻し
評価器実行のあと品質を測る記録・警報・CI赤

本プロジェクトにも両方あります。禁止操作の検査(DROP/DELETEを含むSQLを実行前に拒否)は実行を止めるのでガードレール。トラジェクトリ検査やExecution Accuracyは実行後に採点するので評価器です。

重要なのは同じ検査ロジックが両方に使い回せることです。「存在しないテーブルを参照していないか」という検査は、CIで回せば評価器、本番で実行前に噛ませればガードレールになります。決定論的な検査は速くて安いのでこの転用が利き、judgeは遅くて高いので実行経路には挟みにくい。コードで書ける検査を書き尽くす方針(Q6)にはこの実利もあります。

区別に迷ったら「何が世に出るのを止めているか」で切ると明確です。CIのチェックは悪いコード変更が本番に入るのを止め、実行時ガードレールは悪い応答がユーザーへ届くのを止める。どちらも柵ですが守っている対象が違います。LLM評価の人と話すときは、CI側を「評価のCIゲート」「回帰チェック」、実行時側を「ガードレール」と呼び分けると誤解なく通じます。

目次へ戻る

Q13このプロジェクトはRAG方面へ進むべきか

結論から言うと、RAG方面へは進みません。理由は3つあります。

(1) RAG評価は評価分野で最も商品化が進んだ領域。Ragas・DeepEval・Galileoが揃って faithfulness / context precision / context recall といった指標を標準搭載しており、しかも実装の中身は「練られたプロンプト+LLM-as-judge」。自作しても既製品の劣化版になりやすい領域です。ただし公平を期すと、医療・法務・金融など規制領域のRAG評価は誤答が規制リスクを持つため、経験者に10〜18%の報酬上乗せが付いています。「RAG評価は無価値」ではなく「汎用領域では商品化済み」が正確な認識です。

(2) この題材の強みを捨てることになる。text-to-SQLを選んだ最大の利点は正解が機械的に確定できることで、SQLを実行すれば答えが一意に出るためExecution Accuracyという決定論の物差しが成立する。RAGへ移ると正解が「この回答は妥当か」という曖昧な判断になり、評価の土台がjudge頼みになります。本プロジェクトの主張——決定論を最下層に、judgeは残余だけ——を自ら崩す方向です。

(3) 市場の需要は「取得の品質」から「業務の完遂」へ移っている。2024年末以降、「AI Evals Engineer」「LLM Evaluation Engineer」が独立した職種として求人化しており、2026年時点では「履歴書に評価の記述がなければ、未評価の機能を出荷してきた人と見なされる」水準に達しています。需要はエージェント評価の側にあります。

では次に何へ進むか — τ-bench型の環境

拡張の方向として最初「複数ステップの業務フロー」「業務ルールの遵守検証」「副作用を伴う操作」の3つを別々の候補として挙げたが、これは整理の誤りだった。業界のエージェント評価標準であるτ-bench / τ2-bench(Sierra社。小売・航空・通信ドメイン)を確認すると、この3つは1つの統合パターンとして実装されています。

τ-benchの構成要素内容
業務ポリシー文書ドメインごとに書面の規定を与え、エージェントはそれに従う必要がある
複数ターンのツール使用利用者との対話とツール呼び出しが交互に進み、DBの状態が変化する
二重の合格条件意図の達成(利用者の要求が満たされたか)とポリシー無違反(規定に反する操作をしていないか)の両方を満たして初めて合格
採点の方式DBの最終状態と正解状態の照合+ポリシー違反の機械検査。決定論ベース

重要なのは、この方式が決定論の物差しを保ったまま複雑さを上げられる点です。本プロジェクトの核をそのまま持ち込めます。現在のEC題材の上に「返品を受け付ける」「与信を確認してから注文を確定する」といったポリシー文書つきの業務タスクを載せれば、τ-bench型のミニ環境になります。

追い風: 2026年時点で公開ベンチマークは飽和と攻略が問題化しており(τ-benchも2026年6月に再検証が入った)、業界の関心は「公開ベンチのスコア」から「自社ドメインの自作ハーネス」へ移っています。本プロジェクトでやってきた「自分のデータで自分の評価セットを作る」経験は、この潮流の側にあります。

【方向性の合意 — 2026年8月】 上記の判断はプロジェクト所有者と合意済み。RAG方面の評価ハーネスは別プロジェクトで並行して進めており、本プロジェクトで重複させない。両方を持つことで「取得の品質」と「業務の完遂」の双方を扱える構成になる。次の技術マイルストーンはτ-bench型の環境(ポリシー文書+複数ターンのツール使用+DB状態の決定論照合)で確定。ただし着手の前提として、既存の骨組み4段を所有者自身がローカルで一度通して回すことを優先する。

関連: Q5 検査の振り分け(ポリシー検査も同じ線引きで設計する) / ツール比較(製品側のエージェント評価機能)

目次へ戻る

Q14τ-benchとLangSmith・Phoenixは同じカテゴリか

全くベクトルが違います。カテゴリが3つに分かれます。

  τ-bench LangSmith / Phoenix / Langfuse 本プロジェクト
分類 ベンチマーク
問題集+採点規則
基盤
記録・閲覧・評価の実行環境
自作ハーネス
自ドメインの問題+採点機
誰が問題を決めるか ベンチマークの作者(Sierra社) 利用者。製品は関与しない 自分
主な用途 モデル・エージェントの横並び比較 開発と本番運用の道具 自ドメインの品質保証
常駐するか しない(データと規則の定義) する(サービス) しない(スクリプト)

組み合わせて使うもので、競合しません。τ-benchの問題をLangSmithで実行してトレースを取る、という使い方が一般的です。Q13で「τ-bench型の環境を建てる」と書いたのは、τ-bench本体を導入するという意味ではなく、その設計思想(ポリシー文書を与える/複数ターンで状態を変える/意図の達成とポリシー無違反の両方を要求する)を自ドメインの題材に取り込むという意味です。

2026年時点で公開ベンチマークは飽和と攻略が問題化しており、業界の関心は「τ-benchのスコアが何点か」より「自社ドメインで同じ厳密さの試験を自作できるか」に移っています。τ-benchは真似る対象であって、追いかける対象ではない——これが本プロジェクトの立ち位置です。

関連: Q7 次の方向性 / ツール比較(基盤3製品の機能別比較)

目次へ戻る

Q15トレースを実際に読むと何が見えるか

記録したトレースを探索用ノート(explore.py / marimo)で開き、失敗した実行を1件ずつ辿った記録です。 ノートはエージェントを再実行せずに評価器だけを掛け直せるため、API費用と待ち時間なしで判定の設計を試せます。

例1: 実行は成功、結果は0件、回答は断定

オフライン15問のうち、Q11「6月と比べて7月に売上(税抜)が増えたカテゴリをすべて挙げてください」の実行記録です。

Q11 のspan(探索ノートの表示より)
agent-run (CHAIN, 2336ms) tool:get_schema (TOOL, 0ms) 8 tables llm:generate_sql#1 (LLM, 1447ms, 737tok) SELECT c.category_name FROM categories c JOIN products p ON c.category_id = p.category_id JOIN order_items oi ON p.product_id = oi.product_id JOIN orders o ON oi.order_id = o.order_id WHERE o.status = 'paid' AND strftime('%Y-%m', o.ordered_at) IN ('2024-06', '2024-07') GROUP BY c.category_id, c.category_name HAVING SUM(...) > SUM(...) tool:run_sql#1 (TOOL, 2ms) 正常終了 / 結果 0件 llm:write_answer (LLM) 「6月と比べて7月に売上(税抜)が増えたカテゴリはありません

データは2026年のみです。2024年を指定すれば該当行は存在しないので、SQLはエラーを出さずに0件を返します。 実行エラー率の監視では見つからない型の失敗で、ここで3つの層が別々に働きます。

検出できるもの
実行エラー検出できない。SQLは正常終了している状態
トラジェクトリ検査(決定論)SQL文字列に 2024 がある → 期間外の年として検出
LLM-as-judge結果0件からの断定を検出。「0件だった」と「存在しない」の区別はSQLからは読めず、回答文の読解が要る

例2: 決定論の網を全部すり抜ける失敗

オンライン10問のうち、online-10「在庫が少ない商品を教えて」。このDBに在庫テーブルは存在しません。

online-10 のspan
llm:generate_sql#1 (LLM, 945ms, 623tok) SELECT p.product_id, p.name, p.unit_price, SUM(oi.quantity) AS total_sold FROM products p LEFT JOIN order_items oi ON p.product_id = oi.product_id GROUP BY p.product_id, p.name, p.unit_price ORDER BY total_sold ASC LIMIT 10 tool:run_sql#1 (TOOL, 4ms) 正常終了 / 10件 llm:write_answer 「在庫が少ない商品(売上数量が少ない順)トップ10は以下の通りです: バッグ(29): 237個 / モニター(5): 245個 / …」

SQLは販売数量の少ない順で、在庫の話は一切していません。それでも構文は正しく、規則違反はなく、 回答の数字は実行結果と一致している——決定論の検査はすべて通過します。 括弧内で別の量に言い換えたまま個数を並べており、判定できるのは「質問が求めた量と、回答が報告している量が別物」という 意味の比較だけで、これが substituted 検査(Q13参照)の担当範囲です。

例3: 評価器の制約が例外として現れる

ノートの読み込み先をオフラインのトレースからオンラインのトレースへ切り替えると、 ゴールデンデータセット照合のセルが停止します。

オンラインのトレースにオフライン用の評価器を掛けたとき
KeyError: 'online-01' ok, reason = grade(r.get("result_rows"), gold[r["qid"]]["gold_answer"], ...) ~~~~~~~~~~~~~~~~~ → 本番の質問にはゴールドが存在しないため、照合対象が引けない

不具合ではなく、Q7で述べた制約がそのまま実行時に現れたものです。 Execution Accuracyはゴールドがなければ原理的に測定できません。 一方で同じトレースに対し、トラジェクトリ検査とjudgeは問題なく動きます。 「オフラインとオンラインで使える評価器の集合が変わる」ことの実物です。

関連: Q1 トレースの構造 / Q6 評価器の使い分け / Q7 次の方向性

目次へ戻る