用Claude搭建大模型eval评估系统:从零实现hillclimb迭代优化

📅 发布时间:2026/10/7 12:33:18
用Claude搭建大模型eval评估系统:从零实现hillclimb迭代优化
1. 为什么我要用 Claude 来搭一套 eval 系统第一次听到“用 Claude 设计 eval再一轮轮把分数提上去”这个思路时我正被一个老问题折磨手头有个基于大模型的问答功能上线前拍脑袋觉得效果还行上线后用户反馈时好时坏可到底哪里差、差多少、改完有没有变好全靠感觉。这种“盲人摸象”式的迭代做过 AI 应用的人应该都懂非常痛苦。后来我意识到问题的根子不在模型本身而在于我压根没有一套能稳定量化效果的评估体系。eval 这个词听起来很学术说白了就是“给模型的输出打分”。但真正难的不是打分这个动作而是打什么分、怎么打、打完怎么用这个分数指导下一轮优化。这套东西如果全靠人工成本高到离谱如果全靠规则脚本又覆盖不了自然语言的千变万化。Claude 在这里扮演的角色既是“出题人”也是“阅卷人”。它可以帮我批量生成贴近真实场景的测试用例可以按照我定义的标准去评判模型输出还能在我把分数提上去的过程中持续给出“哪里扣分了、为什么扣分”的细粒度反馈。整个流程串起来就是一个典型的hillclimb爬山过程先有一个基线分数然后每次改动一个变量看分数是涨是跌涨了就保留跌了就回退一步步往山顶爬。这套方法适合谁如果你正在做 AI 应用、Agent、RAG 系统或者任何需要“让模型输出更符合预期”的项目并且已经过了“能跑就行”的阶段开始追求稳定和可控那这套 eval hillclimb 的思路会非常对胃口。它不需要你是算法专家但需要你有耐心把评估标准想清楚剩下的脏活累活可以交给 Claude 和脚本。我下面会把整个搭建过程拆开讲包括 eval 怎么设计、Claude API 怎么调、分数怎么爬、踩过哪些坑。内容偏实操代码和配置都会给到你可以直接抄作业再按自己的场景改。2. eval 体系整体设计与核心思路拆解2.1 先想清楚“好”的定义再谈自动化很多人一上来就写代码调 API结果跑出来的分数自己都不信。我踩过的第一个坑就是这个没有明确定义什么叫“好输出”就让 Claude 去打分它给的分忽高忽低完全没有参考价值。正确的顺序是反过来的。你得先坐下来拿几张纸把“理想输出”和“糟糕输出”的边界画出来。比如我做的那个问答功能我定义了四个维度准确性答案里的关键事实有没有错有没有编造不存在的信息。完整性用户问的三个点是不是都答到了有没有漏。相关性有没有答非所问有没有大段无关的废话。格式合规该用列表的有没有用列表该给代码块的有没有给代码块。这四个维度不是拍脑袋来的是我把过去一个月用户差评的案例翻出来一条条归类总结出来的。这一步非常关键因为 eval 的本质是“把你的主观标准翻译成可重复执行的规则”标准本身模糊后面全白搭。提示维度不要超过 5 个。维度越多Claude 打分时越容易顾此失彼而且你后续分析分数变化时也会被噪音干扰。宁可先粗后细跑通流程再增加维度。2.2 为什么选 Claude 做评判而不是自己写规则规则脚本比如关键词匹配、正则的问题是它只能判断“有没有”判断不了“好不好”。用户问“这个功能怎么用”模型回答“这个功能很好用”关键词全中但明显是废话。这种语义层面的判断规则脚本无能为力。Claude 的优势在于它能理解上下文和语义。我给它一个评分标准rubric再给它用户问题和模型输出它就能像一个人一样去判断。而且 Claude 的输出格式很稳定我要求它返回 JSON它基本不会跑偏这对后续程序化处理分数非常重要。当然用 Claude 做评判也有代价每次打分都要调 API有成本和延迟。所以我的策略是分层能用规则快速过滤的比如输出为空、格式明显错误先用规则拦掉剩下的语义判断才交给 Claude。这样既省成本又保证了判断质量。2.3 hillclimb 的核心一次只动一个变量爬山法的精髓在于“控制变量”。我见过太多人一次性改三四个地方分数涨了也不知道是哪个改动起了作用分数跌了更不知道回退哪个。我的做法是维护一个改动清单每次只从清单里挑一个改动应用上去跑完 eval 看分数。分数涨了这个改动就固化到基线里分数跌了直接回退记录下“这个改动无效”。这样一轮轮下来基线分数会稳步上升而且每一步为什么涨都清清楚楚。这里有个心理准备爬山不是一帆风顺的。有时候你连续试了五六个改动分数纹丝不动甚至倒退。这很正常说明你还没找到真正的瓶颈。这时候不要硬爬回头看看 eval 的细粒度反馈看看扣分集中在哪个维度往往能发现之前忽略的问题。3. 核心细节解析与实操要点3.1 测试用例集怎么建质量和数量同样重要eval 的地基是测试用例。我一开始图省事让 Claude 生成了 200 条用例结果跑下来发现一半都是重复的或者太简单的根本区分不出模型好坏。后来我调整了策略用例集分三层基础层约 30 条覆盖最常见的用户问题这些是必须答对的答错说明有严重问题。边界层约 40 条故意设计一些刁钻的、容易让模型犯错的用例比如问题里有歧义、有多个子问题、有诱导性表述。压力层约 30 条超长输入、多轮上下文、需要结合外部知识的用例。总数控制在 100 条左右每条都经过我人工审核确保它确实能区分好坏。用例不在多在于每条都有明确的“考点”。生成用例的 prompt 我是这样写的你是一个测试用例设计专家。我正在评估一个[问答系统]的输出质量。 请围绕以下场景生成测试用例[场景描述]。 每条用例包含 1. user_input用户的问题 2. expected_points期望答案必须覆盖的关键点列表 3. difficulty难度等级easy/medium/hard 4. trap这条用例可能诱导模型犯什么错 要求 - 不要生成重复或高度相似的用例 - hard 难度的用例要真正有挑战性 - 输出 JSON 数组格式拿到 Claude 生成的用例后我逐条过一遍删掉不合理的补充自己想到的。这一步不能偷懒用例集的质量直接决定 eval 的可信度。3.2 评分 rubric 的写法给 Claude 一本“阅卷手册”rubric 是 eval 的灵魂。我见过有人只写一句“请给这个回答打分1-10 分”这种 rubric 跑出来的分数毫无意义因为 Claude 每次的理解都不一样。好的 rubric 要像高考作文评分标准一样具体。我以准确性维度为例实际用的 rubric 是这样的准确性评分标准0-5分 5分所有关键事实正确无编造无遗漏关键信息 4分关键事实正确但有1处非关键细节不准确 3分有1处关键事实错误或2处非关键细节错误 2分有2处及以上关键事实错误 1分答案与事实严重不符或大量编造 0分完全答非所问或输出为空 注意 - 如果答案中出现了 expected_points 之外但正确的事实不扣分 - 如果答案对不确定的信息做了合理标注如“据我所知”不视为编造 - 判断“关键事实”时以 expected_points 为准你看这里面不仅有分数档位还有“注意”部分来处理边界情况。这些注意条款是我在实际跑 eval 时发现 Claude 判断有偏差后一条条补上去的。rubric 不是一次写好的是迭代出来的。3.3 Claude API 调用的工程细节调用 Claude API 做 eval有几个工程上的坑我踩过这里直接给结论。第一强制 JSON 输出。我在 system prompt 里明确要求返回 JSON并且在 prompt 末尾加上“只返回 JSON不要有任何其他文字”。即便如此偶尔还是会有多余内容所以解析时要加容错用正则提取第一个完整的 JSON 对象。第二温度设低。做评判时我把 temperature 设成 0 或 0.1保证同样的输入每次打分基本一致。如果温度太高同一个输出两次打分差两分你就没法判断分数变化是改动带来的还是随机波动。第三并发控制。100 条用例如果串行跑一条 3 秒要 5 分钟迭代几次就受不了了。我用并发跑但并发数控制在 5-10太高容易触发限流反而更慢。第四结果落盘。每次 eval 的原始结果每条用例的输入、输出、各维度分数、Claude 的评语都要存下来存成 JSON 或 CSV。这些数据是你后续分析瓶颈的金矿千万别跑完就扔。3.4 分数聚合别只看总分一开始我只算一个总分后来发现总分涨了但用户体验没变好。原因是总分把四个维度平均了某个维度涨了掩盖了另一个维度跌了。正确的做法是分维度看。我现在的报告里总分、各维度均分、各难度层均分都会列出来。这样一眼就能看出问题出在哪。比如总分 4.2但边界层只有 3.1说明模型在简单场景没问题一遇到复杂情况就露馅。另外我会特别关注“方差”。如果某个维度均分 4.0 但方差很大说明模型表现不稳定有的用例满分有的零分这种“薛定谔的质量”比稳定 3.5 分更危险。4. 实操过程与核心环节实现4.1 环境准备与依赖安装我是在一台 Ubuntu 机器上跑的Python 环境。核心依赖就两个Anthropic 的官方 SDK 和 pandas用来做结果分析。pip install anthropic pandasAPI key 通过环境变量注入不要硬编码在代码里export ANTHROPIC_API_KEY你的key如果你在 Windows 上开发建议用 WSL因为很多脚本和工具链在 Linux 下更顺。我试过在纯 Windows 环境跑路径和编码问题会多花不少时间。4.2 核心评估脚本的编写整个脚本分四块加载用例、调用 Claude 打分、解析结果、聚合统计。我把关键部分贴出来。import os import json import re from concurrent.futures import ThreadPoolExecutor from anthropic import Anthropic client Anthropic(api_keyos.environ[ANTHROPIC_API_KEY]) RUBRIC 这里放你的完整 rubric包含所有维度和分数档位 def build_prompt(case, model_output): return f请根据以下评分标准对模型输出进行评分。 评分标准 {RUBRIC} 用户问题 {case[user_input]} 期望覆盖的关键点 {json.dumps(case[expected_points], ensure_asciiFalse)} 模型输出 {model_output} 请返回 JSON格式如下 {{ accuracy: {{score: 0-5, reason: ...}}, completeness: {{score: 0-5, reason: ...}}, relevance: {{score: 0-5, reason: ...}}, format: {{score: 0-5, reason: ...}} }} 只返回 JSON。 def judge(case, model_output): resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, temperature0.1, system你是一个严格的评估专家只输出 JSON。, messages[{role: user, content: build_prompt(case, model_output)}] ) text resp.content[0].text match re.search(r\{.*\}, text, re.DOTALL) if not match: return None try: return json.loads(match.group()) except json.JSONDecodeError: return None这里有几个细节值得说。re.search(r\{.*\}, text, re.DOTALL)是为了容错即使 Claude 多说了几句也能把 JSON 抠出来。temperature0.1保证打分稳定。返回 None 的情况要记录日志方便排查。4.3 跑一轮完整 eval 并生成报告加载用例、跑评估、聚合主流程大概长这样def run_eval(cases, model_fn, max_workers8): results [] def process(case): output model_fn(case[user_input]) scores judge(case, output) return { case_id: case[id], difficulty: case[difficulty], output: output, scores: scores } with ThreadPoolExecutor(max_workersmax_workers) as ex: results list(ex.map(process, cases)) return results def summarize(results): dims [accuracy, completeness, relevance, format] summary {} for d in dims: vals [r[scores][d][score] for r in results if r[scores]] summary[d] sum(vals) / len(vals) if vals else 0 summary[total] sum(summary[d] for d in dims) / len(dims) return summary跑完一轮我会把结果存成eval_round_N.json同时打印一份摘要。摘要里除了各维度均分我还会按难度分层统计这样能看出模型在不同难度下的表现差异。4.4 一轮 hillclimb 的完整记录光说方法太虚我拿一次真实的迭代举例。基线分数总分 3.6准确性 4.1完整性 3.2相关性 3.8格式 3.3。看细粒度反馈完整性扣分集中在“多子问题”的用例上模型经常只答第一个子问题。格式扣分集中在“该用列表却用段落”的用例上。第一个改动在 system prompt 里加一句“如果用户问题包含多个子问题请逐一回答用编号列表呈现”。跑完 eval总分 3.9完整性涨到 3.8格式涨到 3.7。这个改动有效固化。第二个改动把输出格式要求写得更具体明确“涉及步骤、清单、对比时使用列表”。总分 4.0格式涨到 4.1。有效固化。第三个改动尝试让模型在回答前先复述一遍问题。总分 3.9相关性反而跌了因为复述占用了篇幅有些用例被判为啰嗦。回退记录“复述问题无效”。就这样一轮轮爬大概十几轮后总分到了 4.4。每一轮改动和分数变化我都记在表格里回头看非常清晰。轮次改动内容总分准确性完整性相关性格式结论0基线3.64.13.23.83.3-1多子问题逐一回答3.94.13.83.83.7保留2明确列表使用场景4.04.13.83.84.1保留3回答前复述问题3.94.13.83.54.1回退4补充关键点检查清单4.24.24.13.84.1保留这张表是我整个项目最有价值的产出之一它把“玄学调优”变成了“有据可查的工程”。5. 常见问题与排查技巧实录5.1 Claude 打分不稳定怎么办这是最常见的问题。同一个输出两次打分差 1 分以上整个 eval 就失去意义了。排查顺序是这样的。先看 temperature如果大于 0.2先降到 0.1 以下。再看 rubric如果分数档位描述模糊比如“较好”“一般”Claude 的理解会漂移要把档位改成可观察的具体标准。最后看用例如果用例本身有歧义Claude 打分摇摆是正常的这种用例要修或者删。我还会做一个“稳定性测试”随机抽 10 条用例每条跑 3 次打分看三次分数的极差。如果极差超过 1 分的用例超过 2 条说明 rubric 还需要打磨。5.2 分数涨了但实际体验没变好这个问题的根源通常是 eval 和真实场景脱节。你的用例集可能太“干净”了真实用户的输入是带错别字、带情绪、带省略的。解决办法是定期从真实日志里采样把真实输入补充进用例集。我每个月会从线上日志里抽 20 条真实输入人工标注期望输出加进 eval。这样 eval 会越来越贴近真实分布。另一个原因是维度权重不合理。如果你最在意准确性但总分是四个维度平均那完整性涨了总分也涨可你实际关心的准确性没动。这种情况要么调整权重要么就盯准你最在意的那个维度看别被总分迷惑。5.3 API 成本和速度的平衡100 条用例每条打分一次用 Sonnet 大概几毛钱成本可以接受。但如果你的用例上千条或者要频繁跑成本就上来了。我的优化策略有三个。第一用规则先过滤明显错误的输出这些不用调 Claude 就能判零分。第二把简单用例easy 难度用更便宜的模型打分复杂用例才用强模型。第三缓存打分结果如果模型输出没变直接复用上次的分数不用重新调 API。速度方面并发是最有效的。但并发数不是越高越好我实测 8 并发比较稳再高就容易触发限流反而要重试总时间更长。5.4 常见问题速查表问题现象可能原因排查动作解决方向打分波动大temperature 高 / rubric 模糊检查 temperature 和档位描述降温、细化 rubric分数与体验不符用例集脱离真实对比用例和线上日志补充真实用例总分涨但某维度跌维度平均掩盖问题看分维度报告调整权重或盯准目标维度API 报错频繁并发过高 / 网络抖动看错误码和重试日志降并发、加重试JSON 解析失败Claude 输出多余文字看原始返回加正则容错、强化 prompt迭代多轮无提升瓶颈不在 prompt看细粒度扣分分布换思路可能问题在检索或数据5.5 几个我踩过的坑第一个坑是用例泄露。我早期把测试用例和训练用的示例混在一起导致模型在 eval 上表现很好实际用起来不行。后来严格分开eval 用例绝不进入 prompt 示例。第二个坑是过度优化单一维度。有一轮我死磕格式把格式分从 3.3 拉到 4.5结果模型变得特别爱用列表简单问题也列一堆相关性反而跌了。这提醒我维度之间是有 trade-off 的爬山时要盯着总分和关键维度别钻牛角尖。第三个坑是忽略失败用例的定性分析。分数只是数字真正有价值的是 Claude 给的 reason。我后来养成了习惯每轮 eval 后把扣分最多的 5 条用例的 reason 读一遍经常能发现分数没体现出来的系统性问题。6. 把这套方法扩展到更多场景跑通问答系统的 eval 后我把这套方法复制到了另外两个场景效果都不错。一个是代码生成。rubric 改成“可运行性、正确性、代码风格、注释完整度”四个维度用例是各种编程任务。Claude 判断代码能不能跑比判断自然语言还准因为它可以静态分析。另一个是长文摘要。rubric 是“信息覆盖、无编造、简洁度、可读性”。这个场景的难点是“信息覆盖”很难量化我的做法是让 Claude 先提取原文的关键点列表再判断摘要覆盖了哪些这样就有了可比较的基准。这套方法的核心其实不依赖 Claude 这个特定模型任何有足够语义理解能力、输出稳定的模型都能用。Claude 的优势在于它对 rubric 的遵循度高你让它按五档打分它不会自作主张给你打 7 分。这个“听话”的特性在 eval 场景里比单纯的聪明更重要。如果你也在做 AI 应用我强烈建议尽早把 eval 建起来。哪怕一开始只有 20 条用例、两个维度也比没有强。有了基线你的每一次改动才有意义你的优化才不是碰运气。我个人的体会是eval 建好之后迭代效率至少翻了一倍因为再也不用靠感觉猜了。