[控场AI]
· 7 分钟阅读· 3,984 字

AI Agent每次回答都不同?用约束测试取代输出匹配

AI Agent每次回答都不同?用约束测试取代输出匹配

AI Agent输出非确定,应测行为约束而非匹配措辞,用三层金字塔实现可工程化的测试。

传统单元测试依赖确定性的输入输出匹配,但LLM驱动的AI Agent每次运行都可能产生不同措辞,导致整个测试范式失效。文章提出核心思维转变:不测Agent"说了什么",而测它"绝不能做什么、必须做什么、结构上必须产出什么"。具体落地为三层方法——结构契约(JSON schema校验)、护栏断言(隐私/成本/语气等硬规则)、LLM-as-Judge(用第二个LLM对主观质量打分)——并组合成按频率分层的测试金字塔:schema每次提交运行,护栏每晚运行,LLM评判每周运行。最终目标不是证明Agent"正确",而是证明它"可以安全部署",阻止的是灾难而非惊喜。

你花了三天时间构建一个能把用户邮件改写得更友好的 AI Agent。周二它表现完美,周三同样的输入却输出了莫名正式的内容,周四它甚至无端道歉,还署名"Warmly, Your AI"。你盯着测试套件,所有断言都是红色——但 Agent 并没有坏,它只是"今天不一样"。

这正是 agentic AI 中最令人抓狂的问题:如何测试一个每次都给出不同答案的系统?

传统测试为何瞬间失效

写过单元测试的人都熟悉这套逻辑:给定输入 X,期望输出 Y,干净、确定、优雅。但 AI Agent 完全不吃这一套。

基于 LLM 的 Agent 可能用十种不同的方式复述同一句话——全部正确、全部通过人工语感检查,却没有一个能匹配你硬编码的期望字符串。写 assertEqual(output, "Sure, I'd love to help!") 就好比测试朋友是否每次打招呼都用"完全相同的措辞"。

这不是测试"不稳定"(flaky)的问题,而是测试范式从根本上就错了。确定性断言与生成式输出之间存在天然的矛盾,继续沿用旧方法只会让你的测试套件变成一片红海。

AI Agent测试难题

反直觉的解法:别测输出,去测约束

真正能挽救你心智的思维转变是:你不测试 Agent 说了什么,而是测试它绝不能做什么、必须做什么、以及结构上必须产出什么。

原文给了一个精妙的类比:测试一位厨师时,你不会检查他是否把配菜摆在精确的 43 度角,而是检查——鸡肉熟了吗?食物里有没有玻璃碎片?用的是不是顾客点的食材?Agent 测试同理,应当作为行为约束(behavioral constraints),而非输出匹配。

作者提出了三个真正有效的测试层级,构成了一套可落地的方法论。

第一层:结构契约(Structural Contracts)

检查输出是否具备正确的"形状"。如果 Agent 返回 JSON,就验证 schema;如果它写邮件,就检查是否包含主题行、正文,且没有空字段。

import jsonschema

agent_output_schema = {
    "type": "object",
    "required": ["subject", "body", "tone"],
    "properties": {
        "subject": {"type": "string", "minLength": 3},
        "body": {"type": "string", "minLength": 20},
        "tone": {"enum": ["friendly", "professional", "casual"]}
    }
}

def test_email_rewrite_structure(agent, sample_email):
    result = agent.rewrite(sample_email)
    jsonschema.validate(result, agent_output_schema)  # 形状不对就抛错

无论 Agent 写的是"Hey there!"还是"Hi friend!",这个测试都能通过——因为你测的是契约,而非措辞。

第二层:护栏断言(Guardrail Assertions)

这是你的"绝不能做"和"必须做"规则,捕捉那些真正要命的问题:

def test_email_guardrails(agent, sample_email):
    result = agent.rewrite(sample_email)

    # 绝不泄露原始发件人的隐私数据
    assert sample_email["sender_ssn"] not in result["body"]

    # 始终保留原始收件人
    assert result["to"] == sample_email["to"]

    # 绝不超出 token 预算(成本控制!)
    assert len(result["body"].split()) < 500

    # 语气必须符合要求
    assert result["tone"] == "friendly"

注意:没有一个硬编码的短语。每条断言都针对输出必须具备的属性,无论 LLM 如何措辞都成立。隐私泄露、成本失控、语气跑偏——这些才是生产环境真正会出事的地方。

第三层:LLM 当裁判(LLM-as-Judge)

对于主观的东西——"这封邮件真的友好吗?"——用第二次 LLM 调用来打分。这听起来有点循环论证,但只要给裁判一份评分标准(rubric),效果出奇地好:

def test_tone_quality(agent, judge_llm, sample_email):
    result = agent.rewrite(sample_email)

    verdict = judge_llm.evaluate(
        prompt=f"""Rate this rewritten email on friendliness (1-5).
        Original: {sample_email['body']}
        Rewritten: {result['body']}
        Score only. No explanation."""
    )

    score = int(verdict.strip())
    assert score >= 4, f"Friendliness score {score}/5 — too low"

运行 5 次,如果 4 次通过,说明 Agent 足够稳健;如果分数在 2 和 5 之间来回摆动,那就是 prompt 出了问题——而现在你拥有的是一个可量化的度量,而不是凭直觉的感觉。

LLM-as-Judge 模式之所以有效,关键在于"评估"与"生成"是两项不同难度的任务——就像人类校对比写作更容易犯错率更低一样。裁判 LLM 无需创作,只需在一个受限的评分维度上做判断,因此其结果比生成结果更稳定、更可预期。实践中有几点值得注意:首先,裁判与生成 Agent 最好使用不同的模型或不同的 system prompt,避免两者共享同一种"偏见"导致互相背书;其次,评分标准(rubric)越具体越好,"友好"这类词应拆解为可操作的子标准,例如"是否包含称呼语""是否有感谢句式""是否避免了命令式语气";第三,多次采样取中位数而非单次判断,能显著降低裁判本身的方差。这一模式已被学术界用于大规模 LLM 能力评测(如 MT-Bench、Chatbot Arena),在生产级 Agent 测试中引入它,本质上是把评测工程的成熟实践下移到 CI/CD 流水线。

为 Agent 设计的测试金字塔

作者建议把这套结构套用到每一个 Agent 上:

  • 底层(每次提交运行): 结构 schema 校验。快速、确定,捕捉输出格式的回归问题。
  • 中层(每晚运行): 在 20 个以上多样化输入上执行护栏断言,捕捉安全/策略违规与行为漂移。
  • 顶层(每周运行): 在精心策划的基准集上做 LLM-as-judge 评估,捕捉细微的质量退化。

底层运行只需毫秒级,顶层每次评估不过几美分的 API 费用。两者都远胜于"我在 playground 里试了一次,感觉还行"。这种分层策略在速度、成本与覆盖度之间取得了务实的平衡。

这里借用的"测试金字塔"概念源自 Mike Cohn 在 2009 年提出的软件测试分层模型:底部是大量快速的单元测试,中部是集成测试,顶部是少量慢速的端到端 UI 测试。核心思想是越往底层越便宜、越往顶层越接近真实用户场景,二者需要在成本与覆盖度之间平衡。将这一框架迁移到 AI Agent 时,"单元测试"变成了 schema 校验这类纯确定性检查,"集成测试"对应在多样化输入上运行护栏断言,"端到端测试"则变成 LLM-as-Judge 的质量评估。需要特别注意的是,传统金字塔建议大量底层、少量顶层,在 Agent 场景中同样成立——过度依赖 LLM-as-Judge 会导致测试套件运行缓慢且成本高昂,而底层 schema 测试的高覆盖度才是快速反馈的基础。

没人告诉你的那件事

文章最核心的洞见在于:Agent 测试的目的不是证明你的 Agent 正确,而是证明它可以安全部署。这是一个更低的门槛,却也是一个有用得多的目标。

你的 Agent 会给你惊喜,它会用你意想不到的方式改写内容,有时甚至写得比你更好。测试套件的职责不是阻止惊喜,而是阻止灾难。

作者最后抛出一个实用挑战:拿起你正在构建的 Agent,为它写下三条护栏断言——不是输出匹配,不是语感,而是三个它必须始终满足的具体属性。这正是从"玄学调试"走向工程化的第一步。对于任何想把 LLM 应用推向生产环境的团队来说,这种从输出验证到约束验证的思维转变,可能比任何框架或工具都更关键。

#AI Agent测试#LLM测试#护栏断言#LLM-as-Judge#非确定性输出#测试金字塔#AI应用工程化#行为约束测试
分享:

相关推荐