【Bug已解决】Eval dataset for agents
一、现象长什么样
想给一个 LangChain 智能体(agent)做评测(evaluation),第一步就卡住了:没有现成的、结构化的评测数据集格式。官方 examples 里要么是零散的"跑一个例子看输出",要么是把评测逻辑硬写在 notebook 里,导致:
- 每次加新用例都要改代码,没法"数据驱动"地批量跑。
- 用例没有统一的字段约定(输入是什么、期望的中间动作是什么、期望的最终答案长什么样),不同人写的评测无法对齐。
- 评测结果无法积累、对比(今天改了 prompt,想跟上周比,发现上周的"数据集"就是一段脚本)。
- 想接 LangSmith / 自定义
EvalChain时,输入格式对不上,得手写一堆 adapter。
一句话:缺一个"agent 评测数据集"的最小规范与加载器,让评测从"手搓脚本"变成"喂数据"。
二、背景
传统 LLM 评测数据集(如 MMLU、GSM8K)只有question和answer两列。但 agent 评测复杂得多:一个任务可能要多次工具调用、产生中间轨迹(trajectory),最终答案也可能不唯一(只要满足某些性质即可)。因此 agent eval dataset 至少要有:
task:自然语言任务描述。tools/expected_tools:可用工具,或期望被调用的工具序列。reference:参考答案(可软性,比如"包含某关键词""数值在范围内")。metadata:难度、领域、是否需多步等。
LangChain 有LangSmith的Dataset概念,但很多团队不想把评测数据绑死在某个 SaaS 上,想要一个本地、可版本化(进 git)、可加载成list[dict]的轻量格式。
三、根因
根因是"没有约定":
- 无 schema:社区没有统一的 agent eval 数据 schema,每家用各自 ad-hoc 字段,无法复用。
- 无加载器:即便写成了 JSON/CSV,也没有把"数据集"映射成"可跑的评测用例"的 loader,每次都要重写解析。
- 评测与数据耦合:eval 逻辑直接读具体字段,数据集一改列名就崩。
本质:把"评测数据"和"评测代码"混在一起,缺少"数据集即数据"的抽象。解决的不是算法,而是工程规范。
四、最小可运行复现
下面演示"无规范"的痛苦:散落的用例 + 硬编码评测。
# 散落在脚本里的"数据集" cases = [ {"q": "北京今天天气?", "want_tool": "weather", "ans_has": "度"}, {"q": "1+1=?", "want_tool": None, "ans_has": "2"}, ] def run_eval(agent, cases): for c in cases: out = agent.invoke(c["q"]) # 期望调用了某工具、答案含某词 —— 全部硬编码判断 assert c["ans_has"] in out换个人接手,字段命名、判断方式全得重读代码,无法"改数据不改代码"。
五、解决方案(第一层:最小直接修复)
先定义一个最小 JSON schema 并写个 loader,把"数据"和"判断"解耦。
[ { "id": "weather-01", "task": "北京今天天气怎么样?", "expected_tools": ["weather"], "reference": {"contains": ["度"], "regex": null}, "metadata": {"domain": "weather", "steps": 1} } ]import json from pathlib import Path def load_agent_eval(path: str) -> list[dict]: data = json.loads(Path(path).read_text(encoding="utf-8")) for row in data: row.setdefault("expected_tools", []) row.setdefault("reference", {}) return data这一层让评测变成"加载 JSON + 遍历",数据可进 git。
六、解决方案(第二层:结构化改进)
把"评测数据集的字段语义与校验"固化成策略对象,作为单一事实来源,确保加载的数据符合约定。
from dataclasses import dataclass, field from typing import Dict, List, Optional @dataclass(frozen=True) class LangChainAgentEvalDatasetPolicy: """Agent 评测数据集策略的单一事实来源。""" required_fields: List[str] = field(default_factory=lambda: ["id", "task"]) allowed_reference_keys: List[str] = field(default_factory=lambda: [ "contains", "regex", "numeric_range", "equals" ]) allow_expected_tools: bool = True forbid_unknown_fields: bool = False def validate_row(self, row: Dict) -> None: for f in self.required_fields: if f not in row: raise AssertionError(f"missing required field: {f}") ref = row.get("reference", {}) for k in ref: if k not in self.allowed_reference_keys: raise AssertionError(f"unknown reference key: {k}") if self.allow_expected_tools and "expected_tools" in row: assert isinstance(row["expected_tools"], list) def validate_dataset(self, rows: List[Dict]) -> None: ids = [r["id"] for r in rows] if len(ids) != len(set(ids)): raise AssertionError("duplicate eval ids") for r in rows: self.validate_row(r)加载时用validate_dataset兜底,数据集格式错误在加载期就报错,而不是跑评测跑到一半炸。
七、解决方案(第三层:断言 / CI 守护)
用 pytest 锁死数据集规范:
import pytest from policy import LangChainAgentEvalDatasetPolicy as P def test_rejects_missing_field(): with pytest.raises(AssertionError): P().validate_row({"id": "x"}) # 缺 task def test_rejects_unknown_reference_key(): with pytest.raises(AssertionError): P().validate_row({"id": "x", "task": "t", "reference": {"bogus": 1}}) def test_rejects_duplicate_ids(): with pytest.raises(AssertionError): P().validate_dataset([ {"id": "a", "task": "t"}, {"id": "a", "task": "t"}, ]) def test_valid_row_passes(): P().validate_row({"id": "a", "task": "t", "expected_tools": ["w"], "reference": {"contains": ["ok"]}})CI 加一条:每次 PR 跑pytest校验eval_datasets/*.json,格式不合规直接阻断。
八、排查清单
- 评测用例散落在脚本里、无法批量?→ 抽成 JSON 数据集 + loader。
- 改数据集列名就让评测崩?→ 用
validate_dataset在加载期校验。 - 无法对比两次改动的效果?→ 数据集进 git,结果落盘可 diff。
- 期望工具/参考答案语义不清?→ 用
reference.contains/regex/numeric_range统一表达。 - 是否绑死某 SaaS?→ 本地 JSON 方案可独立于 LangSmith 使用。
- id 是否唯一?→ 重复 id 会导致评测聚合错乱。
九、小结
"Eval dataset for agents" 的痛点不是算法,而是缺规范:没有统一的 agent 评测数据 schema 与加载器,导致评测与数据耦合、无法复用与积累。第一层定义最小 JSON schema 并写 loader 解耦数据与判断;第二层用LangChainAgentEvalDatasetPolicy把字段语义、reference 类型、id 唯一性固化成单一事实来源;第三层用 pytest + CI 守护数据集规范。做评测基础设施的通用原则:先定数据契约,再写评测代码,让"数据集"成为可版本化、可校验的一等公民。