资讯动态

两个独立Bot双向通讯:从群聊中转升级到点对点直连方案

发布时间:2026/10/17 11:40:36 来源:尧图企业网站定制
两个独立Bot要“对话”绕不开的那几个坎儿我今天一次讲透。做Bot集成的人或者公司里搞自动化流程的同事迟早会撞上这个需求手里有两个独立Bot各管一摊活儿比如一个Bot负责抓取和清洗外部数据另一个Bot负责分析汇总输出结果。现在想让它们协作起来——第一个Bot拿到数据后自动递给第二个Bot去处理处理完再把结果回传。这个协作如果只是单方向推送其实好办直接让Bot A调用Bot B的接口就行。但真正的难点在于“双向通讯”A要给B派活儿B干完要把结果反馈给A两者之间还要能协商、能重试、能处理对方异常。我最早遇到这种需求时脑子里第一反应是“拉个群让两个Bot在群里互喊”。这个方案简单也确实能用但用久了问题一堆。今天这篇主要分享我在这类场景下的完整思路从“群聊中转”这种临时方案讲到真正适合Bot间双向通讯的点对点直连设计包含协议定义、鉴权、重试策略、日志追踪和一些调试经验。不论你是写企业内部自动化机器人、个人项目里挂了两个Bot服务还是做AI Agent之间的消息路由这篇文章都能给你一套可以“抄作业”的方案。1. 场景与需求拆解两个独立Bot为什么要通讯先说清楚我遇到的具体场景。项目里有一个“采集Bot”它连接外部数据源定时抓取内容并做基础清洗它本身不产出分析结论。另一个“分析Bot”只做文本结构化与评分输出不关心数据来源。逻辑上这两个Bot天然有协作关系——采集Bot拿到原始数据分析Bot结果整条链路才算闭环。但两个Bot是独立部署的各自跑在独立的进程和环境里唯一能扯上关系的是它们都加入了公司同一个内部沟通群。你稍微一想就能得出最“省事”的通讯方案让两个Bot在群里互相发消息。采集Bot往群里丢一串JSON分析Bot监听到就取走处理再把结果丢回群里。这个方案我在早期B版本里用过一段时间。它的数据流是这样的采集Bot发送消息到群 → 群消息服务推送给所有群成员 → 分析Bot收到新消息事件 → 解析内容、执行任务 → 发送结果回群 → 采集Bot接收结果。这个链路跑通了但问题也随之暴露。最明显的是丢消息和顺序错乱。群消息服务不是为机器间高可靠通讯设计的偶尔会有消息延迟、重试、去重逻辑不完善的情况。分析Bot收到一条任务处理到一半时采集Bot又发来第二条两条消息的先后顺序可能在群推送层就被打乱了后续结果对应关系也跟着错。还有一环很麻烦群聊环境里一个Bot发的任何内容群里的其他成员——包括真人——都能看到。数据没清洗干净的中间结果、分析过程中的中间日志全被晒到群里既不安全也不优雅。所以我开始重新思考两个独立Bot之间的通讯到底该用什么方式核心诉求其实很清晰消息要可靠送达尽量不丢、不乱序。双向语义清晰A发起任务给BB执行完要能回调A。调用链要能追踪出问题时能快速定位是哪一侧出了问题。不能让第三方比如聊天群成为数据链路的必经环节。顺着这个思路我把方案从“群聊中转”慢慢迁到了“点对点直连”。2. 两种通讯模式的本质差异群聊中转与点对点直连2.1 群聊中转所有消息都在“公开场合”走群聊中转本质上就是把消息系统当成了分布式消息中间件来用。两个Bot共享同一个群聊空间通过监听消息事件实现“通讯”。它的结构可以理解为采集Bot → 群聊 → 分析Bot → 群聊 → 采集Bot这个模式下群聊是唯一的信息交换中心。它的优点上手极快不需要额外开发接口不需要搭建中间件两个Bot本身已经天然支持群消息收发只要写消息监听和解析逻辑就行。但你要清楚群聊中转的方案天花板很低适合原型验证、演示、或者低频小数据量的业务。以下几点会让它在生产环境中越来越难受第一消息体积受限。群聊消息一般有长度限制你没法在一条消息里塞很大的结构化数据或文件内容只能把数据存到某个存储里然后在消息里带地址。这么一来你等于额外维护了一套存储管理和过期策略。第二语义层级缺失。真正的通讯协议需要有请求、响应、确认、错误码、超时重试等概念。群聊消息只是一个“文本投递”你需要自己把所有通讯语义都塞在消息文本里。结果就是里面飘满了各种自定义前缀、JSON字符串、转义字符维护成本极高。第三消息通道耦合在聊天产品里。这直接意味着通讯可用性受限于聊天服务的稳定性聊天服务抖动你的Bot协作链路就断了。而且所有中间数据都会暴露在群成员面前保密性没有保障。所以我说群聊中转只是“能用”不是“好用”。它适合临时验证不适合长期跑生产链路。2.2 点对点直连让Bot直接“对话”点对点直连指的是两个Bot之间不经过任何公开或三方消息通道直接通过HTTP调用来完成请求-响应。Bot A向Bot B暴露的Webhook端点发起POST请求B解析执行完成后再向A的回调地址发起结果通知。这种模式贴近服务端常见的接口通讯方式但你需要注意Bot之间不是“随随便便”就能直连的它需要一套约定的通讯协议。这套协议至少要覆盖消息头消息ID、发送方身份、时间戳、路由信息消息体任务类型、任务参数、上下文数据响应与回调执行成功、执行失败、结果数据重试与幂等同一消息重复送达时的处理规则从群聊中转迁到点对点直连不是简单的把消息发送API换一下而是整个通讯模型从“共享空间的广播”变成了“点对点的路由与响应”。这个变化带来几个直接好处数据链路收窄只有两个Bot参与中间数据不再经过聊天产品。消息语义可以用结构化的字段来表达不必再塞进文本中解析。每个请求都能对应明确的成功或失败响应链路更可控。为之后的分布式扩展打基础以后第三个Bot加入协作只要遵循同一套协议注册即可不需要在聊天群中再拉一个群成员。2.3 两者对比什么场景该用哪种我整理过一张对比表供你参考按实际需求选择对比维度群聊中转模式点对点直连模式上手成本极低利用现有群聊能力中等需要定义接口与协议消息可靠性依赖群聊服务容易丢通过HTTP状态码与重试机制可控数据可见性群内所有成员可见仅通讯双方可见消息大小限制受聊天消息限制取决于接口设计基本无限制可维护性消息语义散落在文本里结构化字段清晰便于扩展适用阶段原型、演示、低频小数据量生产级、高频、复杂链路我个人的建议是如果你的需求是快速搭个Demo给同事看看效果群聊中转足够了。但只要你计划把这个流程放到生产环境甚至有其他成员或系统要陆续接入协作那就早点切到点对点直连。3. 点对点通讯的协议设计从“互喊”到“结构化对话”切到点对点直连后第一步要做的是定协议。协议不定好后面就是两个人各说各话。3.1 消息信封通讯双方都能看懂的结构我设计消息时参考了邮件信封的思路。你寄快递不会只塞一件随手抓的东西进去至少要有收件人、发件人、包裹内容清单、日期。Bot通讯同理每条消息应该包含一个统一的结构我叫它“消息信封”。信封字段大致长这样{ message_id: uuid-xxx, timestamp: 2024-01-15T10:00:00Z, sender: collector-bot, target: analyzer-bot, type: task.assign, payload: { task_id: task-001, data_url: http://internal-storage/data-001.json, priority: 5 }, trace_id: trace-001 }字段含义message_id每条消息的唯一ID接收方用来做幂等判断。同一个ID重复收到说明是一模一样的消息直接丢弃第二次。sender / target发送方和接收方的标识。接收方校验target是不是自己不是就拒收。type消息类型。类型是通讯语义的核心比如task.assign派发任务、task.result回传结果、heartbeat心跳探测等。payload具体业务数据。保持为JSON对象方便扩展。这里的内部地址注意要用内网可访问的地址不暴露公网。trace_id链路追踪ID。一次完整的任务从发起到结果回传所有消息共享同一个trace_id排查问题时靠它把所有日志串起来。消息信封解决的是“对话语法”问题相当于两个Bot约定好了说话的句式。3.2 请求-响应与回调双向通讯的方向感协议里必须明确一个基本模型每一次协作谁是请求方、谁是服务方、结果如何返回。我采用的模型是“异步请求-回调”Bot A发送task.assign给Bot B。Bot B收到后立刻返回202 Accepted表示接单了。Bot B执行任务执行完成后发送task.result给Bot A的Webhook地址。Bot A收到task.result后返回200确认收到。这个模型的关键在于第2步和第4步的“确认”。你可能会想为什么不在第1步就同步等结果因为真实世界里Bot B执行一个任务可能要几秒甚至更久如果A一直挂着连接等响应任何网络抖动或B的处理超时都会导致A那边直接断掉。异步化之后A只需要等B后续主动推结果过来链路更健壮。从实现上看Bot B要开发两个接口接收任务的入口比如POST /api/v1/tasks结果回调的出口调用A的POST /api/v1/callbacks同时Bot A要开发一个结果接收接口接收B的回调POST /api/v1/result)整个链路就是A → B → A。方向感和集群感都有了。3.3 类型定义任务状态机消息里type是极重要的建议你在一开始就把task相关的类型定义清楚避免后面各加各的task.assign派发任务。task.assign.ack接收方确认已受理。task.progress执行方主动上报进度可选。task.result回传最终结果。task.error回传错误信息。我把这四类type组合起来就能表达一次完整的任务生命周期。有了生命周期后续做监控、告警、超时判断就有据可依。3.4 鉴权与安全点对点不代表裸奔两个Bot之间直连就意味着对方的端点暴露在网络上。哪怕只是内网环境也不能不做鉴权。我见过有人直接裸奔跑HTTP接口后来因为扫描器或误调用直接把任务队列打崩。推荐两种方式按你的环境选择一是静态Token最简单。在Bot A和Bot B的配置文件中都写一个共享密钥请求头带上Authorization: Bearer shared_token接收方校验这个Token不符合直接拒绝。适合内部网络且双方都是自己掌控的服务。二是签名校验。发送方用私钥对请求体做签名接收方用公钥验签。能防止Token泄露后的伪造请求适合跨网络、跨组织协作。我建议起步阶段用共享Token就行但一定把所有请求都加上校验。之后如果需要跨网络再升级为双向签名。还有一点务必注意回调地址要配置在Bot A的信任范围内并校验回调来源IP或域名避免有人伪造task.result干扰链路。4. 实操落地从群聊迁移到点对点的具体步骤我以最典型的场景为例采集BotBot A向分析BotBot B派发任务B处理完后再把结果回调给A。这里会给出可直接参考的代码骨架和配置逻辑。4.1 整体架构与模块划分整个链路由三种角色构成采集BotA发起任务接收结果。分析BotB接收任务执行分析回调结果。存储层用于存任务状态与结果数据。两块Bot都可以访问。为什么需要存储层因为HTTP通讯是瞬时的任务执行过程中的状态变更要有个地方能查。我一般会建一张task表字段包括task_idstatuspending/processing/done/failedpayloadresultcreate_timeupdate_time等。这样A发送任务后即使B还没有回调A也能通过查询任务状态知道“任务是否在进行中”。等到回调到达再更新status为done。4.2 Bot B如何实现任务接收WebhookBot B实现一个任务入口解析请求头校验Token解析请求体存储任务再进入异步处理。这是一个最小可用版本的Flask风格实现便于你理解流程from flask import Flask, request, jsonify import threading app Flask(__name__) # 校验共享Token def check_auth(req): token req.headers.get(Authorization, ) return token Bearer my-shared-token app.route(/api/v1/tasks, methods[POST]) def receive_task(): if not check_auth(request): return jsonify({error: unauthorized}), 401 data request.get_json() message_id data.get(message_id) task_id data.get(payload, {}).get(task_id) # 幂等检查 if message_id in processed_ids: return jsonify({status: duplicate}), 200 # 存储任务状态为pending save_task(task_id, data[payload], statuspending) processed_ids.add(message_id) # 异步处理避免阻塞HTTP响应 threading.Thread(targetprocess_task, args(task_id, data)).start() return jsonify({status: accepted}), 202注意几点幂等键用message_id而不是task_id。因为task_id代表业务任务可能因为路由重发产生多条相同业务消息message_id才是每一条消息的唯一标识。收到请求后立即返回202绝不等待任务执行完。任务执行交给后台线程或独立worker。4.3 Bot B执行完如何回调Bot A任务执行完成后B调用A的回调接口。这里必须做重试策略因为A可能临时不可用。import requests def call_back_bot_a(callback_url, result_payload): headers { Authorization: Bearer my-shared-token, Content-Type: application/json } max_retry 3 for i in range(max_retry): try: resp requests.post(callback_url, jsonresult_payload, headersheaders, timeout5) if resp.status_code 200: return True except requests.RequestException as e: # 记日志 log_error(fcallback failed: {e}) time.sleep(2 ** i) # 退避重试 return False这里我用指数退避每次重试等待时间翻倍2秒、4秒、8秒。重试间隔不能太长也不能太短否则要么恢复得慢要么给A造成压力。回调成功与否是任务状态更新的关键。如果多次重试A都没有确认B应该把任务标记为“回调失败”并通过告警通知运维人员人工介入。4.4 Bot A如何接收并处理回调Bot A这里的逻辑相对简单但同样要做验权和幂等。app.route(/api/v1/result, methods[POST]) def receive_result(): if not check_auth(request): return jsonify({error: unauthorized}), 401 data request.get_json() task_id data.get(payload, {}).get(task_id) status data.get(payload, {}).get(status) result data.get(payload, {}).get(result) # 检查任务在本地是否存在、是否已完成 if task_id in finished_tasks: return jsonify({status: duplicate}), 200 update_task(task_id, status, result) finished_tasks.add(task_id) return jsonify({status: ok}), 200A收到结果后要更新自己的本地任务状态。如果发现重复回调直接返回200就可以了不需要再重复处理。一个容易被忽视的细节如果A在发完任务后重启过它的内存里可能丢失“已处理消息”的记录这时再收到B的回调幂等判断就可能失效。所以生产方案建议把幂等键持久化到存储层而不是放在内存集合里。我最初就是内存集合后来不得不改成数据库记录才真正扛住了重启场景。4.5 消息队列方案可靠性的升级版如果你觉得HTTP直连太重试、太容易受双方在线状态影响可以引入一个轻量级消息队列作为通讯中间层。两个Bot不再直接建立HTTP连接而是各自接入同一个消息队列Bot A把消息投递到队列 topic_collector_to_analyzer。Bot B订阅该队列消费任务。Bot B处理完把结果投递到 topic_analyzer_to_collector。Bot A订阅该队列消费结果。这个模式下点对点通讯物理上变成了“点对队列点”但逻辑上仍然是两个Bot之间的直接通讯。消息队列提供的是可靠性保证消息不会因为消费者挂掉而丢失消费者恢复后可以继续消费。优点很明显消费端断开不会影响生产端投递。支持并发消费扩展性好。消息持久化重启不丢。缺点则是需要你额外维护一套队列基础设施比如一台内网消息服务。对于两个Bot的小规模场景可能有点重。我个人建议如果只是两个Bot且都没有全天候在线要求HTTP直连够用如果需要7×24稳定链路或者后续有更多Bot接入上轻量消息队列更省心。5. 实际操作中的问题与排查技巧这一节是我最想写的部分因为协议设计文档谁都能写但真正跑的时候踩的坑才是经验值所在。5.1 回调风暴一个任务被重复执行了出现过一次同类问题任务量增加后A重复收到了同一个task_id的结果回调而且B侧每条消息在处理后都重新发送了一轮。排查后定位到原因B的任务处理逻辑没有正确标记消息ID导致任务消费超时后队列重新把同一条消息推了出来。消息队列的“至少一次投递”特性配合处理端未做幂等就会造成重复。解决方式把幂等判断放到处理端最前面消费到消息后先用message_id查询存储判断是否已经消费过。已经消费过的直接返回成功不做业务处理。个人经验幂等判断不要只依赖内存因为消息处理进程也可能重启。用数据库里的唯一索引以message_id为唯一键插入冲突即视为重复消息这是最稳妥的。5.2 消息顺序错乱另外遇到过更隐蔽的问题两个关联任务比如“任务A更新数据”和“任务B基于更新后数据做分析”因为并发消费导致B先分析后更新。这种情况下直接从回调顺序上看不出异常但结果就是错的而且要花不少时间才能回溯到链路。机制上的原因消息队列在单个分区内保证有序但如果你开了多个分区或并发消费者顺序就不再有保障。解决办法有两个方向如果前后任务有强依赖就设计成同一个发送方串行投递并且接收方只开单消费者。保证顺序。如果做不到串行就在任务参数中带上前置任务的任务ID接收方检查依赖任务是否完成没完成就返回“依赖未完成”并让发送方稍后重试。我个人的选择通常是第二种因为同时跑多个任务实例的吞吐优势比强依赖顺序重要得多。5.3 超时设置不合理导致误判另外要留意超时设置HTTP直连里如果A发任务给B后等待B回调结果时设了很短的超时B稍微处理慢一点就被A判定为失败然后A又发起重试结果B那边处理了两遍。解决思路不要去同步等待B的结果而是把任务状态存在存储中A每隔一段时间主动查询任务状态。通过查询驱动而不是通过“等待回调”驱动。这样无论B处理多久A都不会因为等待超时误判。5.4 日志与追踪trace_id是排查问题的关键两个Bot协作出问题时最怕的就是“A说发了B说没收到”两边日志对不上。我后来强制要求所有消息都带trace_id一次完整任务的发起、接收、处理、回调、结果确认全部把这同一个trace_id打到日志里。排查问题时只需要在日志系统里搜trace_id整条链路的所有环节都会浮出来谁没干活一目了然。如果你还没有日志系统哪怕只是把日志写到文件里也建议把trace_id做成单独字段。我的做法是从任务创建开始生成trace_id之后所有函数调用、日志输出、异常记录都带上它。这可能是唯一一个能让你“从海量日志中找到一条完整调用链”的简单手段。5.5 常见问题速查表我把一些高频问题整理成表方便你对照排查现象可能原因排查方向A发出任务后B没反应1. Token校验失败 2. 网络不通 3. B未启动先看B日志有没有收到请求没有则检查网络与路由B处理完了A没收到结果1. A的回调地址配置错 2. A的接口报错 3. 回调被安全策略拦截看B进程里回调请求的返回状态再看A接口日志同一任务被重复执行消息被多次投递处理端未做幂等检查处理端是否依赖message_id做幂等两台Bot时通时不通可能是网络抖动导致HTTP请求超时,没有退避重试机制为关键请求加退避重试同时优化超时时间任务处理结果错乱并发消费导致消息顺序错乱检查是否有强依赖顺序被并发打破调整消费方式5.6 从群聊迁到点对点的过渡技巧如果你现在还在群聊中转模式上跑并且数据链路已经有一定依赖不建议直接一下切换。我的做法是“双跑过渡”先搭建点对点链路把群聊消息转发到新链路中。让两个Bot同时监听旧群聊和新接口但以新接口的结果为准。跑一段时间确认点对点链路稳定再把群聊监听的消费者停掉。这个过程通常需要3到5天过渡期可以顺手把原先散落在群聊里的日志清洗一遍。双跑过渡阶段可能会偶尔看到两边都输出结果你要做好结果去重的准备我当时是在处理逻辑里以“新接口优先”来判断。双跑逻辑示例task_data parse_content() if new_link_health: send_to_new_link(task_data) else: send_to_group_link(task_data)不用双通道同时发同一个任务而是优先走新链路、失败降级到旧链路。通过健康检查来决定路由这样过渡期业务不会中断。6. 两个Bot之外的扩展多Bot协作架构的设计启发这两个Bot的通讯方案落地后我陆续遇到了更多独立Bot并存的场景。这些经验让我意识到这套点对点通讯的协议和架构往大了延伸其实就是多个AI Agent协作的消息底座。6.1 新增Bot如何接入当第三个Bot加入协作时你不需要重新设计通讯协议只需要让它也遵守信封格式和消息类型然后在新Bot上实现一套对应的Webhook入口和回调出口即可。具体来说新Bot读取配置文件知道自己的标识。在已有注册表或环境配置中声明自己能处理的消息类型。接收方的target字段不再写死为某个Bot名而是按消息类型路由。这样消息从“一对一的对话”变成“一对多的调度”但每条消息本身仍然是点对点投递不会失控。6.2 从直连到注册中心稍微往前进化一点如果有四五个Bot互相通讯互相保存对方的地址会变得混乱。这时可以引入一个简单的注册服务每个Bot启动时上报自己的可用接口和消息类型其他Bot查询注册服务来发现目标地址。但要注意这一步不是必须的。如果你只是三五个Bot的内部协作维护一个配置文件写清各Bot地址完全够用。引入注册中心会增加基础设施复杂度收敛的核心问题只是“地址管理”如果没到一定规模别为了架构而架构。6.3 Bot通讯与AI Agent协作的关联随着AI Agent的概念火热像“Bot A理解用户请求Bot B调用工具Bot C生成回复”这类编排越来越多。本质上这就是多个独立Bot的双向通讯。Agent之间的角色分配、任务传递、结果回传、失败处理跟两个Bot之间的点对点通讯没有本质差异。区别只在于Agent之间传递的可能不只是结构化数据还有上下文提示词、中间思维链、工具调用结果等。这要求协议里的payload字段设计得足够灵活我当时把它定义为JSON对象后来发现这个设计给了很大的扩展空间——你可以在payload里塞入任意结构只要双方约定理解即可。6.4 几种通讯模式的选型参考我把三种模式下我的选择建议列出来你可以直接对照你的规模判断通讯方式适合场景需要引入的组件运维成本群聊中转原型验证、低频小任务无极低HTTP点对点两个或少量Bot中等频率无低轻量消息队列高频、7×24、多Bot协作消息服务中我的倾向是80%的场景用HTTP点对点就够了尤其是两个Bot只在一台内网环境跑时不要去额外装一套队列服务。如果哪天任务量和Bot数量上去了再做消息队列的迁移符合“走出来的时候再换鞋”的原则。我个人在实际操作中养成的一个习惯是所有Bot之间的通讯设计和日志都按事件驱动思路来写。准备迁移到任何新模式时只需要换传输层消息的语义、任务状态流转、幂等规则基本不需要重写。7. 几条实用的落地建议最后总结几条我跑了许多天后沉淀下来的建议算是给准备动手做Bot协作的同行指个方向先把消息信封和消息类型定义清楚再写业务逻辑。协议先行代码后续。不要先把一个Bot写完了再去定义一个接口配合它。幂等和鉴权从第一个接口就要加上。后来补会比较痛苦因为线上链路已经有人在用了你不好随便改协议。链路刚搭好时一定要测一次“Bot B挂掉”的场景。看看A是什么表现消息会不会堆积恢复后会怎么处理。这个场景不通生产上迟早出事。所有关键请求都加日志和trace_id。日志里没有信息出问题时就是盲人摸象。如果只是两个Bot协作用完好的配置文件和清晰的接口描述文档就够了不要轻易引入复杂基础设施。需要扩展时再按需迁移。回调结果最好做成异步确认。不要直接用同步HTTP调用来表达任务完成原因我之前分析过——执行时间不可控。这套方案解决了我实际工作中两个Bot协作的几乎所有核心痛点。从一个临时的群聊中转方案演进到一套可扩展、可观测、可鉴权的点对点通讯机制整条链路的可靠性和可维护性都有了质的提升。如果你手里也正好有两个独立Bot需要打通“双向通讯”我认为你值得花半天时间把协议定义好、把接口搭起来——后面你的任务协作会省非常多的心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑