大多数团队上线 Agent 的流程是:跑几个 demo,同事点头说"挺好",然后就上了。这不是评估,这是赌博。
赌赢了皆大欢喜。赌输了,你会在两周后收到用户反馈:"它明明可以用搜索工具,为什么每次都在瞎猜?""它把数据库写了两次。""它传的 API 参数格式全错了。"
这些问题靠 demo 根本发现不了。它们藏在决策链的中段,藏在工具调用的参数里,藏在 Agent 自作主张的那几步里。
这篇文章讲的是:你真的要落地一个 Agent 系统时,怎么建立一套靠谱的质检机制。不是学术 benchmark,是工程。
为什么 Agent 评估比 LLM 评估难得多
评估一个普通的 LLM 接口,逻辑很简单:给输入,看输出,打分。评估对象是确定的。
Agent 不一样。
想象一下:你叫了个外卖,结果外卖员迷路了、走错楼道、敲错门,最终还是把餐送到了------但花了 90 分钟。你盯着那碗还算热的饭,该打几星?
Agent 就是这个外卖员。一次用户请求,可能触发 5 次工具调用,每一步的决策都会影响下一步。最终输出错了,可能是因为:
• 第一步理解了意图,但选了错误的工具
• 工具选对了,但传参格式不对
• 前三步都对,第四步推理断了
• 全程逻辑正确,最后总结时编造了一个不存在的数据
只看最终输出,你知道"出问题了",但不知道"哪里出的"。不知道哪里出的,就没法针对性地修------你只能靠感觉调 prompt,然后盯着下一次 demo 看"感觉好多了"。这是个死循环。
Langfuse 团队最近发布的 Agent 评估指南里,有个判断我觉得说到点子上了:评估粒度决定了你能修哪层 bug。
三层评估架构:黑盒、破壳、白盒
三层,解决三个不同问题,按需叠加,不需要一次全上。
第一层:最终响应评估(黑盒)
只看输入和最终输出,不管中间发生了什么。成本最低,适合快速验证"到底有没有严重问题"。
工程实现上,用 LLM-as-a-judge 就够。关键不是用哪个模型打分,而是评判 prompt 要写得足够具体。"评估回答是否有用"这种写法,打出来的分方差大、复现性差,等于没用。
推荐写法是:把场景具体化。
ini
EVAL_PROMPT = """
你是一个严格的评估员。
用户问题:{user_query}
AI 助手的回答:{agent_response}
参考标准答案:{ground_truth}
请从以下维度打分(1-5分):
1. 事实准确性:答案是否包含错误信息?
2. 完整性:是否回答了用户的核心问题?
3. 有用性:如果用户是初级工程师,按此回答操作,
能在 30 分钟内解决问题的概率有多大?
输出 JSON:
{{
"accuracy": 分数,
"completeness": 分数,
"usefulness": 分数,
"reason": "简要说明扣分原因(不扣分就写 OK)"
}}
宁可严格,不要宽松。
"""
第二层:轨迹评估(破壳)
检查 Agent 走的路径对不对------把实际的工具调用序列,和你预期的序列对比。
这层是最有价值、也最容易被忽视的。黑盒评估告诉你"什么出错了",轨迹评估告诉你"哪里出错了"。两者加起来,你才能精确定位 bug 所在。
轨迹评估需要你提前建"标准轨迹"的概念------不只标注正确答案,还要标注预期的执行路径:
css
# 测试用例结构:不只有"正确答案",还有"正确路径"
test_case = {
"user_input": "帮我查一下北京明天的天气,并提醒我带伞",
"expected_final_answer_keys": ["天气", "温度", "提醒"],
"expected_trajectory": [
{
"step": 1,
"tool": "weather_query",
"required_params": ["city", "date"],
"param_values": {"city": "北京", "date": "tomorrow"}
},
{
"step": 2,
"tool": "set_reminder",
"required_params": ["content", "time"],
"conditions": "only_if_rain_probability > 0.5"
}
]
}
def evaluate_trajectory(actual_trace, expected_trajectory):
results = []
for expected_step in expected_trajectory:
step_num = expected_step["step"]
# 步骤缺失
if step_num > len(actual_trace):
results.append({
"step": step_num,
"status": "MISSING",
"issue": f"步骤{step_num}未执行"
})
continue
actual_step = actual_trace[step_num - 1]
# 工具选错
if actual_step["tool"] != expected_step["tool"]:
results.append({
"step": step_num,
"status": "WRONG_TOOL",
"expected": expected_step["tool"],
"actual": actual_step["tool"]
})
continue
# 缺参数
missing_params = [ p for p in expected_step.get("required_params", [])
if p not in actual_step.get("params", {})
]
if missing_params:
results.append({"step": step_num, "status": "MISSING_PARAMS", "missing": missing_params})
else:
results.append({"step": step_num, "status": "OK"})
return results
第三层:单步评估(白盒)
类似单元测试,只测某一个决策点,不跑完整流程。
最适合两种场景:一是改了某个工具的 prompt,想验证这个改动有没有副作用;二是某类工具调用总出错,你想单独 debug 那一步的行为逻辑。
python
import pytest
from unittest.mock import patch
class TestAgentSearchStep:
"""单步测试:搜索查询的构造质量"""
@pytest.mark.parametrize("user_query,expected_keywords", [
(
"Python 里怎么做异步 HTTP 请求",
["python", "async", "http"]
),
(
"帮我找 2024 年最新的 RAG 论文",
["RAG", "2024"]
),
])
def test_search_query_has_key_terms(self, user_query, expected_keywords):
agent = YourAgent()
# 拦截实际搜索,只检查查询词构造
with patch.object(agent, '_call_tool') as mock_tool:
mock_tool.return_value = {"results": []}
first_call = agent.get_first_tool_call(user_query)
assert first_call["tool"] == "search"
query_str = first_call["params"].get("query", "").lower()
for kw in expected_keywords:
assert kw.lower() in query_str, f"关键词缺失: '{kw}'"
def test_no_hallucination_on_empty_results(self):
"""搜索返回空时,Agent 不能自己编答案"""
agent = YourAgent()
with patch.object(agent, '_call_tool') as mock_tool:
mock_tool.return_value = {"results": [], "message": "no_results"}
response = agent.run("查一下 xxx-nonexistent-package-zzz 的文档")
# 应该承认没找到,不能编
assert any(w in response for w in ["没有找到", "未找到", "找不到"])
测试数据从哪来:生产失败是金矿
卡在这里的团队很多:三层架构听懂了,但测试用例怎么搞?
最没用的来源是"工程师手工构造典型场景"。工程师能想到的,基本都是系统能处理好的。真正让 Agent 翻车的,是那些你没想到的边界。
最有价值的来源是生产环境里的失败轨迹。具体操作:
• 所有 Agent 调用开启追踪,记录完整的工具调用日志
• 用黑盒评估的 LLM 打分,自动过滤出低分 trace
• 每天人工审 10-20 条低分 trace,标注正确答案和预期轨迹
• 标注完直接进测试数据集
这个流程的好处:数据集自然反映真实用户的行为分布,不是工程师脑补的。跑一个月,你的测试集里全是真正能难倒系统的问题。
追踪实现不需要依赖第三方:
python
import time, uuid
from functools import wraps
class AgentTracer:
def __init__(self, storage_backend):
self.storage = storage_backend
def trace_tool_call(self, tool_name: str):
"""装饰器:自动追踪工具调用的入参、出参、耗时、状态"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
trace_id = getattr(self, '_current_trace_id', str(uuid.uuid4()))
start_time = time.time()
step = {
"trace_id": trace_id,
"step_id": str(uuid.uuid4()),
"tool": tool_name,
"input": kwargs,
"start_time": start_time,
}
try:
result = func(*args, **kwargs)
step.update({"status": "success", "output": result,
"duration_ms": (time.time() - start_time) * 1000})
return result
except Exception as e:
step.update({"status": "error", "error": str(e),
"duration_ms": (time.time() - start_time) * 1000})
raise
finally:
self.storage.save_step(step)
return wrapper
return decorator
# 用法
tracer = AgentTracer(storage_backend=YourStorage())
class MyAgent:
@tracer.trace_tool_call("web_search")
def search(self, query: str, top_k: int = 5):
return search_api.query(query, top_k=top_k)
@tracer.trace_tool_call("database_query")
def query_db(self, sql: str):
return db.execute(sql)
LLM-as-a-Judge 的三个工程坑
用 LLM 打分是目前最主流的方式,但有几个坑踩过才会知道。
坑一:评判模型和被评判模型是同一个
用 GPT-4o 跑 Agent,再用 GPT-4o 做评判------会出现系统性的"自我宽容"偏差。GPT-4o 对自己输出格式的偏好,会让它给同类输出打高分。建议用不同供应商的模型做评判,或者 ensemble 多个取平均。
坑二:评判 prompt 太模糊,分数方差大
把场景具体化,参考上面给出的例子。"30 分钟内能解决"比"是否有用"的评分稳定性高得多,复现性也好。
坑三:只做事后打分,没有接入 CI/CD
评估体系的价值不是算出一个数,而是驱动改进。建议每次改 prompt 或换模型,自动在基准集上跑评估,如果某项分数下降超过阈值,阻断部署:
bash
# .github/workflows/agent_eval.yml
name: Agent Evaluation Gate
on:
pull_request:
paths:
- 'prompts/**'
- 'agent/**'
- 'tools/**'
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Benchmark
run: |
python eval/run_benchmark.py \
--dataset eval/benchmark_v2.jsonl \
--output /tmp/eval_results.json \
--parallel 4
- name: Check Score Thresholds
run: |
python eval/check_thresholds.py \
--results /tmp/eval_results.json \
--thresholds eval/thresholds.yaml
# thresholds.yaml 示例:
# accuracy: 0.85 # 准确性不低于 85%
# trajectory_match: 0.75 # 轨迹匹配率不低于 75%
# tool_param_accuracy: 0.90 # 参数准确性不低于 90%
一个被低估的维度:工具参数精确性
大多数团队评估 Agent 时盯的是"最终答案对不对""选了哪个工具",但很少专门测参数的精确性。
这个维度其实很关键。Agent 选对了工具,但传了个格式错误的参数------工具调用失败。然后 Agent 可能重试、可能换策略、可能直接编答案。三种情况都会产生问题,根因是同一个:参数格式错了。
参数精确性最适合做白盒测试:
python
@pytest.mark.parametrize("user_input,tool_name,param_checks", [
(
"查一下明天北京到上海的航班",
"flight_search",
{
"from_city": lambda v: v in ["北京", "PEK", "beijing"],
"to_city": lambda v: v in ["上海", "SHA", "PVG", "shanghai"],
# 日期必须是 YYYY-MM-DD 格式,不能是"明天"这种自然语言
"date": lambda v: re.match(r'\d{4}-\d{2}-\d{2}', str(v)) is not None,
}
),
(
"帮我查一下最近 10 篇关于 RAG 的论文",
"paper_search",
{
"query": lambda v: "RAG" in str(v) or "retrieval" in str(v).lower(),
# limit 必须是整数,不能是字符串 "10"
"limit": lambda v: isinstance(v, int) and v >= 10,
}
),
])
def test_param_construction(user_input, tool_name, param_checks):
agent = YourAgent()
tool_calls = agent.plan_tool_calls(user_input) # 只规划,不执行
target_call = next((tc for tc in tool_calls if tc["tool"] == tool_name), None)
assert target_call is not None, f"未调用 {tool_name}"
params = target_call["params"]
for param_name, check_fn in param_checks.items():
assert param_name in params, f"缺少参数: {param_name}"
assert check_fn(params[param_name]), \
f"参数 {param_name} 不符合预期: {params[param_name]}"
在线 vs 离线:两个都要有
离线评估(固定数据集跑测试)和在线评估(生产流量持续打分)解决的是不同问题,二选一都是错的。
离线评估的硬伤:数据集会老化。用户的提问方式会变,新功能带来新的查询模式,三个月前的数据集可能已经不代表真实情况。
在线评估的硬伤:没有标准答案。线上流量很少有 ground truth,只能做相对质量评估,或者实时用 LLM 打分------引入额外的延迟和成本。
平衡策略很简单:
• 离线评估做发布门控(PR 合并前跑,不过不让上线)
• 在线评估做健康监控(每天看分数趋势是否下滑)
• 在线发现的失败案例,标注后定期补充进离线数据集
• 离线数据集每季度滚动更新:剔除过时的,加入新的高频失败模式
先追踪,后评分------这个顺序不能反
很多团队想一步到位,上来就设计评分体系。这是个常见失误。
在你真正了解 Agent 在生产里的行为模式之前,任何评分体系都是想象出来的。就像你从没见过这条街,就去设计路牌系统------大概率会贴错地方。
正确做法:先追踪,手动看 100 条 trace,你会发现很多意外的模式。Agent 总是在某类查询上绕远路;某个工具的返回值 Agent 经常误读;某种格式的用户输入会触发死循环......
有了这些一手观察,再设计评分维度和测试用例,才是有根基的。Langfuse 的建议是"追踪优先,评分在后",这个顺序很关键。
一张落地清单
如果要给一个 Agent 系统从零建评估体系,可以按这个节奏:
• 第 1 周:接入追踪,所有调用记完整 trace,先看不评
• 第 2 周:手动审 trace,找高频失败模式,标注 20-30 个失败案例
• 第 3 周:建离线数据集,写黑盒评估脚本,看当前基础分是多少
• 第 4 周:加轨迹评估,针对最高频失败类型写白盒测试
• 之后持续:集成进 CI/CD,建在线监控,数据集每季度滚动更新
评估体系不是一次建好就不动的基础设施,它是随着系统成熟而持续演进的。那些靠"感觉还不错"就上线的 Agent,迟早会以一种你没预想到的方式出问题。
下一个值得探的方向:当系统里有多个子 Agent 协作时,评估会复杂很多------分布式追踪、跨 Agent 的 trace 关联、怎么给"编排层"的决策质量单独打分,这些都还没有标准答案。有机会单独写一篇。