平台优化入手点(AI 大模型评测平台演进路线)

接一个新模型要写 200 行适配器、LLM-as-Judge 的评分漂移在 5% 到 10% 之间来回跳、用户直到月底才知道自己烧了多少 token——上线一年后,瓶颈越来越集中。起初团队的第一反应是推倒重写,把四万行代码重新来一遍,算下来要搭进去六个月人力,还得承担数据迁移和用户流失的风险。后来我们冷静下来,把问题按”影响面 × 紧急度”排了优先级,发现大部分痛点根本不需要重构,靠服务拆分、缓存、队列和模板化就能消化掉。下面这份优化清单就是当时落地顺序的真实记录,按 P0 到 P3 分四档,每一条都标了改法,也标了当初为什么先做它。

一、当前痛点

平台上线一年后遇到的真实问题,不是单点故障,而是分散在接入、评测、计费、展示、调度五个环节的慢性消耗。逐个定位之后,我们把它们收敛成六条:

  1. 模型接入成本:每接一个新模型要写 200 行适配器;
  2. 评分一致性:LLM-as-Judge 漂移 5-10% 不可控;
  3. 成本不透明:用户不知道自己今天花了多少钱;
  4. 报告样式单一:ECharts 模板化,缺乏可定制;
  5. 批量任务长尾:5000 条任务跑 30 分钟,最后 5% 因限流卡住;
  6. 多租户缺失:企业客户要独立部署但平台是单租户。

这六条里,前三条直接吃掉用户信任,后三条决定平台能不能规模化。我们的判断是:与其一次性全改,不如按优先级逐条拆,每拆一条就能带来可感知的改善。接下来两节先讲 P0,因为它和钱、和评测结果可信度直接挂钩,这两处不改,后面所有优化都会在同一个地基上重复返工。

二、优先级 P0:模型接入与成本

模型接入和成本这两件事放在 P0,是因为它们每天都在发生:每来一个新客户就要新模型,每跑一次评测都在花钱。这两个环节的改动收益是即时可见的,先把它们收拾干净,后面的体验优化才有意义。

1. 统一 LLM 网关

先说接入。早期代码里每家模型的适配器散落在各个 Service,新模型进来要改四五个文件,还要各自处理限流和计费。我们新建了独立服务 llm-gateway,把鉴权、计费、限流、日志全部收口到一个入口:

评测平台 → llm-gateway → OpenAI/Anthropic/...
              ↓
        统一鉴权
        统一计费
        统一限流
        统一日志

收益:

  • 新模型接入从 200 行降到 50 行(自动从 OpenAPI 生成 client);
  • 所有 LLM 调用在一个地方限流,平台侧不需要重复实现;
  • 计费逻辑集中,方便对接财务系统。

当时对比过两个方案:在现有应用里加一个 LLM Client 模块,还是单独拆服务。前者改动小,但限流、计费逻辑还是会散落在各业务里;后者多一个部署单元,却把边界划干净了。我们选了拆服务,代价是多维护一个组件,换来的是后续每家新模型的接入成本线性下降,这一步从长期看是值得的。

2. 实时成本仪表盘

成本透明是用户反复提的需求。过去账单靠月底对账,用户中途完全失控,等发现超支时已经来不及了。我们把计费流水实时落库,用户在首页就能看到今天的消耗和剩余预算:

今日已用 ¥12.34 / 日预算 ¥50
├─ GPT-4o: ¥8.20 (66%)
├─ Claude: ¥3.10 (25%)
└─ DeepSeek: ¥1.04 (9%)

本周累计 ¥87.65
本月预计 ¥340

后端用 ClickHouse 实时聚合 model_usage_log 表。

上线这个仪表盘之前我们犹豫过:ClickHouse 对小团队是不是太重?后来发现直接查 MySQL 聚合表在千万级流水上会拖慢主库,而 ClickHouse 的物化视图把聚合成本压到毫秒级,还顺带支撑了后面的用户漏斗分析,这笔投入不亏。仪表盘上线后的直接效果是客诉少了,用户开始主动给自己设预算,有些客户甚至靠它做内部的成本分摊。

三、优先级 P0:评分稳定性

评分是评测平台的命根子。用户提交一版 prompt,最想知道的是”这个版本到底比上一版好多少”,如果评分今天高一分明天低一分,结论就没有说服力。我们为评分稳定性做了三件事,层层加保险。

1. Prompt 版本化 A/B

评分 prompt 每次改动都像踩雷,一个字就能让分布整体平移,这个问题我们是在线上对比数据里真实撞见过的。之后定了一条铁律:任何评分 prompt 上线前必须跑 200 条历史答案做 A/B,对比三个指标:

  • 评分分布(应该相似);
  • 与人工评分相关性(应该 > 0.85);
  • 评委间一致性(应该 > 0.8)。

新版本只在 A/B 胜出后启用。

这条规则看起来简单,执行起来最难的是”忍住”。有一版 prompt 在内部评测里相关性提升明显,但 A/B 时分布右偏了 3%,团队里有人觉得无所谓,我们最后还是按规则回滚了。事后证明,规则比感觉可靠,一旦开了口子,后续每次改动都会有人说”这次例外”。

2. 评分校准集

A/B 只能防回归,不能防系统性偏差。LLM 评委对某些题型会有稳定的偏好,比如对”回答详尽”的答案普遍多给分,这种偏差只有拿人工评分当标尺才能发现。我们加了每日校准任务,抽样高置信度评分做人工复核:

@Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨 2 点
public void calibrate() {
    List<ScoredAnswer> samples = sampleService.randomHighConfidence(100);
    samples.forEach(s -> {
        s.setHumanScore(humanReviewService.getOrQueue(s.getId()));
    });
    metrics.gauge("score.accuracy", 
        samples.stream()
            .filter(s -> Math.abs(s.getAiScore() - s.getHumanScore()) <= 1)
            .count() * 1.0 / samples.size());
}

这段代码每天凌晨跑一次,把 AI 分数和人工分数差在 1 分以内的比例打进指标,作为校准监控。注意这里人工复核环节当时是短板,一开始没有专职标注,只能请运营同事帮忙,后来才引入第三方众包。校准链路里最贵的是人而不是代码,预算上要提前留好,否则校准任务会形同虚设。

3. 评分员持续评估

对单个评委的信任不能是永久的,模型升级换代后,它的评分习惯也会跟着变。我们每月对每个评委独立评分做”金标准对比”,用一套人工精标过的题集当标尺:

GPT-4o: 89% 与金标准 ±1 分
Claude: 92% 与金标准 ±1 分
DeepSeek: 81% 与金标准 ±1 分

低于阈值的评委降权或下线。

这里有个反直觉的发现:综合能力强的模型不一定是最稳的评委,评分稳定性跟模型的指令遵循能力关系更大,所以降权名单每个月都在变。我们索性把评分员评估做成了月度例行任务,而不是一次定终身,评分体系会随着模型能力变化自动调整权重。

四、优先级 P1:报告可定制

评分稳定之后,用户开始追求”报告像自己家的”。P0 阶段我们只能提供固定模板,客户要加一个维度就得排期改代码,反馈周期太长。P1 的目标是把报告能力交还给用户,让他们自己拼装想要的结构。

1. 报告模板系统

我们设计了一套 JSON 模板,用户声明报告由哪些区块组成,系统按声明渲染,不同团队可以沉淀自己的私有模板:

{
    "template": "code-review-v1",
    "sections": [
        { "type": "kpi", "metrics": ["total", "passRate", "avgScore"] },
        { "type": "histogram", "field": "score" },
        { "type": "radar", "models": ["gpt-4o", "claude"] },
        { "type": "table", "fields": ["case", "output", "score"] }
    ]
}

存为模板后可复用、支持分享。

做模板系统时我们纠结过 schema 该多灵活:太宽松怕渲染出错,太死板又失去定制意义。最后选了”区块类型白名单 + 字段黑名单”的组合,既能自由组合,又把风险控制在渲染器可处理的范围内。上线一个月就有客户分享出了二十多个私有模板,这个功能的投入产出比在 P1 里排在前列。

2. 自定义图表

模板解决结构,图表解决表达。部分用户对图表有强需求,内置的几种样式不够用,我们开放了 ECharts option 的上传入口,前端直接消费用户配置:

function renderCustomChart(chartConfig: any) {
    return <VChart :option="chartConfig" autoresize />
}

允许用户传原始 option 是一把双刃剑:灵活是真的,但非法配置会拖垮页面。我们的处理是上传后先做一次 schema 校验和沙箱渲染,渲染失败就回退到默认图。代码里把 chartConfig 的类型从 any 收紧成白名单结构,这类问题就少了大半,页面也不容易被打挂。

五、优先级 P1:批量任务调度

批量评测是平台使用频率很高的场景之一,也是长尾问题比较集中的地方。5000 条任务跑 30 分钟,最后 5% 因为限流卡住,用户盯着进度条干着急。我们把调度这块拆成三个小优化,逐个解决”慢、插队、卡死”三个问题。

1. 动态并发

第一刀动在并发上。原来并发写死 50,模型响应快时浪费吞吐,响应慢时又堆积排队,用户体验两头不讨好。改成按模型实时延迟动态调整:

// 响应快 → 提并发;响应慢 → 降并发
public int adjustConcurrency(String model, int current, Duration avgLatency) {
    if (avgLatency < Duration.ofSeconds(5)) return Math.min(current * 2, 100);
    if (avgLatency > Duration.ofSeconds(20)) return Math.max(current / 2, 5);
    return current;
}

动态并发的关键不是这个判断函数,而是它依赖的延迟指标准不准。我们最开始拿全量平均延迟当输入,结果个别慢请求把均值拉高,并发被误降,调度反而更慢。后来改成按模型分桶的滑动窗口 P50,调整才变得平滑,这个教训提醒我们:算法再对,喂给它的数据不准也是白搭。

2. 优先级队列

用户对进度的忍耐度不同,VIP 客户的批量任务必须插队。我们把普通 FIFO 队列换成优先级队列:

PriorityQueue<Task> queue = new PriorityQueue<>(
    Comparator.comparing(Task::getPriority).reversed()  // VIP 优先
);

队列改成优先级排序后,我们很快发现光改内存队列不够——消费者还是按顺序拉取,VIP 任务依然可能排在队尾。真正生效的是消费者侧也按优先级取任务,内存队列反而退化成临时缓冲。这个坑花了我们两天才定位到,优先级逻辑要贯穿生产到消费的整条链路,只改一头没用。

3. 失败任务自动重排

批量任务跑起来之后,最怕的是”卡死”任务默默消耗资源,用户却看不到任何异常提示。我们加了定时扫描,把超过 30 分钟还没跑完的子任务重新入队:

@Scheduled(cron = "0 */10 * * * ?")
public void requeueStuckTasks() {
    List<EvalSubtask> stuck = subtaskService.listByStatus("running", 
        olderThan(Duration.ofMinutes(30)));
    stuck.forEach(s -> {
        s.setStatus("pending");
        s.setRetryCount(s.getRetryCount() + 1);
        mqSender.send(s);
    });
}

自动重排上线前有个设计分歧:重试要不要限次数?不加限制,一个永远失败的任务会每十分钟重排一次,把死信问题无限放大。我们加了 retryCount 字段,超过三次就转人工处理队列。后来线上跑了大半年,重排救回来的任务远比误伤的多,最后 5% 的长尾问题基本消失。

六、优先级 P1:多租户

多租户是我们拖到 P1 才认真做的,原因是早期没有企业客户,提前做是过度工程。第一个付费企业客户出现后,独立部署和资源隔离成了签单的硬条件,我们才开始动这一块。

1. 数据隔离

第一步是给核心表加租户维度。我们选了”共享库 + 行级隔离”,在业务表上统一加 tenant_id,既能省运维成本,又能靠索引控制查询性能:

ALTER TABLE eval_batch ADD COLUMN tenant_id BIGINT NOT NULL;
CREATE INDEX idx_tenant ON eval_batch(tenant_id, created_at);

所有查询强制带 tenant_id 过滤(MyBatis Interceptor 注入)。

数据隔离最怕的是”漏一两个查询没过滤”。我们做了两道保险:MyBatis Interceptor 统一注入过滤条件,同时定期跑脚本扫描全表 SQL,把没有租户条件的查询拉出来人工确认。即使这样,上线第一周还是抓到一条漏网之鱼,是报表模块里一段手写 SQL。多租户的回归测试必须全量跑,这句话我们是用事故换来的。

2. 资源配额

租户之间要互相不打扰,光隔离数据不够,还得隔离资源,否则一个租户的批量任务就能把整个平台的并发额度吃光。我们抽象了配额模型:

public class TenantQuota {
    private long maxSessionsPerDay;     // 单日评测数
    private long maxConcurrentCalls;    // 最大并发
    private BigDecimal maxDailyCost;    // 单日成本上限
}

超出配额返回 429。

配额模型的难点在”超出后怎么办”。我们选的是返回 429 加 Retry-After,而不是直接拒绝,这样客户端能感知到是临时限制,自动退避后可以继续。配额检查放在网关统一做,避免每个业务方法里复制一遍判断逻辑,也防止有人绕过个别入口。

3. 独立部署

部分企业客户对数据合规要求高,要求完全独立部署,核心代码和评测数据都不能出他们的机房。我们用 Docker Compose 和 Helm Chart 把整套平台打包成一键部署模板,平台核心代码开源 + 商业授权。

独立部署看着是运维活,实际是架构活。早期代码里写死了很多单实例假设,比如本地缓存、单机定时任务,打包出去要么跑不起来要么重复执行。我们花了两周把定时任务改成可配置开关、把本地缓存替换成 Redis,才让独立部署真正可交付。给客户的部署包如果开箱跑不通,签单谈判会很被动。

七、优先级 P2:AI 能力增强

P0 和 P1 解决的是”评测准不准、稳不稳”的问题,P2 开始往”评测还能评什么”扩展。这块不救火,但决定平台未来的想象空间,也是客户续费时最愿意听的新故事。

1. 多模态评测

文本评测之后,客户开始问能不能评图像、音频、视频,比如多模态模型的文生图质量、语音合成效果。我们给评委体系加了多模态接口:

public interface MultimodalJudge extends JudgeModel {
    JudgeResult scoreImage(ImageRequest req);
    JudgeResult scoreAudio(AudioRequest req);
    JudgeResult scoreVideo(VideoRequest req);
}

多模态的成本压力大头不在开发,而在评测时模型调用费会翻几倍。我们内部算过,同样规模的评测任务,切到多模态后成本是纯文本的三到五倍,所以这个方向必须和成本控制一起推进,否则评测平台自己先烧穿预算,功能做得再漂亮也落不了地。

2. RLHF 数据回流

LLM-as-Judge 是过渡方案,长期我们希望训练自有评分模型,把评测成本降下来。评测平台的天然优势是有海量用户对比投票数据,可以回流成训练集:

用户投票 v1 比 v2 好
   ↓
导出 (prompt, v1_output, v2_output, winner=v1) 数据集
   ↓
微调自有评分模型
   ↓
上线新评分模型,逐步替代 LLM-as-Judge

数据回流听着美好,落地要过数据质量关:用户投票里的噪声很大,直接拿去微调会学歪。我们加了一层清洗,只保留投票一致度高、评委置信度也高的样本,宁缺毋滥。这条链路我们只走通了前两步,微调那一步在规划中,但数据从今天开始就在沉淀。

3. 实时排行榜

评测结果的表达方式上,我们参考了 LMSys Chatbot Arena 的思路,做实时排行榜,让用户直观看到不同模型在同一批题上的表现对比:

排行榜
├─ 综合得分
├─ 代码能力
├─ 创意能力
├─ 中文能力
└─ ...

排行榜看起来简单,坑在”实时”两个字。直接全量重算撑不住,我们做成定时增量计算,再配合 CDN 缓存。榜单上线后成了运营的抓手,新模型发布时用户会主动来看排名变化,平台在社区里的讨论度也上来了。

八、优先级 P2:可观测性增强

评测平台的观测难点在于:一次评测横跨模型、网络、队列、评分器多个环节,出问题不知道卡在哪。可观测性这块我们分成用户行为和异常告警两半,一个管”用户体验”,一个管”故障发现”。

1. 用户行为分析

成本仪表盘上线后,ClickHouse 里已经有流水数据,我们把前端埋点也接进来,打通用户行为链路,看用户到底卡在哪个环节:

track('eval.create', {
    sessionId, modelCount, questionLength
})
track('eval.complete', {
    sessionId, durationMs, totalCost
})

埋点进 ClickHouse,做用户画像与漏斗分析。

埋点的教训是”先定指标再埋点”。我们早期埋了二三十个事件,最后发现一半没人看,还白白增加前端体积。后来按转化漏斗反推,只保留真正进入分析模型的几个事件,指标才开始发挥价值。埋点这事,量多不等于价值多,能回答业务问题的才是好埋点。

2. 异常智能告警

传统告警是”阈值触发”,评测平台大量错误是语义性的,比如评分 prompt 输出格式崩了、回答内容被过滤,这种用固定规则很难覆盖。我们尝试让 LLM 做错误日志的根因分析,先把可能性圈出来:

@Scheduled(cron = "0 */30 * * * ?")
public void analyzeErrors() {
    List<ErrorLog> errors = errorLogService.recent(Duration.ofHours(1));
    String summary = llmClient.complete(
        "分析以下错误的根因和影响:\n" + 
        errors.stream().map(ErrorLog::getMessage).toList());
    alertService.send(summary);
}

这个方案效果时好时坏,坏在 LLM 对堆栈里的上下文敏感,日志太少时分析结论等于废话。我们把告警定位成”人审辅助”,不是”人审替代”,值班人先看 LLM 摘要再决定是否深入。它也暴露了一个问题:LLM 告警依赖日志质量,日志治理得先于告警优化,顺序不能反。

九、优先级 P3:技术债清理

P3 是最容易无限期拖延的一档,我们给它定了边界:不追求一次到位,只要求每个版本顺带还一点,让技术债保持在一个不咬人的水平。

1. 拆分巨石应用

最早所有功能都在一个 monolith 里,部署一次要十几分钟,改一行计费逻辑要连带重启整个评测服务,线上风险被放大。我们按边界拆服务:

当前 monolith
   ↓ 拆
├── llm-gateway        (模型接入)
├── eval-service       (评测核心)
├── billing-service    (计费)
├── notification-svc   (通知)
└── report-service     (报告生成)

拆分最疼的是跨服务事务。计费、评测、通知之间有强一致需求,拆开之后我们引入了 Saga 补偿,写了不少回滚代码。如果能重来,我们会把拆分顺序反过来,先拆最容易独立、依赖最少的计费和通知,评测核心最后动,这样每步都能独立上线验证,风险小得多。

2. 切换到 DDD

代码分层从 Controller/Service 一路堆下来,业务规则散落在各个 Service 里,改一处要翻三四个文件,新人上手更是无从下手。我们逐步改成限界上下文结构:

bounded context:
├── evaluation
│   ├── domain/         (业务模型)
│   ├── application/    (用例)
│   ├── infrastructure/ (DB/MQ)
│   └── interfaces/     (REST API)

DDD 改造对我们的价值不在”领域驱动”这个概念,而在强制我们回答”这段逻辑属于哪个上下文”。一开始业务归属模糊的地方会卡壳,讨论清楚之后,代码的修改范围反而变小了。它是慢功夫,但做完之后每个需求都能预估出准确的改动面。

3. 自动化测试覆盖率

测试是技术债里最需要耐心的一块,评测平台的评分链路和调度逻辑尤其需要回归保护。我们用三个月把覆盖率慢慢拉上来:

层级 当前 目标
单元测试 35% 70%
集成测试 10% 50%
E2E 5% 关键流程

覆盖率不能只看数字,得看测的东西对不对。我们把力气花在评分链路和调度这两个核心域,边缘模块保持低覆盖率也能接受。关键是别再让核心域回归时裸奔,多租户的隔离逻辑上线后,这套测试就成了改动的安全网。

十、人员与节奏建议

平台演进不能”all in”——这是我们从一次失败发布里学到的。那次四个模块一起改,结果互相打架,回滚了三次。后来我们定了节奏,让优化有序推进而不是一窝蜂:

  • 4 人团队,分两组:P0(2 人) + P1(2 人);
  • 季度发布一次大版本;
  • 每月 release branch 一次;
  • 紧急 hotfix 24h 内。

节奏的价值是让团队有可预期的呼吸感。P0 组保证评分和成本不出问题,P1 组持续推进体验功能,两组的产出互不阻塞。后来证明,这种”两条腿走路”比所有人扑在同一件事上效率高,也减少了互相等待的时间。

十一、风险与权衡

每项优化都有代价,动手前先把风险摆上台面,比事后救火划算。我们内部做了一版风险对照表:

优化 风险
多租户 数据隔离 bug 导致泄漏,回归测试要全量
LLM 网关 引入新组件,单点是新的故障源
多模态 模型成本翻 3-5 倍
拆服务 跨服务事务变难,需要 Saga

我们的应对原则是:高风险优化必须有明确的止损点。比如 LLM 网关先做高可用部署,不做单节点;多租户先上免费试用版,等数据隔离的回归跑顺了再开放正式版。优化不是做不做的问题,是怎么把风险控制在可承受范围内的问题,这也是整个演进计划里贯穿始终的判断标准。

常见问题(FAQ)

Q1:最值得优化的方向?

LLM 网关和成本透明化,回报率最高。

Q2:要不要重写?

不建议。平台 4 万行代码重写成本 6 个月,渐进式重构即可。

Q3:什么时候考虑多租户?

有第一个企业客户付费时再做。提前做是过度工程。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
其AI的头像其AI普通用户

相关推荐

返回顶部