我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

多 Agent 协作中的跨 Agent 与跨 Session 通信设计

多 Agent 协作中的跨 Agent 与跨 Session 通信设计 我在调一个多 Agent 协作任务时最先遇到的瓶颈不是单个模型能力不够而是 Agent 与 Agent 之间根本不“共享消息”。负责查资料的 Agent 把关键结论输出以后负责写报告的 Agent 像第一次听这件事同一用户在隔了几个小时后发起新一轮 SessionAgent 连自己之前说过什么都忘了。这些问题拆到最后都要落到两个能力上跨 Agent 通信和跨 Session 通信。“Session”在软件开发里并不新鲜。做 Web 时背过 cookie、session、token 的区别用 VSCode 调试设备时遇到过 “pending authentication: please accept debugging session on the device”在 WSL 环境也可能被 systemd user session 启动失败卡住过。它们都涉及同一件事一个带边界的上下文如何在正确的时间、正确的对象之间流转。到了 AI Agent 开发里问题变得更具体一个 Agent 的上下文边界是 SessionAgent 之间需要交换消息时间拉长后还需要在 Session 之间保留和恢复状态。我后来形成一个判断一个 Agent 项目到底是演示 Demo还是在真实场景里能稳定干活的系统不用看架构图画得多漂亮只看它怎么处理跨 Agent 和跨 Session 的信息流动。默认配置能跑的单个 Agent 并不稀罕难的是让多个 Agent 在多轮、多用户、长周期任务中不丢消息、不丢状态、不串权限。1. 先把 Session 和通信放回到 Agent 开发的真实位置1.1 跨 Agent 通信解决的是“空间上的隔阂”跨 Agent 通信指的是多个 Agent 在同一个任务或同一套工作流里交换信息。最典型的结构是A Agent 负责理解用户意图B Agent 负责执行业务动作C Agent 负责检查结果。这里有天然的“空间隔阂”——A 不知道 B 内部怎么处理B 不知道 A 有哪些限制。很多教程只会让你把工具调用写进一个 Agent 里看起来也能完成单个任务。但一旦任务被拆成“规划—执行—检查”三个角色你就必须在 Agent 之间建立清晰的消息通道。A 的输出要能让 B 直接用B 的执行结果要能让 C 验证C 发现的问题再回到 A 修正。这个过程的本质不是写几个函数互相调用而是定义清楚信息流动的方向、格式和边界。如果只靠提示词把上一个 Agent 的全文拼到下一个 Agent 的输入里短期能跑Agent 一多就会失控。信息越长噪声越大真正有用的动作和参数反而被淹没。1.2 跨 Session 通信解决的是“时间上的遗忘”跨 Session 通信解决的是另一类问题用户上一次对话里产生的状态如何带到下一次对话里。一个 Session 通常包含一次相对完整的交互过程。Session 结束时模型上下文中保存的那段“记忆”可能就被清空或不再复用。如果 Agent 每次面对新 Session 都是“冷启动”就会出现很尴尬的场景用户上午刚让 Agent 生成了月度预算下午问“那笔预算还剩多少”Agent 完全不知道他在说什么。这不是模型能力不够而是你根本没有把 Session A 的关键结果写到 Session B 能读取的位置。很多 Agent 项目在上线后被用户评价“聪明但健忘”原因就在这里。这类“时间上的遗忘”在工程里很常见。Web 端用 cookie、session、token 管理用户身份就是尽量让服务端能认出“这是同一个人”。Agent 开发里的跨 Session 通信则更进一步不光要认出同一个人还要知道这个人上次聊到了哪里、做了什么决定、哪些中间结果仍然有效。1.3 为什么项目演示阶段这些问题会被掩盖很多 Agent 项目在演示时非常顺利。因为演示通常只有一条话题线用户就坐在屏幕前所有上下文都还在同一个 Session 里模型可以随时从最近的对话历史里找信息。一旦切到真实场景情况会立刻变化用户可能在多个时间点提问中间隔了几小时甚至几天。问题可能分成多个模块由不同 Agent 分别处理。每个模块有自己的 Session 或上下文窗口。系统可能出现异常重试、超时、异步通知。用户身份和权限需要区分不能把 A 用户的 Session 状态给 B 用户。演示阶段的问题往往暴露不出来。等接上真实数据和多轮业务后“上次的状态在哪”“这个 Agent 是从哪里拿到这段上下文”就成了每天都会问的问题。2. 跨 Agent 通信的设计重点不是“协议”而是分层2.1 消息、状态、记忆不要让三种信息挤在一起很多跨 Agent 通信实现得混乱不是因为缺少通信库而是把三类信息混在了一起消息一次任务里临时传递的数据比如“用户要查订单号 12345 的状态”。状态当前任务进行到哪里了比如“订单已查询进入售后判断阶段”。记忆需要长期保留的语义信息比如“用户偏好用顺丰不喜欢电话联系”。如果你的通信内容永远是“一大段自然语言”这三层就无法区分。Agent B 收到 A 的结果时只能靠模型现场理解既浪费 token也容易出错。比较稳的做法是分开设计消息用结构化字段传递状态放到共享 Session 或状态容器里记忆单独走长期存储。前端开发里有个类似经验组件通信只靠父传子、子传父组件一多就会很痛苦最后往往要引入统一的状态管理。Agent 之间也一样。两个 Agent 可以直接传话三五个 Agent 还可以勉强用编排器串联等角色多了、任务变成动态拓扑就必须有一套清晰的状态和消息存放规则。2.2 两种基本通信模式直接调用与消息总线跨 Agent 通信最常见的两种模式各有适用场景。第一种是直接调用。编排器把 Agent A 的结果拿过来作为 Agent B 的输入。这种方式逻辑清楚适合固定流程。问题在于两个 Agent 会被“硬绑”在一起A 的输出格式一变B 就要跟着改如果中间还要插入新 Agent编排代码也要动。第二种是共享消息或事件总线。A 发出一条消息B 根据订阅关系决定是否处理。这种模式适合异步、多人协作、多任务并发。它的问题是你无法再靠读一遍代码就判断“谁在处理什么”必须依靠消息主题、事件类型、消费组这样的机制来管理。实际项目不一定非此即彼。固定链路里用直接调用复杂协作里用事件或队列组合使用也完全正常。关键是每次通信都要问一句这条消息是发给唯一接收者的还是可能被多个 Agent 关心2.3 容易踩到的三个问题格式、顺序和重试跨 Agent 通信最隐蔽的问题往往不在“通信建立”本身而在三个工程细节上。第一是输入输出格式不兼容。A 返回的是 Markdown 文本B 期待的是结构化 JSONA 用order_idB 用orderId。为了让 Agent 之间能协作你最好为每个 Agent 定义输入 schema 和输出 schema并在入口处校验。第二是消息顺序。一个 Agent 可能同时产出多个事件先更新了状态再抛出一个异常最后又发来一条成功消息。如果接收方按到达顺序处理很可能被“旧的成功”覆盖“新的失败”。这个时候要为主干消息加上序列号或时间戳并明确“过期消息不处理”。第三是失败重试。异步通信里消息可能丢失重发可能重复。如果业务动作是“创建订单”“发起退款”重复执行就会出事。通信层要做幂等控制用任务 ID 判断这条消息是否已经处理过处理过就直接返回原结果。3. 跨 Session 通信关键是让上下文能真正跨出单次会话3.1 单 Session 的“记住”不等于跨 Session 的“记得”很多人会把“模型上下文窗口很大”和“Agent 记住了用户”画等号。实际上模型只是在当前 Session 的有限窗口里暂时看到了这些文字。Session 一换上下文窗口不会自动保留上一轮的内容。用一个比喻来理解Session 像一块临时白板模型工作时会把用户问题、工具结果、中间推理写上去。白板很大所以你看模型好像很聪明。但白板终究会擦掉。跨 Session 通信要做的是把白板上真正重要的一些结论抄到一本可持续保存的工作笔记里。下次新 Session 开始时不是把整块白板搬回来而是按当前任务需要打开相关页面。所以跨 Session 方案的第一步不是“把历史都存起来”而是先定义清楚什么是白板上的临时信息什么是需要长期保留的工作笔记。3.2 三种可落地的跨 Session 方案从工程实践看跨 Session 并没有统一标准通常要看信息生命周期。下面这张表可以作为初选参考方案适合场景保存内容主要风险服务端 Session / Redis短生命周期状态用户标识、当前任务编号、最近一步操作过期策略不清会导致无限增长或突然消失业务数据库 / 业务系统需要幂等和事务的业务数据订单、用户资料、工单状态等真实对象需要提前设计好数据模型不能把对话原文乱塞向量库 / 语义记忆库长期偏好、事实型知识摘要、特征、关键实体检索命中率不确定需要更新和纠错机制三者不是互斥的。很多系统里短状态放 Redis业务数据放数据库长期偏好放向量库。真正难的不是选哪一个而是知道一类信息应该放在哪一层。3.3 选择性保存比保存一切更有价值有一种常见误区既然模型上下文窗口有限那就把历史都存进记忆库需要时再全部取回。但问题很快就会出现检索结果太杂模型仍然不知道哪些信息最重要。更好的思路是“选择性保存”。每个 Session 结束前用 Agent 或固定逻辑做一次小结这次对话用户做出了什么决定产生了哪些事实哪些临时状态可以丢弃这个小结就是未来新 Session 恢复时最值得依赖的内容。细节也很关键。保存用户偏好时不要只写“用户生气了”而要写“用户希望客服不要反复确认同一个问题应该直接给出替换方案”。保存任务状态时不要只写“在退款流程中”而要写“退款申请已提交等待商家审核预计两个工作日内完成”。越靠近业务实体越要在保存前把信息结构化成可用字段。3.4 一个最小的 Session 更新流程如果你正打算给 Agent 加跨 Session 记忆可以先从最简单流程开始不要一上来就设计复杂档案每次新 Session 开始时根据用户 ID 或业务对象 ID 拉取状态摘要。在 Session 内所有 Agent 只共享这个状态摘要里的字段。Session 结束后把新增的关键事件写入持久层。对旧状态进行合并或覆盖避免只追加不更新。定期清理真正没有价值的日志型内容。这套流程跑通后再逐步加向量检索、事件回溯、权限隔离。先让信息有明确流向再让它变得更聪明。4. 用一个最小示例把两类通信串起来只看概念还不行我们做一个偏业务的最小示例。假设场景是用户咨询电商订单可能查状态也可能申请退款。系统里有“意图理解 Agent”和“订单处理 Agent”。用户第一次来问订单状态隔一段时间又回来提退款我们希望 Agent 不用让用户重新报一遍订单号。4.1 为什么这个例子能同时覆盖两类通信“意图理解 Agent”把用户说的话转成结构化动作再把动作交给“订单处理 Agent”这是跨 Agent 通信。“订单处理 Agent”查完订单后把订单 ID 写入某个可持久化区域。下一次新 Session 里哪怕是另一个 Agent 来处理也能知道当前订单是哪一个这是跨 Session 通信。这个例子很小但它涵盖了消息传递、状态共享、跨会话恢复三个核心动作。4.2 一个可运行的最小代码结构下面是示意代码真正落地时模型调用、持久化、日志和鉴权都要换成自己的实现但通信结构可以沿用。session_store {} def load_session(session_id: str) - dict: if session_id not in session_store: session_store[session_id] { order_id: None, last_action: None, events: [], } return session_store[session_id] def save_session(session_id: str, session: dict) - None: session_store[session_id] session def intent_agent(user_input: str) - dict: # 真实场景应调用 LLM 做意图识别与实体抽取 # 这里用最小规则展示结构化输出 if 退款 in user_input: return {action: refund} if 订单 in user_input or 发货 in user_input: return {action: query} return {action: other} def order_agent(intent: dict, session: dict) - dict: action intent[action] if action query: # 模拟从订单系统查询 order_id session.get(order_id) or intent.get(order_id) if order_id is None: return {status: need_order_id} order {order_id: order_id, status: 已发货} session[order_id] order_id session[last_action] query return {status: ok, order: order} if action refund: order_id session.get(order_id) if order_id is None: return {status: need_order_id} # 模拟发起退款这里是需要幂等的动作 return {status: refund_submitted, order_id: order_id} return {status: unknown} def run_new_turn(session_id: str, user_input: str) - dict: session load_session(session_id) intent intent_agent(user_input) # 把 session 中已有的关键字段作为上下文传给订单处理 Agent intent[order_id] session.get(order_id) result order_agent(intent, session) save_session(session_id, session) return result # 第一次 Session用户查询订单 print(run_new_turn(session-001, 我的订单发货了吗)) # 第二次 Session用户没有重新给订单号但订单 ID 能从 Session 恢复 print(run_new_turn(session-001, 我要退款))这段代码证明了你要做的事并不复杂跨 Agent 通信靠 Agent 之间的结构化输入输出跨 Session 通信靠一个能跨 Session 读取的共享对象。4.3 这段代码真正说明的三件事第一Agent 之间不能只传原始文本。intent_agent返回的是{action: query}order_agent读取的是 action 字段。这样以后换模型、加提示词都不影响下流 Agent 解析结果。第二状态放在 Session 里而不是隐式藏在某个 Agent 的局部变量里。order_agent在写完session[order_id]后下一次调用同样靠这个字段恢复上下文。这就是跨 Session 通信的最小实现。第三敏感动作要有保护。refund动作不能只凭一句“退款”就直接执行还要考虑用户身份、订单归属、重复提交。代码里的order_id is None只是最基础的保护真实系统还要查权限、幂等键、业务状态。5. 别等出问题再补工程化检查清单通信逻辑写完以后真正折磨人的往往不是“通不通”而是“稳不稳”。下面是几个我建议在项目里提前考虑的工程化问题。5.1 每个 Agent 既要定义输入输出也要定义读写权限给每个 Agent 定义一个输入 schema 和输出 schema能避免大量低级错误。字段类型、是否可选、由谁产生、谁来消费都应该能在结构里体现出来。权限往往被忽略。一个 Agent 能读 Session 里的哪些字段能写哪些字段跨 Session 后能访问哪些用户数据都应该有边界。不要让任何一个 Agent 都能把整个长期记忆库翻出来。否则你可能只为了修一个小功能就把用户的私密上下文全部暴露给了工具型 Agent。5.2 所有关键消息都该带一个可追踪标识跨 Agent 通信一旦发生错误最需要回答的问题是“这一条消息到底有没有从 A 到 B”没有唯一标识这个问题很难查。比较简单的做法是一个任务从进入系统开始生成一个request_id每次跨 Agent 传递、每次状态写入、每次 Session 保存都把这个标识记进日志。排查时先按request_id把所有日志拉出来消息链路会清晰很多。5.3 跨 Session 读取必须有权限边界Session 不等于用户但 Session 常常承载用户相关数据。如果一个 Agent 只凭前端传来的session_id就去加载状态很容易出现越权。正确逻辑至少应该做两层判断这个 Session 归属于哪个用户或哪个业务对象当前请求的用户是否有权限读取可以参考 Web 里对 session 安全问题的重视。任何连 Session 标识都完全信任的服务端逻辑在 Agent 架构里同样危险。5.4 通信失败要能重跑但不能重复执行副作用Agent 之间要通信就必然有失败可能。可能是网络超时可能是模型解析格式出错也可能是对方 Agent 没来得及启动。重试能解决一部分问题但也会引入重复执行。比如“退款”这样的动作绝不能因为第一次超时就原样再发一遍。工程上常见做法是给不可重入动作加一个幂等键比如业务单号或任务 ID。接收方发现同一个 ID 已经处理过就直接返回上次结果。5.5 一个实用的排查顺序如果跨 Agent 通信出了问题我建议按下面的顺序排查消息是否产生看 A 的日志里有没有写出结果。消息是否送达看 B 的入口日志有没有收到。消息内容与 B 的输入 schema 是否匹配缺字段、类型错误最常见。B 读取的 Session 状态是否最新可能存在旧状态覆盖。用户的权限是否允许 B 读取这条数据有没有重复消息导致同一个动作执行多次如果跨 Session 记忆没有生效就从“Session ID 是否稳定”查起再看持久层是否真的写入、下一次加载时读取的是不是同一份数据最后再看是否被缓存或过期清理逻辑误删。6. 从最小可用场景开始通信设计才不会被浪费6.1 先做“两个 Agent 一个持久 Session”看完整套原则不要急着搭事件总线、消息队列、向量记忆库。第一次实践我建议从一个很小的闭环开始两个 Agent一个负责理解输入一个负责执行业务动作。一个持久化 Session能让第二次 Session 读取第一次 Session 留下的关键字段。结构化中间结果两个 Agent 之间传字典不传大段自然语言。一份日志每次消息流转和状态写入都有记录。这套最小闭环能跑通你才真正理解跨 Agent 通信和跨 Session 通信的区别。后面再往上加东西也知道每一层解决的是什么问题。6.2 什么时候才需要引入事件总线或异步框架如果出现下面几种情况再考虑上更重的通信设施Agent 数量超过三个而且调用关系不是固定链路。一个 Agent 的结果可能被多个下游 Agent 订阅。任务需要长时间执行Agent 之间要异步通知结果。不同 Agent 可能运行在不同服务里需要跨进程通信。这时候明确的话题模型、消息队列和事件订阅会对你有帮助。但也要清楚这些机制会引入额外的部署、监控和运维成本。如果不属于这些场景用编排器同步调用通常更简单、更可控。6.3 什么场景其实不需要跨 Agent/跨 Session 通信所有方案都应该有边界跨 Agent 通信并不适合所有任务。一个单 Agent 只处理单轮工具调用用户每次问题彼此独立那就不需要复杂通信。业务系统已经把所有信息都存储在数据库、每个请求都可以通过用户 ID 查到完整上下文也不必强行把历史照搬到 Session 里。真正需要跨 Agent 和跨 Session 通信的是任务被拆成多个角色、时间被拉开、多个 Session 之间需要共享业务状态和协作成果的场景。这个判断越早做越能避免设计出“什么都能存但什么都不可信”的重型系统。回到最开始那个判断单个 Agent 的强大是单点能力多个 Agent 能否稳定协作依赖的是消息、状态和记忆是否在正确的边界里流动。跨 Agent 通信让不同角色能接上同一根线跨 Session 通信让时间不会抹掉真正重要的进展。设计 Agent 系统时先画清这条信息链路再堆模型能力顺序不应该反。
返回列表