ARTICLE DETAIL

资讯详情

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

用Claude辅助设计AI应用eval:从60分迭代到90分的实战指南

用Claude辅助设计AI应用eval:从60分迭代到90分的实战指南 1. 为什么我要用 Claude 来设计 eval而不是手写测试用例做 AI 应用开发的人都有一个共同的痛点模型输出不稳定今天跑得好好的 prompt明天换个输入就崩了。你改了一版 prompt感觉效果好了但到底好了多少说不清楚。更麻烦的是你改好了 A 场景结果 B 场景又退化了而你根本不知道。这就是 eval 存在的意义。eval 不是单元测试它更像是一套“评分标准 自动化裁判”让你每次改动之后能快速知道分数涨了还是跌了哪个维度涨了哪个维度跌了。但问题来了——写 eval 本身就是一件很痛苦的事。你得设计测试集、定义评分维度、写评分逻辑、处理边界情况。传统做法是人工写一堆规则或者正则费时费力而且覆盖不全。我用 Claude 来做这件事核心思路是让 Claude 帮我生成 eval 的骨架和评分维度我再基于实际 badcase 一轮轮迭代把分数从 60 分推到 90 分以上。这个过程在圈子里叫 hillclimb就是爬山——每一步都往高处走一点最终爬到局部最优。这篇文章适合谁看如果你正在做 AI 应用不管是对话、写作、代码生成还是 RAG并且想让效果可量化、可迭代那这套方法你可以直接抄作业。如果你还没开始做 eval看完这篇你至少知道从哪下手。2. 整体设计思路先让 Claude 帮你把 eval 框架搭起来2.1 eval 的本质是什么很多人一上来就想写一个“完美的评分函数”结果卡在第一步。我的经验是eval 不是一次写完的它是长出来的。你一开始只需要一个粗糙的评分框架能跑通就行。然后拿真实数据去跑看哪些 case 得分低分析为什么低再补规则、补维度、补测试用例。每一轮迭代eval 就更准一点模型效果也更好一点。Claude 在这个过程中的角色是帮你快速生成第一版框架帮你分析 badcase 的原因帮你写评分逻辑的代码。它不是替你思考而是替你省掉那些重复劳动。2.2 为什么选 Claude 而不是别的模型我试过几个模型来做这件事最后选 Claude 的原因很实际长上下文理解稳eval 设计经常需要把一堆 badcase、评分标准、历史迭代记录一起丢进去Claude 在长文本里抓重点的能力比较靠谱。代码生成质量高评分函数很多时候就是一段 PythonClaude 写出来的代码结构清晰边界处理也比较到位。指令遵循好你让它“按这个格式输出评分维度”它不会给你自由发挥。当然这不是说别的模型不行。核心是你要有一个能稳定输出结构化内容的模型来辅助你设计 evalClaude 在我这里表现最好。2.3 整体流程长什么样我把整个流程拆成五步后面会一步步展开定义评分维度让 Claude 根据你的任务描述生成 3-5 个核心评分维度。生成测试集骨架让 Claude 生成一批覆盖不同场景的测试输入。写评分逻辑让 Claude 生成评分函数的代码框架。跑第一轮收集 badcase拿真实数据跑看哪些 case 得分低。迭代 hillclimb分析 badcase补规则、补维度、补测试用例重复第 4 步。这个流程看起来简单但每一步都有坑。下面我逐个拆。3. 核心细节解析评分维度怎么定测试集怎么造3.1 评分维度的设计原则评分维度是 eval 的灵魂。维度定错了后面怎么迭代都是白费。我一般遵循三个原则第一维度要正交。比如你做的是客服对话那“回答准确性”和“语气友好度”是两个独立维度不要混在一起。混在一起的结果是你不知道分数低是因为答错了还是因为语气差。第二维度要可判定。“回答得好不好”这种维度没法判定因为太主观。你要拆成“是否包含关键信息点”、“是否出现事实错误”、“是否超出规定长度”这种可以明确判断的。第三维度不要超过 5 个。超过 5 个之后权重分配会变得很纠结而且迭代时很难定位问题。3-5 个是最舒服的区间。我一般会让 Claude 先根据任务描述生成一版维度然后我自己再调。比如我给它这样的 prompt我在做一个技术文档问答系统用户问技术问题系统从文档里找答案并生成回答。 请帮我设计 4 个评分维度要求 1. 每个维度可以独立打分1-5 分 2. 维度之间尽量正交 3. 每个维度给出明确的判定标准 4. 输出格式维度名称 | 判定标准 | 1分示例 | 5分示例Claude 返回的结果通常可以直接用我只需要微调判定标准的措辞。3.2 测试集怎么造才有效测试集不是越多越好而是越有代表性越好。我见过有人造了 500 条测试用例结果 400 条都是同一个场景的变体跑出来的分数根本没有区分度。我的做法是先按场景分类每个场景造 5-10 条覆盖正常 case、边界 case、badcase。让 Claude 生成测试集的时候我会给它一个场景列表让它按场景生成。比如请为以下 5 个场景各生成 8 条测试输入 1. 简单事实查询答案在文档中直接能找到 2. 需要跨段落推理的问题 3. 文档中没有答案的问题应该拒答 4. 问题有歧义需要澄清 5. 用户输入包含错别字或口语化表达 输出格式场景编号 | 测试输入 | 预期行为这样造出来的测试集覆盖度比随机生成高得多。3.3 评分逻辑的代码框架评分逻辑我一般分两种规则评分和模型评分。规则评分适合那些可以明确判断的维度比如“是否包含关键信息点”、“是否超出长度限制”。模型评分适合那些需要理解的维度比如“回答是否流畅”、“逻辑是否连贯”。Claude 帮我写评分代码的时候我会让它把两种评分分开def rule_based_score(response, expected_keywords, max_length): score 0 # 关键信息点覆盖 covered sum(1 for kw in expected_keywords if kw in response) score (covered / len(expected_keywords)) * 3 # 长度合规 if len(response) max_length: score 2 return score def model_based_score(response, question, client): prompt f请对以下回答的流畅度打分1-5分\n问题{question}\n回答{response}\n只输出分数。 result client.messages.create( modelclaude-sonnet-4-20250514, max_tokens10, messages[{role: user, content: prompt}] ) return float(result.content[0].text.strip())这个框架跑通之后你就可以开始第一轮迭代了。注意模型评分会有波动同一个回答跑两次可能分数不一样。我的做法是跑 3 次取平均或者用 temperature0 来降低波动。4. 实操过程从 60 分爬到 90 分的完整记录4.1 第一轮跑通流程拿到基线分第一轮的目标不是高分而是跑通。我把测试集跑了一遍得到每个维度的平均分维度平均分最低分场景关键信息覆盖3.2跨段落推理事实准确性3.8歧义问题拒答合理性2.5无答案问题回答流畅度4.1口语化输入总分 60 分左右。这个分数很正常第一轮能跑通就不错了。关键是要看 badcase。我把得分低于 3 分的 case 全部拉出来让 Claude 帮我分析原因以下是 10 条低分 case请帮我归类并指出每一类的主要问题是什么。 输出格式问题类别 | 涉及 case 编号 | 主要原因 | 改进建议Claude 返回的结果很有用。比如它指出“拒答合理性”低分的主要原因是模型在文档中没有答案时倾向于编造一个看起来合理的答案而不是明确说“文档中没有相关信息”。4.2 第二轮针对 badcase 补规则知道了原因改起来就有方向了。我做了三件事第一在 prompt 里加拒答指令。明确告诉模型“如果文档中没有找到答案必须回答‘根据现有文档无法回答该问题’不要编造。”第二在评分逻辑里加惩罚项。如果模型编造了文档中不存在的信息直接扣分。第三补充测试用例。针对“文档中没有答案”这个场景我又加了 10 条测试输入覆盖不同类型的无答案问题。改完之后再跑一轮分数变化维度第一轮第二轮变化关键信息覆盖3.23.50.3事实准确性3.84.00.2拒答合理性2.53.81.3回答流畅度4.14.10总分从 60 涨到 72。拒答维度涨得最多因为问题最明确。4.3 第三轮优化跨段落推理第二轮之后最低分变成了“跨段落推理”场景。这个场景的问题是答案分散在文档的不同段落里模型只找到了一部分。我的改进方法是在 prompt 里加“先检索再回答”的步骤。让模型先把相关段落全部列出来再基于这些段落生成回答。这样能减少遗漏。在评分逻辑里加“信息点覆盖完整性”检查。对于跨段落问题我手动标注了每个问题应该覆盖的信息点评分时检查覆盖了几个。这一轮改动比较大我让 Claude 帮我重新生成了 prompt 模板你是一个技术文档问答助手。请按以下步骤回答 第一步从文档中找出所有与问题相关的段落列出来。 第二步基于这些段落生成一个完整的回答。 第三步检查回答是否覆盖了所有相关段落中的关键信息。 如果文档中没有相关段落直接回答“根据现有文档无法回答该问题”。 问题{question} 文档{document}再跑一轮跨段落推理场景的分数从 2.8 涨到 4.2。总分到了 81。4.4 第四轮处理口语化和错别字第三轮之后剩下的低分 case 主要集中在口语化输入和错别字上。比如用户输入“这个咋弄啊”模型有时候会理解偏差。我的做法是在预处理阶段加一层输入规范化。让 Claude 帮我把口语化输入转成标准问法再走后面的流程。这一步的评分逻辑也要调整如果模型对口语化输入的回答和对标准问法的回答一致就给高分。这一轮涨分不多从 81 涨到 85。但这一步很关键因为真实用户输入就是口语化的不处理的话线上效果会很差。4.5 第五轮微调评分权重到第四轮之后分数已经比较高了但我觉得评分权重不太合理。比如“回答流畅度”占了 25% 的权重但实际上这个维度对用户体验的影响没那么大。我重新分配了权重维度原权重新权重理由关键信息覆盖30%35%最重要事实准确性25%30%第二重要拒答合理性20%20%保持不变回答流畅度25%15%降低权重重新算分之后总分到了 88。再补了几条边界 case最终稳定在 91 分左右。5. 常见问题与排查技巧实录5.1 评分波动太大怎么办这是最常见的问题。同一个回答跑两次分数不一样。原因通常是模型评分那部分不稳定。解决方法固定随机种子如果用的 API 支持设置 temperature0。多次取平均跑 3 次取平均分成本不高但效果明显。规则优先能规则判定的维度尽量用规则减少模型评分的比例。5.2 分数涨了但线上效果没变好这种情况通常是 eval 和真实场景脱节了。你的测试集不能代表真实用户输入。解决方法定期从线上日志里采样真实 case补充到测试集里。我一般每周补 20 条保持测试集的时效性。5.3 Claude 生成的评分维度不适用有时候 Claude 生成的维度太抽象没法落地。比如它给你一个“回答质量”维度你根本没法打分。解决方法在 prompt 里明确要求可判定。加上“每个维度必须能用 1-5 分明确判定不能有主观模糊空间”这句话效果会好很多。5.4 迭代到后面涨不动了这是 hillclimb 的典型问题爬到局部最优了。分数卡在 85 左右怎么改都不涨。这时候有两个选择换方向不是继续优化 prompt而是换模型、换检索策略、换预处理方式。接受现状85 分可能已经够用了继续投入产出比不高。我的经验是如果连续三轮迭代涨分不超过 2 分就该考虑换方向了。5.5 常见问题速查表问题可能原因解决方法评分波动大模型评分不稳定设 temperature0多次取平均分数涨线上不涨测试集脱节从线上采样补充测试集维度没法判定维度太抽象重新生成要求可判定迭代涨不动局部最优换方向或接受现状拒答维度低分模型爱编造prompt 加拒答指令评分加惩罚跨段落漏信息检索不完整先检索再回答分步执行实操心得每次迭代只改一个变量。如果你同时改了 prompt 和评分逻辑分数涨了你不知道是哪个起的作用。一次只改一个跑完看结果再改下一个。6. 工具选型与 Claude API 的实操配置6.1 为什么我用 Claude API 而不是网页版网页版适合探索但 eval 迭代需要批量跑、需要程序化调用、需要记录每次的结果。这些网页版都做不了。Claude API 的配置很简单核心就是三行import anthropic client anthropic.Anthropic(api_keyyour-api-key) response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, messages[{role: user, content: 你的 prompt}] )我一般会把评分逻辑封装成一个函数输入是测试集输出是每个 case 的分数和总分。这样每次迭代只需要改 prompt 或评分逻辑然后重新跑一遍就行。6.2 批量跑 eval 的脚本结构我的脚本一般长这样import json from anthropic import Anthropic client Anthropic(api_keyyour-api-key) def load_test_cases(path): with open(path) as f: return json.load(f) def run_eval(test_cases, prompt_template): results [] for case in test_cases: # 生成回答 prompt prompt_template.format(questioncase[question], documentcase[document]) response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, temperature0, messages[{role: user, content: prompt}] ) answer response.content[0].text # 评分 score score_answer(answer, case) results.append({case_id: case[id], answer: answer, score: score}) return results def score_answer(answer, case): # 规则评分 模型评分 rule_score rule_based_score(answer, case[expected_keywords], case[max_length]) model_score model_based_score(answer, case[question]) return rule_score * 0.6 model_score * 0.4 if __name__ __main__: cases load_test_cases(test_cases.json) results run_eval(cases, PROMPT_TEMPLATE) total sum(r[score] for r in results) / len(results) print(f总分{total:.2f}) # 保存结果 with open(eval_results.json, w) as f: json.dump(results, f, ensure_asciiFalse, indent2)这个脚本跑一次大概几分钟成本取决于测试集大小和模型选择。我一般用 Sonnet 跑 eval性价比比较高。6.3 成本控制的小技巧eval 跑多了成本会上去。我的控制方法是测试集控制在 100 条以内太多没必要100 条足够有代表性。评分用便宜模型生成回答用 Sonnet评分可以用 Haiku成本低很多。缓存结果同一个 case 跑过了就缓存不要重复跑。注意评分模型和生成模型最好分开。用同一个模型评分会有偏差因为它倾向于给自己生成的回答打高分。7. 迭代过程中的经验与避坑指南7.1 不要追求一步到位我见过有人想一次性设计一个完美的 eval结果花了两个月还没跑通第一轮。正确的做法是先跑通再优化。第一版 eval 哪怕只有 60 分也比没有强。7.2 badcase 要分类不要逐个改低分 case 可能有几十条你不可能逐个改。正确做法是让 Claude 帮你归类然后按类别改。改一类跑一轮看分数变化。7.3 评分逻辑要可解释如果你的评分逻辑是一个黑盒分数涨了你不知道为什么涨跌了也不知道为什么跌。我的做法是每个维度单独输出分数这样你能看到是哪个维度在变化。7.4 定期回顾测试集测试集会过时。三个月前的测试集可能已经不能代表现在的用户输入了。我一般每个月回顾一次删掉过时的 case补充新的 case。7.5 记录每次迭代的变化这个习惯很重要。我每次迭代都会记录改了什么、分数变化、badcase 变化。这样回头看的时候你能清楚地知道哪次改动最有效。迭代轮次改动内容总分变化关键发现第一轮跑通流程60拒答维度最低第二轮加拒答指令72拒答涨 1.3 分第三轮先检索再回答81跨段落涨 1.4 分第四轮输入规范化85口语化场景改善第五轮调整权重88分数更合理补充边界 case91稳定在 91 左右这个表格是我实际迭代的记录你可以参考这个格式来记录自己的迭代过程。7.6 一个容易被忽略的细节评分的时候不要只看平均分要看分布。平均分 85 可能是所有 case 都在 85 左右也可能是 50 条 100 分加 50 条 70 分。后者的问题更大因为有一半的 case 效果很差。我一般会看三个数平均分、最低分、标准差。标准差太大说明效果不稳定需要重点排查。8. 后续可以怎么扩展这套方法跑通之后可以往几个方向扩展第一自动化迭代。把“分析 badcase → 生成改进方案 → 跑 eval → 看分数”这个流程自动化让 Claude 自己迭代。当然关键决策还是要人来把关。第二多模型对比。同一套 eval跑不同模型看哪个模型在你的场景下效果最好。这个对比结果很有参考价值。第三eval 即文档。你的 eval 测试集和评分维度其实就是你对“好回答”的定义。把它整理成文档新加入的团队成员一看就懂。第四接入 CI/CD。每次改 prompt 或改模型自动跑一遍 eval分数跌了就报警。这样能防止退化。我个人在实际操作中的体会是eval 的价值不在于分数本身而在于它强迫你把“什么是好”想清楚。你想不清楚模型就更想不清楚。用 Claude 来辅助设计 eval本质上是借它的语言能力帮你把模糊的标准具象化然后你在这个基础上迭代。这个过程跑一轮下来你对业务的理解会比之前深很多。
返回列表