下面基于通用工程实现推演一个 AI 大模型评测平台的后端骨架:它用 Spring Boot 分层架构,由数据集管理、评测调度、模型接入、评分引擎、报告生成五个核心模块组成;一次评测任务经调度器入队、Worker 异步消费、结果回流落库,前端通过状态接口读取进度。本文不含该平台的真实源码,所写为行业常见实现模式的合理展开,落地时按团队现状调整。
一、整体分层与核心模块
评测平台按职责切成五块,彼此通过接口与消息解耦:
| 模块 | 职责 | 关键依赖 |
|---|---|---|
| 数据集管理 | 管理数据集版本与用例 | PostgreSQL、对象存储 |
| 评测调度 | 任务入队、状态流转 | 消息队列、调度器 |
| 模型接入 | 屏蔽各家 API 差异 | 统一模型客户端 |
| 评分引擎 | 计算指标、聚合结果 | 规则评分 / LLM 评分 |
| 报告生成 | 产出对比图表与导出 | 前端、文件存储 |
分层上沿用 Spring Boot 的 controller → service → repository 结构,把”调度”与”接入”做成独立 service,便于横向扩容 Worker。
二、核心模块的协作链路
一次批量评测从提交到出报告,按以下顺序流动:
- 前端提交评测任务,包含数据集 ID、模型清单、评分配置;
- 调度模块创建任务记录,状态置为
QUEUED,写入消息队列; - Worker 拉取任务,逐用例调用模型接入层,状态转
RUNNING; - 每个用例返回后交给评分引擎计算指标,结果写回数据库;
- 全部用例完成,报告模块聚合数据,状态置
COMPLETED; - 前端轮询状态接口拿到进度,完成后拉取报告。
2.1 任务状态机
任务在 QUEUED → RUNNING → COMPLETED / FAILED 间流转,单用例失败不阻断整轮,仅记录失败比例,超过阈值才把整轮标为 FAILED,保证长批量任务可断点续跑。
三、任务调度与异步解耦
调度器只负责派发,不阻塞在模型调用上。下面是一段调用入口的 Java 代码:
@Service
public class EvalScheduler {
@Autowired private EvalTaskRepository taskRepo;
@Autowired private TaskQueue taskQueue;
public EvalTask submit(EvalRequest req) {
EvalTask task = new EvalTask();
task.setDatasetId(req.getDatasetId());
task.setModels(req.getModels());
task.setStatus(Status.QUEUED);
taskRepo.save(task);
taskQueue.enqueue(task.getId()); // 投递到消息队列
return task;
}
}
Worker 在独立进程或线程池里消费,按用例维度执行并回写:
@Component
public class EvalWorker {
@Autowired private ModelClient modelClient;
@Autowired private ScoringEngine scoring;
@Autowired private EvalCaseRepo caseRepo;
@RabbitListener(queues = "eval.tasks")
public void run(Long taskId) {
for (EvalCase c : caseRepo.byTask(taskId)) {
String output = modelClient.call(c.getPrompt());
scoring.score(taskId, c.getId(), output);
}
}
}
四、评测数据与结果存储
数据集按版本存哈希,保证可复现;每轮运行保存输入、模型配置快照、评分配置快照,用例结果存原始输出与指标。报告文件放对象存储,数据库只留路径与哈希,避免大对象撑爆表。这样的存储模型让”同一数据集换模型重跑”能直接做回归对比。
五、把推演落地时的注意点
模型接入层要统一超时与重试(指数退避应对 429/5xx);Worker 数随模型并发上限伸缩,避免打满下游配额;评分区分确定性指标(精确匹配、相似度)与 LLM 评分(作为裁判),后者需固定裁判模型版本以保证可比。以上为通用模式,真实系统以团队实际代码为准。
常见问题(FAQ)
Q1:评测平台一定要用消息队列吗?
小数据集同步跑即可,批量场景用队列解耦更稳。
Q2:单用例失败要整轮重来吗?
不必,记录失败比例,超阈值才标整轮失败。
Q3:模型接入层为什么独立成模块?
屏蔽各家 API 差异,换模型不改动调度与评分代码。