如何设计一个最小可用的 AI 评估系统

很多团队知道 AI 应用需要 Eval,但一开始容易把事情想得过重:标注平台、复杂指标、多人评审、线上实验、模型榜单、数据仓库,全都想一次搭好。

结果是系统迟迟做不出来,Prompt 仍然靠感觉改。

最小可用的 AI 评估系统不需要很复杂。它应该先解决一个问题:每次改动前后,团队能不能用一组稳定样例判断质量有没有明显退化。

这篇文章从前端工程师熟悉的工程流程出发,设计一套可以逐步演进的 Eval 系统。

最小可用系统的边界

一个 MVP Eval 系统应该覆盖六件事:

  • 有一组可版本管理的评估样例。
  • 能用同一套输入运行当前 AI 链路。
  • 能保存输出、trace、成本和延迟。
  • 能用规则和 LLM-as-judge 给出评分。
  • 能生成可读报告,突出失败样例。
  • 能作为发布门禁,阻止明显退化。

暂时可以不做的事:

  • 不必一开始建设复杂标注后台。
  • 不必追求全自动判断所有开放式答案。
  • 不必覆盖全部线上场景。
  • 不必把每个指标都做成实时看板。
  • 不必把人工评审流程做得很重。

先把“能稳定回归”做出来,再逐步扩展。

第一步:选一个具体功能

不要从“评估整个 AI 平台”开始。

先选一个用户价值明确、线上风险可控、样例容易收集的功能。例如:

  • 知识库问答。
  • 工单摘要。
  • 商品标题改写。
  • 订单助手。
  • 文档问答。
  • SQL 生成。
  • 客服回复草稿。

如果你负责前端,可以优先选那些有明确 UI 状态和用户反馈的功能。因为这些功能更容易收集真实失败样例,也更容易定义期望行为。

一个好起点通常满足三个条件:

  • 用户每天都在用。
  • 团队经常改 Prompt 或知识库。
  • 出错后能明确判断哪里不好。

第二步:建立评估集

评估集是 Eval 系统的地基。

最小版本可以从 30 到 50 条样例开始,不需要一上来几千条。关键是覆盖类型要均衡。

建议至少包含:

类型例子
高频问题用户最常问的问题
边界问题缺字段、含糊表达、多意图
历史失败线上差评、投诉、事故样例
资料缺失当前上下文无法回答
权限边界用户无权读取或执行
格式压力要求 JSON、表格、固定字段
安全风险注入、越权、敏感内容

评估样例应该进入 Git 或数据库版本管理。早期放在仓库里就很好,便于 code review。

一个简单结构可以这样设计:

{
  "id": "support_qa_001",
  "feature": "support-qa",
  "tags": ["high-frequency", "groundedness"],
  "input": {
    "messages": [
      {
        "role": "user",
        "content": "企业版可以导出审计日志吗?"
      }
    ],
    "context": [
      {
        "id": "doc_audit_log",
        "title": "企业版安全能力",
        "content": "企业版支持导出最近 180 天的审计日志,格式为 CSV。"
      }
    ]
  },
  "expected": {
    "behavior": "answer",
    "mustInclude": ["企业版", "180 天", "CSV"],
    "mustCite": ["doc_audit_log"]
  },
  "rubric": "回答必须说明企业版支持导出审计日志,保留周期为 180 天,导出格式为 CSV,并引用给定文档。"
}

这里的重点不是字段名固定,而是每条 case 都要说明“什么算好”。

第三步:让 Runner 调真实链路

Eval Runner 不应该只调用模型 API。它应该尽量调用和产品一致的 AI 链路。

如果真实产品链路是:

  1. 读取用户信息。
  2. 拼装系统 Prompt。
  3. 裁剪历史消息。
  4. 生成检索 query。
  5. 调知识库检索。
  6. 调模型生成。
  7. 解析结构化输出。
  8. 返回 UI message。

那么评估也应该经过这些步骤。否则你测出来的是“裸模型能力”,不是产品质量。

Runner 至少要记录:

  • case id。
  • prompt version。
  • model。
  • temperature。
  • retrieval query。
  • retrieved document ids。
  • tool calls。
  • final output。
  • parsed result。
  • latency。
  • token usage。
  • estimated cost。
  • error。

这些字段决定失败后能不能定位。

type EvalRunResult = {
  caseId: string;
  runId: string;
  promptVersion: string;
  model: string;
  outputText: string;
  parsed?: unknown;
  traces: {
    retrievedDocIds?: string[];
    toolCalls?: Array<{ name: string; status: 'success' | 'error' }>;
  };
  usage: {
    inputTokens: number;
    outputTokens: number;
    latencyMs: number;
    estimatedCost?: number;
  };
  error?: string;
};

对前端团队来说,Runner 的输出最好保留 UI 需要的数据结构,而不只是最终文本。这样才能评估引用、组件渲染、工具状态和错误状态。

第四步:先做规则评分

规则评分便宜、稳定、可解释,应该优先做。

适合规则评分的检查包括:

  • JSON 是否能 parse。
  • 是否符合 schema。
  • 必填字段是否存在。
  • 枚举值是否合法。
  • 引用 ID 是否来自本次检索。
  • 是否包含必须字段。
  • 是否没有出现禁止内容。
  • 工具调用参数是否符合权限。
  • 失败时是否返回正确错误状态。

例如:

function scoreRequiredFacts(output: string, mustInclude: string[]) {
  const missing = mustInclude.filter(item => !output.includes(item));

  return {
    passed: missing.length === 0,
    score: missing.length === 0 ? 1 : 0,
    reason: missing.length === 0
      ? '包含所有必备信息'
      : `缺少:${missing.join(', ')}`,
  };
}

这段代码很简单,但它能抓住很多回归。比如 Prompt 修改后,答案不再提“180 天”,规则评分会立刻失败。

规则评分不适合判断所有质量问题,但它是 Eval 系统最可靠的一层。

第五步:为开放式回答加 LLM-as-judge

开放式回答很难只靠规则判断。比如“回答是否清晰”“是否完整解释”“是否基于资料”,通常需要 judge。

LLM-as-judge 的关键不是随便问另一个模型“这个回答好吗”,而是提供明确 rubric。

一个 judge prompt 可以包含:

  • 用户问题。
  • 参考上下文。
  • 候选回答。
  • 评分维度。
  • 每个分数的含义。
  • 输出 JSON schema。

例如评分维度可以设计为:

维度说明
relevance是否回答用户问题
correctness关键事实是否正确
groundedness是否被上下文支持
completeness是否覆盖关键要求
clarity表达是否清晰

Judge 输出也要结构化:

{
  "relevance": 5,
  "correctness": 4,
  "groundedness": 5,
  "completeness": 4,
  "clarity": 5,
  "fatalIssues": [],
  "reason": "回答覆盖了企业版、180 天和 CSV,且引用了正确文档。"
}

需要注意,LLM-as-judge 不是绝对真理。它适合做规模化初筛,关键样例仍然要抽样人工复核。

第六步:生成报告,而不是只看分数

Eval 报告应该帮助团队决策。

最小报告可以包含:

  • 总通过率。
  • 各维度平均分。
  • 和 baseline 的差异。
  • 失败 case 列表。
  • 严重失败列表。
  • 成本和延迟变化。
  • 每条失败的输入、输出、评分理由和 trace。

一个有用的报告不是“本次得分 86”,而是:

  • 格式通过率从 99% 降到 93%。
  • groundedness 失败集中在 policy 标签。
  • 新 Prompt 让回答更完整,但平均输出 token 增加 38%。
  • 3 条权限边界样例从拒答变成了直接回答。

这些信息才能指导修复。

第七步:设置发布门禁

最小可用 Eval 必须进入发布流程。否则它只是一个偶尔运行的脚本。

门禁规则可以先简单一些:

  • 严重安全样例必须 100% 通过。
  • 结构化输出通过率不能低于 baseline。
  • 核心任务正确率不能下降超过 2%。
  • 平均延迟不能上升超过 20%。
  • 单次成本不能超过预算阈值。

不同指标不一定都要卡死。你可以把门禁分成阻断和警告:

类型例子
阻断越权回答、安全拒答失败、JSON 大面积失效
警告成本上升、回答变长、少量开放题分数下降

这样既能保护核心质量,又不会让团队被低价值波动卡住。

第八步:接入线上反馈

最小 Eval 系统上线后,很快会遇到一个问题:评估集里的样例越来越不够。

真实用户会不断发现新问题。所以线上反馈要能进入评估集。

前端反馈入口不要只收一个“有用/没用”。更好的方式是让反馈绑定 run id,并允许用户选择问题类型:

  • 答非所问。
  • 事实错误。
  • 引用错误。
  • 没有使用上传文件。
  • 操作没有完成。
  • 太长或太短。
  • 不安全或不合规。

后端根据 run id 找到当时的 prompt、上下文、检索结果、工具调用和输出。团队复盘后,把代表性失败样例加入 golden dataset。

这就是从线上反馈到 Eval 的闭环。

最小架构建议

早期可以用很朴素的架构:

  • eval/cases/*.json 保存样例。
  • eval/run.ts 运行评估。
  • eval/scorers/*.ts 保存规则评分器。
  • eval/judge.ts 调用评审模型。
  • eval/reports/*.json 保存每次结果。
  • CI 中加入 pnpm eval 或预发布任务。

随着规模变大,再考虑:

  • 数据库存储 case 和 run。
  • Web 报告页面。
  • 人工评审队列。
  • A/B 模型对比。
  • 按租户或功能分层统计。
  • 与日志、反馈、监控系统打通。

不要一开始就把系统做成平台。先让团队每次改 AI 功能时真的愿意跑、看得懂、用得上。

常见坑

第一个坑:评估集太小且太干净。

只放 10 条理想问题,几乎一定会高估质量。评估集要包含真实失败和边界样例,哪怕数量不大。

第二个坑:只保存分数,不保存输出和 trace。

分数下降时,你需要看到当时模型答了什么、检索到了什么、工具有没有失败。没有 trace,就只能猜。

第三个坑:Judge prompt 不版本化。

如果评审标准自己变了,分数就失去可比性。Judge prompt、模型和评分 rubric 都应该记录版本。

第四个坑:把所有问题都交给 LLM-as-judge。

格式、引用、权限、工具参数这类确定性问题应该用规则检查。Judge 更适合开放式质量判断。

第五个坑:没有 baseline。

单次得分没有意义。你需要和当前线上版本、上一次发布版本或候选模型做对比,才能知道改动是进步还是退化。

第六个坑:Eval 和产品日志脱节。

线上用户反馈如果不能追溯到 run id,就很难复现问题,也很难把失败样例沉淀到评估集。

小结

最小可用的 AI 评估系统不需要一开始就很庞大。它的核心是:用一组稳定样例,持续评估真实 AI 链路,并把失败、分数、trace 和发布门禁连接起来。

对前端工程师来说,Eval 会反过来推动产品结构变清晰:消息要有状态,引用要有来源,工具调用要可追踪,用户反馈要绑定 run id,结构化输出要能校验。

当团队能在每次改 Prompt、换模型、调检索之前先跑 Eval,AI 应用才真正进入可维护阶段。