多Agent协作在2025年已经不是什么Demo级噱头了,我身边不少团队都在认真评估把一条完整的业务流拆给多个Agent去做。但真正动手的时候你会发现一个现实:模型本身不难接,难的是把“多个Agent之间怎么配合”这件事想清楚。这也是我为什么一直推荐从Agno入手的原因——它把Agent、工具、记忆、Team这些积木都备好了,你只需要专注于设计协作结构。这篇文章我会直接从设计模式讲起,重点拆解三个你绕不开的模式:主从模式、Agent即工具(Agent-as-Tool)、共享记忆,并给出一套可复制的Agno实现。
如果你已经试过单Agent写业务代码,也踩过“一个Agent什么都干,最后什么都干不好”的坑,那这篇内容正好卡在你的痛点上。我会尽量用工程化的视角来讲,不绕弯子,读完你能直接动手搭一个多Agent协作系统,而不是停留在概念层。
1. 先理解Agno:它不是框架,是Agent的乐高底座
1.1 Agno的核心抽象
Agno的前身是Phidata,如果你关注过这个项目,应该知道它从早期做“LLM应用数据管线”转向了完整的Agent开发框架。它最让我喜欢的一点是“克制”:核心抽象只有四个——Agent、Tool、Memory/Storage、Team。没有复杂的中间层,也没有绑定某个特定云平台,模型可以接OpenAI,也可以接Anthropic,甚至本地Ollama。
- Agent是最小执行单元,有角色、指令、模型、工具、记忆。
- Tool是能力扩展,一个普通Python函数加上
@tool装饰器就能变成Agent可调用的工具。 - Memory负责长期记忆,Storage负责会话持久化,底层可以接SQLite、Postgres、向量库。
- Team是多Agent编排容器,你可以直接往里塞多个Agent,指定
route或collaborate模式。
这几个抽象组合起来,正好覆盖了构建多Agent系统的所有关键环节:谁执行任务、谁提供外部能力、谁记上下文、谁做派单调度。新手不需要关注太多底层细节,就能把精力放在“协作结构怎么设计”上,这恰恰是入门阶段最该练的东西。
1.2 为什么多Agent系统需要“设计模式”
很多人第一次搭多Agent系统,习惯性把所有指令和上下文塞进一个Agent的system prompt里,跑起来之后发现:输出越来越长、上下文窗口越用越紧、模型偶尔还会把任务A的结论串到任务B里。这不是模型不够强,而是Agent的职责边界没有切分清楚。
设计模式在这里的作用,和经典面向对象设计模式(也就是你搜到的C++、Java设计模式那一类)在软件工程里的作用一样:把已经被验证过的高频协作结构沉淀成可复用的套路。多Agent系统虽然新,但它本质上还是在做“任务分解、职责分配、结果聚合”这三件事,而这三件事在不同语境下产生了几个稳定解法:
- 主从模式:一个中心节点统一调度,子节点只负责执行。
- Agent-as-Tool:把子Agent包装成工具,主Agent按需调用。
- 路由模式:按任务类型把请求分发给不同专业Agent。
- 协作模式:多个Agent围绕一个共同目标平级协作,互相补充信息。
- 共享记忆:多个Agent共用同一个记忆库,保证上下文一致。
设计模式不是理论考试,它是工程经验。搞清楚这几种模式,远远比你调十次prompt有用。下面我把它们逐个拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 多Agent设计模式基础:三张必懂蓝图
2.1 主从模式:有一个“项目经理”在统一派活
主从模式(Supervisor Pattern)是当前多Agent系统里最常见、也最容易理解的结构。它的特征非常明显:所有任务先进入一个中心Agent(Supervisor),中心Agent负责任务拆解、派单和结果校验,具体的脏活累活丢给下游的子Agent去干。
用生活类比就是项目经理和外包团队的关系。项目经理不亲自写代码、不亲自做设计,但他清楚整体目标和每个环节的进度,他决定“这个需求该交给谁”“做完的结果合不合格”。子Agent就是外包团队,各管一摊,按时交活。
主从模式最大的优势是:任务边界清晰,可追踪、可审计。因为所有调度决策都发生在中心节点,你可以很清楚地看到主Agent先派了谁、后派了谁、每个子Agent返回了什么。遇到问题,回看主Agent的决策链路就行。这在生产环境的排查里极其重要。代价是中心节点会成为瓶颈——所有请求都要过它一道,模型调用链路更长,Token成本也更高。
在Agno里,主从模式最直接的落地方式就是Team。你在Team里选一个Agent当主节点,其他Agent作为成员,由主节点决定任务如何分配。
2.2 Agent-as-Tool:把子Agent当作工具调用,主Agent的视角豁然开朗
我在追踪多Agent社区最新的设计讨论时,发现一个原则性的共识:所谓主从模式,本质上就是把子Agent当作另一种形式的Tool进行调用。这个洞察非常关键,它改变的不是实现代码,而是你的心态。
传统工具(Tool)是一段确定性代码:输入JSON,输出JSON,逻辑固定,结果稳定。子Agent则是一个由LLM驱动、具备自由发挥空间的能力单元。但从调用方的视角看,它们完全一致——主Agent只需要知道:这个能力接收什么输入、会返回什么结果、什么时候该调用它。至于内部是规则引擎还是大模型,在主Agent的决策空间里根本不重要。
把子Agent当作工具看,有三大好处:
- 主Agent的上下文更干净。它拿到的只是子Agent返回的结果摘要,而不是子Agent的完整推理过程,能有效省Token。
- 规划与执行解耦。可以用一个强推理模型当主Agent负责拆任务,用便宜快速的小模型当子Agent执行重复劳动,成本直接减半。
- 结构化思维更清晰。你会开始为每个子Agent定义“工具说明书”式的Instruction,引导主Agent在什么场景下调用它。
这也是我实操时最推荐新手先掌握的模式。你先定义好每个子Agent的输入输出契约,再把它封装成工具,主Agent自然就知道怎么用了。下面第三部分我会给完整代码。
2.3 路由与协作:什么时候并行,什么时候串行
主从和Agent-as-Tool更像“执行视角”,而路由与协作是“组织视角”,解决的是任务之间的依赖关系。
路由模式(Routing)适合处理“不同类型的请求,由不同专业Agent处理”的场景。比如客服系统:咨询类问题走FAQ Agent,投诉类问题走售后Agent,技术支持类问题走工程师Agent。主Agent只做分类器,把请求派给对应Agent,不再干预结果。Agno的Team里mode="route"就是干这个的。
协作模式(Collaboration)适合“多个Agent需要共享信息、共同推进一个复杂产出”的场景。比如做一份含市场分析、技术方案、成本估算的报告,三个Agent各写一部分,但需要互相读取对方的产出以保证数据一致。这时候mode="collaborate"就有用了,Agent之间可以互相暴露工具或共享上下文。
怎么选择?我的经验是:如果任务边界清晰、彼此独立,优先路由;如果任务之间有强依赖、需要共同迭代,选协作或主从串行。不要一上来就上协作模式,它最灵活也最难控制,新手阶段容易变成“一堆Agent在群里讨论但没人干活”。
2.4 共享记忆:多Agent的公共笔记本
多Agent系统里最容易忽略、但也最容易出问题的就是记忆。记忆不是简单存聊天记录,它分为几个层次:
- 工作记忆(Working Memory):单个Agent在本次会话中的上下文,随请求一起发给模型。
- 长期记忆(Long-term Memory):跨会话保留的偏好、结论、事实,比如“用户偏好Python”“项目历史决策记录”。
- 共享记忆(Shared Memory):多个Agent共同访问的记忆区域,可以是同一个Memory实例,也可以是同一个外部存储库。
在Agno中,Memory负责长期记忆,Storage负责会话存储。当你给多个Agent传入同一个Memory(db=...)实例时,它们理论上就具备了“公共笔记本”——任一个Agent写入的关键信息,其他Agent在后续会话中能检索到。
但这里有个非常重要的坑:共享记忆不等于自动同步,更不等于上下文共享。每个Agent拿到同一个记忆库之后,是否把某段记忆放进自己的上下文窗口,仍然由系统prompt和检索策略决定。所以你需要明确告诉Agent:“你可以从记忆中读取这些信息”“你需要把结果写入记忆”。否则,记忆库只是“一个大家都不看的共享硬盘”而已。
3. 实操:用Agno搭一个“需求分析+编码实现”的主从协作系统
3.1 场景拆解:一个端到端需求交付流程
我选一个非常典型的场景:用户提出一个模糊需求,比如“帮我用Python写一个倒计时番茄钟,命令行工具,要能设置25分钟和休息5分钟”,系统需要端到端交付可运行的代码。
如果只用一个Agent,它会同时做需求理解、技术选型、代码实现,经常会出现“需求没理解透就开写,代码写一半发现跑偏”。所以我们把它拆成三个角色:
- 主Agent(项目经理):接收用户需求,先让需求分析师出PRD,再让编码工程师出代码。
- 需求分析子Agent:把模糊需求整理成明确PRD,包含背景、功能清单、验收标准。
- 编码实现子Agent:根据PRD输出完整Python代码和运行说明。
在这个设计里,主Agent就是Supervisor,而两个子Agent分别被封装成工具。它们共用同一个记忆库,用来记录用户偏好(比如“用户偏好Python”),这样下次同一用户提出新需求时,系统能直接继承上次的偏好,不需要重新问。
3.2 直接从代码开始:三个设计模式一次性落地
先装依赖,建议在虚拟环境里操作:
bash复制pip install agno openai
如果你用其他模型,比如Ollama本地模型或Anthropic,按对应文档配置即可。下面这版代码用的是OpenAI模型,环境变量OPENAI_API_KEY配置在系统里。
python复制import os
from agno.agent import Agent
from agno.tools import tool
from agno.memory import Memory
from agno.storage.sqlite import SqliteStorage
os.environ.setdefault("OPENAI_API_KEY", "你的Key")
# ============ 1. 共享记忆库 ============
# 所有Agent连接同一个SQLite存储,这就是共享记忆的物理基础
shared_storage = SqliteStorage(
table_name="shared_memory",
db_file="tmp/shared_memory.db"
)
shared_memory = Memory(db=shared_storage)
# ============ 2. 定义两个子Agent ============
analyst = Agent(
name="需求分析师",
role="负责把模糊需求整理成明确PRD,输出:背景、功能清单、验收标准",
instructions=[
"先拆解目标用户场景,再给功能清单",
"输出要精简,分条列出,不要超过300字",
"如果用户需求不明确,默认按最合理的方式补充"
],
memory=shared_memory,
)
coder = Agent(
name="编码工程师",
role="根据PRD产出Python代码,包含完整实现和运行说明",
instructions=[
"代码必须有注释,关键逻辑要说明清楚",
"优先使用标准库,尽量不要引入额外依赖",
"最后必须给出一个运行示例"
],
memory=shared_memory,
)
# ============ 3. 把子Agent包装成“工具” ============
@tool
def analyze_requirement(requirement: str) -> str:
"""
调用需求分析子Agent,返回结构化PRD。
参数 requirement: 用户原始需求描述
"""
resp = analyst.run(requirement)
return resp.content if hasattr(resp, "content") else str(resp)
@tool
def implement_code(prd: str) -> str:
"""
调用编码子Agent,根据PRD返回可运行代码。
参数 prd: 需求分析Agent输出的PRD
"""
resp = coder.run(prd)
return resp.content if hasattr(resp, "content") else str(resp)
# ============ 4. 主Agent:只关心“工具调用” ============
supervisor = Agent(
name="项目经理",
role="协调需求分析和编码实现两个子Agent,完成端到端交付",
instructions=[
"第一步:调用 analyze_requirement 分析用户需求,拿到PRD",
"第二步:调用 implement_code 把PRD交给编码Agent,生成代码",
"如果第一次调用工具失败或返回异常,可以重新调用一次",
"最后向用户汇总PRD简版和代码文件清单"
],
tools=[analyze_requirement, implement_code],
memory=shared_memory,
show_tool_calls=True,
)
# ============ 5. 运行 ============
if __name__ == "__main__":
supervisor.print_response(
"帮我用Python写一个倒计时番茄钟,命令行工具,"
"要能设置25分钟和休息5分钟",
stream=True
)
注意tmp目录要提前建好,否则SQLite会报错。跑完以后,你会看到主Agent先调analyze_requirement,拿到PRD,再调implement_code,最后给你汇总。
3.3 代码逐段说明:每行都在解决什么问题
上面这段代码虽然不长,但每一块都在落实设计模式的关键点。
先说共享记忆的初始化。SqliteStorage负责把记忆持久化到本地文件,Memory(db=shared_storage)把存储层包装成Agent可用的记忆接口。三个Agent传同一个shared_memory,就实现了“公共笔记本”的物理层。你可以试着在第一次跑完后打开tmp/shared_memory.db里的表,能看到已经被写入的记忆记录。
再看子Agent的定义。我给每个Agent都设置了role和instructions,这比单纯写一句“你是一个需求分析师”有效得多。role决定了模型对这个Agent整体定位的理解,instructions是具体的执行约束。这里有个技巧:instructions里每条都尽量写成“做了什么、达到什么标准”,模型才能准确执行。
接下来是核心——@tool装饰器。analyze_requirement和implement_code本来只是普通Python函数,但加上@tool后,它们在主Agent眼里变成了可调用的工具。函数内部调用的是子Agent的run方法,run返回对象里有.content属性,我们把它转成字符串返回给主Agent。这就是“Agent-as-Tool”的标准用法:子Agent被包装成工具,主Agent感知不到Agent的存在,只看到两个普通函数。
最后是主Agent。它的tools参数传入了两个工具,同时instructions明确告诉它“第一步做什么、第二步做什么”。这里就体现了Supervisor的职责:真的就像项目经理一样,判断先分析后编码的顺序,而不是把两个工具一起乱调。
3.4 运行与调参:密钥、模型、会话ID一个都不能错
第一次跑这段代码前,有几个配置点容易踩坑。
- 模型选择:默认
Agent会走OpenAI默认模型,也就是gpt-4o或gpt-4o-mini。如果你希望在成本敏感场景下用轻量模型,可以在Agent里显式指定model="gpt-4o-mini"。主Agent和子Agent可以用不同模型,我把主Agent设置为强推理模型、子Agent设为小模型,是生产上省成本的通用套路。 - 环境变量:
os.environ里必须存在OPENAI_API_KEY,否则运行时会报401。建议不要硬编码在代码里,放在.env文件或环境变量配置更安全。 - 会话隔离:如果多个用户共用这套系统,需要在调用
print_response时传入session_id,否则Agno会把所有对话记到同一个会话里,造成用户之间的上下文串扰。传法如下:
python复制supervisor.print_response(
"再来一个CLI工具,这次帮我统计目录下代码行数",
session_id="user_123",
stream=True
)
这样每次请求都会绑定到user_123这个会话,历史记录能正确恢复。
- 流式输出:
stream=True可以把结果逐段打印出来,体验更好。如果不需要流式,直接去掉这个参数,等待完整输出即可。流式模式在Web应用里配合SSE(Server-Sent Events)非常顺手。
4. 常见问题与排查技巧实录
4.1 主Agent“失灵”:怎么都不调用子Agent
我调试时最常遇到的现象是:主Agent收到用户需求后,自己脑补了一个答案,完全不调用任何工具。原因通常是三类:指令不够明确、模型太弱、工具描述不清楚。
排查思路是:先看show_tool_calls=True的日志输出,如果日志里没有tool_call记录,说明主Agent根本没有把任务识别为“需要工具协助”的场景。解决方法是把主Agent的instructions写得像操作手册:“必须调用analyze_requirement来分析需求,禁止直接输出PRD”,并且给工具描述加上“当用户提出技术需求时,这个工具最重要”这类引导。
如果指令已经很清楚还是不调用,就换更强的主Agent模型。弱模型容易在“多工具选择”上犯迷糊,这是模型能力问题,不是代码问题。
4.2 子Agent输出太长,上下文爆炸
这是多Agent系统里最现实的成本问题。子Agent一旦没有约束,可能会生成几千字的分析报告,主Agent把这些报告读进上下文,Token消耗直接翻倍。
解决方式有两个。第一,在子Agent的instructions里强制压缩输出:“只输出核心结论,用20条以内列表,丢弃推理过程”。第二,做“结果摘要层”:在@tool函数内部,调用子Agent拿到完整输出后,再让一个轻量小模型做一次摘要,只把摘要返回给主Agent。这个模式在复杂场景里几乎是必备的。
我自己的习惯是:工具返回值控制在300字以内,如果子Agent输出超长,就在工具函数里截断或摘要。主Agent只关心结果,不关心过程细节。
4.3 记忆串台:共享记忆和会话隔离要分清
共享记忆和会话存储是两个维度,很多人一开始会搞混。共享记忆解决的是“跨会话是否记住用户偏好”,会话存储解决的是“当前这段对话的上下文是否完整”。
你需要在同一个系统里同时用好两者:
| 功能 | 使用的组件 | 数据范围 |
|---|---|---|
| 记忆持久化 | Memory(db=...) |
跨会话长期记忆 |
| 会话历史 | Storage(db=..., session_id=...) |
单会话上下文 |
| 用户隔离 | 不同session_id |
不同用户/会话 |
如果发现A用户的需求被B用户的偏好污染,多半是session_id没传或者传了同一个。如果发现跨会话的偏好没有被记住,检查Memory是不是真的被写入,以及Agent的instructions里是否包含“处理需求时先读取记忆”的指令。
4.4 循环调用与成本失控
多Agent系统跑着跑着,偶尔会出现主Agent反复调用同一个工具,甚至工具内部又调工具,形成循环。这个在Agno里并不常见,因为默认链路是主Agent调用子Agent,子Agent通常不会再反向调用主Agent,但如果你的子Agent里也挂了共享工具,就有循环风险。
排查手段:在Agent配置里可以设置最大迭代步数(不同版本参数名不同,通常是max_retries或iterations),超出即终止。生产环境里务必加上这个限制,否则一次异常任务可能产生几十次模型调用,账单会非常难看。
另外,建议给工具调用做简单的“调用次数日志”,用print或日志库记录每一步的是谁调用了谁、耗时多少、Token消耗多少。这个日志在你复盘系统设计时价值极高。
最后分享一点实战体会
我最初搭多Agent系统时,总是追求“一步到位”,想用最复杂的协作结构。后来发现,真正好用的系统往往结构简单、边界清晰。Agno最让我省心的地方,是它把Agent、Tool、Team、Memory这些概念统一得很干净,你不需要在框架层面做太多妥协。等你把主从、Agent-as-Tool、共享记忆这三种模式跑熟练了,再回头去看官方文档里那些更复杂的Team协作示例,理解成本会低很多。新手阶段,先让一个主Agent学会“调用两个工具”就够了,模式跑通之后,再逐步往里面加Agent、加记忆策略,整个系统的复杂度才控得住。
