
1. 为什么我决定用 Claude 来搭一套 eval 体系第一次认真考虑做 eval是因为一个很尴尬的场景我改了一版提示词主观感觉这次肯定更好了结果上线之后用户反馈反而变差。问题出在哪我根本没有任何量化手段去判断好和更好之间的差距。凭感觉调 prompt本质上就是在盲人摸象。后来我把目光投向了 Claude。原因很直接它本身就是一个足够强的模型可以充当裁判的角色同时它的 API 调用成本可控、输出格式稳定非常适合做自动化打分。更重要的是Claude 在长文本理解和指令遵循上的表现让我可以放心地把评估标准用自然语言写清楚而不需要花大量精力去构造复杂的规则引擎。这套 eval 体系解决的核心问题是把我觉得变好了变成分数从 62 涨到了 78。它适合所有在做 prompt 工程、Agent 开发、RAG 系统调优的人。不管你用的是 Claude 还是别的模型只要你的系统有输入-输出的结构就可以用这套方法把效果量化出来。我把它叫做爬山hillclimb——因为整个过程就是先测出当前基线分数然后针对性地改一个点再测分数涨了就保留跌了就回滚一轮一轮往上爬。听起来很笨但实测下来这是最稳的提分方式。2. Eval 体系的整体设计与核心思路拆解2.1 为什么不用人工评估也不完全依赖自动指标人工评估的问题很明显慢、贵、不可复现。你今天觉得这个回答好明天可能标准就变了。而传统的自动指标比如 BLEU、ROUGE在开放式生成任务上几乎没用——它们衡量的是字面重叠度不是语义质量。我最终选择的方案是LLM-as-Judge用 Claude 作为裁判模型给它一套明确的评分标准rubric让它对每个输出打分并给出理由。这个方案的好处是可复现同样的输入和标准分数基本稳定temperature 设为 0可解释每次打分都有理由能看出模型为什么扣分可迭代评分标准本身也可以版本化管理随时调整但这里有个关键前提裁判模型必须比你被评估的模型更强或者至少同级。如果你用一个弱模型去评判强模型的输出它会系统性地给出错误判断。我用 Claude 做裁判被评估的可以是 Claude 自己、也可以是其他模型但裁判这一环必须保证质量。2.2 整体架构三层结构整套 eval 体系我分成了三层第一层是测试集test set。这是一批固定的输入样本覆盖你系统需要处理的主要场景。测试集的质量直接决定了 eval 的有效性——如果测试集只覆盖了简单场景你爬上去的分数就是虚的。第二层是评分器judge。每个测试样本对应一个或多个评分维度每个维度有明确的评分标准和分值范围。评分器调用 Claude API输入是原始问题 系统输出 评分标准输出是分数和理由。第三层是爬山循环hillclimb loop。每次修改系统的一个变量prompt、参数、工具配置等跑一遍全量测试集对比分数决定保留还是回滚。这三层的关系是测试集定义考什么评分器定义怎么判分爬山循环定义怎么进步。2.3 评分维度的设计原则这是整个体系里最需要花心思的地方。我踩过的坑是一开始只设了一个总体质量维度结果分数波动很大而且扣分了也不知道具体哪里差。后来我改成了多维度评分每个维度 1-5 分最后加权汇总。典型的维度包括维度说明权重建议准确性事实是否正确有无幻觉0.35完整性是否覆盖了问题的所有要点0.25格式合规是否符合要求的输出格式0.20简洁性有无冗余、重复、废话0.10语气风格是否符合目标场景的调性0.10权重的分配取决于你的业务场景。比如做客服机器人语气风格的权重可能要提到 0.2做代码生成准确性和格式合规的权重应该更高。注意维度不要超过 6 个。维度太多会导致评分器注意力分散每个维度的打分质量都会下降。我试过 8 个维度结果模型经常在某个维度上给出自相矛盾的分数。3. 核心细节解析与实操要点3.1 测试集怎么建少而精覆盖边界很多人一上来就想搞几百条测试样本我觉得没必要。20-50 条高质量样本覆盖核心场景和边界情况比 500 条随机样本有用得多。我的做法是从真实用户请求中采样按场景聚类每个场景选 3-5 条代表性样本手动补充 5-10 条边界样本比如超长输入、模糊问题、多意图混合每条样本标注期望输出的关键要点作为评分参考关键要点这一步很重要。它让评分器有一个明确的对照物而不是凭感觉打分。比如一个问答样本期望要点可能是提到 A 概念给出 B 的具体数值不包含 C 的错误说法。3.2 评分器的 prompt 怎么写评分器的 prompt 是整个体系的核心。我经过多轮迭代总结出一个比较稳定的模板结构你是一个严格的评估专家。请根据以下标准对回答进行打分。 【原始问题】 {question} 【期望要点】 {expected_points} 【待评估回答】 {answer} 【评分标准】 - 准确性1-5分事实错误扣2分以上轻微不精确扣1分 - 完整性1-5分每遗漏一个期望要点扣1分 - 格式合规1-5分格式错误直接给1分 - 简洁性1-5分有明显冗余扣1-2分 - 语气风格1-5分不符合目标调性扣1-2分 【输出格式】 请严格按以下 JSON 格式输出不要添加任何其他内容 { accuracy: {score: X, reason: ...}, completeness: {score: X, reason: ...}, format: {score: X, reason: ...}, conciseness: {score: X, reason: ...}, tone: {score: X, reason: ...} }几个实操要点第一要求 JSON 输出并强制格式。这样解析起来不会出错。我一开始用自然语言输出结果解析正则写了一堆还是经常匹配失败。第二给每个分数段写清楚判定标准。不要只说1-5分要说什么情况扣几分。否则模型在不同样本上的打分尺度会不一致。第三temperature 设为 0。评分任务不需要创造性要的是稳定性。我实测 temperature0 时同一输入跑 5 次分数完全一致temperature1 时分数能差出 1-2 分。3.3 调用 Claude API 的工程细节用 Claude API 做批量评分有几个工程上的坑要注意并发控制。50 条样本 × 5 个维度如果串行调用会很慢。我用 Python 的concurrent.futures做并发但并发数控制在 5-10 之间。太高会触发 rate limit反而更慢。重试机制。API 调用偶尔会失败网络抖动、限流必须加重试。我的做法是失败后等 2 秒重试最多重试 3 次3 次都失败就标记为评分失败不纳入统计。结果缓存。同一个样本 同一版系统输出 同一版评分标准评分结果应该缓存起来。这样你调整爬山策略时不需要重复评分没变的部分。import json import time from concurrent.futures import ThreadPoolExecutor, as_completed def score_one_sample(sample, answer, rubric, max_retries3): prompt build_judge_prompt(sample, answer, rubric) for attempt in range(max_retries): try: resp call_claude(prompt, temperature0) return json.loads(resp) except Exception as e: if attempt max_retries - 1: time.sleep(2) else: return {error: str(e)} def run_eval(test_set, system_fn, rubric, max_workers8): results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: futures {} for sample in test_set: answer system_fn(sample[input]) future executor.submit(score_one_sample, sample, answer, rubric) futures[future] sample[id] for future in as_completed(futures): results.append({id: futures[future], **future.result()}) return results这段代码是我实际在用的简化版。核心就是并发跑、失败重试、结果结构化。3.4 分数聚合加权总分 分维度追踪拿到每个样本的评分后需要聚合成一个总分来对比。我的做法是总分 Σ(维度分数 × 维度权重) / 满分 × 100比如准确性 4 分、完整性 3 分、格式 5 分、简洁性 4 分、语气 4 分权重分别是 0.35/0.25/0.20/0.10/0.10那么总分 (4×0.35 3×0.25 5×0.20 4×0.10 4×0.10) / 5 × 100 (1.4 0.75 1.0 0.4 0.4) / 5 × 100 79但光看总分不够。必须同时追踪每个维度的平均分否则你只知道涨了 3 分不知道是哪个维度贡献的。我通常会输出一个表格维度上一版当前版变化准确性3.84.10.3完整性3.23.20格式合规4.54.50简洁性3.93.6-0.3语气风格4.04.20.2加权总分76.279.02.8这样一眼就能看出这次改动提升了准确性和语气但牺牲了简洁性。如果简洁性的下降是可接受的就保留如果不可接受就要调整策略。4. 爬山循环一轮一轮把分数提上去4.1 爬山的基本节奏爬山循环的核心逻辑非常简单跑当前版本的 eval记录基线分数做一个改动只改一个变量再跑 eval对比分数分数涨了 → 保留改动更新基线分数跌了 → 回滚改动回到第 2 步这里最关键的原则是每次只改一个变量。如果你同时改了 prompt 和 temperature分数涨了你不知道是哪个起了作用分数跌了你也不知道该回滚哪个。我见过太多人一次性改一堆东西结果分数波动完全无法归因。4.2 改动的优先级排序不是所有改动都值得试。我通常按以下优先级来第一优先级修 bug 类改动。比如发现某个场景下系统会输出错误格式直接修掉。这类改动几乎必然涨分先做。第二优先级补缺失信息。比如评分器反馈遗漏了期望要点 B那就在 prompt 里补充关于 B 的说明。这类改动通常也能涨分。第三优先级调语气和格式。这类改动影响较小但容易反复放在后面做。第四优先级调参数。比如 temperature、max_tokens、top_p 等。这类改动效果不确定需要多轮实验。4.3 一次真实的爬山记录我拿一个实际项目举例。这是一个技术问答系统初始版本的总分是 62 分。以下是爬山过程第 0 轮基线总分 62.0。准确性 3.1完整性 2.8格式 3.5简洁性 3.8语气 3.6。第 1 轮发现系统经常编造不存在的 API 名称。在 prompt 中加入如果不确定明确说不知道不要编造。总分涨到 67.4准确性从 3.1 涨到 3.9。第 2 轮发现回答经常遗漏问题的第二个子问题。在 prompt 中加入请逐一回答问题的每个部分。总分涨到 71.2完整性从 2.8 涨到 3.6。第 3 轮发现输出格式不统一有时用列表有时用段落。在 prompt 中明确要求用 markdown 列表输出。总分涨到 73.8格式从 3.5 涨到 4.4。第 4 轮尝试加入回答要简洁的指令。总分跌到 72.1简洁性涨到 4.2 但完整性跌到 3.1——因为模型为了简洁省略了要点。回滚。第 5 轮改为在保证完整的前提下尽量简洁。总分涨到 75.6简洁性 4.0完整性保持 3.6。第 6 轮调整 temperature 从 0.7 降到 0.3。总分涨到 77.2准确性和格式都有小幅提升。第 7 轮尝试 temperature 降到 0。总分跌到 76.5语气变得过于机械。回滚到 0.3。最终停在 77.2 分。从 62 到 77一共 7 轮每轮改动都很小但累积效果显著。4.4 什么时候该停爬山不是无限爬的。出现以下信号时我会考虑停连续 3 轮改动都没有涨分说明当前方案已经接近局部最优需要换思路而不是继续微调分数波动小于 1 分说明已经进入噪声区间再调也分不清是真实提升还是随机波动改动开始互相冲突比如为了提升 A 维度必然牺牲 B 维度说明需要重新设计而不是继续调参这时候正确的做法是回到设计层面重新审视测试集是否合理、评分维度是否遗漏了关键因素、系统架构是否有根本性缺陷。5. 常见问题与排查技巧实录5.1 评分器打分不一致怎么办这是最常见的问题。同一份回答跑两次评分器给出不同分数。原因通常有三个temperature 没设为 0。这是最常见的。检查你的 API 调用参数。评分标准太模糊。比如回答质量好这种标准模型每次理解都不一样。解决方法是把标准写具体最好给出正例和反例。样本本身处于边界。比如一个回答刚好在 3 分和 4 分之间模型每次判断会有摇摆。这种情况可以在评分标准中明确如果处于边界取较低分。5.2 分数涨了但实际效果没变好这说明你的 eval 体系有漏洞。可能的原因测试集过拟合。你针对测试集调优但测试集不能代表真实场景。解决方法是定期用新的真实样本替换部分测试集。评分维度遗漏了关键因素。比如你只评了准确性和完整性但用户最在意的是响应速度那分数涨了用户体验未必好。评分器被讨好了。模型可能学会了输出评分器喜欢的格式但内容质量没提升。这种情况要增加对抗性样本——专门测试模型是否在投机取巧。5.3 API 调用成本太高50 条样本 × 5 个维度 250 次 API 调用每轮爬山都要跑一遍。如果做 10 轮就是 2500 次调用。成本确实不低。我的优化策略用更小的模型做初筛先用便宜的小模型跑一遍只对分数处于边界的样本用 Claude 精评缓存评分结果没变的样本不重复评分减少测试集规模20 条精选样本通常就够了批量调用如果 API 支持 batch把多个评分请求合并5.4 常见问题速查表问题可能原因解决方法评分器输出格式错误prompt 未强制 JSON在 prompt 中明确要求 JSON 并给出示例分数波动大temperature 非 0设 temperature0所有样本分数相同评分标准太宽泛细化评分标准给出具体判定条件分数虚高评分器太宽松在 prompt 中加入严格评分指令或换更强的裁判模型爬山停滞进入局部最优换思路重新审视测试集和评分维度API 频繁失败并发太高降低并发数加重试机制5.5 几个我踩过的坑坑一测试集里混入了错误答案。有一次我发现系统输出明显错误但评分器给了高分。查了半天才发现是测试集的期望要点写错了。测试集本身必须经过严格审核。坑二评分维度权重拍脑袋定。一开始我随便设了权重后来发现某个低权重维度其实对用户体验影响很大。权重要基于业务目标来定不是拍脑袋。坑三忘了记录每次改动。爬到后面分数涨了但忘了是哪次改动起的作用。一定要用表格记录每轮的改动内容和分数变化。坑四用同一个模型既当选手又当裁判。这会导致模型系统性地给自己打高分。如果条件允许裁判模型和被评估模型应该不同。6. 把这套方法扩展到更多场景这套 eval 爬山的框架不只适用于 prompt 调优。我现在把它用在了好几个地方RAG 系统调优。测试集是问题 期望答案要点评分维度增加引用准确性和信息覆盖度。每次调整检索策略或 chunk 大小跑一遍 eval 看分数变化。Agent 工具调用。测试集是任务描述 期望的工具调用序列评分维度包括工具选择正确性参数正确性调用顺序合理性。这个场景下评分器需要能理解工具调用的语义。多轮对话。测试集是完整的对话历史评分维度增加上下文一致性和指代消解准确性。这个场景下评分器的 prompt 需要包含完整的对话历史。内容生成。测试集是生成要求 期望风格特征评分维度侧重风格匹配度创意性可读性。这个场景下评分标准最难写因为好内容很主观。每个场景的评分维度和权重都不同但核心方法论是一样的定义标准 → 自动评分 → 小步改动 → 对比分数 → 保留或回滚。最后分享一个我在实际操作中的体会eval 体系本身也需要 eval。也就是说你要定期检查你的评分器是否真的在衡量你关心的东西。我每隔一段时间就会手动抽 10 个样本自己打分然后和评分器的分数对比。如果偏差超过 1 分就说明评分标准需要调整了。这个习惯帮我避免了好几次分数涨了但实际效果变差的情况。