博思AI智能体 Multi-Agent 并行工作流升级实录:让复杂任务“拆得开、扛得住、丢不了”

核心结论:复杂任务从“单 Agent 串行”全面升级为 TaskPlanner → RabbitMQ → 双实例 Worker 并行 → 收敛压缩的完整编排链路。全链路测试 PASS=12 / FAIL=0,架构评估得分提升至 92/100。

一、破局:一个复杂任务为什么这么慢

过去,面对诸如“帮我写一份公司数字化转型实施报告”这类复杂请求,博思AI走的是单 Agent 串行模式:一次 LLM 长回答从头到尾由同一个上下文生成。这种模式代价高昂:
  1. 性能恶化:上下文越来越长,首 token 延迟与总耗时呈线性恶化。
  2. 容错率低:9 个业务维度(流程改造/技术选型/数据治理等)挤在一次生成里,任何一块失败整篇作废。
  3. 输出失控:动辄几千字的输出没有结构也没有重点。
于是,这一晚我们集中火力回答了三个核心问题:能不能拆?拆了会不会被打爆?挂了怎么办?最终的答案是:能拆、能扛、能自愈。

二、核心链路:一次请求的“奇幻漂流”

我们将复杂任务的执行过程重构为一条严密的编排链路:
请求 → TaskPlanner(意图判断 → LLM 拆解 ≤9 个子任务)
→ 子任务写 Redis meta + 入队 RabbitMQ(agent.task)
→ 双实例 SubAgentWorker ×10(prefetch=1 + manual ack)并行消费
→ 子结果写 Redis hash(幂等覆盖 + received 计数)
→ SubTaskResultCollector 轮询收敛 → LLM 汇总压缩 ≤1000 字
→ 全局并发闸门:Redis Lua 原子计数 agent:workflow:active ≤8
→ WorkflowReconciler 30s 对账兜底(结果齐但未收敛 → 重新触发收敛)
这条链路分为三个关键阶段:
拆解(TaskPlanner):先做意图判断,简单问题(如“你好”)不拆解,直接走普通流式;复杂/跨域问题才由 LLM 拆成最多 9 个独立子任务(max-tasks=9),每个子任务自带独立上下文入队。
并行执行(SubAgentWorker):双 core 实例(9090/9092)各 10 个 Worker 并发消费。改造后采用 manual ack + prefetch=1——Worker 忙时不拉消息,RabbitMQ 按真实处理能力公平分发,彻底杜绝“一个慢 Worker 拖住一堆任务”。
收敛(SubTaskResultCollector):轮询 Redis 中已收到的子结果,received 计数到齐后,用轻量模型(qwen-turbo)把 N 个结果汇总成一篇 ≤1000 字(converge-max-chars=1000 硬截断)的结构化回答,写回 DB 终态 done。

三、过载保护:100 并发进来会怎样

拆解能力放开了,闸门也必须跟上。最初我们用本地 Semaphore 限流,但双实例各持一把锁,9092 释放 9090 的许可时会导致计数错乱。为此,我们改为一套 Redis Lua 原子限流:
Key:agent:workflow:active,INCR/DECR 由 Lua 脚本保证原子。
上限:max-concurrent=8,双实例共享同一额度。
超限策略:降级普通流程(不排队、不拒绝)——超出的请求走原来的单 Agent 路径兜底,保证可用性。
实测效果(T03,20 并发):并行工作流数精准控制在 8(全局限流生效),降级数为 12。20 个请求全部有明确去向,无一丢失。也就是说:100 并发 → 8 个并行工作流 + 92 个降级,系统不会雪崩,服务始终在线。

四、可靠投递与故障兜底:子任务一个都不能丢

并行拆解最怕“拆了 9 个,只回来 8 个”。为此我们实施了双保险机制:
消息不丢:SubAgentWorker 成功则 basicAck,失败则 basicNack(requeue=false) 防止死循环重投;SubTaskResultCollector 失败则 requeue=true 重新入队,依赖 Redis 幂等覆盖避免脏数据。
对账兜底:如果承载收敛的实例恰好在结果到齐前崩溃,plan 会永久卡住。我们落地了 WorkflowReconciler(收敛对账),每 30s 扫描 Redis 中的 plan。若满足 received ≥ total 且无 converged 标记,即重新触发收敛。通过三层去重(converged 标记 → DB 状态 → SETNX 锁)防重复收敛,把卡住的 plan 救活。
踩坑洞见:仅靠 DB 状态去重会误判——内部 API 请求根本没有 DB 行,会把正常 plan 当成“卡住”反复重收敛;必须以 Redis 的 converged 标记为主判据。

五、测试验证:12/12 全绿

我们编写了完整测试套件 scripts/test-multiagent-suite.sh,将闭环钉死在代码里:
T01:全链路(拆解 → 分发 → 并行 → 收敛 → DB 终态,答案 ≤1000 字)
T02:简单问题不拆解(普通流式兜底)
T03:20 并发 → 8 并行 + 12 降级(轮询等全部终态后统计)
T04:子任务零丢失(等全部执行完成后断言,规避时序误报)
T05/T06:输出压缩 ≤1000 + DB 终态 done
方案 A:伪造卡住 plan → 30s 内 Reconciler 触发恢复;删锁不再重复触发 ×2
套件结果:PASS=12 / FAIL=0;快速回归(--quick)PASS=8 / FAIL=0,确认对既有链路无影响。

六、避坑指南:当晚的真实教训

  1. Semaphore 跨实例错配:换 Redis Lua 原子计数,从根上消除。
  2. Redis 环境差异:Redis 实际跑在主服务器(无密码),不在 Milvus 服务器;-a 参数反而报 AUTH 错。
  3. Nacos 网络与配置:公网 8848 被防火墙挡,需本机 curl 127.0.0.1:8848;且 group 是 CHAT 不是 DEFAULT_GROUP。
  4. UA 黑名单拦截:测试需伪造浏览器 UA + 随机 X-Forwarded-For(否则 403 / 单 IP 限流)。
  5. 日志追踪断层:收敛日志没有 req_id,测试要先经 req_id 反查 planId 再匹配。
  6. 统计污染与时序误报:未加 RID_PREFIX 隔离会把历史日志统计进来;任务还在执行中就断言会误报 → 完整前缀隔离 + 轮询等待全部终态。
  7. 历史遗留数据干扰:部署 Reconciler 后误触发 4 个历史遗留 T03 plan → 加 converged 标记去重 + 清理 27 个遗留 plan 键。

七、配套同步与遗留风险

配置落位:Nacos chat-core-prod.yml(group=CHAT)新增 reconcile-interval-ms: 30000;max-tasks=9 / max-concurrent=8 / converge-max-chars=1000 / converge-model=qwen-turbo,listener 10/20、prefetch=1 就位。文档同步更新 10 个文件共 314 行。架构评估综合得分由 91 提升至 92(架构设计 14/15 → 14.5/15)。
遗留风险与下一步:
  1. 工作流状态依赖 Redis:Redis 故障则工作流中断。下一步计划 Redis AOF 持久化 + 高可用,Reconciler 增加 DB 源对账。
  2. Worker 失败不重试:nack(requeue=false) 即终态。下一步计划失败子任务进死信队列 + 指数退避重试。
  3. 服务器单点 + 内存紧张:双服务器均为单点,可用内存 ~500MB。下一步需第二台服务器 + 备份演练,扩容/压缩 JVM 堆。
 
posted on 2026-08-13 07:10  溯衍  阅读(13)  评论(0)    收藏  举报