AI 应用评估与可观测性
问题
如何判断一次 Prompt、模型、RAG 或 Agent 改动真的变好了?如何建设离线评测、线上指标、LLM-as-a-Judge、轨迹评估、Tracing 和发布门禁?
AI 应用不能只测“接口成功”和“JSON 能解析”,要建立三层质量闭环:
- 离线 Evals:固定代表性数据集,每次改 Prompt、模型、检索或工具都跑回归。
- 线上观测:任务完成率、用户反馈、转人工、延迟、成本、工具错误与安全事件。
- 失败回流:把线上失败脱敏、去重、标注后加入评测集。
评分优先级通常是:确定性断言 > 人工标注 > 经过校准的 Judge。Agent 还要评估轨迹、工具选择、参数、权限、步骤和最终结果,不能只看最后一句话。
一、为什么传统测试不够
同一个输入可能有多种正确表达,模型和检索又具有概率性。传统单元测试仍然必要,但只覆盖确定性部分:
- Schema、权限、金额、URL、SQL、工具参数。
- 流协议、取消、重试、幂等和状态机。
- 引用资源是否存在、用户是否有权访问。
语义质量则需要任务特定评测:答案是否正确、证据是否充分、风格是否合适、工具路径是否安全。
二、从业务目标定义 Eval
不要从“模型准确率”开始,要从用户任务开始:
| 产品 | 业务目标 | 离线指标 | 线上指标 |
|---|---|---|---|
| 客服 | 正确解决问题 | 答案正确、政策遵循、引用支持 | 一次解决率、转人工率、复开率 |
| RAG | 找到并使用正确资料 | Recall@K、NDCG、忠实度、引用覆盖 | 来源点击、纠错、无结果率 |
| 代码 Agent | 完成变更且不破坏系统 | 测试通过、Diff 约束、工具轨迹 | PR 接受率、回滚、缺陷逃逸 |
| 数据分析 | 查询正确且解释可信 | SQL 结果、数值、图表 Schema | 保存/分享率、人工修正率 |
一个平均 90 分的系统,可能在普通问题上 99 分、权限问题上 0 分。安全、隐私和高风险业务约束应作为硬门槛或单独切片,而不是被平均值稀释。
三、评测集设计
数据来源
- 产品需求和验收用例。
- 真实线上请求的脱敏、聚类与抽样。
- 历史事故和人工转接记录。
- 专家构造的边界、对抗和拒答样本。
- 合成数据,用于扩充表达方式;必须人工抽查。
切片维度
interface EvalCase {
id: string;
input: unknown;
expected?: unknown;
rubric?: string[];
tags: Array<
| 'happy-path'
| 'long-context'
| 'multilingual'
| 'prompt-injection'
| 'permission'
| 'tool-use'
| 'no-answer'
>;
source: 'product' | 'production' | 'incident' | 'synthetic';
dataPolicy: 'public' | 'internal' | 'restricted';
}
评测集必须版本化,训练/调参集与最终留出集分开,避免团队反复针对同一批样本“刷题”。
四、评分器的选择顺序
1. 确定性评分
最可信、便宜、可解释:
function gradeOrderAnswer(output: OrderAnswer, expected: ExpectedOrder) {
return {
orderIdCorrect: output.orderId === expected.orderId,
amountCorrect: Math.abs(output.amount - expected.amount) < 0.01,
sourceAllowed: output.sources.every(source => expected.allowedSources.includes(source)),
noSecret: !containsSecret(output.text),
};
}
能用数据库、测试、编译器、计算器、JSON Schema 或规则判定的部分,不要交给另一个 LLM 猜。
2. 人工评分
适合建立金标准、评估主观体验和校准 Judge。要提供清晰 Rubric、示例和冲突处理流程,并记录评分者一致性。
3. LLM-as-a-Judge
适合大规模比较开放式答案,但存在位置偏差、长度偏差、自我偏好、知识错误和 Prompt Injection 风险。
推荐:
- 优先 pairwise 比较,而不是让 Judge 随意打 1–10 分。
- 随机交换候选答案顺序,检测位置偏差。
- 只提供评分必要内容,外部文档当作不可信数据。
- 用人工金标准计算一致率、召回和误判切片。
- Judge 模型、Prompt、Rubric 和版本都要入库。
interface PairwiseJudgment {
winner: 'A' | 'B' | 'tie';
criteria: {
correctness: 'A' | 'B' | 'tie';
completeness: 'A' | 'B' | 'tie';
citationSupport: 'A' | 'B' | 'tie';
};
evidence: string[];
}
五、RAG 评估拆层
RAG 失败时要先判断哪一层坏了:
| 层 | 指标 | 典型问题 |
|---|---|---|
| 解析 | 内容覆盖、结构保留 | 表格、页码、代码块丢失 |
| 召回 | Recall@K、MRR、NDCG | 正确证据没进候选集 |
| 重排 | Top-K 命中、排序增益 | 候选有正确文档但排得太后 |
| 上下文 | 证据覆盖、token、权限 | 截断、重复、越权片段 |
| 生成 | 忠实度、引用精度、拒答 | 有证据却答错或无证据硬答 |
端到端“答案好不好”不能告诉你应该调 chunk、Embedding、Reranker 还是 Prompt。
六、Agent 评估
Agent 评测至少包含四层:
- 最终结果:文件、工单、订单或测试是否达到目标。
- 轨迹:是否选择正确工具、参数和顺序。
- 效率:步骤、token、工具时间、重试和费用。
- 安全:是否越权、是否在副作用前确认、是否泄漏数据。
interface AgentStep {
index: number;
toolName?: string;
inputHash?: string;
outcome: 'success' | 'error' | 'denied' | 'approval-required';
durationMs: number;
inputTokens?: number;
outputTokens?: number;
}
interface AgentEval {
taskSuccess: boolean;
invalidToolCalls: number;
unauthorizedAttempts: number;
duplicateWrites: number;
totalSteps: number;
totalCostUsd?: number;
}
不要要求 Agent 复制一条“标准轨迹”:不同安全路径都可能正确。应定义允许条件、禁止动作和最终不变量。
七、线上可观测性
Trace 结构
Span 建议包含低基数、可聚合字段:
- request/workflow ID、功能、环境、租户散列。
- Provider、能力角色、模型版本。
- operation、结束原因、错误类型。
- 输入/输出/缓存/推理 token。
- 首块延迟、总耗时、工具和检索耗时。
- Prompt、策略、检索器和评测版本。
Prompt、工具参数、工具结果和系统指令可能含敏感数据。OpenTelemetry 的 GenAI 语义约定也明确提示相关字段可能敏感。默认记录摘要、哈希、长度和分类;原文采样必须经过脱敏、权限和短期保留。
前端指标
- 提交到首个可见内容的时间。
- 流中断、取消、重新生成和恢复成功率。
- 引用展开/点击、复制、采纳和纠错。
- 工具审批接受/拒绝和用户等待时间。
- 错误提示是否给出可执行的下一步。
八、发布门禁与灰度
interface EvalReport {
taskSuccess: number;
safetyPass: number;
p95LatencyMs: number;
meanCostUsd: number;
slices: Record<string, { passRate: number; count: number }>;
}
function canRelease(candidate: EvalReport, baseline: EvalReport): boolean {
return (
candidate.safetyPass === 1 &&
candidate.taskSuccess >= baseline.taskSuccess - 0.005 &&
candidate.p95LatencyMs <= baseline.p95LatencyMs * 1.1 &&
candidate.slices.permission?.passRate === 1
);
}
阈值只是示例。真实门禁要根据风险和样本置信度制定,并查看分布和切片。上线先做 shadow/canary,预先定义回滚指标,避免看到波动后临时解释。
九、常见反模式
- 只看点赞率,不看任务是否真正完成。
- 用同一个模型生成答案又给自己打分。
- 每次换模型都更换评测集,无法与基线比较。
- 只报告平均分,不看权限、语言、长上下文等切片。
- 把线上完整 Prompt 全量记录到 Trace。
- 只评最终答案,不评 Agent 的越权尝试和重复写入。
- Judge Prompt 被候选答案中的恶意指令劫持。
- 在没有统计意义的小样本上宣布提升。
常见面试问题
Q1: AI Eval 和普通单元测试有什么区别?
答案:单元测试适合确定性规则;Eval 衡量开放式、概率性语义质量。成熟系统同时需要两者,能确定性判断的部分优先用普通测试。
Q2: 评测集从哪里来?
答案:需求验收、真实线上失败、历史事故、专家边界用例和经过审查的合成数据。需要脱敏、去重、切片、版本化,并保留独立测试集。
Q3: LLM-as-a-Judge 有哪些偏差?
答案:位置、长度、风格、自我偏好、知识错误和注入。通过顺序随机化、pairwise Rubric、多 Judge、人工金标准和切片校准降低风险。
Q4: RAG 为什么要拆层评估?
答案:答案错误可能来自解析、召回、重排、上下文或生成。拆层指标才能定位应该改模型、分块、过滤还是 Prompt。
Q5: Agent 评测为什么不能只看最终答案?
答案:Agent 可能通过越权、重复写入或高成本路径得到正确结果。需要同时评轨迹、工具参数、授权、步骤、成本和不变量。
Q6: 什么是评测切片?
答案:按语言、长度、用户类型、风险、工具、文档类型等维度分组。它能防止平均分掩盖关键场景退化。
Q7: 如何做模型升级门禁?
答案:固定数据集和 Prompt,比较质量、安全、延迟与成本;关键切片设硬门槛;灰度观察线上指标并预设回滚。
Q8: 线上反馈能直接当标签吗?
答案:不能。点赞受 UI、用户预期和选择偏差影响。需要结合任务结果、后续行为和人工标注,并过滤滥用与重复数据。
Q9: 为什么 Trace 不应默认记录完整 Prompt?
答案:Prompt 和工具结果可能包含 PII、密钥和商业数据。默认记录元数据和哈希,需要内容时受控采样、脱敏和短期保留。
Q10: 如何评估流式 UI?
答案:测提交到首块、流畅度、中断率、取消传播、恢复、最终一致性和可访问性,而不只是后端总耗时。
Q11: 如何防止团队对评测集过拟合?
答案:训练/开发/留出集分离,定期从新线上样本扩充,保留隐藏集,并关注真实业务指标是否同步改善。
Q12: 最有价值的 Eval 指标是什么?
答案:没有通用单一指标。首选与业务结果直接相关的任务成功率,再用安全、质量、延迟和成本切片解释结果。