几个月前我接手了一个 AI Agent 项目,功能很酷——能自动处理用户工单、查数据库、调用 API、发通知。看起来一切完美,直到生产环境出了事。
问题是这样的:Agent 在测试环境表现不错,95% 的任务都能正确完成。上线第一周,准确率直接掉到 60%。不是代码变了,是用户的提问方式变了。
之前测试用的都是"标准问题"——我知道答案的问题。但真实用户不会按剧本提问。他们会问模糊的问题、有歧义的问题、甚至前后矛盾的问题。Agent 的"能力"在可控环境下看起来很好,但一遇到真实世界的噪音就露馅了。
踩了这个坑之后,我开始认真研究怎么系统地评估和测试 AI Agent。这篇文章就是想把这些思考写下来,给正在或准备构建 AI Agent 的人一些参考。
传统测试为什么不够用
做过后端开发的都知道,写单元测试是基本功。输入 A,期望输出 B,结果不对就报错。这套逻辑在传统软件里很好用——确定性的输入输出,明确的边界条件。
但 AI Agent 不一样。
它内部是一个 LLM,输入自然语言,输出行动序列。同一个问题,今天问和明天问,结果可能完全不同。更关键的是,一个任务可能有多条正确的完成路径。比如"帮我查一下上周的销售数据",Agent 可以是查 SQL 数据库,可以是查 BI 报表,也可以是用 API 拉数据——只要结果对,过程不重要。
传统断言式的测试根本覆盖不了这种场景。你没法写"assert agent.do_task() == expected_result"然后指望它像单元测试一样通过或不通过。
这个问题在我之前做 React Native 性能优化的项目里也遇到过——当时我写了一篇技术选型的文章记录对比过程。但 Agent 测试的问题更棘手,因为它不是"选错了框架"这种确定性错误,而是"有时候好用有时候不好用"的随机性问题。
Eval-Driven Development 是什么
Eval-Driven Development(EDD,评估驱动开发)是我借鉴别人的实践,加上自己踩坑总结出来的一套方法。核心思路就一句话:
先定义「好用」的标准,再写 Agent 逻辑。
听起来像 TDD 的变体?确实类似,但重点不同。TDD 关注的是"功能是否正确实现",EDD 关注的是"Agent 是否按照期望的方式完成任务"。
EDD 的工作流长这样:
定义评估标准 → 创建评估数据集 → 写评估脚本
↓
跑 Agent → 收集结果 → 评分
↓
不满意?→ 改 Prompt / 调参数 / 加工具 → 重新评估
↓
满意了?→ 部署到生产 → 持续监控 → 补充数据集
跟传统开发最大的区别是什么?写代码之前先写评估集。
传统开发是先实现功能,再写测试来验证。EDD 是先定义"什么样的结果算好",再让 Agent 去试。评估标准本身就是规格文档。
一个具体的例子:做客服回复 Agent,先不要急着写 Prompt 说"你要礼貌地回复用户",而是先定义:
- 回复是否解决了用户的问题(是/否)
- 回复时是否引用了知识库条目(是/否)
- 回复语气是否专业(1-5 分)
- 回复中是否包含不准确的信息(是/否)
然后找 50-100 个历史工单,人工写好期望的回复。这些人工标注的数据就是评估集。Agent 写好了,用这套评估集去测,得分过线了再考虑上线。
构建评估体系的四个维度
在实践中,我总结了一个四维评估框架。每个维度关注不同的问题,合在一起能比较全面地衡量 Agent 的质量。
维度一:任务完成率
最基础的维度。给定一个任务,Agent 是否成功完成了?
这个看似简单,实际上坑很多。因为"完成"的定义不总是二元化的。比如"帮用户修改订单地址",Agent 可能改了地址但没发通知邮件——算完成还是不算?
我的做法是任务颗粒度分层:
Level 0: Agent 完全没有动作(卡住了、报错了)
Level 1: Agent 开始执行但不完整(改了数据库但没更新缓存)
Level 2: Agent 完成了主流程但遗漏了副流程(改了地址没发通知)
Level 3: 全部完成
我一般只把 Level 3 算作"完成",Level 1-2 算部分完成,单独统计。
维度二:执行效率
Agent 完成一个任务花了多少代价?包括:
- 步骤数:调用了多少次工具?一个优秀的 Agent 应该用最少的步骤完成任务。我见过一个 Agent 查个简单的用户信息调了 5 次 API——先查用户 ID,再查用户详情,再查订单,再查最近活动,再查偏好设置,而实际上一次 API 就能搞定。
- Token 消耗:总输入输出 token 数。这不只是省钱的问题,token 多通常意味着 Agent 做了不必要的推理或重复查询。
- 延迟:从接受到任务的端到端耗时。这个容易被忽视,但对用户体验影响很大。
维度三:决策质量
这个维度衡量 Agent 在关键决策点的表现:
- 工具选择是否合适:查天气用天气 API 是正确的,用网页爬虫去爬天气网站就是不合适的
- 参数传递是否正确:调用数据库查询时传的参数是否合理
- 错误处理是否得当:API 返回 429 时,Agent 是直接放弃还是重试?
- 安全边界是否遵守:有没有尝试执行权限之外的敏感操作?
这个维度最难自动化评估,通常需要人工抽检或者用更强的模型做裁判。
维度四:可复现性
这里有一个关键问题:Agent 做对一次,和它每次都能做对,是两回事。
因为 LLM 生成的结果有随机性,同一个 Prompt 同一套工具,今天能完成任务明天可能就不行。可复现性衡量的就是 Run-to-Run 的稳定性。
具体做法:同一个评估用例跑 N 次(建议 N≥5),统计成功的概率。
用例:查询用户 #12345 的最近订单
第一次:✅ 成功了
第二次:✅ 成功了
第三次:❌ 查了两次不同 API,然后说"用户没有订单"
第四次:✅ 成功了
第五次:✅ 成功了
成功率:80%(4/5)
如果成功率低于 90%,说明这个用例是脆弱的——表面的成功可能是碰巧。
实际跑通的工具链
说了这么多理论,分享一下我现在实际在用的工具链。这套东西不完美,但够用。
评估数据集
每个 Agent 项目维护一个 evals/ 目录:
evals/
├── datasets/
│ ├── basic.json # 基础功能测试(20-30 条)
│ ├── edge-cases.json # 边界情况(10-15 条)
│ └── regression.json # 回归测试(往期失败的用例)
├── scorers/
│ ├── task_completion.py
│ ├── decision_quality.py
│ └── consistency_check.py
├── report/
│ └── 2026-06-10.json
└── run.py
每个评估用例的结构:
{
"id": "basic-001",
"description": "用户查询自己最近的订单",
"input": {
"user_message": "帮我看看我最近买的耳机发货了没",
"user_id": "usr_12345",
"context": {
"user_orders": ["order_001", "order_002"],
"current_order": "order_002"
}
},
"expected": {
"tool_calls": [
{
"tool": "query_order",
"params": { "order_id": "order_002" }
}
],
"final_answer_contains": ["已发货", "快递单号"],
"should_not_contain": ["我不知道", "系统错误"]
},
"criticality": "high"
}
expected.tool_calls 不是要精确匹配——Agent 调 API 的次序和参数不完全一样很正常。我实际用的是模糊匹配:检查关键工具是否被调用了,关键参数是否正确传入,最终回答是否包含了期望的信息。
评分脚本
一个简化的评分器:
class TaskCompletionScorer:
"""任务完成率评分器"""
def __init__(self, weight=0.4):
self.weight = weight
def score(self, trace, expected):
"""
评估一个 trace(Agent执行轨迹)是否完成了任务。
返回 0.0 ~ 1.0 的分数。
"""
score = 0.0
# 1. Agent 有没有实际执行操作?
if not trace.tool_calls:
return 0.0
# 2. 关键工具是否被调用了?
required_tools = expected.get("required_tools", [])
called_tools = [c.tool for c in trace.tool_calls]
tool_coverage = sum(
1 for t in required_tools if t in called_tools
) / max(len(required_tools), 1)
# 3. 最终回答是否包含期望的关键信息?
answer = trace.final_answer
required_content = expected.get("final_answer_contains", [])
content_coverage = sum(
1 for phrase in required_content
if phrase.lower() in answer.lower()
) / max(len(required_content), 1)
# 4. 回答中是否有不应该出现的表述?
forbidden = expected.get("should_not_contain", [])
has_forbidden = any(
phrase.lower() in answer.lower()
for phrase in forbidden
)
score = (tool_coverage * 0.5 + content_coverage * 0.5)
if has_forbidden:
score *= 0.5 # 出现不应该出现的表述,分数减半
return score
持续集成集成
评估脚本接入了 CI,每次修改都自动跑:
# .github/workflows/agent-eval.yml
name: Agent Evaluation
on: [push, pull_request]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run agent evaluations
run: |
python evals/run.py \
--dataset evals/datasets/basic.json \
--model claude-sonnet-4 \
--runs 3 \
--output evals/report/${{ github.sha }}.json
- name: Check minimum score
run: |
python -c "
import json
report = json.load(open('evals/report/${{ github.sha }}.json'))
score = report['aggregate_score']
print(f'Aggregate score: {score}')
assert score >= 0.8, f'Score {score} below minimum 0.8'
"
注意 --runs 3——这是关键。每次都跑 3 轮取平均,避免单次运气的干扰。在本地开发时跑 1 轮就行了,合并 PR 前至少跑 3 轮。
真实案例:MCP Weather Server 的评估
拿 MCP 官方文档里的 Weather Server 举个例子,说明评估是怎么落地的。
这是官方示例的架构:Agent 通过 MCP 服务器暴露两个工具 get_alerts 和 get_forecast,Agent 根据用户的天气查询选择调用。
好的评估数据集
我扩充了官方的测试,自己构建了评估集:
[
{
"id": "weather-001",
"input": "今天旧金山天气怎么样?",
"expected": {
"tool_calls": ["get_forecast"],
"params_check": { "latitude": true, "longitude": true },
"expected_answer_contains": ["温度", "天气状况"]
}
},
{
"id": "weather-002",
"input": "洛杉矶有台风预警吗?",
"expected": {
"tool_calls": ["get_alerts"],
"expected_answer_contains_not": ["是", "有"]
}
},
{
"id": "edge-001",
"input": "巴黎的天气怎么样",
"expected": {
"tool_calls": ["get_forecast"],
"params_check": {
"latitude": { "expected_range": [48.8, 48.9] },
"longitude": { "expected_range": [2.3, 2.4] }
}
}
}
]
注意 edge-001 这个用例——我故意给了巴黎的坐标范围。因为有些 Agent 会把"巴黎"的坐标搞混(Texas 也有个 Paris),这个用例帮我发现了一个坐标映射错误。
用评估发现问题
跑评估的第一轮结果:
weather-001: ✅ (分数: 0.92)
weather-002: ✅ (分数: 0.88)
weather-003: ✅ (分数: 0.95)
edge-001: ❌ (分数: 0.35)
- 调用了 get_forecast ✅
- 但传的坐标是 33.7, -95.5(Texas Paris)❌
- 回答的是 Texas 的天气,不是法国巴黎
这个 Bug 在人工测试时没被发现——测试人员随手写了"查一下巴黎天气",看了眼结果有温度有天气状况,觉得没问题。但在 EDD 流程里,我明确标注了 Paris 的坐标范围,Agent 超出预期范围就会被扣分。这种边界情况在实际生产中很常见,传统测试很难覆盖。
修复方案:在 MCP Server 的工具描述里加一行说明。
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""获取指定位置的天气预报。
注意:如果用户输入的是城市名,请先核实该城市的坐标。
例如"Paris"可能是法国巴黎也可能是美国德克萨斯州的巴黎(Paris, TX)。
如有歧义,请向用户确认。
"""
改完之后重新跑:
edge-001: ✅ (分数: 0.85)
- 调用了 get_forecast ✅
- 请求确认城市 ✅(Agent 问"请问是哪个巴黎?")
这种问题用传统测试很难发现,但在 EDD 框架下,从"感觉不靠谱"变成了"可测量、可修复、可验证"。
坑与经验
踩了不少坑,有些经验可以提前分享。
坑 1:评估集就是你的规格文档
最开始我觉得 20 条评估用例就够了,结果越加越多。每条用例都在定义"Agent 应该怎么做"。
到后面我意识到:评估集就是 Agent 的规格文档。 需求变了,优先改评估集,再改 Prompt。因为评估集是可执行的——改完跑一遍就知道回归有没有破坏旧功能。
坑 2:评分器本身也会有问题
评分器的逻辑也可能出错。比如 final_answer_contains 检查"已发货"这个关键词,Agent 的回答是"您的订单已于昨日**发货,快递单号是 SF123456"——正常来说这是对的。但如果评分器只检查子串匹配,"已发货,快递单号"是包含的。但如果 Agent 说"您的订单状态为已发货,快递公司为顺丰","快递单号"这个关键词不存在,会被判定为缺失。
所以评分器也要写测试。我现在会给评分器写单元测试:
def test_scorer_detects_shipping_info():
scorer = TaskCompletionScorer()
trace = MockTrace(final_answer="您的订单已于昨日发货,快递单号是 SF123456")
expected = {"final_answer_contains": ["已发货", "快递单号"]}
score = scorer.score(trace, expected)
assert score >= 0.9, f"Expected high score, got {score}"
def test_scorer_penalizes_fake_completion():
scorer = TaskCompletionScorer()
trace = MockTrace(final_answer="我无法查询您的订单状态")
expected = {"final_answer_contains": ["订单状态", "发货时间"]}
score = scorer.score(trace, expected)
assert score < 0.5, f"Expected low score, got {score}"
坑 3:善用模型评估模型
有些维度很难用规则评分器衡量——比如"回复是否专业"、"方案是否最优"。这时候可以用更强的大模型来做评判(LLM-as-a-Judge)。
但我发现一个陷阱:裁判模型也会出错。 我有一次用 Claude 评估一个客服 Agent 的回复,Claude 打了高分因为"回复很礼貌",但实际回复内容是根据错误的知识库条目给出的。裁判模型被外表的礼貌迷惑了。
后来我在评估 Prompt 里加了明确的惩罚项:
评估标准:
1. 回复是否准确(权重 50%)—— 必须引用知识库具体条目编号
2. 回复是否完整(权重 30%)—— 是否覆盖了用户所有问题
3. 回复语气是否合适(权重 20%)—— 不过分热情也不冷淡
扣分项:
- 回复中包含不准确信息 → -50 分(一票否决)
- 回复中有语义矛盾 → -30 分
- 回复超出权限范围 → -40 分
把评估标准结构化,让裁判模型有章可循,比笼统地说"你觉得这个回复好不好"靠谱得多。
应该投入多少
说实话,这套体系有成本。一个中等复杂度的 Agent 项目,评估数据集需要 50-100 条用例,每条人工标注预计 5-10 分钟,加上评分器开发和 CI 集成,初始投入大概 2-3 天。
值不值?我的判断标准很简单:
| 场景 | 建议投入 |
|---|---|
| 个人玩具项目 / Demo | 10-20 条用例 + 简单规则评分器,半天搞定 |
| 内部工具 / 团队使用 | 30-50 条用例 + 规则评分器,1-2 天 |
| 面向客户的 Agent | 100+ 条用例 + 规则 + 模型评分,3-5 天 |
| 金融 / 医疗 / 合规场景 | 500+ 条用例 + 多维度评分 + 人工抽检,持续投入 |
值得注意的是,评估集不是一次性的。每次生产环境出了新问题,就把问题用例补充到评估集里。这样 Agent 的质量会逐步提升,因为"犯过的错不会再犯"。
下一步
这篇文章写的是怎么评估 Agent 的"输出质量",但其实还有几个方向我没深度展开:
- 安全性评估:Agent 能否抵御 prompt injection?边界在哪里?
- 成本评估:不同模型组合的 token 消耗和成本平衡
- 用户满意度追踪:评估分数和用户实际满意度之间的关联
这些我会在后续的文章里持续分享。目前这套 EDD 框架是我在项目中验证过、确实有效的方案,它解决了我最头疼的问题——"我怎么知道 Agent 改好没改坏"。
如果你也在做 AI Agent 项目,不妨试试从这个框架入手。从 20 条用例开始,慢慢扩充。最重要的是:先定义「好用」的标准,再动手写代码。
这篇文章基于我在三个 Agent 项目中的实践。每个项目遇到的评估挑战都不太一样,但核心思路是一致的——用评估驱动开发,让 Agent 的质量从「感觉还行」变成「我知道它能用」。