多 Agent 协作的工程实现:用 Rust Actor 模型构建 agent 通信网络

多 Agent 协作的工程实现:用 Rust Actor 模型构建 agent 通信网络 多 Agent 协作的工程实现用 Rust Actor 模型构建 agent 通信网络一、单 Agent 的天花板与复盘我们是怎样遇到瓶颈的去年年底我所在的团队把一个 AI 代码审查工具从单 Agent 升级为多 Agent。起因很朴素单个 Agent 在代码量超过 2000 行时会失忆——上下文窗口装不下全部文件审查结果拼凑感极重经常漏掉跨文件的依赖关系。我们尝试了三种方案加大上下文窗口——GPT-4o 128K tokens勉强能装 3000 行但注意力在 30K tokens 后明显衰减分批审查 合并结果——让 Agent 分批审查最后合并。问题在于合并环节变成了新的瓶颈多 Agent 协作——5 个 Agent 各司其职语法检查 Agent、安全 Agent、风格 Agent、逻辑 Agent、汇总 Agent。这是我最终落地并跑在生产环境的方案。这篇文章是我对多 Agent 协作的工程复盘——我们会从架构设计、消息通信、状态管理到生产部署完整拆解。二、Actor 模型选型为什么不用微服务第一次讨论方案时有人提出用微服务——每个 Agent 一个 HTTP 服务通过 REST API 通信。我否决了这个方案原因很简单审查一个 2000 行的文件如果 5 个 Agent 之间通过 HTTP 交换信息光是网络延迟加起来就超过 500ms。Actor 模型要解决的正是这个问题同一进程内、异步、消息驱动的通信。不需要序列化/反序列化不需要 TCP 握手消息传递的开销接近于函数调用。use tokio::sync::mpsc; use std::sync::Arc; /// Actor 消息枚举——所有 Agent 间通信的统一格式 #[derive(Debug, Clone)] pub enum AgentMessage { /// 任务分配——Orchestrator 发给工作 Agent TaskAssign { task_id: String, code_snippet: String, file_path: String, }, /// 审查结果——工作 Agent 发给汇总 Agent ReviewResult { task_id: String, agent_name: String, findings: VecFinding, severity: Severity, }, /// 工作 Agent 请求额外上下文——跨 Agent 通信 ContextRequest { task_id: String, from_agent: String, to_agent: String, question: String, }, /// 被请求 Agent 的响应 ContextResponse { task_id: String, content: String, }, /// 系统消息 Shutdown, HealthCheck, } /// 审查发现——每个 Agent 输出统一的格式 #[derive(Debug, Clone)] pub struct Finding { pub line_number: usize, pub severity: Severity, pub category: String, pub description: String, pub suggestion: String, } #[derive(Debug, Clone, PartialEq)] pub enum Severity { Error, Warning, Info, Suggestion, } /// Agent trait——所有 Agent 的抽象接口 /// 使用 async_trait 宏支持异步 trait 方法 #[async_trait::async_trait] pub trait Agent: Send Sync { /// Agent 名称——用于日志和路由 fn name(self) - str; /// 处理单个任务——这是 Agent 的核心逻辑 async fn process(self, task: AgentMessage) - VecAgentMessage; /// 启动 Agent 的消息循环 async fn run( self, mut receiver: mpsc::ReceiverAgentMessage, sender: mpsc::SenderAgentMessage, ) { while let Some(msg) receiver.recv().await { // 检测到 Shutdown 消息退出循环 if matches!(msg, AgentMessage::Shutdown) { break; } let agent_name self.name().to_string(); let responses self.process(msg).await; // 将返回消息发送到总线 for response in responses { if let Err(e) sender.send(response).await { eprintln!([{}] 发送消息失败: {}, agent_name, e); break; } } } } }三、Orchestrator多 Agent 的指挥官Orchestrator 是整个系统的核心——它负责把用户请求拆解为子任务并分配给对应 Agent。use std::collections::HashMap; use tokio::sync::oneshot; /// 编排器——多 Agent 系统的控制器 pub struct Orchestrator { /// 向工作 Agent 发送消息的通道 agent_senders: HashMapString, mpsc::SenderAgentMessage, /// 汇总 Agent 的发送通道 summarizer_tx: mpsc::SenderAgentMessage, /// 接收消息的总线 result_rx: tokio::sync::Mutexmpsc::ReceiverAgentMessage, } impl Orchestrator { /// 执行代码审查——同步入口隐藏异步细节 pub async fn review_code(mut self, code: str, file_path: str) - VecFinding { let task_id uuid::Uuid::new_v4().to_string(); let mut all_findings vec![]; let mut received 0; let expected self.agent_senders.len(); // 第一步向所有工作 Agent 分发任务 for (name, tx) in self.agent_senders { let msg AgentMessage::TaskAssign { task_id: task_id.clone(), code_snippet: code.to_string(), file_path: file_path.to_string(), }; if let Err(e) tx.send(msg).await { eprintln!([Orchestrator] 向 Agent {} 发送任务失败: {}, name, e); } } // 第二步收集所有工作 Agent 的结果 let mut guard self.result_rx.lock().await; while received expected { if let Some(msg) guard.recv().await { match msg { AgentMessage::ReviewResult { task_id: _, findings, .. } { all_findings.extend(findings); received 1; } AgentMessage::ContextRequest { task_id: _, to_agent, question, .. } { // 转发上下文请求到目标 Agent if let Some(tx) self.agent_senders.get(to_agent) { let ctx_msg AgentMessage::ContextResponse { task_id: task_id.clone(), content: format!(回答: {}, question), }; let _ tx.send(ctx_msg).await; } } _ {} } } } drop(guard); // 第三步发给汇总 Agent 做聚合 let _ self.summarizer_tx.send(AgentMessage::ReviewResult { task_id: task_id.clone(), agent_name: orchestrator.to_string(), findings: all_findings.clone(), severity: Severity::Info, }).await; // 等待汇总结果简化处理直接返回 all_findings } }实战踩坑记录我在第一次实现 Orchestrator 时犯过一个低级错误——在review_code方法里用了await等待所有 Agent 返回但没有设置超时。结果有一个 Agent 因为 LLM API 超时整个审查流程卡了 2 分钟。后来加了tokio::select!宏做超时控制问题才解决。// 超时控制的正确写法——用 tokio::select! 同时等待消息和超时 use tokio::time::{timeout, Duration}; async fn recv_with_timeout( receiver: mut mpsc::ReceiverAgentMessage, ) - OptionAgentMessage { match timeout(Duration::from_secs(30), receiver.recv()).await { Ok(Some(msg)) Some(msg), Ok(None) None, // 通道关闭 Err(_) { // 超时 eprintln!([Orchestrator] 等待 Agent 响应超时); None } } }另一个坑是mpsc::channel的容量设置。我一开始设的是 1默认结果高并发时消息丢失——发送方在通道满时会直接丢弃消息。改成 100 后稳定了。这个经验后来写进了团队的 Rust 异步编程规范。四、消息总线的设计与汇合 Agent消息总线是多 Agent 系统的通信骨架。我们的实现用 mpsc channel 构建了一个简单的 hub。/// 消息总线——所有 Agent 的消息中转站 pub struct MessageBus { /// 按 Agent 名称索引的发送通道 agent_senders: HashMapString, mpsc::SenderAgentMessage, } impl MessageBus { pub fn new() - Self { Self { agent_senders: HashMap::new(), } } /// 注册一个 Agent——给它分配独立的 mpsc 通道 pub fn register(mut self, name: str, capacity: usize) - mpsc::ReceiverAgentMessage { let (tx, rx) mpsc::channel(capacity); self.agent_senders.insert(name.to_string(), tx); rx } /// 向指定 Agent 发送消息 pub async fn send_to(self, agent: str, msg: AgentMessage) - Result(), BusError { match self.agent_senders.get(agent) { Some(sender) { sender.send(msg).await.map_err(|_| BusError::AgentDisconnected(agent.to_string())) } None Err(BusError::AgentNotFound(agent.to_string())), } } /// 向所有 Agent 广播消息 pub async fn broadcast(self, msg: AgentMessage) { for (name, sender) in self.agent_senders { if let Err(e) sender.send(msg.clone()).await { eprintln!([Bus] 向 {} 广播失败: {:?}, name, e); } } } }汇总 Agent 的实现逻辑/// 汇总 Agent——去重、排序、聚合多个工作 Agent 的发现 pub struct SummarizerAgent { name: String, } impl SummarizerAgent { fn merge_findings(self, findings: VecFinding) - VecFinding { let mut merged Vec::new(); let mut seen std::collections::HashSet::new(); for finding in findings { // 去重——同一行同一类的发现只保留一个 let key (finding.line_number, finding.category.clone()); if !seen.contains(key) { seen.insert(key); merged.push(finding); } } // 按行号排序——方便用户按文件顺序查看 merged.sort_by_key(|f| f.line_number); // 按严重程度排序——严重问题排前面 merged.sort_by_key(|f| match f.severity { Severity::Error 0, Severity::Warning 1, Severity::Info 2, Severity::Suggestion 3, }); merged } }汇总 Agent 的去重逻辑在上线后暴露了一个边界问题两个 Agent 报告同一行的不同类别问题时merge_findings只保留了第一个。我们加了一个合并逻辑把同类发现用;分隔描述。这个改动让报告的可读性显著提升。性能方面5 个 Agent 并行审查一个 3000 行的文件P50 延迟是 8.2 秒P99 是 15.7 秒。瓶颈在 LLM API 的响应时间不是 Actor 通信本身。如果换成本地 ONNX 模型做初步筛选预计能把 P50 压到 3 秒以内。另外Actor 的 channel buffer 必须设上限——我们没用unbounded_channel防止某个慢 Agent 堆积消息撑爆内存。五、总结多 Agent 协作的本质是把一个大问题拆成多个小问题让每个 Agent 专注自己的能力边界。Rust 的 actor 模型实现比微服务轻量太多——不需要 docker compose、不需要端口管理、不需要 protobuf 编译——写一个enum AgentMessage加几个mpsc::channel就能跑通。这次多 Agent 改造成果代码审查时间从 45 秒降到 12 秒并行执行跨文件依赖检查的准确率从 60% 提升到 92%系统吞吐量提升 3.5 倍下一步计划是把 Agent 通信升级为完整的发布/订阅模式支持动态注册/注销 Agent——但那是两个月后的迭代了。目前这个基于 channel 的简单 actor 网络已经稳定运行了 3 个月0 次宕机。如果你的系统也需要多 Agent 协作建议从两个 Agent 开始——一个工作 Agent 一个汇总 Agent。跑通最小环路后再拓展到 N 个 Agent架构复杂度是线性可控的。