AI 应用工程化 03:LLM 评测、回归测试与可观测性怎么做
wxk1991 Lv6

AI 应用工程化 03:LLM 评测、回归测试与可观测性怎么做

传统软件有一个很朴素的判断:

1
输入固定,输出应该固定。

大模型应用不是这样。

同一个问题,模型可能每次措辞不同;换一个模型版本、系统提示、temperature、检索结果,答案质量都会变化。

所以 AI 项目最危险的开发方式是:

1
我刚刚试了几个问题,感觉效果不错,可以上线。

这就是 vibe-based evaluation。

真正的 AI 工程需要回答:

1
2
3
4
这次修改在哪些真实任务上变好了?
哪些任务退化了?
准确率、延迟和成本变化了多少?
线上失败能不能沉淀成下一次回归测试?

这篇文章讲一套不依赖特定平台的 LLM 评测和可观测性流程。

资料核对时间:2026-10-01。


一、评测对象不是只有模型

AI 应用通常由多层组成:

1
2
3
4
5
6
7
8
用户输入
-> 输入处理
-> Prompt
-> RAG 检索
-> 模型
-> 工具调用
-> 输出解析
-> 业务规则

换模型只是其中一个变量。

系统效果还受这些因素影响:

  • system prompt
  • few-shot examples
  • chunking
  • embedding 模型
  • topK
  • reranker
  • 工具描述
  • temperature
  • 最大输出长度
  • 输出 schema

所以评测对象应该是完整应用版本:

1
2
3
4
5
6
7
{
"model": "model-v3",
"promptVersion": "support-v12",
"retrieverVersion": "hybrid-v4",
"toolsetVersion": "tools-v7",
"temperature": 0.2
}

没有版本记录,分数变化后你不知道是谁造成的。


二、先定义任务,再定义分数

不要先问:

1
应该用 BLEU、ROUGE 还是 LLM-as-a-Judge?

先问业务目标:

1
2
3
4
客服回答要解决什么问题?
合同抽取哪些字段必须正确?
代码 Agent 什么情况下算完成?
RAG 回答什么时候必须拒答?

例如退款客服助手的成功标准可以是:

1
2
3
4
5
政策结论正确
引用正确
不得承诺不存在的退款权益
需要人工处理时正确转人工
不能泄露其他用户信息

再为每一项选择评测方式。

指标应该服务于任务,而不是反过来。


三、建立最小黄金数据集

不用一开始准备一万条。

先准备 50-100 个高价值案例,覆盖:

1
2
3
4
5
6
7
8
常见问题
边界问题
历史事故
容易混淆的问题
应该拒答的问题
恶意输入
超长输入
工具调用任务

数据结构可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id": "refund-017",
"input": {
"question": "企业年付套餐购买 10 天后可以全额退款吗?",
"tenantId": "tenant_demo"
},
"expectations": {
"decision": "not_eligible_for_full_refund",
"requiredEvidence": ["billing-policy-7-day-window"],
"mustMention": ["7 天"],
"mustNotMention": ["无条件退款"],
"requiresEscalation": false
},
"tags": ["billing", "boundary", "rag"]
}

不要只存一段“标准答案”。

自然语言有很多正确表达,更实用的是存:

1
2
3
4
5
必须满足的事实
禁止出现的错误
期望工具调用
期望引用
是否需要拒答或转人工

四、数据集要分层

建议至少分三层:

Smoke set

10-20 条,几十秒内跑完。

每次提交都跑,检查最基本能力没有坏。

Regression set

100-1000 条,覆盖核心业务和历史问题。

合并重要 Prompt、模型、检索改动前运行。

Challenge set

高难、攻击、长上下文、低频边界案例。

定期运行,不一定阻塞每次发布。

这样既能快速反馈,也不会让评测成本失控。


五、优先使用确定性评分

能用代码判断的,不要先用大模型判断。

例如结构化抽取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type ExtractionResult = {
orderId: string
amount: number
currency: string
}

function scoreExtraction(
actual: ExtractionResult,
expected: ExtractionResult
) {
return {
orderId: actual.orderId === expected.orderId,
amount: actual.amount === expected.amount,
currency: actual.currency === expected.currency,
}
}

工具调用可以检查:

1
2
3
4
5
工具名是否正确
参数是否正确
调用次数是否超限
是否调用了禁止工具
最终状态是否完成

RAG 可以检查:

1
2
3
正确证据是否在 topK
引用 ID 是否存在
是否引用了未检索到的文档

确定性评分便宜、稳定、容易回归。


六、什么时候使用 LLM-as-a-Judge

有些质量无法靠字符串相等判断:

  • 回答是否完整
  • 语气是否合适
  • 摘要是否保留关键事实
  • 解释是否清晰
  • 回答是否忠实于证据

这时可以用 LLM 作为评审。

但 Judge 也会波动和偏见。

评审提示要尽量具体:

1
2
3
4
5
6
7
请根据以下标准分别评分:
1. 事实正确性:0-2
2. 证据忠实度:0-2
3. 是否完整回答用户问题:0-2
4. 是否包含政策外承诺:是/否

先引用支持评分的具体文本,再输出 JSON。

不要只问:

1
这个回答好吗?打 1-10 分。

还要定期让人类标注一批样本,检查 Judge 和人类是否一致。

OpenAI 的评测实践也建议把自动指标和人工判断结合,并用人工反馈校准自动评分。


七、对比评测通常比绝对评分更稳定

如果要比较 Prompt A 和 Prompt B,可以让评审做 pairwise comparison:

1
2
3
4
5
同一个输入
同一份证据
回答 A
回答 B
哪个更符合标准?

还要随机交换 A/B 顺序,减少位置偏差。

对比结果可以统计:

1
2
3
4
A 胜率
B 胜率
平局率
各标签下的胜率

例如整体 B 更好,但在 long-context 标签下明显退化,这比一个总分更有指导意义。


八、RAG 要拆成检索评测和回答评测

RAG 最常见的误区是只评最终答案。

应该至少拆成:

检索指标:

1
2
3
4
Recall@K
MRR
nDCG
权限过滤正确率

生成指标:

1
2
3
4
答案正确性
faithfulness
引用准确率
拒答准确率

如果正确证据根本没检索到,生成模型很难凭空修复。

如果正确证据已经排第一,但回答仍然错,问题才在生成层。

拆开评测,才能知道该改哪里。


九、Agent 要评轨迹,不只评结果

Agent 最后完成任务,不代表过程安全、高效。

例如两个 Agent 都成功创建工单:

1
2
Agent A:查一次客户 -> 创建工单,2 步完成
Agent B:查 8 次数据库 -> 调用错误工具 -> 重试 -> 创建工单

最终结果一样,但 B 的成本和风险更高。

Agent 评测需要记录:

1
2
3
4
5
6
7
8
任务是否完成
工具选择是否正确
参数是否正确
总步骤数
无效工具调用数
是否越权
是否触发审批
总 token、成本、延迟

安全任务中,错误动作即使最终被修正,也可能直接判失败。


十、建立一个简单评测 Runner

不一定一开始就上平台。

一个 TypeScript runner 就够用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type EvalCase = {
id: string
input: unknown
expectations: Record<string, unknown>
tags: string[]
}

type EvalResult = {
caseId: string
passed: boolean
scores: Record<string, number | boolean>
latencyMs: number
usage?: {
inputTokens: number
outputTokens: number
}
output: unknown
}

async function runCase(testCase: EvalCase): Promise<EvalResult> {
const startedAt = Date.now()
const response = await runApplication(testCase.input)
const scores = await scoreResponse(response, testCase.expectations)
const passed = Object.values(scores).every((value) =>
typeof value === 'boolean' ? value : value >= 0.8
)

return {
caseId: testCase.id,
passed,
scores,
latencyMs: Date.now() - startedAt,
usage: response.usage,
output: response.output,
}
}

输出 JSON,保留到 CI artifact 或数据库:

1
2
3
4
5
6
7
8
{
"experiment": "prompt-v12-model-v3",
"baseline": "prompt-v11-model-v3",
"passRate": 0.93,
"baselinePassRate": 0.91,
"p95LatencyMs": 2840,
"averageCostUsd": 0.013
}

十一、发布门槛要同时考虑质量、成本和延迟

一个版本准确率提升 1%,但成本增长 5 倍,不一定值得上线。

可以设置门槛:

1
2
3
4
5
核心任务通过率不得下降
安全测试必须 100% 通过
总体质量至少提升 2%
P95 延迟增长不超过 20%
单请求平均成本增长不超过 15%

不同业务权重不同。

医疗、金融、权限操作应该更看重错误率和安全;内容生成可能更看重偏好和成本。


十二、线上可观测性要记录完整 Trace

普通 API 日志只记录:

1
2
3
路径
状态码
耗时

AI 请求还需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
trace_id
model
prompt_version
retriever_version
输入输出 token
首 token 延迟
总延迟
检索结果 ID 和分数
工具调用轨迹
重试次数
错误类型
估算成本
用户反馈

不要默认记录完整用户输入和输出。

涉及个人信息、商业数据、密钥和代码时,要做脱敏、采样、权限控制和保留周期管理。

Phoenix 和 MLflow 等工具都支持围绕 trace 做 LLM/Agent 可观测性。它们也越来越多地基于 OpenTelemetry/OpenInference,方便接入现有监控体系。


十三、Trace 要能回答“慢在哪里、错在哪里”

一次 RAG 请求可以拆成 spans:

1
2
3
4
5
6
7
8
request
-> embed_query
-> vector_search
-> keyword_search
-> rerank
-> build_context
-> model_generate
-> validate_citations

一次 Agent 请求:

1
2
3
4
5
6
7
agent_run
-> model_plan
-> tool_search_customer
-> model_decide
-> approval_wait
-> tool_create_ticket
-> model_final

每个 span 记录耗时、状态和必要元数据。

用户说“AI 很慢”时,你能看到是 embedding 100ms、检索 80ms,还是模型首 token 等了 6 秒。


十四、把线上失败变成回归用例

线上数据是最有价值的评测来源。

流程可以是:

1
2
3
4
5
6
7
8
低评分或人工投诉
-> 找到 trace
-> 隐私脱敏
-> 分析根因
-> 补充正确期望
-> 加入 regression set
-> 修复
-> 确认新版本通过

这样每次事故都会扩大系统的防线。

注意不要把所有生产日志自动加入数据集。需要去重、脱敏、标注和质量审核。


十五、抽样做线上评测

离线数据永远追不上真实用户分布。

可以对线上请求做采样:

1
2
3
4
1% 普通请求做自动质量评分
100% 安全相关请求做规则检查
所有低置信度请求进入人工队列
用户点踩请求优先复审

线上评测重点监控趋势,不一定每个请求都用昂贵 Judge。

例如:

1
2
3
4
引用缺失率突然升高
拒答率一周内翻倍
工具调用平均步骤增加
某类问题成本异常

这些变化可能意味着知识库、Prompt、模型或用户流量发生了改变。


十六、常见反模式

只跑公开 benchmark

公开榜单适合初筛模型,不能代表你的业务任务。

只看平均分

平均分会掩盖关键类别退化。必须按标签切片。

测试数据来自 Prompt 作者

容易只覆盖自己预期的问法。要加入生产数据和其他人编写的边界案例。

Judge 和被测模型相同且没有校准

可能出现风格偏好和自我偏好。需要人工样本校准。

每次修改同时换模型、Prompt、检索器

最后不知道哪项变化有效。尽量单变量实验。

只记录成功响应

失败、超时、重试和拒答更值得分析。


十七、一个可落地的最小流程

如果项目还没有任何评测系统,可以这样开始:

1
2
3
4
5
第 1 周:整理 50 个真实案例和明确期望
第 2 周:实现确定性评分和 smoke set
第 3 周:加入 LLM Judge,并用人工样本校准
第 4 周:接入 trace,记录版本、延迟、token 和工具轨迹
之后:每个线上失败都尝试沉淀为回归用例

CI 中:

1
2
3
4
每次提交跑 smoke set
重要变更跑 regression set
安全测试失败直接阻止发布
质量、成本、延迟生成对比报告

上线后:

1
2
3
4
抽样自动评测
收集用户反馈
监控质量与成本趋势
定期人工复审

十八、最后的判断

大模型应用不会因为“今天效果不错”就一直不错。

模型会换,知识库会更新,Prompt 会修改,用户问题也会变化。

所以评测不是上线前的一次验收,而是持续循环:

1
观察 -> 收集案例 -> 评测 -> 修改 -> 回归 -> 上线 -> 再观察

真正稳定的 AI 产品,不一定每次都使用最强模型。

它更重要的能力是:

1
2
3
知道自己在哪些任务上可靠,
知道改动是否造成退化,
知道线上失败发生在哪一层。

当这些问题都有数据回答时,AI 应用才从“试出来的 Demo”变成了“可以维护的软件”。


参考资料