如何自托管 AI Agent 评测并跟踪通过率
用真实追踪记录构建基准集,先运行低成本确定性检查,再用 LLM 评审,并按提交记录通过率,例如从 60 个用例通过 58 个降至 51 个。
什么是自托管的 AI agent 评测
自托管的 AI agent 评测包含 4 项内容,全部保存在您自己的代码仓库中:保存测试用例的文件、运行 agent 的脚本、为每个答案评分的一组检查项,以及可查询的结果表。这些内容都不需要依赖供应商。整个流程只需几百行 Python 代码和一个 SQLite 文件。
演示中的 agent 能正常工作,是因为您亲自选择了 5 个输入。到了第 2 周,它可能因为提示词中的一行发生变化、模型发生变化,或工具描述发生变化而失败,而原有测量没有覆盖这些变化。评测流程可以将“感觉现在更差了”转化为“在提交 4f1c9ab 上,通过率从 60 个用例中的 58 个降至 60 个中的 51 个”。
该流程包含 4 个步骤,本指南每个步骤对应一个章节:收集真实运行轨迹,将有价值的轨迹整理为测试用例,在每次变更后对所有用例进行评分,并将通过率与生成该结果的提交一起保存。无论 agent 运行在哪种环境中,同一流程都适用;值得运行的自托管 agent 框架主要区别在于它们会免费提供多少运行轨迹信息。
代理为什么在第二周失效
代理由提示词、模型、工具定义以及运行时检索到的上下文组成。这四项都可以在不修改应用代码的情况下发生变化,因此常规代码审查通常看不出问题。
最常见的原因是修改了提示词。你增加一句话来避免回复无礼。这句话会改变此前未重新测试的输入的行为,跟踪记录会清楚显示这一点:上周针对同一问题的跟踪记录包含 create_refund 工具调用,本周则没有,回复也变成了礼貌的道歉。整个过程没有报错,因此不会触发告警。
第二个原因是模型。每次运行都记录发送的完整模型字符串,claude-haiku-4-5-20251001 不要只记录你凭记忆使用的简写,因为只有在模型名称位于对应记录中时,才能诊断模型切换当天通过率下降的问题。
第三个原因是工具。重新措辞工具描述会改变模型决定调用工具的时机。如果工具通过 在 VPS 上运行的 MCP 服务器提供,架构定义位于另一个进程中,因此它可能在你不知情的情况下发生变化,而你的代码仓库中完全没有差异。第四个原因是检索:同一个问题命中了夜间重建的索引,答案随之采用了新文档。
根据已经收集的追踪记录构建基准集
不要凭空设计评估用例。直接从网络流量中提取。如果您已经为代理运行 自托管的 Langfuse 追踪,每个请求都会保存输入、工具调用和输出,这正是用例所需的原始材料。
通过公共 API 导出一段时间内的 root 观测记录。该 API 使用基本身份验证,以 public key 作为用户名,以 secret key 作为密码。
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'在编写任何解析代码前,先读取一条记录。数据行位于 data 下,但保存问题和回复的字段名取决于代理如何为 span 添加检测信息。因此,应根据实际返回内容进行映射,不要按照预期字段名映射。然后手动编写用例,每行一个 JSON 对象,保存到 evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}以下 5 条规则可以确保该集合具有运行价值:
- 一开始准备 40 到 80 个用例即可。少于 20 个时,一个不稳定的用例就会使通过率变化 5 个百分点;无故波动的数字最终会被忽略。
- 每个修复的生产环境缺陷,都应在修复当天转化为一个用例。坚持这样做,集合才能沿着正确方向增长。
- 每个用例只验证一种行为。同时检查退款金额和语气的用例失败时,您无法判断具体问题。
id永远不变,因为系统通过该 id 对比本次运行与上个月的运行结果。- 提交前先进行脱敏。该文件会提交到 git,因此应删除客户姓名以及不属于您的订单号。
优先使用确定性检查进行评分,因为它们无需成本
凡是存在明确正确答案的内容,都直接使用断言。无需调用模型、无需成本,也不存在歧义。确定性检查可以捕获结构性回归,而这类问题会破坏代理周围的系统:JSON 无法解析、工具根本未被调用、被禁止的短语再次出现,或答案没有引用任何来源。
只有一个函数需要了解您的代理。测试框架中的其他部分都应保持通用。
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failures将工具调用预算保留在该列表中。代理今天通过 3 次调用解决问题,明天却需要 11 次调用,这就是回归,即使最终答案正确,因为每次调用都需要付费。
LLM 作为评审,以及它出错的 4 种方式
通过断言检查的结果仍需要一个能够阅读内容的评分器。LLM 评审就是再次调用模型:它接收问题、代理的回答和一项标准,然后返回判定。这是评估“回答是否回应了用户的问题”唯一实用的方法。
以下 4 条规则可以让评审真正可用:
- 只返回二元判定,不要使用 1 到 10 的评分。评分制会让几乎所有结果都落在 7 或 8,分数不会变化,因此你无法从中获得信息。
- 每次调用只使用一项标准。只询问退款金额或语气,不要同时询问两者。
- 如果案例有预期答案,就始终提供给评审。根据参考答案评分,比抽象地评分容易得多。
- 强制规定输出格式,并严格解析结果。
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)下面介绍失败模式。每种模式都有一个你今天下午就能运行的测试。运行这些测试很重要,因为未经检查的评审会产生看似精确、实际毫无意义的数字。
长度偏差。 较长的回答更容易通过测试。测试方法:选出 10 个被评审判定为失败的回答,分别加入两段自信但没有新增事实的填充内容,然后再次评分。如果某个判定因此从失败变为通过,就是长度偏差,需要修正评分标准。
自我偏好。 评审通常会更宽松地评价来自同一模型系列的输出,而不是来自其他模型系列的输出。测试方法:使用两个不同模型系列的评审,对相同的 30 个回答进行评分,然后逐个比较判定结果。对于不一致的案例,直接人工阅读。
位置偏差。 如果使用评审比较两个回答 A 和 B,请交换它们的顺序后再次运行。如果判定结果因交换顺序而改变,说明这种成对比较尚不能安全用于该评分标准。
评分标准漂移。 模糊的标准会产生迎合性的评审。“回答是否有帮助”几乎什么都能通过。“回答是否以美元说明退款金额”只会让符合要求的内容通过。重写每项标准,直到它明确指出要检查的事实。
有一项防护措施可以覆盖全部 4 种问题。保留 30 个由人工标注的案例。每次更换评审模型或评审提示词时,都将评审结果与这些人工标签进行比较。如果它在 10 个案例中有超过 1 个案例与你的判断不一致,请先修正评分标准,再信任它生成的任何通过率。评审属于代码,因此应像代码一样进行版本控制和评审。
低价模型初筛,必要时升级到前沿模型
每次提交都用最昂贵的模型评判所有案例,评测费用最终会超过被测试的代理。按价格从低到高排列评判器,答案一旦明确就立即停止。
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]这些数据假设每次评判调用约包含 1,200 个输入 token 和 120 个输出 token。对于一个问题、一个答案和一条标准来说,这个规模符合实际。评判 1,000 个案例时,使用 Claude Haiku 4.5 的费用为 1.80 美元,使用 Claude Opus 5 的费用为 9.00 美元。差额看似很小,但在数量增加后就会变得明显。一个包含 60 个案例的集合,每次提交都进行评判,每周提交 40 次,则每周会产生 2,400 次评判调用,这还没有计算每晚运行的任务。
两种折扣适用于评测工作,而且可以叠加。评测运行不是交互式任务,因此 Batch API 可将输入和输出价格都降低一半,以异步交付结果作为交换;这对应图表的第一根柱。每次调用中的评分标准和指令都逐字节相同,因此适合使用提示缓存:缓存读取费用是基础输入价格的十分之一,5 分钟缓存写入费用是基础输入价格的 1.25 倍,因此缓存命中一次后就能收回成本。这些是截至 2026 年 8 月的 Anthropic 标价;Sonnet 5 的入门价格持续到 2026 年 8 月 31 日,因此该日期之后第三根柱会升高。
评判梯度如下,按顺序执行:
- 对每个案例执行确定性检查。不产生任何 API 费用。
- 对通过这些检查的案例使用小型模型评判。
- 仅在小型模型判定失败,或判定通过但置信度较低时,使用前沿模型评判。
- 每周进行一次人工复核,检查少量样本。
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]这种方法会以部分评判准确率换取成本降低,因此应测量这种取舍,而不是想当然。每月使用严格评判器评判一次完整集合,然后比较两列结果。如果两者在超过少数案例上不一致,说明评分标准对小型模型来说过于宽松;应修正评分标准,而不是先修改模型。控制代理自身的支出是另一项工作,详见 VPS 上 AI 代理的成本控制。
持续跟踪自有系统中的通过率
无法与某个提交关联的通过率只是主观感受。每次运行中的每个用例存储一行,并将提交和模型写入该行。
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;使用 sqlite3 evals/results.db < evals/schema.sql 加载架构,然后使用 sqlite3 -box evals/results.db < evals/passrate.sql 读取趋势。按每天运行一次、每次运行 60 个用例计算,一年的数据约为 22,000 行,因此存储不会发展成独立项目。在 VPS 上将 SQLite 用于生产环境介绍了在此文件需要在多台机器之间共享时才开始重要的设置。
运行器会为人员打印相同的信息:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amount针对可能导致 agent 出现问题的变更运行测试套件,也就是提示词编辑、模型变更和工具变更,而不是仓库中的每次提交。使用 pre-push hook 可以覆盖快速子集:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-push完整运行速度较慢,应按计划执行。VPS 上的每夜 systemd 服务和计时器会针对已部署的提示词运行完整测试集。这样可以捕获仓库外部产生的变更,例如托管工具的行为发生变化。
人工审核,抽样而非穷举
评审器需要根据人工标注进行校准,因此必须有人生成这些标注。每周读取一批样本:包括评审器判定失败的所有案例,再加上随机抽取的 10 个通过案例。随机通过案例是其中更重要的一半,因为如果评审器已经悄悄开始放过错误答案,那么在基于自身判定结果构建的任何仪表板上,它看起来都会表现完美。
每周审核 15 个案例,每个案例用时 3 分钟,共需 45 分钟。这样可以在您与评审器意见不一致时修正评分标准,并为此前未考虑过的失败类型补充新案例。将人工判定结果写入同一个表,并将 graded_by 设置为 human,这样评审器与人工之间的一致性就可以通过查询获取,而不必依赖记忆。
评估测试框架本身会出现的问题
首次完整运行中的 anthropic.RateLimitError。 六十个用例同时展开会超过您所在层级的请求数或令牌限制。将并发数限制为 4 个工作进程,并将夜间运行迁移到 Batch API。
来自评判器的 json.JSONDecodeError: Expecting value: line 1 column 1 (char 0)。 模型返回了散文格式的内容,或将 JSON 包在代码围栏中。重试 1 次,然后将该用例记录为错误。解析失败绝不能算作通过,因为会将错误转换为通过的测试套件,会在代理质量下降的同时逐渐逼近 100%。
不稳定的用例。 同一输入在一次运行中通过、下一次运行中失败,是因为代理会对输出进行采样。将不稳定用例运行 3 次,并记录通过比例,而不是删除该用例。一个用例在 3 次运行中通过 2 次,仍然是实际的稳健性缺陷,客户也会发现它。
基准集腐化。 有人修改预期答案,以便让测试套件变绿。应像仔细审查代理代码的差异一样,仔细审查指向 evals/cases.jsonl 的差异,因为该文件是您对“正确”的书面定义。
永不失败的测试套件。 如果通过率连续 1 个月保持在 100%,说明该集合已停止反映产品状态。提取最近的 10 条跟踪记录,找出代理处理不当的记录,并将其加入测试集。然后故意破坏某项功能,确认运行结果变为红色。这就是 变异测试适用于测试套件 的检查,也是确认测试集仍然有效的唯一方法。
FAQ
AI agent 评测集需要多少个用例?
先从 40 到 80 个用例开始,再根据真实故障扩大集合。少于约 20 个用例时,一个不稳定的结果就会使通过率波动 5 个百分点,因此通过率不再具有参考价值。超过几百个用例后,每次运行都会消耗实际的资金和时间,而新增用例带来的覆盖范围很小。真正重要的指标不是用例数量,而是已知生产故障类型中至少在集合中出现过一次的比例。
可以信任 LLM 评审员为我的 agent 评分吗?
只有在根据您自己的标注对其进行测量后才可以。保留 30 个由您人工评分的用例,并在更换评审模型或评审提示词后,用这些用例重新评估评审员。评审员会表现出长度偏差:填充内容较多的答案更容易通过;还会表现出自偏好:对来自相同模型系列的输出评分更宽松。这两种偏差都可以测试:为失败答案添加填充内容后重新评审,或使用其他模型系列的评审员为相同答案评分。如果评审员与您的标注在十分之一以上的用例中不一致,评分标准就过于模糊,无法使用。
应使用哪个模型为评测评分?
先使用低成本方式评分,再逐步升级。确定性断言不产生费用,因此应先对每个用例运行。小型模型负责处理明确通过的用例。只有失败用例和低置信度判定才交给 frontier model。在 2026 年 8 月的公开价格下,使用 Claude Haiku 4.5 为 1,000 个用例评分的费用约为 1.80 美元,使用 Claude Opus 5 的费用约为 9.00 美元。由于评测运行是异步的,Batch API 会将这两项费用分别降低一半。
评测能替代生产监控吗?
不能,因为两者回答的问题不同。评测套件用于判断即将发布的更改会使固定用例集变好还是变差。Tracing 和监控用于了解真实用户当前遇到的问题,其中包括评测用例未覆盖的输入。两者相互补充:traces 提供新用例,评测套件则判断修复是否真正生效。