AI评估系统架构:稳定与灵活的平衡之道

AI评估系统架构:稳定与灵活的平衡之道
做AI项目评估系统这件事最初看起来很简单拉一批测试题让模型跑一遍人工打打分出个报告就完了。可真到要把它做成一个能长期运转、能嵌进研发流程、能拦住劣质模型上线的平台时你会发现所有问题都集中在一个矛盾上——评估场景天天在变评估结果却不能天天飘。我在这类系统上踩了不少坑也推倒重来了一版最后沉淀下来的核心经验只有一句话把稳定的东西做成基础设施把灵活的东西做成配置和插件两者之间用契约来隔离。这篇博文不聊虚的直接讲清楚我是怎么设计系统架构、怎么在灵活性上做减法、在稳定性上做加法的包括数据模型、执行引擎、打分机制、缓存策略以及踩过以后才明白的排查经验。适合正在做AI评测平台、模型选型工具、Agent评估系统的开发者参考也适合想把自己手里那套“脚本式评测”升级成正规军的团队看。1. 先想清楚评估系统的核心矛盾与设计原则1.1 灵活性与稳定性为什么天生打架评估系统本质上是个“裁判系统”。裁判的工作有两个要求第一规则要随时能改因为被测对象模型、Agent、提示词每个版本都不一样评测场景也随业务走第二判罚要稳定同一套题同一个模型跑两遍结果不能天差地别否则团队没法用它做版本对比更没法设卡点。这两个要求在设计层面是冲突的。灵活性要求你把评估维度、评分逻辑、数据集选择全部参数化、可热更新稳定性要求你链路固定、流程刚性、结果可复现。如果一味追求灵活最后系统会变成一个大泥球——什么都能配配完没人知道是怎么算出分的出了线上事故也查不到是哪条配置导致的。如果一味追求稳定系统又会退化成硬编码脚本业务方提一个新评测需求你要排期两三周AI项目迭代根本等不起。所以我的第一个建议是不要试图架构一个“万能评估系统”而是架构一个“有边界的可插拔评估系统”。边界以内全部标准化边界以外全部插件化。1.2 我经历的演进路线从单体脚本到插件化平台最早我们团队只有十几个测试用例、一个OpenAI调用脚本、一份Excel评分表当时谈不上架构就是“能跑就行”。但到第二个阶段要同时评测多个模型、多套提示词、多组数据集还要支持RAG场景和Agent工具调用评估脚本已经失控了——改一个评分逻辑要动主函数加一个数据集要改代码重新部署评测结果偶发不一致团队内部对分数都不信任。后来我调研了一圈发现市面上的开源评测框架比如各类LLM evaluation库大多偏向学术benchmark按它们思路能跑分但没法很好地贴合我们内部的业务场景比如私有知识库问答、客服工单分类、多轮对话兜底策略。最后我决定自研一个轻量级平台核心理念是三句话评估任务编排是一个稳定的流程状态机。评估维度、评分指标、被测对象都是可配置的可插拔单元。所有配置、数据、结果都走版本化存储任何一次评估都可以精确回放。这个决策等于给整个项目定了调灵活性放在配置层和数据层稳定性放在编排层和存储层二者通过明确定义的接口解耦。后面的架构基本就是围绕这个判断展开的。1.3 三条设计原则后续所有方案都从这里推导第一可复现性优先于实时性。任何一次评估运行完必须能马上回答“这次评测用了哪个数据集版本、哪个模型版本、哪套打分配置、依赖哪个规则包”。做不到这一点后面所有稳定性工作都是空谈。第二扩展点要收敛不要发散。常见的扩展需求我归纳为四类新增数据集、新增评测维度、新增被测模型、新增评分策略。设计架构时只需要为这几类提供标准扩展点其它奇奇怪怪的需求宁可先拒绝也不要为了讨好业务把系统改花。第三失败要显性化不要静默降级。模型调用超时、judge输出格式异常、某个评估分片算不出来这些情况必须能被监控到、被记录、被告警而不是悄悄跳过或者给个默认分。静默错误对评估系统的危害最大它会让你对模型能力的判断产生系统性偏差。2. 稳定性优先评估基础设施的架构分层2.1 分层设计接入层、编排层、能力层、存储层整个系统我分成了四层每一层各司其职禁止跨层调用。接入层只负责接收“发起评估”的请求做鉴权和参数校验编排层负责把评估请求解析成任务DAG控制执行流程能力层是变化最多的地方模型调用、指标计算、judge打分都在这一层以插件方式挂载存储层负责所有数据落地包括评估任务、用例、结果、快照、日志。这样分层最直接的好处是当能力层新增一个指标插件时编排层不需要改代码当编排层调整执行策略比如从串行改成并发时能力层完全无感。整个系统像流水线流水线上某个工位换了个新工具流水线的主控逻辑不用动。2.2 数据模型设计的几个关键实体评估系统表面上是在跑任务本质上是在处理一批有依赖关系的数据对象。我把核心实体收敛为下面几个EvaluationTask评估任务一次完整评估运行的根对象记录目标模型、数据集版本、评估配置版本、执行状态、开始/结束时间。EvaluationCase评估用例具体的一道测试题包含输入、期望输出、上下文、标签、所属数据集版本。MetricResult指标结果一个用例在一个评估维度上的得分是系统产出的最核心数据。RunSnapshot运行快照一次任务其所有配置、代码版本、依赖版本的不可变快照。在表结构上我坚持所有业务表都带task_id和project_id。task_id用于追溯某次运行的所有中间结果project_id用于多项目隔离。这个设计一开始看起来冗余后来发现是做数据隔离和问题排查的关键没有这两列的评估系统上线后查一次数据要全表扫痛苦到怀疑人生。2.3 执行引擎的幂等设计与任务隔离评估任务经常因为上游模型服务超时、队列积压、发布重启等原因中断。如果执行引擎不做幂等一个任务被重试一次就会产生两份结果最终得分可能被重复计算。我用的方案是每个EvaluationTask生成一个全局唯一的task_id所有子结果表都task_id外键写结果时使用“去重插入”策略——同task_id、同case_id、同indicator_id的记录如果已存在直接覆盖更新而不是追加。隔离这个问题很多自研系统会忽略等到多人共用时就炸了。我做了两个层面的隔离数据层面所有表都有project_id分区不同项目的数据物理隔离谁也别想看到谁的case和结果资源层面每个项目可以配置独立的并发上限和独立队列防止一个重任务打爆共享资源把同一套环境里的其他评估任务拖死。这个设计很像服务治理里的“舱壁隔离”成本不高但能显著降低系统内部的互相影响。3. 灵活性落地配置驱动与插件机制3.1 评估场景如何抽象成标准配置灵活性最刚需的场景是“评测集维度组合”的任意变化。比如这周要测“摘要准确性 有害性”下周要测“问答相关性 时效性 指令遵循度”如果每次都要改代码那不叫灵活叫开发资源黑洞。我把一次评估抽象成这样一个配置结构数据集来自哪个版本的哪个set、预处理规则可选、评估维度列表、每个维度的评分方式、以及是否开启人工复核。这套配置的载体是YAML或JSON存库里可热更新每次创建任务时给这份配置打一个version。下面是一个简化的例子evaluation: name: 客服问答质量回归 dataset: project: customer-service set: faq-v3 version: 2025-06-01 dimensions: - name: answer_relevance judge: type: llm model: judge-a prompt_version: rel-v2 temperature: 0 - name: answer_grounding judge: type: hybrid rules: [citation_exists, no_contradiction] llm: model: judge-b prompt_version: grounding-v1 review: enabled: true rate: 0.2这套配置从设计上逼着我们把所有评测维度都收敛为“由哪种judge负责”所谓judge可以是规则、模型或者二者的组合。这样表达力足够覆盖绝大多数场景同时不会让配置变成图灵完备的编程语言——我见过有人想用配置描述极其复杂的多轮Agent评估逻辑那种复杂度已经超出配置驱动能承担的边界了应该交给自定义插件而不是硬塞进配置里。3.2 插件机制指标计算和评测维度的扩展点如果只做配置驱动“新评分策略”仍然要动主代码。所以我在能力层定义了一个标准接口所有指标插件按协议实现用框架的entry point机制注册运行期通过配置里的type字段自动发现。一个简化版的插件接口大概是这样的class BaseMetric: name: str version: str def evaluate(self, case: EvaluationCase, response: ModelResponse, context: EvalContext) - MetricResult: raise NotImplementedError接口参数固定为用例、模型响应和上下文插件内部想怎么算都行但输入输出协议必须统一。我在实际项目里添加过keyword覆盖规则、正则命中率、文本相似度、基于独立judge模型的打分等七八种插件全部通过标准化注册上线核心代码分支不需要改动。这个模式本质上是对扩展开放、对修改关闭一开始写的时候多花一点时间设计接口后续每个插件节省的时间都远超初始成本。3.3 多模型接入的适配器层被测对象不只是OpenAI的GPT系列也可能是开源模型、内部微调模型、自研Agent框架、甚至某个prompt模版。为了让评测逻辑不绑定具体厂商我加了一层model adapter统一把上游封装成标准的generate/chat接口上游返回格式、超时时间、流式与否都在适配器内部处理。适配器层还有个容易被忽略的作用参数归一。比如OpenAI的temperature、top_p开源模型可能叫repetition_penalty对于评估一致性而言这些参数必须在适配层做固定映射。否则同一个“gpt-4o-mini”的评估任务昨天用的是默认temperature今天某个同事在调用代码里显式传了0.8这批分数就没法跟昨天比。评估系统的配置文件里每个模型至少固定temperature和max_tokens并随快照记录这是可复现的最低要求。4. 核心流程实现从评测集管理到结果产出4.1 评测集管理数据版本与不可变原则评测集是评估系统的“考卷”考卷质量直接决定评估结果的可信度。工程上我特别强调两点不可变和版本化。所谓不可变是指一旦一个评测集版本发布里面的case不允许修改删除。要改case只能新建一个版本。这样做的好处是线上已经跑过的评估结果不至于因为数据集偷偷改动而被破坏——这跟软件工程里发布tag之后不能改提交是同一个道理。版本化我借鉴了git的思路每次导入或修改数据集系统自动算一个内容hash作为版本号并记录导入时间、导入人、case数量、来源信息。实际运行评估时任务绑定的是数据集版本号而不是“最新数据”。这套设计落地后团队再没有出现过“上周报告和这周报告对不上不知道谁改了数据”的撕扯。4.2 打分引擎规则与LLM Judge的双轨策略评估维度的打分策略五花八门我不是让所有维度都无脑用大模型打分那是烧钱且不稳定。实际项目里我用“规则优先、LLM兜底、双轨可切换”的策略能用规则解决的问题关键词存在、格式合规、数值范围、引用是否出现绝不动用LLM。需要语义理解的问题回答相关性、摘要一致性、有害性、立场判断才用LLM judge。同一个维度可以在配置里同时配置规则和LLM规则通过给分规则不通过进入LLM做语义判断。这个双轨策略在实际运行中非常香。它既控制了成本又形成了交叉验证。比如“answer_grounding”维度我们发现规则判断和LLM判断经常互为补充规则抓硬伤模型抓软伤合在一起比单一方案准确率高不少。而且当LLM judge因上游抖动偶尔失联时规则轨道仍然能保住一部分核心指标的产出不至于整个任务大范围fail。对于LLM judge我踩过的坑是温度一定设为0prompt要固定版本judge模型要独立配置不能与被测模型共用同一个实例否则会出现“A模型给B模型打高分”这种离谱的系统性偏差。更多细节我放在后面问题排查部分展开。4.3 人工复核闭环自动打分与人工校验的结合全自动评估是个理想状态实际场景里至少要对以下情况人工复核置信度低的判分、争议case、新评测集上线的前几十条结果。我在系统里把人工复核设计成一个闭环系统按配置的抽样比例比如10%或20%随机挑出部分结果进入review任务池人工在界面上确认“同意”或“修改”修改后的分数会被记录并打上“human_revised”标记。这个标记特别有用。第一它让自动评估的准确性有了度量基准你可以统计人工修改率来反推自动打分质量第二它让人工反馈能回流到评测集和prompt优化中——当某个维度的修改率连续多天超过20%我会优先review该维度judge的prompt和评分标准文档而不是怀疑业务方有问题。这套闭环让评估系统从“单向打分”进化成“持续校准的裁判”。4.4 评估快照一次运行不可变的前因后果前面反复提到可复现性具体落地的载体就是RunSnapshot。每次创建评估任务时我把所有与结果相关的因子快照下来包括数据集版本含内容hash被测模型服务地址、模型版本、sampling参数所有评测维度的配置版本、judge模型版本、prompt内容hash插件代码版本部署包hash系统执行引擎版本所有快照内容统一序列化成JSON存到对象存储并在数据库里保存一个快照引用。线上出了任何分数争议直接从快照恢复一整套环境重新跑一遍即可跑出来结果应该完全一致。这个能力在AI项目里价值极高因为模型迭代、prompt优化、数据更新发生得太频繁没快照基本等于没有追溯能力。我甚至遇到过因为这个机制帮算法团队定位出问题不在评估系统、而在上游模型灰度版本的情况。5. 保障稳定性的关键工程手段5.1 任务调度与并发控制既要快又不能打爆上游评估任务往往是IO密集型的大量时间消耗在模型调用上。如果串行执行跑完一个几百条的评测集可能要几小时如果无限并行可能几秒钟就把上游模型服务的限流打爆触发大量503反而更慢。我采用的方式是自适应并发控制器针对每个上游模型服务单独维护一个并发令牌桶初始并发默认8每次调用成功令牌桶加1上限32每次超时或5xx则减半下限2。实测下来这个简单的自适应策略比静态并发配置稳定很多既充分利用了上游吞吐又能在上游抖动时快速退避。调度器本身我用的是任务队列方案评估任务入队后拆成case级子任务分发到worker。队列按项目和优先级划分保证核心业务的回归评测不会被大规模的批量评测任务阻塞。5.2 超时、重试与熔断别把评估系统做成雪崩源头真实环境下上游模型服务可能因为负载高、网络抖动、集群扩容等原因变慢甚至不可用。评估系统如果对每次模型调用无限等待worker会越积越多最后把整个队列拖垮。这里我定了三条规则单次模型调用超时时间为30秒这是根据线上模型P95延迟的3倍来定的重试策略为最多2次退避时间为1秒、3秒连续10次调用失败触发熔断熔断30秒内不再调用该模型直接把相关case标记为failed并进入告警。这个策略成熟以后我再也没见过因为模型服务抖动而导致评估系统整体瘫痪的情况。顺便说一句超时时长一定要可配置、进入快照不同模型服务的延迟特征差异非常大。5.3 缓存策略与结果幂等降低重复评估的成本很多评估任务其实是重复的——同数据集、同模型版本、同评测维度昨天跑了一次今天业务方只是想加个维度再跑一次结果把几百条case又重跑了一遍浪费钱和时间。我在系统里加了三级缓存第一级完整评估请求的键为“数据集hash 被测模型版本 评测维度配置hash”完全命中则直接返回历史结果。第二级case级缓存键为“单条case hash 模型版本 评测维度hash”命中则跳过该case的执行。第三级模型响应缓存键为“对话内容hash 模型参数hash”命中则直接用历史响应做指标计算。第三级缓存虽然能省最多钱但也最需要小心。模型响应缓存只适用于确定性参数temperature0的评估场景如果允许temperature0或者被测模型有随机性这个缓存必须关闭否则会掩盖模型的真实分布表现。我在系统里默认开启第一、第二级缓存第三级默认关由项目自行决定是否开启。5.4 结果可复现性的最后一道防线随机种子与判定参数锁定模型评估的随机性来源不只是模型采样参数还有代码层面的并发顺序、哈希迭代次序、甚至某些字符串处理逻辑里潜藏的随机行为。要保证同一次快照重跑结果一致我要求所有涉及随机的地方都显式固定随机种子并且在每次任务开始时把seed写入快照。另外LLM judge的判定结果要保证稳定除了temperature设0我还会在prompt中明确要求“只输出JSON不要多余解释”然后在代码里做结构化解析。解析失败时不是直接判0分而是把该条case标记为“judge_parse_error”进入修复队列同时累计错误率。当错误率超过阈值会触发告警因为这说明judge prompt需要优化而不是某条数据的问题。6. 踩过的坑常见问题与排查思路6.1 LLM Judge输出不稳定同一case两次打分不一致这是评估系统最常见的稳定性杀手。现象是同一个case同一条prompt跑两次LLM judge分数一次4分一次5分。排查后发现原因有三个——temperature没有固定为0、judge prompt里给的few-shot示例顺序固定但模型对示例的敏感度不同、以及上游模型服务虽然指定了版本实际被路由到了同版本的不同部署实例。我的处理方案是把所有LLM judge的temperature强制设为0并写入快照judge prompt采用固定模板few-shot示例抽取规则固定按case类型分层抽样在缓存层对“judge模型输出”做结果级缓存——同一个case同一条judge prompt只调用一次后续复跑直接复用。这样一套组合拳下来judge两遍不一致的问题基本消失。另外提醒一句不要过度迷信“temperature0绝对一致”模型实现层面仍然可能有非零的随机性所以结果可复现的重点应该是“能解释差异”而不是“绝对无差异”。6.2 指标漂移同一个维度叫法没变计算逻辑变了团队刚开始给评测维度起名很随意谁都能新增一个同维度的不同实现导致“answer_quality”这个指标在任务A和任务B里的计算逻辑完全不同跨任务的分数对比毫无意义。这个问题在小规模时完全暴露不出来一旦报表对比起来就是灾难。我后来在维度管理上做了强制约束维度名称全局唯一维度配置一经发布进入版本控制任何修改必须生成新版本旧任务只能引用旧版本不允许静默继承。从平台的角度看这就好比接口的语义化版本管理破坏性变更必须升级主版本不能原地修改。6.3 评测集被悄悄改动历史分数一夜失真这个坑深有体会。有个同事在跑完一轮评测后觉得某条case的期望答案写得不严谨顺手改了没告诉任何人。第二天回归评测用同一个数据集版本标记继续跑结果某个模型的分数涨了2%算法团队差点以为自己模型变强了。查了半天才发现是数据被改了。从那以后评测集的不可变原则被当成铁律写进系统数据库层面禁止update历史caseAPI层面也不提供“修改case”的入口只能“新建版本”。团队再有人想改数据必须走创建新版本的流程并且新版本编号清晰可见评测报告里也会显示具体数据集版本。这件事直接让“分数涨了是因为数据变了”这类乌龙事件彻底绝迹。6.4 长尾case拖垮整批任务评估数据集中总有一些极端case超长输入、复杂多轮对话、需要长时间工具调用的Agent任务。这些case的响应时间可能是正常case的几十倍如果不做单case级别的超时控制一个worker可能被一个长尾case卡住半小时整个任务队列锚定延迟急剧上升。我的解决方案是给每条case包上独立的context超时控制超时即按“执行失败”处理并记录失败原因。同时在任务配置里增加“长尾case策略”是重试一次、降低优先级还是直接跳过留待人工处理。这个策略必须有显式默认值不能依赖worker内部隐式行为。6.5 对比表格评估系统典型异常速查现象可能原因快速排查方式规避手段同一个case两次分数不同judge温度未固定 / prompt版本不一致查快照中judge参数温度置0prompt版本快照模型分数突然大幅上涨评测集被修改 / 模型灰度版本变更对比数据集hash和模型版本快照数据集不可变版本记录大量case超时失败上游模型限流 / 并发过高查模型服务的错误率与熔断状态自适应并发、熔断降级新维度上线后指标空白插件未注册 / 接口返回格式异常查看worker日志和插件加载状态标准插件接口上线检查评估报告分数对不上上周缓存被污染 / 参数配置被改动回放RunSnapshot快照机制、配置版本锁定judge输出解析失败率高prompt指令不明确 / 模型能力不足查看解析错误样例固定输出JSONfew-shot约束6.6 过程质量可观测日志、指标追踪与告警最后补一个很容易被忽略的点评估系统本身也必须被监控。不要只监控“任务有没有跑完”还要看过程指标——比如“judge平均延迟”“case失败率”“缓存命中率”“人工复核修改率”。这些指标能提前暴露质量问题比如judge延迟持续升高往往说明judge模型服务快扛不住了最好是提前扩容而不是等任务大面积超时才救火。我在系统里加了一个“评估健康度面板”聚合了这几个关键指标并配置了三级告警黄色告警某维度修改率超过20%、橙色告警case失败率超过5%、红色告警评估任务大批量失败或者关键指标产出中断。这套监控体系上线之后评估系统的可维护性上了一个台阶很多问题在用户发现之前就被处理掉了。从我个人的实践体会来看AI项目评估系统的架构设计本质上是给自己立规矩的工程规范数据模型、收敛扩展点、锁定配置版本、守住可复现底线。灵活性和稳定性并非只能二选一只要把可变的部分约束在配置层和插件层、把不可变的部分沉淀在基础设施层这套系统就能同时保证业务快速迭代和结果长期可信。如果现在有人让我重新再做一遍我还是会沿用这个框架但在最开始就会把监控和快照机制放进第一版而不是等出了问题再补。

最新新闻

日新闻

周新闻

月新闻