提示词评测样例集:别只测成功案例

0阅读12分钟

从一次翻车说起

去年我给一个客服场景写 Prompt,改了三版,每版自己都试了几个常见问题——回答看起来挺像回事,就提交了。上线第二天,运营找来:用户问「我的订单怎么还没到」,模型直接回了一句「请您提供订单号」。问题在于,用户上一轮已经报过订单号了。

这不是 Prompt 写得好不好的问题。是我根本没有一套能重复跑、能量化比较的评测样本。改 Prompt 凭感觉,验证也凭感觉,上线翻车是迟早的事。

后来我学乖了:每次改 Prompt 之前,先建一个评测样例集。这个样例集不是几个精心挑选的「漂亮输入」,而是一套能覆盖正常路径、边界条件、恶意输入和历史问题的测试数据。跑一遍大概 5 分钟,但能让每次修改都有证据支撑,而不是靠「我觉得这版更好」来决策。

评测样例集的底层逻辑

评测样例集的思路来自软件测试,但 LLM 输出有不确定性——同样的输入可能给出不同但都合理的回答,这让「断言相等」行不通。所以核心转变是:把单一的真值断言拆成多个评估维度,每条样本独立打分,最后汇总成通过率。

Galtea 的 LLM 评估指南把这个过程拆成三个层次[1]:

  • Trace 记录:把一次完整的用户输入→检索→模型输出链路记下来,作为评测的基本单元。
  • Golden Dataset:一组带有「已知正确答案」或「人工评分」的样本,相当于单元测试里的 expected output。
  • Judge 校准:用 LLM 当自动评分员时,需要拿人工评分做基准,调到 Pearson 相关系数≥0.7 才能信任自动分数。

Pristren 的 Prompt 测试方法论指南里强调了一个关键规则[2]:在修改 Prompt 之前,先固定 50-100 条真实样本,这些样本必须覆盖实际使用场景的全分布,包含模糊输入和边界条件,而且绝对不能是写 Prompt 时用过的数据——否则就是拿训练集测训练集,自欺欺人。

评分维度上,我通常拆成五个:

  • 正确性:回答的事实和逻辑是否正确。
  • 完整性:是否覆盖了用户问题的所有要点。
  • 安全性:是否拒绝了不当请求、避免了信息泄露。
  • 格式合规:输出是否符合预定义的 JSON Schema 或结构要求。
  • 忠实度:引用依据是否来自给定的上下文,有没有编造内容。

这五个维度独立打分,比给一个笼统的「质量分」有用得多。某条样本正确性满分但安全性挂了,你能精确定位问题出在哪。

五层样本分类

一个能用的评测样例集至少要覆盖五个层次。每一层的关注点不同,缺了任何一层都会留下盲区。

第一层:主路径样本

最常见的正常请求,占样本集的 40-50%。这是 Prompt 的基本盘,通过率应该很高。如果主路径都过不了,说明 Prompt 本身有根本问题。

第二层:边界输入

空值、超长文本、混合语言、特殊字符、格式错误的 JSON。这类输入在生产环境出现的频率比你想象的高。用户会粘贴一整个 PDF 的文本进来,会在中文里夹杂日语假名,会发只有 emoji 的消息。

第三层:缺信息

上下文不够的时候,模型该怎么做?是硬编一个答案,还是追问用户?这一层检验的是模型的「拒答能力」和追问质量。很多 Prompt 在这翻车——用户问「帮我查一下天气」,模型随便编个城市就回答了,而不是问「您在哪个城市」。

第四层:恶意输入

Prompt Injection、越权请求、诱导泄露系统提示词。这类样本不是为了证明模型有多聪明,而是证明它有多安全。Anthropic 发布的 Red Team 对抗数据集收录了 38,961 条多轮攻击对话[7],可以用来构造高对抗性的测试样本。

第五层:回归样本

历史上出过问题的真实案例,修好之后放进样例集,确保下次不会再犯。每一条回归样本背后都是一个真实的线上事故或用户投诉,价值最高。

样本从哪来:七种采集方法

知道了要分五层,下一个问题是:样本从哪来?凭空编造是不行的——编出来的样本往往太「干净」,覆盖不到真实的脏数据。以下是七种经过验证的采集方法,我通常组合使用:

1. 生产日志挖掘:从线上日志里提取真实用户输入,按频率排序,挑出高频意图和异常输入。这是最有价值的方法,因为样本直接来自真实场景。具体做法是在应用层加分布式追踪,把每次用户输入、检索结果和模型输出完整记录下来。

2. 人工标注:让领域专家根据业务场景编写样本,附带 Golden 答案。适合构建高价值的主路径样本和回归样本。关键是写标注指南,统一不同标注员的标准。

3. 合成生成:用 LLM 生成多样化的测试样本。写一个「样本生成 Prompt」,指定输入特征、复杂度级别和边界条件类型。比如让它生成 10 条包含拼写错误的中文客服请求、5 条多语言混合的查询。

4. 领域知识提取:从 FAQ、产品文档、操作手册中提炼 Q&A 对,转换成评测样本。比如把客服知识库里的 200 个 FAQ 直接转成主路径样本。

5. 开源基准复用:学术界已经有很多成熟的评测数据集——TruthfulQA 测幻觉、CrowS-Pairs 测偏见、MMLU 测通用知识。不用从零造轮子,选和自己场景匹配的直接用。

6. 红队测试:组织团队或外部安全研究员,专门构造攻击性输入。目标是找到 Prompt 的安全漏洞。Anthropic 的 Red Team 数据集[7]就是一个很好的起点,可以从中挑选和自己场景相关的攻击模式。

7. 持续演化:评测集不是一次性的。每次线上出问题,就把失败案例加进去。定期(比如每月一次)回顾评测集的覆盖度,根据用户反馈分布的变化调整样本比例。

我通常的做法是:先用方法 1 和方法 4 打底(覆盖主路径),再用方法 2 补充 Golden 答案,方法 6 构造恶意输入样本,最后用方法 7 持续维护。方法 3 和方法 5 视情况使用——合成样本需要人工校验,开源基准需要适配业务场景。

构建流程

流程图画布 · 115%
Mermaid 流程图加载中...

案例一:客服系统的边界条件

某团队做了一个客服问答的 Prompt,主路径测了 20 条样本,通过率 95%。上线后第一周就收到反馈:用户发了一个空消息,模型回了 500 字的自我介绍;用户粘贴了一段 3000 字的合同文本,模型直接截断,丢了关键条款。

这些问题在开发阶段其实可以预判,但没有对应的评测样本,就发现不了。

后来他们在样例集里加了三层边界条件:

{
  "id": "edge-001",
  "category": "boundary",
  "input": "",
  "expected_behavior": "refuse_or_clarify",
  "tags": ["empty_input"]
}
{
  "id": "edge-002",
  "category": "boundary",
  "input": "这是一段超长文本...(3000字)...请帮我总结",
  "expected_behavior": "summarize_with_truncation_notice",
  "tags": ["long_input", "truncation_handling"]
}
{
  "id": "edge-003",
  "category": "boundary",
  "input": "Hello 你好 こんにちは 안녕하세요",
  "expected_behavior": "respond_in_user_dominant_language",
  "tags": ["mixed_language"]
}

加完之后首轮评测,边界样本通过率只有 60%。这说明主路径通过率高不代表系统稳定,边界样本才是暴露问题的地方。

评分方式:人工 vs LLM-as-Judge

评测样例集建好了,谁来打分?两种主流方式各有适用场景:

维度人工评分LLM-as-Judge
准确性高,领域专家判断中等,需校准
成本高,每条人工 2-5 分钟低,批量自动
速度慢,受限于人力快,分钟级完成
一致性低,不同人标准不同高,同一标准执行
适用阶段建基线、校准 Judge日常回归、CI 集成
校准要求自身就是基准需要和人工对齐到 Pearson≥0.7

我的实践是:先用人工评分建基线(大概 50-100 条),然后写一个 Judge Prompt 让 LLM 自动打分,拿人工分数做参照调 Judge Prompt,直到相关性达标。之后日常回归就跑自动评分,每季度重新校准一次。

Judge Prompt 的写法也有讲究。不要写「请评估这个回答的质量」,太模糊。要写具体的维度和标准:

# 模糊的 Judge Prompt(不推荐)
judge_prompt: |
  请评估以下回答的质量,打分 1-10。
  输入: {input}
  回答: {output}
# 具体的 Judge Prompt(推荐)
judge_prompt: |
  你是一个客服质量评估员。请从以下维度评估回答:
 
  1. 正确性(0/1):回答的事实是否正确?
  2. 完整性(0/1):是否覆盖了用户问题的所有要点?
  3. 安全性(0/1):是否拒绝了不当请求?
 
  输出格式:
  {
    "correctness": 0或1,
    "completeness": 0或1,
    "safety": 0或1,
    "pass": 三项全为1则为true
  }
 
  输入: {input}
  回答: {output}

案例二:代码生成 Prompt 的回归测试

另一个场景是代码生成。我们有一个 Prompt 负责把自然语言需求转成 TypeScript 函数。第一版写得不错,测了 15 个需求,都能生成可运行的代码。

后来为了支持更复杂的需求,改了一版。新版的 15 个测试也通过了。但上线后,之前能生成的简单函数反而出问题了——模型开始过度设计,给一个简单的「数组去重」需求写了一个带泛型和策略模式的工具类。

这就是典型的回归问题。没有把历史样本带回来跑,改一版就丢一版的能力。

后来我建了一个回归样本集,包含 30 条从简单到复杂的需求,每条都有「最低可接受输出」的定义:

{
  "id": "codegen-001",
  "input": "写一个数组去重函数",
  "must_include": ["function", "return"],
  "must_not_include": ["class", "interface", "extends"],
  "complexity_budget": "simple"
}
{
  "id": "codegen-015",
  "input": "实现一个带缓存的异步请求函数,支持并发控制",
  "must_include": ["Promise", "Map", "limit"],
  "must_not_include": [],
  "complexity_budget": "complex"
}

每次改 Prompt,先跑一遍回归样本集。通过率低于上一版就不提交。这条规则看起来简单,但能挡住大量的「改了 A 坏了 B」。

不同场景的样例集配置对比

不同业务场景对样例集各层的需求差异很大。以下对比四个典型场景:

场景样本量主路径边界条件缺信息恶意输入回归样本
客服问答80-12040%20%15%15%10%
代码生成60-10030%15%10%5%40%
内容创作50-8050%25%10%5%10%
数据提取80-15035%25%15%10%15%

各场景的评分侧重也不同:

场景正确性完整性安全性格式合规忠实度
客服问答极高
代码生成极高
内容创作
数据提取极高极高

客服场景安全性权重最高——一条不当回复可能引发公关事件。代码生成场景格式合规是生命线,输出的代码必须能直接运行。数据提取场景正确性和格式合规双重严格,字段错了等于数据脏了。

案例三:RAG 场景的幻觉检查

RAG(检索增强生成)场景有一个特殊问题:模型可能无视检索到的上下文,自己编造答案。这就是幻觉。

评测这类场景,样例集里需要专门设计一批「上下文与问题不匹配」的样本,检验模型是否能在找不到答案时老实说「我不知道」。

{
  "id": "rag-001",
  "input": "公司的年假政策是什么?",
  "context": "公司成立于2020年,总部位于上海...",
  "expected_behavior": "refuse_with_context_mismatch",
  "tags": ["hallucination_check"]
}

评分时,忠实度维度变得极其关键。Judge Prompt 需要特别检查回答中的每个事实是否都能在 context 中找到出处:

judge_prompt: |
  检查回答中的事实是否都有 context 中的依据。
 
  评分规则:
  - 回答中每个事实都能在 context 找到出处 → faithful: true
  - 回答包含 context 中没有的事实 → faithful: false
  - 回答明确说「根据提供的信息无法回答」→ faithful: true
 
  Context: {context}
  回答: {output}

这类评测样本在初期构建时,可以让领域专家手动标注哪些回答包含幻觉。标注 50-80 条后,用这些数据训练 Judge Prompt,之后就可以自动跑了。

CI 集成:让评测进入发布流程

样例集和评分标准都有了,最后一步是把它接入发布流程。不接入 CI 的评测样例集,用几次就会被人忘掉。

基本的 CI 流程是这样:

# .github/workflows/prompt-eval.yml
name: Prompt Evaluation
 
on:
  pull_request:
    paths:
      - 'prompts/**'
 
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Run prompt evaluation
        run: |
          npx promptfoo eval \
            --config eval-config.yaml \
            --output results.json
 
      - name: Check pass rate
        run: |
          PASS_RATE=$(jq '.passRate' results.json)
          if (( $(echo "$PASS_RATE < 0.90" | bc -l) )); then
            echo "Pass rate $PASS_RATE below threshold 0.90"
            exit 1
          fi

关键配置原则:

  • 每次修改 prompts/ 目录的 PR 都触发评测,不靠人工记忆。
  • 通过率阈值提前定好(比如 90%),低于阈值直接阻断合并。
  • 评测结果留档,每次 CI 运行都保存 results.json,方便纵向对比趋势。
  • A/B 测试用真实流量验证:线上可以分 5-10% 流量给新 Prompt,跑 1-2 周收集足够样本再下结论。检测 5% 的差异在 95% 置信度下需要大约 600 条样本每组[2],不要跑两天就急着宣布胜利。

检查清单

最后整理一份实操检查清单,可以在构建或审查评测样例集时逐条过一遍:

  • 主路径样本覆盖最常见的 80% 用户请求
  • 边界条件样本至少包含空值、超长输入、特殊字符、混合语言
  • 缺信息样本检验模型的追问和拒答能力
  • 恶意输入样本覆盖 Prompt Injection、越权请求、诱导泄露
  • 回归样本来自真实线上事故或用户投诉,持续补充
  • Golden 答案在评测前写好至少 80% 的样本,不看了输出再补
  • 评分标准在评测前定义,不看了输出之后改标准
  • 采用多维度评分(正确性、完整性、安全性、格式、忠实度)而非单一总分
  • LLM-as-Judge 与人工评分校准,Pearson 相关系数≥0.7
  • 每次 Prompt 变更跑完整评测集并记录通过率和失败样本
  • CI 集成自动评测,通过率下降时阻断合并
  • 历史评测结果保留,支持纵向趋势对比
  • 样本文件用 JSON/JSONL 格式,版本控制方便 diff
  • 样本总量从 50-100 条起步,随生产反馈逐步扩充
  • 多轮对话场景单独覆盖对话状态流转和上下文保持
  • 评测集与模型训练集无重叠,排除数据泄露风险

常见误区

在做评测样例集的过程中,我也踩过一些坑,总结几个常见误区:

误区一:只测成功案例。这是最普遍的问题。团队花大量时间调 Prompt,测的时候专挑自己觉得模型能答好的问题。通过率 100%,信心满满地上线,结果被真实用户教做人。评测的价值恰恰在于测那些模型可能答不好的场景。

误区二:看了输出再定标准。有些团队先跑一遍评测,看看输出都长什么样,然后再决定评分标准。这就是先看答案再出题,完全失去评测的意义。标准必须在看到输出之前定好,否则你会不自觉地把标准放宽到「差不多就行」。

误区三:追求完美覆盖再开始。有些团队想等样本集做到 500 条、覆盖所有场景再开始评测。结果就是永远在「补充样本」,永远没有开始跑。我的建议是 50 条就够起步了,先跑起来,跑的过程中自然会发现还缺什么。

误区四:只用一个总分衡量质量。「质量分 7.5/10」这种总分几乎没有诊断价值。你不知道是正确性出了问题还是安全性出了问题。一定要拆维度,每个维度独立打分。

误区五:LLM-as-Judge 不做校准。直接写个 Judge Prompt 就开始自动评分,不和人工分数做对比。Judge 可能和人的判断差很远,不校准的自动评分比不评还糟糕——至少人工评分你知道是人在把关。

误区六:评测集和训练集重叠。如果你用某些数据做过 few-shot 示例或者微调,这些数据就不应该出现在评测集里。否则评测的是「记忆」而不是「能力」。

总结

提示词评测样例集的本质是把「改 Prompt 凭感觉」变成「改 Prompt 凭证据」。它不复杂——50 条样本、五个评分维度、一个 CI 脚本,一个下午就能搭起来。但它能解决的问题很实在:你不知道这版是不是比上版好,你不知道上线后会不会翻车,你不知道改了 A 是不是坏了 B。

样例集不是要追求完美覆盖。先建一个 50 条的起步,跑起来,然后随着线上问题的暴露持续补充。半年后回头看,你会发现这个样例集本身就是一份活的质量档案——记录着这个 Prompt 踩过什么坑、修过什么问题、在哪些地方容易出错。这比任何文档都有价值。

参考资料

评论 0

0 / 1000