给大模型输出装上一把量尺:Golden Set 与 CI 评测的工程化实践

简介: 针对大模型概率性输出特点,Golden Set 提供可版本化的业务样本集,结合确定性规则评测契约(如必含/禁含内容、格式等),实现CI质量门禁与变化可追溯,兼顾准确性、安全性与成本可控。(239字)

传统单元测试通常检查确定性的输入输出:给函数一个参数,断言返回值等于预期。但大模型任务具有概率性,答案可能存在多种合理表达,供应商、模型版本、提示词、上下文长度和采样参数的变化,都可能改变结果。

因此,生产系统需要回答的不是“这次调用有没有报错”,而是更具体的三个问题:

  1. 关键任务是否仍然满足业务约束?
  2. 模型升级后,哪些样本变差了?
  3. 质量提升是否以不可接受的延迟、成本或安全风险为代价?

Golden Set 可以理解为一组经过人工确认的代表性样本。它不是“标准答案库”的简单堆积,而是对真实业务分布的可版本化抽样。每条样本至少应包含输入、必须满足的条件、禁止出现的条件,以及必要时的参考答案。

先定义评测契约

评测的第一步不是选择模型,而是把“好结果”写成可检查的契约。例如客服摘要任务可以规定:必须保留用户诉求和处理结论;不能虚构订单状态;输出必须是合法 JSON;摘要长度不能超过指定上限。

建议把样本存为 JSONL,每行一条,便于 Git 追踪、增量评测和失败样本定位:

{
   "id":"refund-001","input":"用户说商品已退回,但七天后仍未收到退款。","must_include":["退款进度"],"must_not_include":["已完成退款"],"format":"json"}
{
   "id":"refund-002","input":"用户询问如何修改收货地址。","must_include":["修改地址"],"must_not_include":["保证可以修改"],"format":"json"}

样本不应只覆盖“正常提问”。至少要加入边界输入、空字段、错别字、长文本、冲突信息和潜在诱导内容。样本数量没有脱离业务的通用标准;在样本很少时,分数波动会很大,评测结果只能作为趋势信号,不能被解释为稳定的总体准确率。

用确定性规则先筛一遍

很多约束不需要另一个模型判断。格式、字段、关键词、长度和敏感操作可以用程序直接检查,结果更容易复现,也不会产生额外模型费用。下面是一个最小评测器:

# evaluate.py
import json
import os
import sys
from pathlib import Path


def check_case(case: dict, output: str) -> list[str]:
    errors = []
    for text in case.get("must_include", []):
        if text not in output:
            errors.append(f"缺少必要内容: {text}")
    for text in case.get("must_not_include", []):
        if text in output:
            errors.append(f"出现禁止内容: {text}")
    if len(output) > int(os.getenv("MAX_OUTPUT_CHARS", "1200")):
        errors.append("输出超过长度限制")
    if case.get("format") == "json":
        try:
            json.loads(output)
        except json.JSONDecodeError:
            errors.append("不是合法 JSON")
    return errors


def main() -> int:
    result_file = Path(sys.argv[1])
    cases = [json.loads(line) for line in Path("golden.jsonl").read_text().splitlines()]
    results = json.loads(result_file.read_text())
    failed = []
    for case in cases:
        errors = check_case(case, results[case["id"]])
        if errors:
            failed.append({
   "id": case["id"], "errors": errors})
    report = {
   "total": len(cases), "failed": len(failed), "failures": failed}
    Path("evaluation-report.json").write_text(json.dumps(report, ensure_ascii=False, indent=2))
    print(json.dumps(report, ensure_ascii=False))
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main())

这个脚本假设模型调用阶段已经把结果写入 results.json,格式是 {"样本 id":"模型输出"}。把调用和评分分开,有两个好处:同一批模型结果可以重复评分;规则变更时不必重新消耗 API 配额。

把模型调用封装成可替换端点

模型调用层应只负责请求、超时、重试和结果归档,不应把供应商细节散落到评测逻辑中。下面以常见的 OpenAI 兼容请求形态举例。实际字段、路径和模型名必须以所选服务的当前文档为准,不能因为“兼容”就假设所有参数都可用。

# run_eval.py
import json
import os
from pathlib import Path
import httpx

BASE_URL = os.environ["MODEL_BASE_URL"].rstrip("/")
API_KEY = os.environ["MODEL_API_KEY"]
MODEL = os.environ["MODEL_NAME"]


def ask(text: str) -> str:
    payload = {
   
        "model": MODEL,
        "temperature": 0,
        "messages": [
            {
   "role": "system", "content": "只输出符合要求的结果。"},
            {
   "role": "user", "content": text},
        ],
    }
    with httpx.Client(timeout=45) as client:
        response = client.post(
            f"{BASE_URL}/chat/completions",
            headers={
   "Authorization": f"Bearer {API_KEY}"},
            json=payload,
        )
        response.raise_for_status()
        data = response.json()
    return data["choices"][0]["message"]["content"]


cases = [json.loads(x) for x in Path("golden.jsonl").read_text().splitlines()]
outputs = {
   }
for case in cases:
    outputs[case["id"]] = ask(case["input"])
Path("results.json").write_text(json.dumps(outputs, ensure_ascii=False, indent=2))

例如,若选择 HaerAPI 作为中转或模型接入层,只有在其当前文档明确说明兼容上述请求路径和字段时,才应将 MODEL_BASE_URL 配置为对应地址;模型标识、计费方式、限流策略和数据处理范围都应以实际文档及合同为准。

本地运行时可以这样注入配置,密钥不会进入代码或仓库:

export MODEL_BASE_URL="https://your-endpoint.example/v1"
export MODEL_API_KEY="replace-with-environment-secret"
export MODEL_NAME="your-model-id"
python run_eval.py
python evaluate.py results.json

在 CI 中设置质量门禁

最简单的门禁是“失败样本数不得超过阈值”。对于关键流程,可以要求零失败;对于开放式生成任务,则可设定分层阈值,并把失败明细作为构建产物保存。

# .github/workflows/llm-eval.yml
name: llm-eval
on: [pull_request]

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install httpx
      - name: Run golden set
        env:
          MODEL_BASE_URL: ${
   {
    secrets.MODEL_BASE_URL }}
          MODEL_API_KEY: ${
   {
    secrets.MODEL_API_KEY }}
          MODEL_NAME: ${
   {
    vars.MODEL_NAME }}
        run: python run_eval.py
      - name: Check contract
        run: python evaluate.py results.json
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: llm-evaluation-report
          path: evaluation-report.json

生产中还应记录每次评测的提交号、模型标识、提示词版本、参数、耗时、token 用量和失败原因。若接口返回这些字段,应原样归档;若不返回,就不要自行推断。为了降低成本,可以在拉取请求阶段运行小型冒烟集,合并或定时任务运行完整集,但两者必须使用清晰的样本标签,避免把部分通过误读为整体通过。

评分器和人工复核如何配合

规则评分适合检查硬约束,不适合判断语义是否自然、答案是否真正解决问题。模型评分器可以辅助处理开放式任务,但它本身也会漂移,必须固定评分提示词、输出结构和判定标准,并定期抽样由人工复核。不要把单一模型的自评结果当作客观真值。

更稳妥的流程是:先用规则筛掉格式和安全问题,再对剩余样本做语义评分;当新版本出现“总分上升但关键类别下降”时,按类别查看混淆和失败案例,而不是只看一个平均数。每个修复过的线上问题都应回流 Golden Set,并标注来源和预期行为,这样评测集才会随着真实风险增长。

常见问题

为什么同样输入每次结果仍可能不同?

temperature=0 只能降低一部分随机性,不能保证不同服务、不同模型版本或并发条件下完全一致。评测应关注契约是否满足,并保留模型和配置元数据。

Golden Set 越大越好吗?

不一定。重复、低价值样本会增加成本,却不一定提高覆盖率。应按业务类别、风险等级和历史失败类型分层,优先补充能区分版本质量的样本。

CI 失败是否意味着模型一定变差?

不一定。也可能是接口字段变化、网络超时、样本更新、解析器缺陷或服务限流。报告必须区分业务断言失败、调用失败和评测程序失败。

可以把用户数据直接放进评测集吗?

应先完成脱敏、授权和访问控制,并确认数据处理要求。无法确认合规边界时,使用合成数据或经过审批的最小化样本,不要把生产日志直接上传到第三方接口。

总结

Golden Set + CI 的核心价值,不是给大模型制造一个看似精确的分数,而是建立可追溯的变化检测机制:用版本化样本描述真实风险,用确定性规则守住硬约束,用受控的语义评测覆盖开放问题,再把失败案例持续回流。模型、端点或提示词发生变化时,团队因此能够看到具体受影响的样本和原因,并在质量、成本、延迟与合规之间作出有证据的决策。

相关文章
|
存储 数据处理 数据库
归档式项目整理工具深度解析:复杂项目如何实现有序收纳与快速检索
项目结束后,信息常散落难寻,经验难以传承。归档式项目整理工具通过系统收集、结构化存储与多维检索,将项目成果转化为可复用的知识资产,提升团队效率与项目复盘能力。
|
3月前
|
人工智能 缓存 架构师
DeerFlow 2.0 的 lead_agent 任务总调度 架构设计与实现解析
DeerFlow 2.0 的 lead_agent 任务总调度 架构设计与实现解析
DeerFlow 2.0 的 lead_agent 任务总调度 架构设计与实现解析
|
2月前
|
人工智能 自然语言处理 数据挖掘
用ChatGPT和Codex搭建个人AI工作流:从一人部门到开源实践
本文探讨AI时代“一人部门”工作法:用ChatGPT拆解任务、构建知识库,用Codex将流程工具化,结合复盘与沉淀,打造可持续的个人AI工作系统(OPC)。非替代团队,而是以工具+流程+知识,提升单人可复用、可迭代的系统性产出能力。
1100 7
|
5月前
|
消息中间件 人工智能 安全
【从零手写 ClaudeCode:learn-claude-code 项目实战笔记】(8)Background Tasks (后台任务)
本节详解s08后台任务机制:通过守护线程异步执行`npm install`等耗时命令,用线程安全通知队列将结果注入LLM调用前的上下文,实现“边跑边想”。含完整代码、踩坑注释与实操示例,助新手理解AI Agent并发设计核心。
1125 0
|
机器学习/深度学习 人工智能 API
大模型推理服务全景图
国内大模型推理需求激增,性能提升的主战场将从训练转移到推理。
3830 142
|
机器学习/深度学习 人工智能 自然语言处理
缓存 监控 前端开发
29 0
存储 JSON 缓存
45 0
编解码 JSON 缓存
20 0
自然语言处理 安全 数据处理
34 0

热门文章

最新文章