同一个接口每次返回都不一样?大模型输出的可靠断言怎么写

举报
霍格沃兹测试学社 发表于 2026/08/12 14:38:03 2026/08/12
【摘要】 AI 测试开发面试高频题:“大模型的输出是不确定的,你怎么做自动化测试?”这篇用一个真实线上故障,完整还原 AI 测试开发工程师的处理过程:怎么定位、怎么写代码、最后沉淀成什么方法。 一、真实故障:断言天天红,后来有人把它关了某电商公司的智能客服项目,测试工程师小周给"订单状态查询"这个 LLM 接口写了自动化用例,断言写得很传统:assert response.text == "您的订单 ...

AI 测试开发面试高频题:“大模型的输出是不确定的,你怎么做自动化测试?”
这篇用一个真实线上故障,完整还原 AI 测试开发工程师的处理过程:怎么定位、怎么写代码、最后沉淀成什么方法。

一、真实故障:断言天天红,后来有人把它关了

某电商公司的智能客服项目,测试工程师小周给"订单状态查询"这个 LLM 接口写了自动化用例,断言写得很传统:

assert response.text == "您的订单 TEST-1001 已发货,预计3天内送达"

上线第一周,这条用例在 CI 里天天红。同样的输入,模型今天回"已发货,预计3天送达",明天回"您好,包裹已在路上啦~",后天直接换成 markdown 列表。小周先是把断言放宽成 assert "发货" in response.text,折腾几轮后烦了,直接把用例禁用,备注写了句:“LLM 输出随机,断言没意义”。

三周后,半夜告警:订单查询功能整体不可用。排查发现,模型输出悄悄变成了"带 markdown 代码块的 JSON"——内容其实没错,但下游服务直接 json.loads(),遇到 ``` 包裹就抛异常。致命的是,这条用例早就被禁用了,发布前没有任何拦截,故障持续了 40 分钟。

复盘时团队才意识到:不是大模型不能测,而是用精确匹配的旧思路去测,一定会经历"天天误报 → 麻痹放弃 → 故障裸奔"这个死亡循环。

二、先破除一个误区:temperature=0 也不保证逐字相同

面试里很多人第一反应是:“把 temperature 设成 0,输出不就固定了吗?”

不够。temperature=0 只是让采样退化为取最大概率 token,但服务端的 continuous batching、浮点累加顺序、并行解码等实现细节,仍然可能让同一输入产生细微的措辞差异。这在 OpenAI 官方论坛和 GitHub issue 里是被反复讨论过的已知现象。

所以正确的目标不是"逐字相等",而是把断言分层:机器能稳定的部分用硬断言锁死,语言天然漂移的部分用软断言兜住。

三、核心代码:四层断言策略

第 1 层:请求侧先约束输出

能结构化的绝不自由发挥。temperature=0 + 结构化输出,把 80% 的不确定性在源头掐掉:

import json
from openai import OpenAI

client = OpenAI()

def ask_order_status(order_id: str) -> str:
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0,
        response_format={"type": "json_object"},   # 强制 JSON 输出
        messages=[
            {"role": "system", "content": (
                "你是客服助手,只返回 JSON:"
                '{"status": "shipped|pending|refunding", "summary": "不超过30字"}'
            )},
            {"role": "user", "content": f"查询订单 {order_id} 的状态"},
        ],
    )
    return resp.choices[0].message.content

第 2、3 层:格式硬断言 + 语义软断言(LLM-as-Judge)

格式、字段、枚举值这些"机器契约",必须硬断言,一个字符都不能让;语义措辞交给裁判模型判断:

JUDGE_PROMPT = """你是严格的测试评审员。
参考答案:{expected}
模型实际输出:{actual}
判断实际输出与参考答案语义是否一致,且不含事实错误。
只返回 JSON:{{"pass": true/false, "reason": "..."}}"""

def assert_llm_output(actual: str, expected: str):
    # —— 第2层:格式硬断言(复现故障的关键防线)——
    data = json.loads(actual)                      # 不是合法 JSON 直接失败
    assert {"status", "summary"} <= set(data), "缺少必需字段"
    assert data["status"] in {"shipped", "pending", "refunding"}, "枚举值非法"

    # —— 第3层:语义软断言(LLM-as-Judge)——
    verdict = client.chat.completions.create(
        model="gpt-4o", temperature=0,
        messages=[{"role": "user",
                   "content": JUDGE_PROMPT.format(expected=expected, actual=actual)}],
    )
    result = json.loads(verdict.choices[0].message.content)
    assert result["pass"], f"语义断言失败:{result['reason']}"

注意裁判模型要选比被测模型更强的型号,并且裁判的提示词、温度都要固定——裁判本身也要"可复现"。

第 4 层:稳定性测试,用"一致性率"替代单次判定

单次通过不算通过。同样的输入跑 N 次,格式一致性必须是 100%,语义一致性给一个可接受的阈值:

def test_output_stability(n=10):
    results = []
    for _ in range(n):
        raw = ask_order_status("TEST-1001")
        json.loads(raw)                    # 任何一次格式失败都算不通过
        results.append(json.loads(raw))

    semantic_ok = sum(r["status"] == "shipped" for r in results)
    rate = semantic_ok / n
    assert rate >= 0.9, f"语义一致性率 {rate:.0%},低于阈值 90%"

这条用例就是小周团队复盘后补上的:它不管模型怎么措辞,只要 10 次里有 1 次格式不对,CI 立刻红——故障里那种"格式漂移"从此不可能静默上线。

四、沉淀成方法:AI 测试开发的断言四层模型

层级 断言对象 方式 容忍度
L1 请求侧约束 输出格式 temperature=0、response_format、严格 schema 从源头减少漂移
L2 格式硬断言 JSON 结构、字段、枚举 代码断言 零容忍
L3 语义软断言 内容正确性 LLM-as-Judge / G-Eval 阈值制(如 ≥0.8)
L4 稳定性断言 一致性率 多次采样统计 格式 100%,语义 ≥90%

配套两条工程纪律:一是禁止禁用失败用例,LLM 用例红了要么修断言要么修模型,不许关掉;二是裁判提示词纳入版本管理,裁判变更也要走回归。开源社区的 DeepEval(G-Eval 指标)、OpenAI Evals 都是这套思路的成熟实现,可以直接复用,不必从零造。

五、面试追问,你答得上来吗

  1. LLM-as-Judge 自己也会出错、也会漂移,怎么办?——答:固定裁判模型与提示词版本、温度设 0、定期用人工标注集校准裁判的准确率(裁判的准确率本身也是一个被测指标)。
  2. 为什么不直接用余弦相似度做语义断言?——答:相似度对措辞敏感、对事实不敏感,"3 天送达"和"5 天送达"相似度很高但都是错的;语义判断交给更强的裁判模型更可靠,相似度只适合做粗筛。
  3. 稳定性测试跑 10 次成本高、CI 慢,怎么权衡?——答:分层执行——格式断言每次 PR 跑;语义与稳定性测试每晚定时跑全量,发布前跑冒烟子集。

下一篇预告:《RAG 上线后为什么总"答非所问"?——黄金数据集与检索质量评测》

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。