一套智能体互联协议代码需要多少行?这个问题我最近被问过好几次。问的人多半是正在做多智能体系统的开发,或者刚接触 Agent 编排,在网上看到了 MCP、A2A 这类协议,觉得概念都懂,但一到自己动手写,心里没底,就想找个“标准答案”来对齐一下预期。
先说结论:一套能跑通 demo 的智能体互联协议,核心代码大概 500 到 800 行。一套能稳定交付给业务方使用的,3000 到 5000 行是常态。如果把它做成通用基础设施,比如连接多种消息中间件、支持热插拔插件、带完整的可观测性和后台管理系统,那两万行以上一点都不夸张。
这个范围区间可能让很多人觉得“是不是太宽泛了”。别急,这篇文章我会把智能体互联协议按功能模块拆开,逐个分析每块代码到底在干什么、必须写多少、哪些地方可以精简、哪些地方省了之后会让你后期疯狂返工。文章里还会给出可以直接参考的关键片段和行数估算依据,最后聊聊我踩过的一些坑。
1. 智能体互联协议到底在解决什么问题
1.1 没有协议时智能体之间是怎么“说话”的
先还原一个常见场景。假设你现在有两条智能体:一条负责检索企业知识库,一条负责调用业财系统生成经营分析报告。你要让它们协作完成“帮我分析上海区域 Q3 营收下降原因”这个任务。
最简单的做法是什么?你在代码里直接写:
agent_a_result = knowledge_base_agent.search("上海 Q3 营收下降原因") report_agent.generate(knowledge_base_result=agent_a_result)看起来没问题,但这只是“在一个进程里的两个函数互相调用”,不是“智能体互联”。当你的系统变成 10 个智能体、100 个智能体,分布在不同的服务、不同的机器、甚至由不同团队开发时,这种直接调用就会引发一系列连锁问题:
- 每个智能体的输入输出格式都不一样,A 返回的是 JSON,B 返回的是 Markdown,C 返回的是二进制图片,集成成本爆炸。
- 智能体之间调用关系变成蜘蛛网,A 调 B,B 调 C,C 又调 A,没人能梳理清楚。
- 某个智能体挂了,整个协作链路崩掉,没有任何重试、降级机制。
- 没有统一的消息追踪 ID,出问题时根本没法排查。
所以智能体互联协议的核心价值,就是定一套统一规范,让不同智能体之间可以用标准化的语言互相通信、协作、调度,屏蔽底层实现差异。它不是某个具体的功能,而是智能体之间对话的“通用语言”。
1.2 一套协议要覆盖的四个基本能力
我拆解了从零开发到实际落地需要的核心能力,基本逃不出下面四块:
- 身份与发现:智能体如何注册自己,其他智能体如何找到它。
- 消息传递:定义消息格式、消息类型、发送与接收规则,包括请求、响应、事件通知等不同模式。
- 任务编排与路由:一条消息发过来之后,系统如何决定由哪个智能体处理,以及如何处理复杂任务分解与结果归集。
- 状态、错误与审计:如何处理超时、失败,如何追踪一条完整的调用链,保证系统可观察、可排查。
听起来功能不多,但每一项往下拆,都会引出大量细节。比如“任务编排”里要不要支持多轮对话记忆?“消息传递”要不要支持流式输出?“错误处理”是只做超时重试还是还要做幂等?“发现机制”是走中心化注册中心还是走分布式广播?
这些选择,直接决定了你的协议代码行数是在 800 行附近还是在 5000 行附近。
提示:做协议设计时最忌讳的就是“功能一步到位”。先把消息结构定好,把最核心的通信流程跑通,再逐步扩展握手、重试、审计等能力。一上来想把所有东西都塞进去,结果往往是把简单的 demo 做成了一锅复杂的粥。
2. 按功能模块拆解:协议栈里到底有哪些代码
2.1 消息模型:智能体对话的“信封”设计
消息模型是协议的地基。类比一下寄快递:你寄东西时需要一个包裹,包裹上写着寄件人、收件人、地址,里面放着真正的物品。智能体之间的消息也一样,外层是信封,内层是业务数据。
实际工程里,消息信封至少要包含这些字段:
{ "protocol_version": "1.0", "message_id": "68e0f25e-2d63-4f19-9c30-8d61d7d1f13c", "message_type": "request", "sender": "agent.knowledge.v2", "target": "agent.report", "task_id": "task_20241012_001", "timestamp": "2024-10-12T10:00:00Z", "payload": { "action": "generate_report", "params": { "region": "上海", "quarter": "Q3" } }, "trace_id": "trace_8fd9c1a2" }你会发现这里没有直接写“给我一份报告”,而是把语义拆成了action加params的结构。这个设计的核心目的是让协议具备通用性,让智能体的能力可以被标准化描述。比如“generate_report”是报告智能体的能力,后续如果需要调用其他智能体来做情感分析,只需要换成action: "sentiment_analysis",信封、路由逻辑完全不用动。
这一部分代码做下来,包括模型定义、序列化、反序列化、字段校验,大概需要 100 到 200 行(如果用 Python 的 dataclass 加 pydantic 这类工具,封装的样板代码会多一点,但带来的类型安全收益是值得的)。
2.2 传输与路由:消息怎么送到目标智能体
消息模型定好之后,就要考虑传输了。这一层需要解决三个问题:用什么传输协议、走什么网络模型、消息到了之后怎么找到正确的处理函数。
这一模块的代码量大约在 150 到 400 行,取决于你的传输层是自己写还是复用现有框架。用 WebSocket 实现一套简单的双向通信,核心发送接收循环代码不到 100 行,但加上心跳、链接保活、异常重连,工作量翻倍。
路由部分则需要实现一个能力注册表。注册表维护一张表,记录“哪个智能体提供哪些能力”,收到消息后,根据target或action字段匹配到对应的处理器。
class Router: def __init__(self): self.handlers = {} def register(self, action: str, handler: Callable): self.handlers[action] = handler def route(self, message: AgentMessage): handler = self.handlers.get(message.payload.action) if not handler: raise NoHandlerError(f"no agent can handle action: {message.payload.action}") return handler(message)这张能力表可以存在内存里,也可以存在 Redis 里,支持多实例横向扩展。真要支撑大规模高并发,路由表的存取逻辑、缓存更新、多节点一致性,就是另外 300 行左右的工作。
2.3 状态管理与故障恢复:不能只发消息不管结果
很多初版协议最容易忽略的模块是这里。发出去消息之后就没有后续了,那是“广播”,不是“协议”。真正的智能体互联,必须能跟踪一条消息从发出、接收、处理到返回结果的完整生命周期。
为了保证智能体在处理任务时不重复执行,需要一个任务状态机。状态至少包括:
PENDING:等待某个智能体处理RUNNING:正在处理中COMPLETED:处理完成FAILED:处理失败TIMEOUT:等待超时RETRYING:准备重试
状态管理模块通常配合结果存储一起实现,消息响应要落到数据库或 Redis,方便后续追溯和补结果。这一块的代码量在 150 到 300 行之间,如果你用数据库做持久化,还要算上表结构定义和操作函数,会增加 50 行左右。
2.4 安全与身份:互联的底线
这部分最容易被忽略,也最容易在后期被迫返工。安全至少要考虑三个点:
- 消息来源可信:接收方要确认这条消息确实来自声称的发送方。
- 消息传输防篡改:消息在传输过程中有没有被人改过。
- 权限控制:某个智能体有没有权限调用另一个智能体。
常用实现是 Token 签名机制:每个智能体启动时申请一个身份凭证,发送消息时对消息体计算签名(HMAC 或 RSA),接收方验签通过才处理。用这一套,加上权限校验中间件,大概需要 200 到 300 行代码。
注意:安全模块不建议从零实现。加密签名用
PyNaCl、cryptography这类成熟库,权限可以用简单的中间件模式做,不要在协议代码里重复造密码学的轮子。造轮子非常容易引入安全漏洞。
3. 从零实现一套可用协议需要多少行
3.1 极简版:约 500 到 800 行就能跑通
如果你就是想快速验证“两个智能体通信”这个想法,可以做一个最小可用实现。前提是你愿意砍掉大部分“看起来不错,但暂时用不到”的功能。
极简版包含:
- 消息模型定义:约 80 行
- 发送接收核心循环:约 100 行
- 内存版注册表与路由:约 80 行
- 简单超时重试:约 80 行
- 错误处理与日志:约 100 行
- 一个示例智能体:约 100 行
合计 540 行左右。这个版本能跑通一对一、一对多的消息传递,智能体可以注册自己的能力,其他智能体可以发现并调用。缺点是完全不具备故障恢复能力,消息只是尽量发送,失败就用简单重试逻辑顶一下,不保证最后一定成功。
3.2 完整版:约 3000 到 5000 行才算“能交付”
如果你要做成公司内部多个团队可以共同使用的内部基础设施,极简版是不够的。完整版要补齐这些能力:
- 支持多种传输方式(WebSocket、HTTP、消息队列如 RabbitMQ/Kafka):约 600 到 1000 行
- 服务发现与注册中心:约 400 行
- 任务状态机与持久化:约 500 行
- 消息追踪、链路 ID、日志聚合:约 300 行
- 权限控制与签名验证:约 300 行
- 多智能体任务编排、结果归集:约 400 行
- 通用配置系统与启动入口:约 300 行
- 测试用例:约 600 到 1000 行
加起来正好落在 3000 到 5000 行这个区间。如果团队对工程质量要求高,类型断言、接口抽象、Lint 规范全面上齐,则还会涨一截。
3.3 企业级:两万行起步,关键在“非协议”代码
企业级方案的数字看起来夸张,但两万行里真正属于“协议”本身的可能只有 4000 行,剩下的都在支撑协议可用。
举几个例子:
- 智能体管理后台:图形化查看智能体、任务流、调用链,前后端代码加起来 5000 行起步。
- 连接器生态:支持接入 Slack、钉钉、企业微信、Webhook 等不同消息渠道,每个连接器 500 到 1000 行,做五六个就 3000 行以上。
- 动态扩缩容、负载均衡、流量控制:需要额外 2000 行以上。
- 多语言 SDK:Python、Node.js、Java 各写一遍,每套薄封装也要 1000 到 2000 行。
协议本身不复杂,复杂的是让协议在各种“非理想环境”里依然稳定运行。这一认识能在你做技术方案时省下很多预期管理方面的麻烦。
4. 核心代码片段讲解:看懂最关键的几个函数
前面讲了行数估算,这一节用实际代码把几个核心环节串起来。建议你直接照着思路梳理,再结合自己的传输层实现落地。
4.1 消息结构定义
用 Python 的dataclass加上pydantic,把消息模型定义清楚。
from pydantic import BaseModel, Field from typing import Any from datetime import datetime import uuid class AgentMessage(BaseModel): protocol_version: str = "1.0" message_id: str = Field(default_factory=lambda: str(uuid.uuid4())) sender: str target: str action: str task_id: str = "" trace_id: str = "" create_time: datetime = Field(default_factory=datetime.utcnow) payload: dict[str, Any] = {} def to_json(self) -> str: return self.model_dump_json()好的消息定义会让后续排查轻松很多,尤其是trace_id这个字段,全链路排查时能省下大半的力气。task_id和message_id的职责是不同的:message_id是单条消息的唯一标识,task_id是跨多个消息、多个智能体的业务任务标识。
4.2 注册与握手
智能体启动后,先向协议网关发起注册。注册信息包括智能体 ID、能力列表、回调地址。握手成功后才进入消息处理阶段。
class RegistrationCenter: def __init__(self): self.agents = {} def register(self, agent_info: AgentInfo): if agent_info.agent_id in self.agents: # 更新心跳时间,刷新能力列表 self.agents[agent_info.agent_id].capabilities = agent_info.capabilities else: self.agents[agent_info.agent_id] = agent_info return RegistrationResult(success=True, token=self._issue_token(agent_info.agent_id)) def _issue_token(self, agent_id: str) -> str: # 生成一个临时访问令牌,后续消息签名会用到 return hmac_sign(agent_id, server_secret)这个流程很像多人协作时大家先互相加微信好友、备注好对方可以做什么事,后面有事直接在已有联系人里找,不用每次开会都从自我介绍开始。
4.3 路由选择逻辑
路由模块根据消息里的action与目标任务,找到最合适的智能体处理器。这里可以做得简单,也可以做得复杂。简单版是精确匹配,复杂版会加入能力权重、负载均衡、失败转移等逻辑。
def route(self, msg: AgentMessage) -> AgentEndpoint: # 1) 按目标智能体 ID 精确匹配 if msg.target and msg.target in self.agents: return self.agents[msg.target] # 2) 按能力 action 匹配可处理该任务的智能体 candidates = [ agent for agent in self.agents.values() if msg.action in agent.capabilities ] if not candidates: raise NoHandlerError(f"No agent can handle action={msg.action}") # 3) 从候选里选择一个,例如取负载最低的那个 selected = min(candidates, key=lambda a: a.current_load) return selected4.4 超时重试与幂等处理
智能体处理消息过程中,下游接口延迟甚至挂了,这时不能无限等待,需要超时机制配合重试。处理消息可能因为网络原因导致同一个请求被投递多次,接收端必须做好幂等判断。
class RetryPolicy: def __init__(self, max_retries: int = 3, base_timeout: float = 5.0): self.max_retries = max_retries self.base_timeout = base_timeout def execute(self, msg: AgentMessage, send_func: Callable): for attempt in range(self.max_retries): try: resp = send_func(msg) if resp.is_duplicate: return resp.last_result # 幂等命中,直接返回上次结果 return resp except TimeoutError: wait_time = self.base_timeout * (attempt + 1) # 递增等待 print(f"attempt {attempt+1} timeout, wait {wait_time}s") time.sleep(wait_time) raise MaxRetryException(f"message {msg.message_id} failed after {self.max_retries} retries")心得:智能体互联赛道上,超时时间不能设置得过短,尤其当接入了像大模型推理这种耗时长的下游能力时,5 秒可能连 prompt 加工都来不及。实际项目里我会根据业务类型做分级超时,普通查询类给 10 秒,报告生成类给 60 秒以上。多个智能体串行编排时,每一跳的耗时都要在预估任务总耗时里算进去,否则很早就被网关超时杀掉。
5. 影响代码行数的关键变量
5.1 序列化格式的选择
协议里消息体用 JSON 还是 Protobuf,对代码行数影响很明显。JSON 方案直接用现成的 JSON 库,定义模型加校验规则就好,大概 100 行能搞定,调试方便,肉眼可读。但长文本传输效率偏低,Debug 时可以直接把报文打印出来看。
Protobuf 方案需要额外编写.proto文件,再执行生成代码工具,同一份模型要维护 proto 和运行时对象两份定义,代码量会多出大概 200 到 300 行,但性能更好,适合高吞吐内部系统。如果团队没有兼容性包袱,优先讲开发效率,JSON 是更务实的选择。
5.2 多模态消息支持
现在的智能体互联经常要传图片、音频、视频。多模态支持不是简单在 payload 里加一个image_url字段就完事,你还要考虑:
- 附件存储:文件放对象存储还是存 Base64 塞进消息体。
- 附件鉴权:下载链接要做临时签名,防止未授权访问。
- 附件生命周期:一直留在存储里还是定期清理。
Base64 内嵌最省事,但传输开销大,一个几兆的图片会让报文膨胀 30% 以上。使用对象存储加临时 URL 是工程最佳实践,但相关代码要增加 300 行到 500 行。超过 10 兆的附件,基本就不能走实时消息链路,得改成先传文件、再传引用。
5.3 消息可靠性级别
很多协议实现都忽略了一个关键事实:不同消息对可靠性要求不同。同一个系统里,通知类的消息丢几条无所谓,但涉及交易确认、任务审批的消息一条都不能丢。
要支持按消息级别配置可靠性,结构上会复杂很多。持久化发送带消息表、事务表,接收确认机制必须更严密,这些逻辑展开就是上千行。极简版不需要支持这个能力,所以代码量能控制在 800 行内。
5.4 协议版本兼容处理
智能体互联协议上线之后,不可能所有智能体一起升级。总会存在旧的智能体还在用 v1.0,新的已经在用 v1.2 的情况。为了兼容,协议代码里通常要写版本协商逻辑,收到消息时先看版本号,再用对应版本解析规则去解析,并做好旧版本能力缺失时的降级。
如果没有从一开始就做版本字段,后期加这个能力会非常痛苦。我见过一个项目,早期协议里没有版本号,到最后只能靠字段是否存在做猜测式兼容,维护成本很高。版本协商逻辑加上配套测试,预期会有 200 到 400 行成本,但这笔钱值得花。
6. 常见问题与排查技巧实录
6.1 协议设计阶段最容易踩的三个坑
第一个坑:把协议当 API 设计,智能体直接写死。协议的核心是可扩展、可演化。如果消息类型只为一两个固定场景设计,可复用性会很差。设计时多问一句:如果下个月新增 10 个智能体,这套消息结构还够用吗?
第二个坑:只看消息收发,不管任务状态。很多初版协议把消息发出去就结束了。结果一个多智能体协作任务执行到一半,某个智能体挂掉了,任务状态查不到、恢复不了,整个流程只能从头再来。协议代码里一定要有任务状态字段和管理逻辑。
第三个坑:字段命名随意,语义不清晰。我看到过协议里有个字段叫data,里面在不同场景下可能是字符串、数组或者对象。短期似乎灵活,后期写路由、写监控、做权限控制基本无能为力。与其追求极致的抽象,不如把payload.action、payload.params这种结构化设计稳定下来。
6.2 运行时最容易出现的四个问题及排查思路
| 问题现象 | 可能原因 | 排查思路 |
|---|---|---|
| 消息发出去没响应 | 目标智能体没注册或注册过期 | 检查注册中心智能体列表,确认目标在线状态与心跳间隔 |
| 消息被重复执行 | 消费端没有做幂等 | 引入消息 ID 去重表,处理前检查message_id是否已存在 |
| 调用链复杂度高,定位困难 | 缺少全链路追踪 ID | 给每条消息加trace_id,日志里打印完整调用链 |
| 长时间无响应后突然攒批执行 | 消息队列积压,或消费线程池过小 | 查看消费端线程池队列深度和消息拉取速率 |
排查时建议按照“先看消息有没有到,再看处理有没有错”的顺序来推进。第一步先看目标智能体的日志,确认消息是否到达处理器。第二步看处理器有没有抛异常。第三步看返回值有没有回到路由网关。三步定位完,大部分问题都能找到根因。
6.3 一个从 800 行膨胀到 4000 行的真实案例
之前我做过一个内部版本,一开始设计目标就是“简化”,只提供同步请求响应。等到业务方接入时,各种需求就来了:希望支持消息推送、希望支持任务取消、希望接收端主动上报进度、希望控制并发请求数量。每加一个能力,就要在原有路由层、消息层、存储层各改一遍代码。到了第四个需求进版时,原来 800 行的文件变成了 4000 行,而且开始出现大量重复逻辑。
后来做了拆分重构,把路由、传输、状态管理、存储访问拆成独立模块,每个模块按抽象接口编程,才把代码量控制在可控范围内。协议演进是不可避免的,能做的不是阻止需求变更,而是把代码结构理好,让变更只影响一个模块,不牵连其他模块。独立模块之间的接口设计得越清晰,扩容带来的额外行数就越少。
这段经历也改变了我的设计习惯:从第一天就把传输方式、存储方式这些可能变化的部分抽象成接口。后续想切换消息队列或数据库,不需要改业务逻辑。合理的抽象不会增加太多代码,但能显著降低后续演进成本。
7. 个人经验:该花时间的地方不要手软
最近聊这个话题比较多,我发现大多数团队真正需要的不是“代码越少越好”,而是“系统越可控越好”。代码行数只是衡量复杂度的一个表面指标,更重要的是每一行代码是否解决了对应的实际问题。
我自己在实际操作中的体会是:协议的核心部分一定要认真设计,尤其消息模型、路由、状态管理这三块,前期花一周时间打磨完全值得。相比后期排查因为协议设计缺失导致的各种疑难杂症,前期多花的时间成本几乎可以忽略。
至于那些辅助模块,比如注册中心的持久化、控制台、连接器生态,可以按需引入,先把核心跑通,让团队看到真实效果,再逐步完善周围配套。如果一开始就追求大而全,项目风险会明显上升。
最后再分享一个不是很显眼但很实用的小技巧:协议代码里每个消息从收到到被处理完成,都打一条结构化日志,包含message_id、task_id、sender、action、耗时、状态。项目跑起来之后想看一眼系统健康度,直接按task_id聚合日志就能还原每个任务的完整过程,这在多智能体协作的场景下比什么 Dashboard 都直观。