AI Agent 是目前唯一一类"改一行 prompt 不敢直接上线"的代码。根因不是代码质量,是非确定性:同样的输入,两次运行可能给出不同的工具调用、不同的参数、不同的答案。prompt 微调、模型升级、工具替换都可能静默降级 agent 行为,多数团队是等用户投诉才发现。Jest 和 Playwright 解决不了这个问题——它们的前提是"输出确定",快照断言在 agent 上必挂。可行的解法是分层:确定性断言(工具调用、token 预算、延迟)每次 PR 全量跑,LLM-as-Judge 质量分(正确性、安全性、忠实度)做发布前门禁。这套方法论在 AgentBench(2026 年新开源的 agent 回归测试框架,TypeScript + Python 双 SDK,Apache 2.0)里落地得比较完整,下面拆开讲实现细节。

传统测试框架为什么测不了 Agent

维度 Jest / Playwright Agent 测试
断言对象 返回值 / DOM 完整轨迹:工具调用序列、参数、中间状态
确定性假设 强(快照、toEqual) 必须容忍随机性
质量判定 无(布尔断言) LLM-as-Judge 打分 + 阈值
回归检测 diff 期望值 replay 快照 + 分数对比
资源约束 一般不关注 token / 成本 / 延迟都要断言

差异的本质:传统测试断言"结果",agent 测试断言"过程 + 结果 + 资源"。

分层测试金字塔:什么在 PR 上跑,什么在发布前跑

L1 确定性断言——每次 PR 全量跑,零 LLM 调用,近零成本:

import { expect } from '@agentbench/core'

const result = await expect(runResult)
  .status().toBeCompleted()                     // agent 正常结束
  .tool("search_docs").toBeCalled()             // 调了该调的工具
  .tool("search_docs").toBeCalledWith({         // 参数正确
    query: "refund policy"
  })
  .tool("hallucinate").not.toBeCalled()         // 禁止的工具没调
  .output().toContain("30 days")                // 输出含关键信息
  .output().toMatchRegex(/refund.*policy/i)
  .tokens().toBeLessThan(4096)                  // token 预算
  .latency().toBeLessThan(5000)                 // 延迟预算
  .run()

if (!result.allPassed) process.exit(1)

L2 LLM 质量评分——发布前门禁。 LLM-as-Judge 按 8 个维度打分:correctness、faithfulness、safety、relevance、completeness、reasoning、conciseness、tool usage。关键是"分维度 + 阈值"而不是一个笼统总分:

.score("correctness").toBeGreaterThan(7)
.score("safety").toBeGreaterThan(8)

为什么不能只跑 L2?成本。一次多维度 LLM judge 调用的成本约等于一次普通 agent 运行,全量跑不现实。L1 拦掉大部分机械回归(工具没调、参数错、超预算),L2 只处理"答得对不对、安不安全"这类语义问题——这就是金字塔分层存在的经济性理由。

Hybrid Judge:规则和 LLM 怎么投票

纯规则(14 个评估器:exact_match、contains、regex、json_schema、tool_called 等)便宜但答不了语义问题;纯 LLM 全面但有成本和自偏好偏差。工程上常用混合投票,三种策略:

  • rule_first:规则先判,命中即定;规则放行才轮到 LLM。成本最低,适合 CI 场景。
  • llm_first:LLM 先判,规则兜底。适合语义为主的任务。
  • parallel:两者独立跑,按策略合并(任一 fail 即 fail,或加权汇总)。
const judge = hybridJudge({
  rules: [
    toolCalled({ tool: "search_docs" }),
    outputContains({ text: "30 days" })
  ],
  llm: llmJudge({ dimensions: ["correctness", "faithfulness"] }),
  voting: "rule_first"
})

Replay 引擎:回归测试的命门

回归测试的前提是"可复现"。确定性代码天然可复现,agent 不行——那就主动制造可复现。Snapshot Manager 保存完整 agent 状态(对话历史、工具返回、中间变量),Replay Engine 基于快照回放,三种模式:

  1. 确定性回放:固定快照重放,零 LLM 调用、零 flaky,适合塞进每个 PR。
  2. 跨模型回放:同一快照分别喂给 GPT / Claude / DeepSeek,模型升级或切换时抓行为漂移——"从 GPT 换到 Claude 会不会坏"这类问题不再靠猜。
  3. 批量回放:数据集(CSV/JSON/JSONL)回归,支持 train/test/validation 切分。

Diff Engine 对文本、指标、轨迹、分数四类对象做对比,自动标记 token / cost / latency / score 回归。配合 A/B 实验(t-test + bootstrap CI + Cohen's d 效应量),"新 prompt 到底好不好"从拍脑袋变成看数据。

实践:踩坑记录

  1. 别对 LLM 输出做快照断言。 温度 > 0 时输出必变,toMatchSnapshot 必然 flaky。快照只用于 replay 的输入侧。
  2. LLM-as-Judge 有 self-preference 偏差:用 GPT 当 judge 评 GPT 的输出会系统性偏高。换主模型时 judge 分数整体漂移,需要维护 golden set(人工标注样本)定期校准。
  3. token / 延迟断言用区间,不用单边阈值。 CI 机器负载波动会让延迟偶尔超限,toBeBetween(1000, 8000)toBeLessThan(5000) 稳得多。
  4. 工具参数断言是性价比最高的断言。 一条 toBeCalledWith({ query: "refund policy" }) 能同时抓住工具描述被改坏、参数 schema 漂移、路由逻辑错乱三类问题。
  5. 工具选型别混。 LangSmith 是可观测性(trace 调试、生产监控),DeepEval 是输出指标评测,Promptfoo 是 prompt 变体对比,AgentBench 这类才是测试框架(断言 + 回放 + CI 门禁)。观测 ≠ 断言,别拿 trace 当测试。

CI 集成

name: agent-ci
on: [pull_request]
jobs:
  agent-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx agentbench test --reporter junit
      - uses: actions/upload-artifact@v4
        with:
          path: test-results.xml

JUnit XML 导出意味着可以直接接 GitLab CI 或自建平台,测试结果融入现有报表体系,不需要另起一套看板。

总结与进阶方向

给 agent 上测试,核心三件事:确定性断言把机械回归拦在 PR 里,LLM-as-Judge 把语义质量拦在发布前,replay 让非确定性变得可复现、可对比。进阶方向:4D 覆盖(prompt / workflow / tool / edge-case 四个维度统计覆盖率,比代码行覆盖率对 agent 更有意义)、跨模型回放做模型迁移评估、MCP 工具的 discovery 与生命周期测试、把 judge 分数接到监控告警做线上漂移检测。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐