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

相关文章
|
机器学习/深度学习 人工智能 API
大模型推理服务全景图
国内大模型推理需求激增,性能提升的主战场将从训练转移到推理。
3827 142
安全 jenkins 持续交付
44 4
缓存 JSON 移动开发
54 1
|
17天前
|
缓存 算法 Java
Heapify反直觉辟谣:建堆为什么不是NlogN
堆排序中“建堆是O(n)”常被误读为n次O(log n)操作。实则因多数节点靠近叶子,下沉步数极少;按高度分组计算总成本,级数收敛于O(n)。本文辟谣+Java实现,助你真正理解Heapify本质。
存储 缓存 安全
45 1
消息中间件 JSON 自然语言处理
73 1
|
13天前
|
SQL API 数据库
把 SQL 性能诊断接入模型 API:从谓词分析到可验证优化
业务SQL性能波动常因WHERE条件隐含陷阱:索引列函数调用、隐式类型转换、OR误用、NULL语义混淆。本文提出“模型辅助+人工验证”闭环——仅传脱敏SQL/执行计划,模型归因并排序验证步骤,工程师终审优化。强调只读诊断、本地规则预筛、边界可控、可回退。
|
15天前
|
缓存 自然语言处理 算法
分词不只靠最长匹配:Trie 与动态规划逐格展开
本文提出基于Trie与逆向动态规划的中文分词算法,以“未知字符最少、词数最少”为双重目标,克服最长匹配的局部贪心缺陷。通过从右向左递推、路径还原与反例验证,给出完整Python实现,兼顾准确性、稳定性和可扩展性。(239字)
|
18天前
|
存储 JSON 自然语言处理
把视频识别 API 做成可审计流水线:抽帧、异步任务与结构化校验
本文详解视频识别在生产环境中的工程实践:如何通过抽帧降载、异步任务调度、JSON结构约束与严格校验,构建高可靠、可观测、可审计的识别流水线,并提供Python最小可行实现。
安全 Java 测试技术
121 0

热门文章

最新文章