1. 为什么我要用 Claude 来设计 eval
1.1 从“凭感觉调 prompt”到“用数据说话”的转变
做 AI 应用开发的人都有一个共同的痛点:改了一版 prompt,感觉效果好像好了,但到底好了多少、有没有在别的场景上变差,心里完全没底。我以前也是这么干的,改完 prompt 随手试几个 case,觉得没问题就上线,结果经常被用户反馈打脸。后来我开始认真做 eval,才发现这件事的价值远超想象。
所谓 eval,就是评估集,说白了就是一套带标准答案或者评分标准的测试用例集合。你每次改完 prompt、换了模型、调了参数,都拿这套题去跑一遍,看分数是涨了还是跌了。这就像考试一样,没有考试你永远不知道学生学得怎么样。
那为什么我选择用 Claude 来设计 eval 而不是自己硬写?原因很直接:设计好的 eval 题目本身就是一件极其消耗脑力的事情。你需要考虑覆盖哪些场景、边界情况怎么设计、评分标准怎么定、参考答案怎么写才合理。这些工作如果全靠人工,一个中等规模的 eval 集可能要花好几天。而 Claude 在这方面的能力非常突出,它能快速生成大量高质量的测试用例,并且能理解你想要的评分维度。
1.2 什么是 hillclimb,为什么它比一次性优化有效
hillclimb 这个词直译是“爬山”,在优化领域指的是逐步迭代、每次只做一个小改动、看分数是否提升、保留提升的改动、继续下一轮的策略。它和一次性大改的区别在于:一次性大改你根本不知道是哪个改动起了作用,而 hillclimb 每一步都可追溯。
我实际操作的流程是这样的:先有一个 baseline 分数,然后每次只改一个变量(比如调整 system prompt 的某一段、增加一个 few-shot 示例、修改输出格式要求),跑 eval 看分数变化。如果涨了就保留,跌了就回滚。这样一轮一轮下来,分数会稳步上升,而且你清楚地知道每个改动带来了多少收益。
这个方法论听起来简单,但实际操作中有很多细节决定成败。比如 eval 集的质量、评分的一致性、改动的粒度控制等等。下面我会逐一拆解。
1.3 这套方法适合谁
如果你正在做以下任何事情,这套方法都直接适用:
- 开发基于大模型的对话应用,需要持续优化回复质量
- 做 RAG 系统,需要评估检索和生成的整体效果
- 构建 Agent 工作流,需要验证每一步的准确性
- 任何需要“让模型输出更符合预期”的场景
不需要你是 ML 专家,但需要你会写基本的 API 调用代码,理解 prompt engineering 的基本概念。我用的是 Claude API 来做 eval 的设计和评分,整个流程用 Python 脚本串起来,门槛并不高。
2. 整体方案设计与核心思路拆解
2.1 三层架构:生成层、评分层、迭代层
我的整个 eval 系统分为三层,每层职责明确:
生成层负责用 Claude 批量生成测试用例。我会给 Claude 一个任务描述和几个示例,让它生成几十到上百条测试数据。每条数据包含输入(用户问题)和期望输出(参考答案或评分标准)。
评分层负责对模型的输出进行打分。这里有两种方式:一种是精确匹配(适合有确定答案的场景),另一种是用 Claude 作为 judge 来打分(适合开放式生成场景)。我大部分时候用的是后者,因为实际应用中很少有完全确定的答案。
迭代层就是 hillclimb 的核心循环。每次修改 prompt 后,自动跑一遍 eval,记录分数,和上一轮对比,决定是否保留改动。
这三层用 Python 脚本串起来,整个流程可以半自动化运行。为什么说半自动化?因为改 prompt 这件事目前还需要人来判断方向,Claude 可以帮你生成候选改动,但最终决策还是人来做的。
2.2 为什么选择 Claude 作为 eval 生成器和 Judge
市面上能做这件事的模型不少,我选 Claude 有几个实际考量:
第一,Claude 在指令遵循方面表现稳定。当你给它一个复杂的评分标准时,它很少跑偏。这一点在 judge 角色上特别重要,因为评分标准不一致的话,整个 eval 的数据就废了。
第二,Claude 生成的测试用例多样性好。我试过让不同的模型生成 eval 题目,Claude 生成的边界 case 明显更丰富,不会反复出同一种类型的题。
第三,API 调用方便。Claude 的 API 设计简洁,批量调用和结果解析都很顺畅。我用的是 claude-sonnet 系列模型来做 eval 生成和评分,性价比和效果平衡得比较好。
当然也有需要注意的地方:Claude 作为 judge 会有一定的位置偏差(比如倾向于给先出现的选项更高分),这个后面会讲怎么处理。
2.3 eval 集的设计原则:覆盖度、区分度、稳定性
设计 eval 集有三个核心原则,我踩过坑之后体会特别深:
覆盖度指的是你的测试用例要覆盖所有重要场景。比如做一个客服机器人,你要覆盖:常见问题、模糊问题、多轮追问、情绪化表达、超纲问题等。如果 eval 集里只有常见问题,那你的优化方向就会偏。
区分度指的是好的输出和差的输出在分数上要有明显差异。如果所有模型的输出都能拿 80 分以上,那这个 eval 集就没有区分能力。我通常会在 eval 集里故意放一些“陷阱题”,看模型能不能识别出来。
稳定性指的是同一个输出多次评分,分数波动要小。如果同一个回答第一次评 85 分第二次评 70 分,那这个评分标准就有问题。解决办法是把评分标准写得足够具体,减少主观判断空间。
注意:eval 集不是一次做完就固定的。随着你对业务理解的深入,需要不断补充新的测试用例。我一般每两周会 review 一次 eval 集,把区分度低的题目替换掉。
3. 核心细节解析与实操要点
3.1 用 Claude 生成 eval 集的 prompt 怎么写
这是整个流程的起点,prompt 写得好不好直接决定了 eval 集的质量。我经过多次迭代,总结出一个比较稳定的模板:
EVAL_GEN_PROMPT = """你是一个测试用例设计专家。我需要你为以下任务生成测试用例。 任务描述:{task_description} 要求: 1. 生成 {n} 条测试用例 2. 覆盖以下场景类型:{scenario_types} 3. 每条用例包含:input(用户输入)、expected_output(期望输出)、difficulty(难度等级:easy/medium/hard)、scenario_type(场景类型) 4. 难度分布:easy 30%,medium 50%,hard 20% 5. 输出格式为 JSON 数组 以下是两个示例: {examples} 请直接输出 JSON,不要有其他内容。"""这个模板有几个关键点:明确要求覆盖的场景类型,避免生成的内容过于集中;指定难度分布,确保 eval 集有梯度;给出示例,让 Claude 理解你想要的格式和粒度。
我实际用下来,一次生成 30-50 条比较合适。太多了质量会下降,太少了不够用。如果需要的量大,可以分多次生成,每次换不同的场景类型组合。
3.2 评分标准的制定:从模糊到精确
评分标准是 eval 系统的灵魂。我最初写的评分标准是这样的:“请评估回答的质量,1-10 分”。结果发现同一个回答,Claude 每次给的分数能差 3 分。后来我改成了分维度评分:
SCORING_CRITERIA = """请从以下维度对回答进行评分: 1. 准确性(0-3分): - 3分:所有信息完全正确 - 2分:核心信息正确,有轻微不准确 - 1分:有明显错误但不影响主要结论 - 0分:核心信息错误 2. 完整性(0-3分): - 3分:覆盖了所有关键点 - 2分:覆盖了大部分关键点 - 1分:只覆盖了少部分关键点 - 0分:完全遗漏关键信息 3. 表达清晰度(0-2分): - 2分:逻辑清晰,易于理解 - 1分:基本可读,但有些混乱 - 0分:难以理解 4. 格式规范性(0-2分): - 2分:完全符合要求的格式 - 1分:基本符合,有小偏差 - 0分:不符合格式要求 总分:10分"""改成这样之后,评分的一致性大幅提升。同一个回答多次评分的标准差从 1.5 降到了 0.3 左右。关键就是把每个维度的评分标准写得足够具体,让不同的人(或同一个人在不同时间)看了都能给出一致的分数。
3.3 批量调用与结果收集的工程细节
实际跑 eval 的时候,你会有几十上百条测试用例要跑,每条都要调用 API。这里有几个工程上的坑:
并发控制:不要一次性发太多请求,会被限流。我一般用 5-10 个并发,配合重试机制。用 Python 的concurrent.futures或者asyncio都可以。
结果持久化:每次跑完 eval 都要把结果存下来,包括模型输出、评分、时间戳、prompt 版本号。我用的是 JSON 文件按日期存储,方便后续对比。
错误处理:API 调用失败是常态,要有重试逻辑。我一般重试 3 次,每次间隔递增。如果 3 次都失败,记录下来跳过,不要让整个 eval 卡住。
import json import time from datetime import datetime def run_eval(test_cases, model_fn, judge_fn, prompt_version): results = [] for case in test_cases: try: output = model_fn(case["input"]) score = judge_fn(case["input"], output, case["expected_output"]) results.append({ "input": case["input"], "output": output, "score": score, "scenario_type": case["scenario_type"], "difficulty": case["difficulty"], "prompt_version": prompt_version, "timestamp": datetime.now().isoformat() }) except Exception as e: results.append({ "input": case["input"], "error": str(e), "prompt_version": prompt_version }) # 保存结果 filename = f"eval_results_{prompt_version}_{datetime.now().strftime('%Y%m%d_%H%M')}.json" with open(filename, "w") as f: json.dump(results, f, ensure_ascii=False, indent=2) return results3.4 如何分析 eval 结果找到优化方向
跑完 eval 不是看个总分就完了。我通常会做几个维度的分析:
按场景类型分析:哪个场景类型的得分最低?那就是下一步优化的重点。比如发现“多轮追问”场景得分只有 5.2 分,而其他场景都在 7 分以上,那说明你的 prompt 在处理多轮对话时有问题。
按难度分析:hard 题目的得分率是多少?如果 hard 题目得分率低于 30%,说明模型在处理复杂情况时能力不足,可能需要增加 few-shot 示例或者调整推理步骤。
错误模式分析:把低分 case 的输出拿出来看,找共同点。是格式不对?还是信息遗漏?还是理解错了问题?这个分析最好人工做,因为 Claude 分析自己的错误有时候会“护短”。
我一般会做一个简单的表格来跟踪:
| 场景类型 | 平均分 | 样本数 | 主要问题 |
|---|---|---|---|
| 常见问题 | 8.2 | 15 | 无明显问题 |
| 模糊问题 | 6.5 | 12 | 倾向于猜测而非追问 |
| 多轮追问 | 5.2 | 10 | 丢失上下文信息 |
| 情绪化表达 | 7.1 | 8 | 共情不够 |
| 超纲问题 | 4.8 | 5 | 不会拒绝,强行回答 |
有了这个表,优化方向就非常清晰了。
4. 实操过程与核心环节实现
4.1 从零搭建 eval 系统的完整步骤
我以做一个“技术文档问答助手”为例,完整走一遍流程。
第一步:定义任务和场景
任务描述:用户提问技术文档相关的问题,助手需要基于文档内容给出准确回答。
场景类型:直接查找、概念解释、代码示例、对比分析、故障排查。
第二步:用 Claude 生成 eval 集
我调用了 Claude API,用前面提到的模板生成了 50 条测试用例。生成之后我人工过了一遍,删掉了 5 条质量不行的,补充了 3 条我觉得重要的边界情况。最终 eval 集有 48 条。
第三步:建立 baseline
用最初的 prompt 跑一遍 eval,得到 baseline 分数。我的第一版 prompt 很简单,就是“你是一个技术文档助手,请回答用户问题”。baseline 平均分 6.3 分。
第四步:hillclimb 迭代
第一轮改动:在 system prompt 里加入“如果不确定答案,请明确说不知道,不要编造”。分数从 6.3 涨到 6.8。主要提升在“超纲问题”场景,从 4.8 涨到 7.2。
第二轮改动:加入输出格式要求“请先给出直接答案,再给出详细解释”。分数从 6.8 涨到 7.1。主要提升在“表达清晰度”维度。
第三轮改动:针对“多轮追问”场景,加入“请参考对话历史中的信息”的指令。分数从 7.1 涨到 7.4。
第四轮改动:增加两个 few-shot 示例,一个展示如何处理模糊问题(先追问澄清),一个展示如何做对比分析。分数从 7.4 涨到 7.9。
第五轮改动:调整了评分标准中“完整性”的描述,让 judge 更关注关键信息是否覆盖。分数从 7.9 涨到 8.1。
每一轮我都只改一个变量,记录分数变化。五轮下来,分数从 6.3 提升到 8.1,提升了 28%。
4.2 每轮迭代的决策逻辑
hillclimb 的核心是每次只改一个东西。我见过很多人一次改好几个地方,分数涨了也不知道是哪个改动起了作用,分数跌了也不知道该回滚哪个。
我的做法是维护一个改动日志:
{ "version": "v1.0", "baseline_score": 6.3, "changes": [], "score": 6.3 } { "version": "v1.1", "changes": ["在system prompt中加入'不确定时说不知道'"], "score": 6.8, "delta": "+0.5", "decision": "keep" } { "version": "v1.2", "changes": ["加入输出格式要求:先直接答案再详细解释"], "score": 7.1, "delta": "+0.3", "decision": "keep" }如果某一轮改动后分数下降了,我会回滚到上一个版本,然后换一个方向尝试。有时候一个改动在总体分数上是持平的,但在某个特定场景上有提升,这种我也会保留,因为后续其他改动可能会放大这个优势。
4.3 参数选择与计算过程
在跑 eval 的时候,有几个参数需要仔细选择:
Temperature:做 eval 评分时我用 temperature=0,保证评分的一致性。做模型输出生成时,我用 temperature=0.7,模拟真实使用场景。如果你用 temperature=0 来生成输出,那 eval 分数会偏高,因为模型每次都选最确定的答案,但实际用户使用时不会这样。
Max tokens:根据任务类型设置。问答类任务一般 1024 够了,长文生成类需要 4096 或更多。设置太小会导致输出被截断,评分自然低。
评分样本数:对于关键决策,我会对同一个输出评 3 次分,取平均值。这样可以把评分随机误差从 ±0.5 降到 ±0.2。虽然 API 成本翻了三倍,但对于重要版本的评估是值得的。
eval 集大小:我建议至少 30 条,最好 50-100 条。太少了统计意义不够,太多了跑一次成本太高。我一般维持在 50 条左右,每两周更新 10% 的题目。
4.4 实操现场记录:一次完整的 hillclimb 循环
让我记录一次真实的迭代过程,展示每一步的具体操作和思考。
背景:技术文档问答助手,当前版本 v2.3,eval 平均分 7.6。目标是提升到 8.0 以上。
分析当前 eval 结果:
| 场景类型 | 平均分 | 主要失分点 |
|---|---|---|
| 直接查找 | 8.5 | 无 |
| 概念解释 | 7.8 | 解释过于冗长 |
| 代码示例 | 7.2 | 代码缺少注释 |
| 对比分析 | 7.0 | 对比维度不清晰 |
| 故障排查 | 7.5 | 排查步骤跳跃 |
确定优化方向:代码示例和对比分析得分最低,优先优化这两个场景。
设计改动:在 system prompt 中加入“提供代码示例时,请添加关键行的注释”和“做对比分析时,请使用表格形式,列出至少 3 个对比维度”。
跑 eval:48 条测试用例,约 15 分钟跑完(含评分)。
结果:平均分从 7.6 涨到 7.9。代码示例场景从 7.2 涨到 8.1,对比分析从 7.0 涨到 7.8。但概念解释场景从 7.8 跌到 7.5,因为表格形式的指令让模型在概念解释时也倾向于用表格,反而降低了可读性。
决策:保留改动,但把指令改得更精确:“做对比分析时使用表格形式”改为“仅在做对比分析时使用表格形式,其他场景用段落形式”。重新跑 eval,概念解释恢复到 7.7,总分 8.0。
这个例子说明了一个重要点:改动可能会有副作用,需要仔细分析每个场景的变化,不能只看总分。
5. 常见问题与排查技巧实录
5.1 评分不一致怎么办
这是最常见的问题。同一个输出,Claude 第一次评 8 分,第二次评 6 分。排查思路:
首先检查评分标准是否足够具体。如果标准里有“回答得好”“质量高”这种模糊表述,那评分不一致是必然的。把每个分数段的行为描述清楚。
其次检查是否有位置偏差。如果你让 Claude 同时比较两个输出,它可能倾向于给第一个更高的分。解决办法是随机打乱顺序,或者分别评分而不是比较评分。
最后检查 temperature 设置。评分时一定要用 temperature=0。
如果以上都做了还是不一致,那就接受一定的噪声,用多次评分取平均来抵消。
5.2 eval 分数涨了但实际效果没变好
这种情况通常说明 eval 集不能代表真实场景。可能的原因:
eval 集过于简单,模型很容易拿高分,但真实场景更复杂。解决办法是增加 hard 难度的题目比例。
eval 集的场景分布和真实使用不符。比如你的 eval 集里 80% 是直接查找,但用户实际问的最多的是故障排查。解决办法是分析真实日志,按实际分布调整 eval 集。
评分标准和用户满意度不相关。比如你评的是“信息准确性”,但用户更在意“响应速度”或“语气友好度”。解决办法是重新审视评分维度,确保和业务目标对齐。
5.3 API 调用成本控制
跑 eval 确实费钱,尤其是用 Claude 做 judge 的时候。几个省钱技巧:
缓存机制:如果同一个输入和输出已经评过分,直接读缓存,不要重复调用 API。
分级评分:先用便宜的模型(如 Haiku)做初筛,只对分数在边界附近的 case 用贵模型(如 Sonnet)精评。
批量调用:有些 API 支持批量请求,价格更低。可以攒一批一起发。
控制 eval 频率:不是每次改 prompt 都要跑全量 eval。小改动可以只跑相关的子集,大改动才跑全量。
我自己的经验是,一个 50 条规模的 eval 集,用 Sonnet 做 judge,跑一次的成本大约在 1-2 美元。如果每天跑一次,一个月也就 30-60 美元,完全可以接受。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 评分波动大 | 标准模糊/位置偏差 | 同一输出评3次看方差 | 细化标准,随机顺序 |
| 分数虚高 | eval集太简单 | 看hard题目得分率 | 增加难题比例 |
| 分数不涨 | 改动方向不对 | 分析各场景得分 | 换优化方向 |
| API超时 | 并发太高 | 看错误日志 | 降低并发,加重试 |
| 输出截断 | max_tokens太小 | 检查输出长度 | 增大max_tokens |
| 评分偏差 | judge模型偏见 | 人工抽检 | 换judge模型或人工校准 |
5.5 独家避坑技巧
技巧一:保留一个“黄金测试集”。我从 eval 集里挑出 10 条最有代表性的题目,每次改动都先跑这 10 条。如果这 10 条上分数没变化,那大概率改动没效果,不用跑全量了。这能节省大量时间。
技巧二:用 Claude 分析 Claude 的错误。把低分 case 的输入、输出、评分理由一起发给 Claude,让它分析“这个回答为什么得分低”。虽然不能全信,但经常能发现我忽略的问题。
技巧三:定期人工校准。每跑 10 轮 eval,我会人工看 5 条 case 的评分,确认 Claude 的评分和我的判断一致。如果偏差大,就调整评分标准。
技巧四:记录每次改动的“假设”。改 prompt 之前,先写下你预期这个改动会提升哪个场景的分数。跑完 eval 后对比预期和实际。如果经常不符,说明你对模型行为的理解有偏差,需要调整。
技巧五:不要过度优化。当分数达到 8.5 以上时,继续提升的边际收益很低,而且容易过拟合到 eval 集上。这时候应该把精力放在扩充 eval 集、覆盖新场景上,而不是继续抠那零点几分。
6. 工具链与自动化脚本
6.1 我用的工具栈
整个流程我用到的工具不多,都是轻量级的:
- Python 3.10+:主语言
- anthropic SDK:调用 Claude API
- pandas:分析 eval 结果
- json:存储 eval 集和结果
- concurrent.futures:并发调用
没有用复杂的框架,因为这套流程本身不复杂,用太多框架反而增加维护成本。
6.2 核心脚本结构
我的项目结构是这样的:
eval_system/ ├── data/ │ ├── eval_set.json # eval 测试集 │ └── results/ # 历次 eval 结果 ├── prompts/ │ ├── system_prompt.txt # 当前 system prompt │ └── versions/ # 历史版本 ├── scripts/ │ ├── generate_eval.py # 生成 eval 集 │ ├── run_eval.py # 跑 eval │ ├── analyze_results.py # 分析结果 │ └── hillclimb.py # 迭代主循环 └── config.yaml # 配置文件hillclimb.py是主入口,它做的事情是:读取当前 prompt 版本,跑 eval,和上一版对比,输出报告。我手动改 prompt 后运行这个脚本,看结果决定是否保留。
6.3 自动化程度的选择
我选择的是半自动化,而不是全自动化。原因很简单:改 prompt 需要理解业务、判断方向,这些目前 AI 还做不好。全自动化让 Claude 自己改 prompt 自己评,很容易陷入局部最优,而且改出来的 prompt 可能过拟合 eval 集。
半自动化的流程是:我分析 eval 结果,决定改什么,手动改 prompt,然后脚本自动跑 eval 和对比。这样既保证了方向正确,又节省了跑 eval 和记录结果的时间。
如果你想让 Claude 帮你生成候选改动,可以这样做:把当前 prompt 和 eval 结果发给 Claude,让它提出 3 个可能的改进方向。你从中选一个实施。这样能获得一些灵感,但最终决策还是在你手里。
7. 从 6.3 到 8.5 的完整复盘
7.1 各阶段的关键突破
回顾我从 baseline 6.3 分提升到 8.5 分的过程,有几个关键突破点:
突破一:明确“不知道”的边界。这个改动单独贡献了 +0.5 分。核心是让模型学会说“我不确定”,而不是强行编造。这在技术文档场景特别重要,因为错误的技术信息比没有信息更糟糕。
突破二:输出结构化。要求模型先给直接答案再给详细解释,这个改动贡献了 +0.3 分。用户反馈也证实了这一点,他们更喜欢快速看到答案,需要细节再往下看。
突破三:few-shot 示例。加入两个精心设计的示例,贡献了 +0.5 分。示例的力量在于它同时传达了格式、语气和处理复杂情况的方式,比纯文字指令有效得多。
突破四:场景化指令。针对不同场景类型给出不同的处理指令,贡献了 +0.4 分。比如对比分析用表格、故障排查用步骤列表、概念解释用类比。
突破五:评分标准迭代。调整评分标准让 judge 更关注关键信息覆盖,贡献了 +0.2 分。这个改动不改变模型输出,但让评分更准确,从而让后续的优化方向更清晰。
7.2 哪些改动没有效果
不是所有改动都有效。我记录了几个失败的尝试:
尝试一:增加“请仔细思考”的指令。分数没变化。模型并不会因为你说“仔细思考”就真的思考得更仔细。
尝试二:要求输出 Markdown 格式。分数反而下降了 0.2。因为有些简单问题用 Markdown 反而显得冗余。
尝试三:增加大量 few-shot 示例(从 2 个加到 6 个)。分数没提升,但 token 消耗增加了 40%。示例不是越多越好,2-3 个精准的示例就够了。
尝试四:调整 temperature 从 0.7 到 0.5。分数没变化,但输出多样性下降了。对于需要创意的场景,低 temperature 反而不好。
这些失败尝试的价值在于:它们帮我划定了边界,让我知道哪些方向不用再试了。
7.3 最终 prompt 的结构
经过多轮迭代,我的最终 prompt 结构是这样的:
[角色定义] 你是一个技术文档问答助手... [核心原则] 1. 准确性优先,不确定时说不知道 2. 先给直接答案,再给详细解释 3. 根据问题类型选择合适的输出格式 [场景化指令] - 直接查找:简洁回答,附上文档出处 - 概念解释:用类比帮助理解,控制在200字以内 - 代码示例:添加关键行注释 - 对比分析:使用表格,至少3个维度 - 故障排查:按步骤列出,每步说明预期结果 [输出格式] ... [示例] 示例1:... 示例2:...这个结构看起来简单,但每一部分都是经过 eval 验证的。没有多余的指令,每一条都有明确的 purpose。
8. 一些个人体会和后续扩展方向
8.1 我踩过的最大的坑
最大的坑是过早优化。一开始我就想做一个完美的 eval 系统,花了很多时间设计复杂的评分维度、写各种分析脚本。结果真正跑起来发现,最简单的方案反而最有效。现在我用的就是最朴素的 JSON 存储 + Python 脚本,没有任何花哨的东西。
第二个坑是eval 集和真实场景脱节。我早期自己拍脑袋想了很多测试用例,跑出来的分数很好看,但上线后用户反馈完全不是那么回事。后来我强制要求自己:eval 集里至少 50% 的题目要来自真实用户日志。这个改变让 eval 的指导意义大幅提升。
第三个坑是过度依赖 Claude 的评分。有一段时间我完全信任 Claude 的评分,结果发现它在某些场景下有系统性偏差。比如对于包含代码的回答,它倾向于给更高分,即使代码有错误。后来我加入了人工抽检环节,每轮 eval 抽 10% 人工复核。
8.2 后续可以怎么扩展
这套方法目前我用在文本问答场景,但它的框架是通用的。后续我计划往几个方向扩展:
多模态 eval:加入图片、表格的评估。这需要调整评分标准,因为多模态输出的评估维度更多。
在线 eval:不只跑离线测试集,还接入线上真实流量做 A/B 测试。离线 eval 和在线指标结合,才能全面评估效果。
自动化 prompt 搜索:目前是人工改 prompt,后续可以尝试用 Claude 生成候选 prompt,自动跑 eval 筛选。但前提是 eval 集足够大、评分足够稳定,否则容易过拟合。
跨模型对比:同一套 eval 集跑不同模型的输出,找出每个模型擅长的场景,做模型路由。这个在实际应用中很有价值。
8.3 给刚开始做 eval 的人的建议
如果你刚开始做 eval,我的建议是:先跑起来,再优化。不要一开始就追求完美的 eval 集和评分标准。先用 20 条题目、一个简单的评分标准跑起来,感受一下整个流程。然后根据实际遇到的问题逐步改进。
另外,记录比分析更重要。每次 eval 的结果、每次改动的记录、每次的决策理由,都要保存下来。这些数据积累到一定程度,你会发现自己对模型行为的理解越来越深,改 prompt 的直觉越来越准。
最后,不要追求满分。eval 分数只是参考,最终还是要看实际效果。我见过有人把 eval 分数刷到 9.5,但实际用户体验很差,因为 eval 集被过拟合了。保持 eval 集的独立性和代表性,比追求高分更重要。
这套方法我用了大半年,从最初的 6.3 分提升到现在的 8.5 分,中间经历了大概 30 多轮迭代。每一轮都不复杂,但积累起来效果显著。如果你也在做类似的事情,希望这些经验对你有帮助。