过去一年里,Agent 类应用的开发热度一直很高。团队里做 API 封装、Prompt 工程和工具编排的同学,基本都经历过同一个阶段:功能能跑通,但没有人能说清楚"这次改动到底让 Agent 变强了还是变弱了"。语气词调一调,示例换一换,回答质量就忽上忽下。问题久了,大家开始发现,Agent 开发最缺的不是模型能力,而是一套靠谱的评测体系。Agent Review Studio 这类工具,正好切中了这个需求:它定位是 local-first 的 Agent evaluation workbench,也就是一个本地优先的 Agent 评测工作台。
这篇文章不会只解释概念,我会围绕这类评测工作台的通用设计,带你拆解 Agent 评测的核心模块,并动手实现一个最小可用的评测 Runner,覆盖评测集管理、运行、指标计算、报告生成和回归对比这些关键环节。无论你是刚接触 Agent 开发,还是已经在做 RAG 或工具调用类应用的工程化,这套方法论都可以直接迁移到自己的项目里。
1. 背景:Agent 开发多了,评测却跟不上
1.1 从"能跑通"到"稳定好用"
以前写一个传统 Web 接口,验证方式很直接:给定输入,检查输出和数据库状态。只要逻辑没有分支遗漏,测试用例覆盖到位,质量就是可控的。
Agent 不太一样。它不是一个简单的输入输出函数,而是一个会规划、会调工具、会基于中间结果改变下一步行为的执行系统。同一个问题,模型对 Prompt 里一个标点的敏感程度都可能影响最终答案。所以,单纯用"通不通过"来衡量 Agent,已经不够了。我们需要更细的维度,比如回答是否相关、工具调用是否合理、推理过程是否稳定、失败时是否优雅降级。
评测工作台要解决的,就是这些问题。它不是替代你写单元测试,而是提供一套更贴近 Agent 运行方式的评测流程,让你能把"模型输出质量"这种抽象事情,量化成可以追踪的指标。
1.2 为什么评测和测试不一样
传统测试里,我们期待确定性的结果。如果你写一个加法函数,输入 1 和 2,输出必须是 3,不管是第几次运行。
Agent 评测则要处理概率性输出。同一个问题问两次,答案可能表述不同,但语义等价;同一段上下文,模型可能有时选择调 A 工具,有时选择调 B 工具。因此,Agent 评测需要引入更多判断手段:精确匹配、关键词匹配、LLM 作为裁判、人工 Review 等。
评测工作台的核心价值,就是把这一套判断流程标准化。它让团队可以用同一批用例、同一种打分规则,去对比不同模型、不同 Prompt、不同工具编排策略的差异。
1.3 为什么强调 Local-First
local-first 的意思是,核心数据和执行都留在本地,而不是必须上传到某个云端平台。这个设计在 Agent 评测场景里非常实用。
首先是数据隐私。Agent 评测集往往会包含真实的业务对话、用户问题甚至内部文档片段。把这些数据传到第三方评测平台,很多合规要求都不允许。本地优先意味着评测集、中间日志、评测报告都保存在你控制的文件系统或数据库里,安全感强很多。
其次是成本和效率。云上评测行为,一次跑几百条用例可能产生大量 API 费用,而本地评测可以无缝连接本地模型(Ollama、vLLM 等),也可以用本地缓存和 mock 工具来降低消耗。调试时还能直接看完整输入输出,不用在一堆网络请求里翻日志。
最后是工程集成的灵活性。本地工作台天然适合接入 Git、CI/CD 和本地脚本。评测配置、数据集和指标规则都可以作为文件管理,走代码评审流程,这让 Agent 质量评估真正变成了一个工程化动作,而不是一次性的临时脚本。
2. Agent Review Studio 的设计定位
2.1 一句话理解 Agent Review Studio
Agent Review Studio 是一个帮助你"评审 Agent 行为"的工作台。你可以把一批评测用例放进去,让 Agent 依次执行,然后统计它在工具调用、回答质量、任务完成度等方面的表现。跑完一轮评测后,你能得到一份结构化报告,并且可以对比不同版本之间的差异。
它和常见的 Eval 框架(比如 LangSmith、OpenAI Evals)不同的地方,在于更强调 Review。也就是说,不光自动跑分,还要方便人工一条一条地看日志、看工具调用链、打标签、留下评审意见。
2.2 核心能力拆解
一个完整的 local-first Agent 评测工作台,通常包含下面几个模块。
- 评测集管理:以文件形式管理用例,支持 JSON / JSONL / YAML,可以带标签和元信息。
- 评测运行器:负责加载 Agent,逐条执行用例,记录每一步的输入输出和耗时。
- 指标计算:内置多种评价指标,包括准确率、工具调用成功率、幻觉率、平均延迟等。
- 报告生成:输出 JSON / Markdown / HTML 报告,方便查看和存档。
- 回归对比:把多轮评测结果放在一起,观察指标变化,定位引入劣化的改动。
- 人工 Review:提供一个本地界面,让人逐条标记回答是否合规、是否有有害内容、是否需要人工修正。
2.3 和云上评测平台的差异
| 对比维度 | 云端评测平台 | Local-First 评测工作台 |
|---|---|---|
| 数据存放 | 通常需要上传到云端 | 数据保存在本地 |
| 离线能力 | 依赖网络 | 可以完全离线运行 |
| 成本 | 按 API 调用计费 | 可接入本地模型,成本可控 |
| 可控性 | 平台升级可能影响结果 | 版本锁定,结果可复现 |
| 协作方式 | 平台账号内协作 | Git + 文件共享协作 |
| 私有化定制 | 受平台功能限制 | 自由扩展和二次开发 |
如果你的团队对数据安全比较敏感,或者希望评测流程完全跟着代码走,local-first 的评测工作台会是更合适的方向。
3. 从 0 拆解一个 Eval 工作台
为了让后面实战部分更好理解,我们先从逻辑层面拆一下:一次完整的 Agent 评测,经历了哪些步骤。
3.1 评测集管理
评测集是一批"问题 + 期望行为"的样本集合。每一条样本通常包含:
- id:唯一标识。
- query:用户输入。
- context(可选):给 Agent 的初始上下文,比如 RAG 候选文档。
- expected / references:参考答案,或者期望的工具调用序列。
- tags:用于分组,比如按业务线、按难度、按 Agent 类型。
评测集最好独立于代码存储。推荐使用 JSONL,一行一条,方便 Git 对比增量。
{"id": "case_001", "query": "帮我查一下上周的销售日报", "expected_tool": "query_report", "tags": ["report", "easy"]} {"id": "case_002", "query": "客户说发票金额不对,怎么处理?", "expected_actions": ["search_kb", "create_ticket"], "tags": ["customer_service"]}3.2 评测运行器
运行器是整个工作台的心脏。它负责把一条评测用例交给 Agent,拿到 Agent 的完整行为序列,再交给指标层计算分数。
在真实实现里,运行器会做几件事:
- 组装环境:初始化 Agent、工具集、模型客户端。
- 执行用例:记录每一步的输入输出。
- 捕获异常:Agent 超时、工具报错、模型不可用,都要落到日志里。
- 归一化输出:把 Agent 的不同输出格式统一成评测模块能识别的格式。
3.3 指标计算
指标是指标层读取"评测用例"和"Agent 执行记录"后算出的分数。常用指标包括:
- 任务完成率:用例是否达到预期目标。
- 工具调用准确率:Agent 是否在正确时机调用了正确工具。
- 回答相关性:输出与问题的相关程度。
- 延迟:平均响应时间、工具调用次数。
其中回答相关性既可以人工判断,也可以通过"LLM-as-a-Judge"来自动评分。评测工作台需要让这两者并存,因为纯自动指标容易漏掉语义细节,纯人工又成本太高。
3.4 报告与回归对比
跑完一轮评测,工作台会生成一份报告。报告里除了总分数,还要能下钻到具体用例。
回归对比是评测中最有价值的部分。你可以把当前代码分支的评测报告和主干报告的指标进行 diff,如果某条用例从通过变成了失败,就能快速定位是哪次 Prompt 改动、哪个工具参数调整引起的。
3.5 人工 Review
自动评分之外,工作台要保留一个人工复核的入口。评审者可以对每一条用例进行标记:通过、不通过、存疑、需要修改。存疑的用例可以回流到评测集里,作为后续迭代的重点验证对象。
4. 环境准备与版本说明
Agent Review Studio 这类 local-first 工具,核心依赖通常是 Python 生态,但具体技术栈在不版本上有差异。这篇文章不绑定某一个发布版本,会以通用环境为例,保证你理解的是一套可以迁移的思路。
4.1 运行环境
建议准备以下环境:
- 操作系统:Linux / macOS / Windows(带 WSL 也可以)。
- Python 3.9 以上,用于编写评测 Runner 和处理数据集。
- Git,用于管理评测集和版本对比。
- 可选的模型推理环境:本地模型(Ollama、vLLM)或云端 API Key。
版本需要根据你的项目实际情况调整,本文示例以常见环境为主,重点演示配置思路。
4.2 项目目录结构
一个清晰的项目结构,能让评测集、配置、报告相互隔离,后面维护起来更省心。
agent-review-workspace/ ├── datasets/ │ ├── doc_qa.jsonl │ └── tool_calling.jsonl ├── configs/ │ └── eval_config.json ├── src/ │ ├── runner.py │ ├── agent_adapter.py │ └── metrics.py ├── reports/ │ ├── report_20250101.json │ └── report_20250110.json └── scripts/ └── compare_reports.py如果你只是尝试一下,不需要这么多目录。但一旦评测集超过几十条,目录规范化会带来很大的好处:你可以用 Git 分支来管理不同评测方案,还能在 CI 里精确找到报告产物。
5. 快速开始:配置第一个评测任务
我们先配置一个最简评测任务,后面再逐步扩展 Runner 代码。这个过程中你会感受到评测工作流的整体节奏。
5.1 定义评测集
在 datasets 目录下创建 doc_qa.jsonl,内容模拟一个"本地文档问答 Agent"的评测用例。
{"id": "q001", "query": "支付超时应该联系谁?", "expected_topic": "payment", "timeout": 30} {"id": "q002", "query": "如何重置密码?", "expected_topic": "account", "timeout": 30} {"id": "q003", "query": "能帮我写一首诗吗?", "expected_topic": "out_of_scope", "timeout": 30}每条用例除了记录 query,还保存了一个 expected_topic,方便我们后续做一些简单的规则判定。
5.2 编写评测配置
配置的作用是把"运行哪些数据集、使用哪个 Agent、计算哪些指标"声明出来。这里用 JSON 格式。
{ "name": "doc-qa-eval", "dataset": "./datasets/doc_qa.jsonl", "agent_adapter": "local_doc_agent", "metrics": ["topic_accuracy", "latency", "failure_rate"], "output_dir": "./reports" }相比把所有逻辑写在 Python 脚本里,用配置文件的好处是:你可以通过切换配置来快速测试不同的 Agent 变体,而不需要修改核心代码。
5.3 运行评测
假设我们的工作台提供了一个命令行入口,运行方式大致如下。
python src/runner.py --config configs/eval_config.json运行结束后,会在 reports 目录生成结果文件。这个过程里你可能需要根据自己的 Agent 环境切换模型供应商,可以先从一个简单假 Agent 开始,跑通评测链路后再接入真实模型。
6. 核心实现:一个最小可用的评测 Runner
下面来实现一个简化但完整的评测 Runner。它不会依赖任何第三方评测库,只用 Python 标准库,因为它演示的是评测工作台的核心流程:读取用例、执行 Agent、计算指标、输出报告。
我们假设存在一个 agent 函数,它接收 query,返回一个 dict:
{ "answer": "回答内容", "topic": "识别出的主题", "tool_calls": ["search_doc"], "latency_ms": 123 }在真实项目里,这个 dict 由你的 Agent 执行引擎生成,这里我们用 mock 数据代替,重点看评测逻辑。
6.1 数据集读取
使用标准库逐行读取 JSONL,同时做好异常处理。
import json from pathlib import Path def load_dataset(path): path = Path(path) if not path.exists(): raise FileNotFoundError(f"Dataset not found: {path}") cases = [] with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: cases.append(json.loads(line)) except json.JSONDecodeError as e: print(f"Skip invalid line: {line}, error: {e}") return cases6.2 执行 Agent
这里用一个简单的 mock adapter 替代真实 Agent,方便完整运行示例。
import random def run_agent(query): # 真实场景中,这里会调用你的 Agent 框架 # 例如:agent.run({"messages": [{"role": "user", "content": query}]}) topics = ["payment", "account", "out_of_scope"] topic = random.choice(topics) latency = random.randint(50, 300) return { "answer": f"这是关于 {topic} 的模拟回答", "topic": topic, "tool_calls": ["search_kb"], "latency_ms": latency, }这个 mock 并不真实,但足够帮我们理解 Runner 的组装方式。等你要接入真实 Agent 时,只需要替换这个函数。
6.3 指标计算
核心指标之一是 topic 准确率。它会逐一对比预测主题和期望主题。
def calculate_metrics(results): total = len(results) passed = sum(1 for r in results if r["passed"]) topic_accuracy = passed / total if total else 0 latencies = [r["latency_ms"] for r in results] avg_latency = sum(latencies) / len(latencies) if latencies else 0 failures = sum(1 for r in results if r["error"] is not None) failure_rate = failures / total if total else 0 return { "total_cases": total, "passed": passed, "failed": total - passed, "topic_accuracy": round(topic_accuracy, 4), "avg_latency_ms": round(avg_latency, 2), "failure_rate": round(failure_rate, 4), }6.4 生成报告
报告除了汇总指标,还要保留每条用例的详细结果,方便下钻 Review。
def generate_report(config_name, results, metrics): report = { "config": config_name, "metrics": metrics, "cases": [ { "id": r["id"], "query": r["query"], "expected_topic": r["expected_topic"], "predicted_topic": r["predicted_topic"], "passed": r["passed"], "latency_ms": r["latency_ms"], "error": r["error"], } for r in results ], } return report6.5 整合 Runner 主流程
主流程把上面几个模块串起来,并加上命令行支持。
import argparse import json import time def run_eval(config): cases = load_dataset(config["dataset"]) results = [] for case in cases: start = time.time() error = None try: output = run_agent(case["query"]) predicted_topic = output["topic"] latency_ms = output["latency_ms"] except Exception as e: predicted_topic = None latency_ms = int((time.time() - start) * 1000) error = str(e) passed = (predicted_topic == case.get("expected_topic")) and (error is None) results.append({ "id": case.get("id", "unknown"), "query": case.get("query", ""), "expected_topic": case.get("expected_topic"), "predicted_topic": predicted_topic, "passed": passed, "latency_ms": latency_ms, "error": error, }) metrics = calculate_metrics(results) report = generate_report(config.get("name", "eval"), results, metrics) return report def main(): parser = argparse.ArgumentParser(description="Minimal Agent Eval Runner") parser.add_argument("--config", required=True, help="Path to eval config JSON") parser.add_argument("--output", help="Path to output report JSON") args = parser.parse_args() with open(args.config, "r", encoding="utf-8") as f: config = json.load(f) report = run_eval(config) print(json.dumps(report["metrics"], indent=2, ensure_ascii=False)) if args.output: with open(args.output, "w", encoding="utf-8") as f: json.dump(report, f, indent=2, ensure_ascii=False) if __name__ == "__main__": main()保存成src/runner.py,然后运行:
python src/runner.py --config configs/eval_config.json --output reports/first_report.json预期会看到类似输出:
{ "total_cases": 3, "passed": 1, "failed": 2, "topic_accuracy": 0.3333, "avg_latency_ms": 173.0, "failure_rate": 0.0 }由于 mock 是随机预测,具体数字每次都会变。这正好说明了一个真实问题:评测如果依赖随机行为,结果就不可复现。所以后面接入真实模型时,尽量固定随机种子、固定模型版本,并重复运行取均值。
6.6 回归对比
对比两次报告,是评测工作台最有价值的功能。我们可以写一个小脚本,读取两个报告里的 metrics,输出 diff。
import json def compare_reports(old_path, new_path): with open(old_path, "r", encoding="utf-8") as f: old = json.load(f) with open(new_path, "r", encoding="utf-8") as f: new = json.load(f) old_metrics = old["metrics"] new_metrics = new["metrics"] keys = set(old_metrics.keys()) | set(new_metrics.keys()) print("metric old new diff") for k in sorted(keys): old_v = old_metrics.get(k, "N/A") new_v = new_metrics.get(k, "N/A") diff = "" if isinstance(old_v, (int, float)) and isinstance(new_v, (int, float)): diff = round(new_v - old_v, 4) print(f"{k:16s} {str(old_v):10s} {str(new_v):10s} {diff}")在真实项目中,你可以在 CI 的 MR 检查阶段运行这个对比脚本,如果某条硬性指标劣化超过阈值,就让 CI 失败,阻止合并。
7. 实战:评测一个带工具调用的 Agent
前面的最小 Runner 只做了主题分类,太简单。下面我们升级一下场景:评测一个会调用工具的本地文档 Agent。
7.1 场景设计
假设一个 Agent 能执行两类操作:
- search_document(keyword):本地文档库搜索,返回相关片段。
- ask_follow_up(question):当信息不足时,主动向用户追问。
评测目标有两个:
- 工具选择是否合理:需要搜索时是否调用了搜索工具。
- 信息充分时能否直接回答:不需要追问时就不要反问。
这种评测关注的不只是最终回答,还有 Agent 的决策过程。
7.2 模拟 Agent 与环境
创建一个src/agent_adapter.py,其中定义 run_agent_with_tools 函数,接收 query 和可用工具列表,返回决策记录。
def run_agent_with_tools(query): # 模拟一个简单规则 Agent: # 如果 query 包含“推荐”“对比”“总结”,则调用搜索工具; # 如果 query 包含“怎么做”,则先追问细节。 decision = None if any(w in query for w in ["推荐", "对比", "总结"]): decision = "search_document" elif "怎么做" in query: decision = "ask_follow_up" else: decision = "direct_answer" return { "decision": decision, "tool_calls": [decision] if decision != "direct_answer" else [], }真实模型驱动的 Agent 会复杂很多,但这里的规则模拟可以让你把注意力集中在评测逻辑上。
7.3 评测用例
创建datasets/tool_calling.jsonl:
{"id": "t001", "query": "帮我对比一下两款云主机的价格", "expected_decision": "search_document"} {"id": "t002", "query": "怎么申请退款?", "expected_decision": "ask_follow_up"} {"id": "t003", "query": "你好", "expected_decision": "direct_answer"}7.4 运行工具调用评测
在 eval 主流程里,把 agent 调用从 run_agent 改成 run_agent_with_tools,同时把指标改成"决策准确率"。
核心判断逻辑:
def match_decision(predicted, expected): return predicted == expected这样跑完后的指标含义就变成了:Agent 在需要使用工具时是否做出了正确决策。这是 Agent 应用里很关键的评测维度,因为工具选择错误往往会导致后面的整个流程跑偏。
运行结束后,你可以打开生成的 JSON 报告,逐条查看哪些用例的决策和预期不一致。如果恰好是某次 Prompt 调整引入了问题,这一步能非常快地暴露出来。
8. 常见问题与排查思路
本地 Agent 评测工作台搭建过程中,会遇到一些很典型的问题。下面整理一张排查表。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 评测结果波动大 | 模型输出具有随机性,未固定温度参数 | 固定 temperature,多次运行取均值,用相同模型版本 |
| 数据集明明更新了,报告没变 | 缓存未清理,或 runner 读取了旧文件 | 检查路径,清理本地缓存,确保数据集版本被 Git 跟踪 |
| 工具调用报错导致大量失败 | 工具 mock 不完整,或测试环境缺少依赖 | 为每个工具提供稳定的 mock 实现,先跑通再接入真实服务 |
| 某个用例前后表现不一致 | Python 随机种子未固定,工具返回值不稳定 | 固定随机种子,给工具函数注入确定性返回 |
| 自动指标和人工判断不一致 | 指标定义太简单,无法衡量语义质量 | 引入 LLM-as-a-Judge 或人工 Review 通道 |
| 本地模型评测很慢 | 模型推理耗时大,用例多 | 增加并发执行,控制用例规模,分片运行 |
生产中更容易踩的坑是"上下文泄漏":评测用例里包含和答案高度相关的关键词,导致模型不需要真正推理就能答对。这种情况会让分数虚高,掩盖真实问题。所以评测集需要和 Prompt、工具描述、参考文档保持足够的隔离。
还有一个容易被忽略的问题是评测集的自我污染。当你在调试过程中持续把失败的用例"优化"进数据集,评测会逐渐失去区分度。保持一套稳定、定期审计的评测集,比不断堆用例更重要。
9. 最佳实践与工程建议
从最小演示走向团队级应用时,下面这些实践能让你的评测工作台更可靠。
9.1 评测集当代码一样管理
评测集不是一次性的测试数据,它是团队的重要资产。建议放在 Git 仓库里,使用单独的目录,每次修改都走代码评审。每条用例尽量带上清晰的元信息:编写日期、创建人、对应需求、期望行为的来源。这样当指标异常时,你能快速回溯是谁、在什么背景下加入了哪条用例。
9.2 保持评测运行的确定性
为了让评测结果有参考价值,需要尽可能消除随机因素。固定模型版本、固定温度参数、固定随机种子、固定工具返回结果。如果是调用外部模型 API,还要记录调用时刻和模型版本,避免模型服务端悄悄升级导致结果不可比。
9.3 分层设计评测指标
不要只用一个大而全的分数。建议分层:
- 任务成功率:自动化判断最终目标是否完成。
- 过程正确率:工具选择、参数生成、步骤顺序是否正确。
- 体验指标:延迟、表达一致性、是否出现幻觉内容。
分层的好处是,当整体分数下降时,你能立刻定位到是哪一层出了问题。
9.4 自动评分加人工复核
完全依赖自动评分会漏掉很多语义细节,完全依赖人工又太慢。更好的方式是:自动评分先粗筛,把不确定的用例标记出来,交给人工 Review。工作台要支持这种"自动流水线 + 人工抽查"的混合流程。
9.5 安全与最小权限
如果评测集包含敏感数据,务必注意权限控制。本地工作台虽然数据不离开本地,但报告文件可能会被复制、备份、同步到网盘。建议:
- 对包含敏感信息的评测配置做脱敏处理。
- 报告生成时默认隐藏敏感字段。
- 只有必要人员才能访问评测集和完整日志。
- 接入真实工具时,使用最小权限的测试账号,避免评测过程修改生产数据。
9.6 与 CI/CD 集成
评测工作台价值最大化,是把它接入到 CI/CD 流程里。每次合并请求触发小规模评测,比如核心用例 50 条;每个版本发布前触发全量评测。把评测结果作为发布质量的卡点,能有效阻止"感觉没问题"就上线的风险。
10. 下一步:把 Eval 变成日常习惯
Agent 评测不是一次性的上线动作,而是一个持续迭代的循环。你可以从一套 10 到 20 条的评测集开始,先覆盖最常见的主流程,然后每周花一点时间补充边界用例。等到评测集稳定了,再逐步加入回归对比和人工 Review 环节,最终形成一套和代码一样可维护的质量体系。
如果你准备进一步深入,可以关注这几个方向:评测集自动生成、LLM-as-a-Judge 的可靠性验证、RAG 检索质量评测、Agent 轨迹和工具调用的结构化分析。Agent 应用的质量门槛会越来越高,提前把评测基础设施搭好,后续迭代才会踏实。