跳至正文
来两杯美式
返回

Agent 评测工程化(八):不用消息队列,也能跑长任务

By 来两杯美式
发布于

批量 Agent 评测是一个很典型的长任务。

用户上传一份 Excel 或 CSV,里面可能有几百到几千条对话。系统要做数据质检、会话上下文构建、输出解析、LLM-as-judge 逐条评测、坏行修复、问题聚类、行动项生成和报告落库。中间任何一步都可能慢,任何一次模型调用都可能失败。

这类任务当然可以上消息队列。但在中等吞吐的内部评测平台里,第一选择不一定是 Kafka 或 RabbitMQ。很多时候,一张设计清楚的数据库任务表,加上事务原子领取、阶段状态、覆盖率和失败恢复,就足够稳。

这一篇从 Rubric 评测任务的角度,讲不用独立消息队列也能跑长任务的设计。

一、为什么评测不能放在 HTTP 请求里

同步 HTTP 请求适合短操作,不适合评测任务。

评测任务的耗时不可控:文件越大,样本越多;规则越复杂,prompt 越长;模型接口越慢,整体耗时越长。如果把整个流程放在一次请求里,浏览器会超时,网关会超时,用户也不知道任务到底执行到哪一步。

正确交互应该是:

上传文件 → 创建任务 → 立即返回 taskId → 后台 Worker 执行 → 前端轮询进度或报告

上传接口只负责三件事:鉴权、解析文件、落库任务。后续评测由 Worker 接管。

这样用户可以关闭页面再回来,任务状态仍然在数据库里;某个阶段失败,也能展示明确错误,而不是让请求直接断掉。

二、任务表就是轻量队列

一张任务表可以承担队列的核心职责:保存任务、表达状态、支持领取、记录进度。

任务可以有这些字段:

id
ownerUserId
fileName
status
stage
rubricKey
rubricVersion
rubricSnapshot
model
totalRows
totalInstances
diagnosedInstances
failedInstances
coverageRate
warnings
errorMessage
startedAt
completedAt
createdAt
updatedAt

其中 statusstage 要分开。

status 表示任务终态或运行状态:

stage 表示当前执行到哪一步:

这两个字段不能混在一起。一个任务可能在 DIAGNOSIS 阶段失败,也可能在 ACTION_GENERATION 阶段部分失败后仍然生成报告。status 告诉用户结果是否可用,stage 告诉系统和排障人员流程停在哪里。

三、原子领取避免多实例重复消费

生产环境里可能有多个应用实例,每个实例都跑内嵌 Worker。如果没有领取机制,同一个任务可能被多个 Worker 同时执行。

数据库任务表可以通过事务和条件更新实现原子领取。

伪流程:

begin transaction
  找到最早的 PENDING 任务,或超时的 RUNNING 任务
  update where id = ? and status = 原状态
  如果更新行数为 1,领取成功
commit

这个 compare-and-set 语义很重要。多个 Worker 同时看到同一个任务时,只有一个能更新成功,其他 Worker 会拿到 0 行更新,然后继续找下一个任务。

如果任务处于 RUNNINGupdatedAt 很久没变化,可以认为 Worker 异常退出,将它重新纳入领取候选。这个机制相当于轻量租约。

这套方案的好处是简单:任务状态、进度、归属和数据都在同一个数据库事务边界里,不需要处理消息队列和数据库之间的双写一致性。

四、任务开始时固定 Rubric 快照

Rubric 评测任务有一个普通异步任务没有的要求:规则必须可复现。

任务创建时,应该读取当前默认发布版本的 Rubric,并把它的完整 snapshot 保存到任务上。后续执行、报告、导出都使用这份 snapshot,而不是运行时再去读最新规则。

create task
  → load published rubric version
  → save rubricKey / rubricVersion / rubricSnapshot
  → save model / promptVersion / analysisVersion
  → save rows

这样即使评测过程中规则包发布了新版本,也不会影响正在跑的任务。三个月后打开旧报告,也能解释当时每个分数和红线的判断依据。

对评测系统来说,版本快照不是审计锦上添花,而是结果可信度的一部分。

五、行级诊断状态比任务级状态更重要

批量评测最容易犯的错,是只在任务级记录成功或失败。

真实情况更细:一个任务有 1000 条样本,其中 980 条诊断成功,20 条模型输出坏了或接口失败。此时任务不应该整体失败,也不应该假装全部成功。

所以需要为每条可评测样本建立诊断记录:

rowId
taskId
status
attemptCount
intentKey
score
redFlagTypes
referenceBasis
questionsCheck
errorMessage
completedAt

任务进入诊断阶段前,先为所有候选样本初始化 PENDING 记录。每个批次完成后,校验通过的行更新为 COMPLETED,失败行更新为 FAILED,同时刷新任务的 diagnosedInstancesfailedInstancescoverageRate

行级状态带来三个好处:

  1. 好行可以先保存,不被坏行拖累;
  2. 失败行可以单独重试;
  3. 覆盖率可以实时更新。

这也是长任务可靠性的基础。

六、并发要受控,不要把模型接口打爆

评测任务最慢的部分通常是 LLM 调用。为了加速,需要并发;为了稳定,又不能无限并发。

可以拆成两个维度配置:

batchSize:每次请求放多少条样本
concurrency:同时跑多少个批次

小 batch 的优点是隔离性好,一条坏输出影响范围小;缺点是请求次数多。大 batch 的优点是吞吐高;缺点是响应更长、更容易出现结构化错误。Rubric 评测因为规则多、输出严格,通常更适合小 batch 加受控并发。

并发上限也要保守。模型接口有速率限制,应用实例也有资源限制。把并发开太高,表面上加速,实际可能造成超时、重试、失败率上升,整体更慢。

比较稳的策略是:默认小批次,最大并发设硬上限,通过环境变量调节,运行日志记录每批耗时和失败率,再逐步校准。

七、覆盖率决定报告是否可用

评测任务不一定 100% 成功。系统要决定什么时候可以出报告,什么时候只能提示数据不足。

覆盖率可以这样计算:

coverageRate = diagnosedInstances / totalInstances

然后定义终态:

终态含义
COMPLETED覆盖率 100%
PARTIAL覆盖率达到阈值,但有失败行
INSUFFICIENT_DATA覆盖率低于阈值,不输出普通报告

阈值可以配置。比如内部试用阶段可以低一些,生产报告可以要求更高。

这里最重要的是文案边界:覆盖率表示报告可信度,不表示 Agent 质量。覆盖率低只能说明诊断完成得少,不能说明 Agent 答得差。

部分完成报告也要显式提示:当前结论基于已完成的诊断样本,请结合覆盖率判断。

八、失败恢复要围绕阶段和行展开

长任务失败大致分三类。

第一类是任务级失败,比如文件解析失败、Rubric 快照缺失、数据库不可用。这类失败应该让任务进入 FAILED,并保存错误信息。

第二类是阶段级失败,比如聚类或报告生成失败。诊断明细可能已经存在,但最终报告不可用。这时 stage 能帮助定位停在哪一步。

第三类是行级失败,比如某些样本模型输出无法修复。这类失败不应该拖垮整个任务,只影响覆盖率。

重试策略也要分层:

这要求中间结果必须及时落库,而不是等整条流水线结束再一次性保存。长任务的可靠性,很多时候就来自「走一步存一步」。

九、报告生成不应该依赖内存状态

任务跑完后,报告应该完全由数据库状态生成,而不是依赖 Worker 内存里的临时对象。

原因很简单:Worker 可能重启,用户可能过几天才打开报告,任务可能由一个实例执行、另一个实例服务页面请求。如果报告依赖内存,系统就不可靠。

报告生成应该只依赖这些持久化数据:

这样任何时候重新打开任务,都能重建同一份报告。需要缓存导出内容时,也应该保存为 export snapshot,而不是靠运行时临时拼。

十、什么时候真的需要消息队列

不用消息队列不是说永远不用。

当系统出现这些情况时,就可以考虑引入 MQ:

在此之前,数据库任务表往往更适合早期和中等规模内部平台。它简单、可查、可事务化,本地开发也轻。评测系统真正复杂的地方是 Rubric、诊断协议、失败隔离和报告闭环,不一定要把基础设施复杂度提前拉满。

十一、总结

Agent 评测长任务总结图:数据库任务表、原子领取、阶段状态、行级诊断、覆盖率和失败恢复支撑批量评测任务

批量 Agent 评测慢、贵、会失败,所以它必须被当成长任务设计。

但长任务不等于一上来就需要消息队列。对于中等吞吐的评测平台,一张数据库任务表,加上事务原子领取、阶段状态、行级诊断、覆盖率、失败恢复和持久化报告,已经能支撑很长一段路。

这篇的核心不是反对 MQ,而是强调任务语义先于架构热闹。先把状态机设计清楚,把每一步结果落库,把失败边界划明白,再决定是否需要更重的队列基础设施。

评测任务慢且会失败,状态机比热闹架构更重要。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.把存量系统交给 Agent,先想清楚 API、Skill、MCP 各是什么
  2. 02.存量系统 Agent 化,物理上到底怎么「接」
  3. 03.单点跑通之后,怎么把 Agent 能力沉淀成全公司可复用
  4. 04.DeepSeek Harness:把 Agent 宿主变成可组合的基础设施,企业能拿它做什么
  5. 05.一个系统,两种访客:给浏览器和 Agent 设计同一套身份体系
  6. 06.定时是 Agent 平台的另一半:Trigger/Action Registry 与调度原语分层
  7. 07.Agent 刚才干了什么:审计、调用日志与敏感数据豁免
  8. 08.提示词安全攻防全解析:越狱、注入与信息泄露
  9. 09.给生产 Agent 做体检(一):只用对话文本的黑盒诊断工作流
  10. 10.给生产 Agent 做体检(二):诊断之前,先定义人口——数据质检与风险筛选
  11. 11.给生产 Agent 做体检(三):让 LLM 稳定输出结构化诊断——严格 JSON Schema 实践
  12. 12.给生产 Agent 做体检(四):LLM 输出不完美怎么办——归一化、一次修复与失败隔离
  13. 13.给生产 Agent 做体检(五):不要消息队列——基于数据库的任务编排与可靠性
  14. 14.给生产 Agent 做体检(六):从诊断明细到 PM 能用的报告——评估结果的产品化
  15. 15.AI Agent:从工具到同事,中间隔着一层「自主性」
  16. 16.Agent 评测工程化(一):为什么不能只看平均分
  17. 17.Agent 评测工程化(二):Rubric 不是 Prompt,而是可执行的质量协议
  18. 18.Agent 评测工程化(三):从低分样本到问题簇
  19. 19.Agent 评测工程化(四):红线、一票否决与 1-5 分
  20. 20.Agent 评测工程化(五):让 LLM-as-judge 稳定输出结构化结果
  21. 21.Agent 评测工程化(六):评测前,先把 Agent 输出拆开
  22. 22.Agent 评测工程化(七):从评分报表到 PM 决策工作台
  23. 23.Agent 评测工程化(八):不用消息队列,也能跑长任务
  24. 24.Agent 评测工程化(九):把一次性评测变成持续优化体系

上一篇
Agent 评测工程化(九):把一次性评测变成持续优化体系
下一篇
Agent Skills 最佳实践:从评测、结构拆分到安全审查