ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 Claude 设计 Eval:从 62 分到 91 分的 LLM 应用评测与 Hillclimb 实战

用 Claude 设计 Eval:从 62 分到 91 分的 LLM 应用评测与 Hillclimb 实战 1. 为什么我要用 Claude 来设计 eval第一次听到“用 Claude 设计 eval”这个说法很多人会下意识觉得别扭eval 不就是评测集吗写几条测试用例、跑个分、看准确率跟模型本身有什么关系我一开始也是这么想的直到自己在一个真实项目里被评测集坑了整整两周才彻底改变看法。事情的起因很简单。我手上有一个基于 Claude API 的文本处理流程输入是用户提交的原始需求描述输出是结构化的任务拆解。上线初期效果还行但随着输入分布越来越杂badcase 开始成片出现。我当时的做法非常“传统”手动收集几十条失败样本写成一个 JSON 数组跑一遍看通过率然后改 prompt再跑一遍。问题在于这个循环极其低效——我改完 prompt 之后旧 case 过了新 case 又挂了而且我根本不知道到底是哪一类输入在拖后腿。后来我换了个思路让 Claude 自己来帮我设计 eval。具体来说就是把我对“好输出”的判断标准用自然语言描述给 Claude让它生成一批覆盖不同难度、不同边界的测试用例再由我人工筛选和补充。这个做法听起来有点“用魔法打败魔法”的味道但实测下来它把我从“拍脑袋想 case”的泥潭里拽了出来。这篇文章想聊的就是这套完整的方法论怎么用 Claude 设计 eval怎么把 eval 跑起来怎么根据分数一轮一轮做 hillclimb爬山式迭代把分数从 60 分推到 90 分以上。适合正在做 LLM 应用、需要系统性评测、又不想把大量时间耗在手工造数据上的同学。哪怕你只是用 Claude Code 写点小工具这套思路同样能迁移过去。2. 先搞清楚 eval 到底在评什么2.1 eval 不是测试用例的堆砌很多人对 eval 的理解停留在“一堆输入 期望输出”的层面这其实只对了一半。真正有价值的 eval核心不在于用例数量而在于它能否稳定地反映你的业务目标。举个我踩过的坑早期我写了一个 eval里面 80% 的 case 都是“正常输入”结果模型分数一直很高但线上还是天天出问题。原因很简单——我的 eval 分布和真实流量分布严重不匹配它评的是一个“理想世界”不是我的真实场景。所以设计 eval 的第一步不是写 case而是先回答三个问题我的系统最怕哪类输入出错高风险场景哪类输入最常见高频场景哪类输入最难边界场景这三个问题的答案决定了 eval 的骨架。Claude 在这里的价值就是它能基于你对场景的自然语言描述快速生成覆盖这三类的候选用例而不是让你从零开始憋。2.2 评分标准比用例本身更重要我见过太多 eval 死在评分标准上。比如“输出是否合理”这种模糊标准不同人打分能差出 30 分。我的做法是把评分拆成可判定的维度每个维度要么是二值通过/不通过要么是有限档位0/1/2。举个例子对于一个结构化输出任务我会拆成维度判定方式权重格式合法性JSON 能否解析必须通过字段完整性必填字段是否齐全0.3内容准确性关键信息是否与输入一致0.5表达简洁性是否有多余废话0.2这张表不是拍脑袋来的而是我先用 Claude 生成了一版初稿然后结合线上 badcase 反复调整出来的。权重一定要反映业务优先级比如格式合法性我设成“必须通过”因为格式错了后面全白搭。2.3 用 Claude 生成 eval 的正确姿势直接跟 Claude 说“帮我写 50 条测试用例”出来的东西大概率是泛泛而谈。我的经验是prompt 里必须包含四样东西任务定义系统输入是什么、输出是什么、约束是什么。场景描述真实用户会怎么用有哪些典型和极端情况。评分维度你关心哪些方面哪些是硬性要求。输出格式要求 Claude 以 JSON 数组返回每条包含 input、expected、category、difficulty。我常用的一个模板长这样你是一个评测集设计专家。我有一个任务[任务描述]。 输入格式[输入格式]输出格式[输出格式]。 请生成 30 条测试用例覆盖以下类别 - 正常场景10 条 - 边界场景10 条 - 高风险/易错场景10 条 每条用例包含字段id, category, difficulty(1-3), input, expected_output, rationale。 以 JSON 数组返回不要额外解释。这个模板的关键在于分类生成而不是让模型自由发挥。分类能强制它覆盖不同难度避免全生成简单 case。3. 从零搭一套可复现的 eval 流程3.1 环境与工具准备我现在的标准配置是 Claude Code 一个轻量的 Python 脚本。Claude Code 负责生成和迭代 evalPython 脚本负责批量跑分。为什么不用现成的评测框架因为大多数框架要么太重要么评分逻辑不够灵活而我的场景需要频繁改评分维度自己写反而更快。基础环境很简单python -m venv eval-env source eval-env/bin/activate pip install anthropic pandas tqdmClaude Code 的安装这里不展开官方文档写得很清楚核心就是装好之后能在终端里直接调用。我习惯把 eval 相关的文件放在一个独立目录里结构大概是这样eval-project/ cases/ v1_cases.json v2_cases.json scripts/ generate_cases.py run_eval.py score.py results/ v1_result.csv v2_result.csv prompts/ system_prompt_v1.txt system_prompt_v2.txt这个结构的好处是版本可追溯。每次改 prompt 或改 eval都新建一个版本文件绝不覆盖旧的。我吃过亏——有一次直接改了 v1 的 case结果分数涨了但我根本不知道是 prompt 变好了还是 case 变简单了。3.2 用 Claude 生成第一版 eval第一版 eval 不要追求完美目标是快速拿到一个能跑的基线。我的做法是先用 Claude 生成 30 条人工过一遍删掉明显不合理的补上几条自己知道的 badcase然后就开始跑。生成脚本的核心逻辑是这样import anthropic import json client anthropic.Anthropic() def generate_cases(task_desc, n30): prompt f你是一个评测集设计专家。任务描述{task_desc} 请生成 {n} 条测试用例覆盖正常、边界、高风险三类场景。 每条包含 id, category, difficulty, input, expected_output, rationale。 以 JSON 数组返回。 resp client.messages.create( modelclaude-sonnet-4-5, max_tokens8000, messages[{role: user, content: prompt}] ) text resp.content[0].text return json.loads(text)这里有个细节max_tokens 一定要给足。我第一次只给了 2000结果 JSON 被截断解析直接报错。30 条用例大概需要 6000 到 8000 token保险起见给 8000。生成完之后我会人工做三件事删掉重复或过于相似的 case把 expected_output 里明显错误的改掉补充 3 到 5 条真实 badcase这一步不能省。Claude 生成的 case 质量整体不错但它不了解你的业务细节有些“期望输出”在业务上其实是不对的。3.3 跑分脚本怎么写才靠谱跑分脚本看起来简单其实坑很多。我第一版脚本犯的错是没有做并发控制30 条 case 串行跑等了快十分钟。后来改成并发但并发太高又触发限流。最后我的方案是限制并发数为 5配合重试机制。核心代码大概是这样import asyncio from anthropic import AsyncAnthropic client AsyncAnthropic() sem asyncio.Semaphore(5) async def run_one(case, system_prompt): async with sem: for attempt in range(3): try: resp await client.messages.create( modelclaude-sonnet-4-5, max_tokens2000, systemsystem_prompt, messages[{role: user, content: case[input]}] ) return resp.content[0].text except Exception as e: if attempt 2: return fERROR: {e} await asyncio.sleep(2 ** attempt)重试机制用的是指数退避这个很关键。限流报错如果不重试你的分数会被拉低而且你根本不知道是模型不行还是网络不行。跑完之后结果存成 CSV字段包括 case_id、category、difficulty、output、score、reason。score 和 reason 一定要分开存reason 是后面分析问题的关键。3.4 评分环节的自动化与人工结合评分我分两层自动评分 人工抽检。自动评分负责可判定的维度比如格式合法性、字段完整性。这部分用代码写死不依赖模型。内容准确性这种需要语义判断的我用 Claude 来打分但会加一个约束要求它给出打分理由。def score_with_claude(case, output): prompt f请对以下输出打分。 输入{case[input]} 期望{case[expected_output]} 实际输出{output} 评分维度准确性(0-2)、简洁性(0-1)、格式(0-1)。 返回 JSON{{accuracy: x, conciseness: y, format: z, reason: ...}} # 调用 Claude 打分人工抽检我一般抽 20%重点看那些自动评分给出高分但实际有问题的 case。这一步能发现评分标准的漏洞。我有一次发现 Claude 给一个明显答非所问的输出打了满分原因是我的评分 prompt 里没强调“必须回答输入的问题”后来补上这条约束分数立刻正常了。4. 一轮轮把分数提上去的 hillclimb 实战4.1 第一轮建立基线别急着优化第一版 eval 跑完我的分数是 62 分满分 100。这个分数不高不低正好适合作为基线。第一轮最重要的不是提分而是搞清楚分数是怎么丢的。我把结果按 category 和 difficulty 做了交叉统计类别难度1难度2难度3平均分正常95887686边界82654865高风险70523552这张表一眼就能看出问题难度越高分数掉得越狠高风险场景尤其严重。这说明我的 prompt 在处理复杂和边缘情况时不够稳健。第一轮我做的优化很克制只改了一处——在 system prompt 里加了一段“遇到不确定的情况时优先保证格式正确并在内容中标注不确定”。这一改高风险场景的格式分立刻上去了总分从 62 涨到 68。4.2 第二轮针对薄弱类别做定向优化第二轮我开始针对“高风险 难度3”这一类做定向优化。做法是把这一类里所有失败的 case 拿出来逐条看失败原因然后归纳出共性问题。我发现三个高频问题输入里有多个任务时模型只处理了第一个输入包含否定条件时模型容易忽略输入格式不规范时模型直接崩溃针对这三个问题我在 prompt 里加了三条明确的处理规则并且在 eval 里补充了对应的 case。注意这里补充 case 不是为了刷分而是因为原来的 eval 覆盖不够。补完之后总分从 68 涨到 74。这一轮我最大的体会是优化 prompt 和优化 eval 是两件事但必须同步做。只优化 prompt 不补 case你会误以为问题解决了只补 case 不优化 prompt分数会虚低。4.3 第三轮引入 few-shot 和结构化约束到第三轮单纯改 prompt 文字已经边际收益递减了。我引入了两个新手段第一few-shot 示例。我从 eval 里挑了 3 条最有代表性的 case把输入和期望输出作为示例放进 prompt。这一步效果立竿见影总分从 74 涨到 81。原因是 few-shot 给了模型一个具体的“参照物”比抽象规则有效得多。第二结构化约束。我要求模型在输出前先输出一段“思考过程”再输出最终结果。这个做法有争议因为会增加 token 消耗但实测下来思考过程能显著降低复杂 case 的错误率。我的做法是让思考过程放在一个特定标签里最终结果放在另一个标签里解析时只取结果部分。请按以下格式输出 thinking你的分析过程/thinking result最终结果/result这一轮之后高风险难度3的分数从 35 涨到了 62总分 81。4.4 第四轮处理长尾和稳定性问题81 分之后我遇到了瓶颈。分数卡在 81 到 83 之间来回波动怎么改 prompt 都上不去。后来我意识到问题不在 prompt而在评分的稳定性。我做了一个实验同一个 prompt同一个 case跑 5 次看分数波动。结果发现有些 case 的分数在 0 到 2 之间跳说明模型输出本身不稳定。针对这个问题我做了两件事把 temperature 从默认值调到 0减少随机性对每个 case 跑 3 次取平均分而不是跑 1 次这两件事做完分数波动明显减小总分稳定在 85。虽然分数没大涨但稳定性提升本身就是巨大的进步因为线上系统最怕的就是时好时坏。4.5 第五轮用 Claude 反向生成更难 case到 85 分我手里的 eval 已经有点“不够用”了——模型能过的 case 越来越多剩下的都是硬骨头。这时候我做了一个反向操作让 Claude 基于当前失败的 case生成更难、更刁钻的变体。具体做法是把失败 case 的输入和输出喂给 Claude让它分析“这个 case 为什么难”然后生成 5 条同类型但更难的 case。这一步相当于用模型来扩展 eval 的难度边界效果非常好。新生成的 case 里有几条直接把我打回 78 分但也正是这几条让我发现了 prompt 里两个隐藏的逻辑漏洞。修完漏洞之后总分到了 89。最后一分多是从细节抠出来的比如统一输出里的标点、处理空输入、处理超长输入。最终稳定在 91 分。5. 常见问题与排查技巧实录5.1 分数忽高忽低怎么办这是最常见的问题。排查顺序我一般是先看 temperature。如果不是 0先调到 0 再跑一遍。再看评分脚本。评分 prompt 里如果有模糊表述Claude 打分本身就不稳定。最后看 case 本身。有些 case 的期望输出本身就有歧义这种 case 要么改清楚要么删掉。我踩过最深的坑是评分脚本里用了“大致合理即可”这种表述导致 Claude 打分完全看心情。后来改成明确的档位描述问题就解决了。5.2 Claude 生成的 case 质量参差不齐这是必然的别指望一次生成就完美。我的处理流程是先按 category 分组每组抽 3 条人工检查如果某组问题率超过 30%整组重新生成生成时在 prompt 里加“避免生成过于简单或重复的 case”另外生成 case 的模型和跑 eval 的模型最好用同一个这样难度分布更匹配。我用 Sonnet 生成用 Sonnet 跑一致性明显好于混用。5.3 跑分太慢怎么优化30 条 case 串行跑大概 8 到 10 分钟并发 5 大概 2 分钟。如果 case 上百条建议并发数控制在 5 到 10太高容易限流用异步而不是多线程Python 里 asyncio 更省资源结果边跑边存别等全部跑完再写文件防止中途崩溃丢数据5.4 分数上不去但不知道卡在哪这种情况我一般做失败 case 聚类。把所有失败 case 的输入拿出来让 Claude 帮我归类看主要集中在哪几类问题。这个方法比逐条看快得多而且能发现你自己没意识到的模式。有一次聚类结果显示40% 的失败都集中在“输入包含多个并列条件”这一类我之前完全没注意到。针对这一类优化之后分数直接涨了 6 分。5.5 常见问题速查表现象可能原因排查动作分数波动大temperature 非 0 / 评分模糊调 temperature明确评分档位高分但线上差eval 分布不匹配补充真实 badcase分数卡住不动优化方向错误做失败 case 聚类跑分报错限流 / token 截断加重试调大 max_tokenscase 质量差生成 prompt 太泛加分类约束和难度要求6. 几个我反复验证过的实操心得第一eval 要跟着业务一起演进。我现在的习惯是每两周回顾一次 eval把线上新出现的 badcase 补进去把已经稳定通过的简单 case 删掉。eval 不是一次性的产物它是一个活的资产。第二hillclimb 的节奏比幅度重要。我见过有人一次改一大堆 prompt分数涨了 10 分但根本不知道是哪处改动起的作用。我的做法是一次只改一个变量改完跑分记录再改下一个。慢是慢了点但每一步都可追溯。第三别迷信分数。91 分不代表系统完美它只代表在你的 eval 上表现好。eval 本身有盲区所以人工抽检永远不能省。我到现在还保持着每周抽 20 条线上真实输出人工看的习惯这个习惯帮我发现了至少 5 个 eval 没覆盖到的问题。第四Claude 是助手不是裁判。用 Claude 生成 case、打分、聚类都很香但最终的判断标准必须由你来定。我见过有人完全信任 Claude 的打分结果 eval 分数很高上线一塌糊涂。记住Claude 帮你提效但业务判断这件事它替代不了你。这套流程我从 62 分跑到 91 分前后大概花了三周中间踩的坑基本都写在上面的排查表里了。如果你也在做类似的 eval 迭代建议先从 30 条 case 的小规模开始跑通整个流程再逐步扩大。规模不是关键闭环才是。
返回列表