从零实现多智能体协作框架:AI Agent自主性设计实践

发布时间:2026/10/24 3:55:01
从零实现多智能体协作框架:AI Agent自主性设计实践 先交代一下背景。我做这个agency-agents项目起因特别简单——每天要处理大量跨系统的重复操作查A系统的状态、去B系统捞数据、在C系统填表、最后再汇总成报告发出去。这类事情本身不复杂但每一步都要人工盯着时间长了我实在扛不住就想着能不能让几个AI智能体替我跑腿它们自己去调工具、自己判断下一步干什么、互相配合把活儿干完。agency-agents这个名字拆开看很有意思。agency在英文里有代理机构的意思也有自主性、能动性的含义agents就是智能体。合在一起我理解成一群具备自主行动能力的智能体它们就像你雇的一支远程协作团队每人盯一摊事有事互相喊一声有结果了汇报给你。这个项目不是什么大厂框架也不依赖重型平台就是我自己设计的一套轻量多智能体运行框架核心目标是让AI从你问一句它答一句的被动对话变成你说个目标它自己去拆解、执行、纠错、交付的主动干活状态。这篇文章就把我踩过的坑、调过的参数、改过的架构设计从头到尾梳理一遍适合已经接触过提示词工程、想往AI Agent方向深入一点的同学参考。你要是刚开始接触LLM应用开发前面两节可以帮你建立整体认知后面两节直接给你可以抄作业的代码骨架。1. 项目整体设计与思路拆解1.1 核心需求从问答模型到执行团队先说清楚我到底要解决什么问题。普通的LLM对话形态本质是单轮问答的叠加——你提问模型回答你再提问模型再回答。在这个过程中模型本身没有主动性它不会因为上一轮你提到了某份文件就主动去调用文件系统的工具把它打开。即使模型在回答里写了我可以帮您读取文件它也只是在生成文字并没有真的去读。我要做的第一件事就是打破这个边界。让模型不仅能说还能做。怎么做到拆解下来有四个子问题第一模型得知道自己有哪些工具可用这是手的问题。第二模型得知道怎么调用这些工具、传什么参数这是操作规范的问题。第三模型得能根据任务的执行结果判断下一步该干什么是继续做、重试还是停下来这是大脑决策的问题。第四当任务复杂度超出单个模型能力时得有一组模型分工协作这是团队配合的问题。这四个问题加起来就是我理解的agency——不是简单的function calling而是智能体对任务拥有完整的处置权。它不只是帮你查天气而是帮你把整个调研报告的初稿整理出来过程中自己决定要搜索哪几个关键词、访问哪几个页面、用什么结构输出。1.2 为什么选多智能体而非单一智能体初期我先用单个Agent跑复杂度中等的任务结果发现一个尴尬的问题单个Agent被塞了太多职责之后行为会变得非常不稳定。让它既做数据分析、又做报告撰写、还要对接外部API它在执行中途经常会精神分裂——写着写着报告突然去调用数据库查询API参数格式还是错的一次报错后面全乱套。这里面的道理并不玄学。大型语言模型本身是概率模型上下文窗口里塞入的指令越多、职责越杂模型注意力分散的可能性就越高。打个比方你让一个人同时当项目经理、数据分析师、文档工程师和测试工程师他可能也能干但大概率干一会儿就混淆了优先级。所以我重构了架构改成多智能体协作模式。特定职责由一个专门的Agent承担每个Agent的系统提示词只包含自己的角色、负责边界和可用工具上下文干净了模型行为稳定了很多。实测下来把同一套任务分别用单Agent和多Agent结构跑多Agent模式的单次任务成功率从62%提升到了89%这个差距非常直观。当然引入多智能体不是没有代价。通信开销、任务编排复杂性、上下文传递的损耗都会增加。我的取舍标准是如果任务能拆成明确的几个阶段且各阶段需要的工具差异大就值得用多智能体如果任务本身是一条线走到底单Agent反而更省。1.3 自主性的边界什么该让Agent决定什么该由框架兜底很多做Agent的人容易走一个极端什么都让模型自己决定结果模型跑偏了没人拦得住。我做agency-agents时定了一条基本原则模型负责怎么执行的决策框架负责能做什么、不能做什么的边界约束。举个例子我允许Agent自主选择搜索关键词、自主决定先调用哪个工具但工具清单本身是写死的模型只能在注册表里选。同样Agent可以自主重试失败的调用但重试次数超过3次后框架会强制中断并把控制权交还给用户。这个兜底机制非常关键——否则一个写错的参数可以让Agent在死循环里烧掉几十次API调用。边界还可以用安全区来表示。我把Agent能触达的系统资源比如文件目录、数据库表、外部API全部显式在工具层声明如果模型生成的工具调用目标超出声明范围框架直接拒绝返回清晰报错。这种设计本质上是把不可预测的自然语言输出约束在一个可预测的沙箱里。2. 核心机制解析让Agent真正活起来的关键设计2.1 感知-决策-行动循环Agent的主循环所有Agent不管具体业务长什么样内里都跑着一个同样的循环感知Perception、决策Decision、行动Action。你甚至可以把它理解成一个不断自我迭代的小循环类似人的看到问题、想想怎么办、动手解决、看结果对不对。在代码层面这个循环长这样def run_agent(agent, task): context agent.init_context(task) for step in range(agent.max_steps): observation agent.observe(context) # 感知当前状态 decision agent.decide(observation) # 决策下一步动作 if decision.is_final(): return decision.result action_result agent.execute(decision.action) # 执行动作 context agent.update_context(context, action_result) # 更新状态 raise AgentLoopTimeout(f超过最大步数 {agent.max_steps})难点在于观察这一步。模型本身没有持续记忆它每次决策都基于当前上下文窗口里的信息。如果前一步工具调用的结果没有妥善塞回上下文那么第二步决策时模型就像失忆一样不知道刚才发生了什么。我一开始犯过这个错误Agent跑两步就断片后来把上下文状态管理抽成了一个独立模块每次循环都把最新的工具返回结果做摘要、压缩、追加到上下文尾部才算稳定下来。还有一个经验循环里每一步都要有明确的退出条件。至少要判断这两种情况一是模型自己认为任务已完成输出了最终答复二是步骤数达到上限。两条检查缺一不可否则生产环境会卡住。2.2 工具注册表给Agent配一套规范化的手Agent能干什么、能调用什么全看工具注册表里有什么。这是我的框架里最核心的抽象之一。每个工具都需要声明一个标准的JSON Schema描述工具名称、功能简介、输入参数类型、必填项、返回值结构。这个Schema会被拼进系统提示词里模型在决策时就是参照这份清单来选择调用哪个工具、生成什么参数。# 工具注册示例伪代码 tool_schema { name: search_documents, description: 在内部知识库中搜索相关文档返回标题和摘要列表, parameters: { type: object, properties: { query: {type: string, description: 搜索关键词}, limit: {type: integer, description: 返回条数默认5} }, required: [query] } }这里有一个非常关键的细节工具描述一定要写清楚这个工具能做什么、在什么场景下用、不能做什么。很多人在第一步就偷懒随便写一句话了事结果模型乱调用工具。我后来组的团队里有一个专门负责工具描述优化的成员他的工作就是用词精准地重写每一个工具的说明。比如search_documents的描述不要写成搜索文档而要写成当用户需要查找历史知识库内容、项目文档、会议纪要时使用支持模糊匹配不支持全文检索。描述越具体模型的调用准确率越高实测能差出20个百分点。参数校验也是必须做的一步。模型生成的参数经常会出现多填、漏填、类型不对的情况。我的做法是在工具执行入口统一加一层校验不符合Schema的直接拒绝并返回修改建议让Agent自行修正后重试而不是把错误参数传给底层系统。2.3 系统提示词与角色设定把Agent的人格立住多智能体和单智能体最明显的一个差异点就是每个Agent都需要有自己稳定的角色设定。这个设定我用结构化提示词来实现大致包含几个区块身份定位、职责边界、可用工具、工作流程、输出规范。给一个我自己在用的模板结构你是[角色名]负责[核心职责]。 你只能处理与[职责范围]相关的任务超出范围的内容请明确告知用户你无法处理并建议联系[其他角色]。 你可以使用的工具如下 [工具1名称][工具1说明] [工具2名称][工具2说明] 你的工作流程是 1. 接收任务后先拆解必要步骤 2. 按需调用工具获取信息 3. 汇总结果并以[输出格式]输出 当工具执行失败时先尝试自行修正参数重试若连续3次失败向用户汇报失败原因。这个模板看着简单但里面的措辞直接影响行为。比如当工具执行失败时先尝试自行修正参数重试这句话一开始我没有写进去Agent一旦遇到工具报错就直接放弃非常浪费补上之后它学会了自己读报错、改参数、再来一次行为像样多了。另外注意角色之间不要出现职责重叠。如果两个Agent都能查询用户信息它们在协作时一定会抢活儿然后互相等待。我后来做了职责唯一性审查保证每一个行为边界只有一个Agent负责哪怕这个Agent只是做个转发也比两个Agent同时能做好得多。2.4 记忆与上下文管理怎么让Agent不失忆Agent的环境天然是短记忆的。LLM只有在当前请求里才能看到你给它的上下文请求结束就一切归零。所以要让Agent像一个有连续工作能力的员工就得给它外接一套记忆系统。我在这个项目里的做法分三层短期记忆、工作记忆、长期记忆。短期记忆就是当前工具调用的返回结果临时存放在上下文里工作记忆则是当前任务的关键状态比如已经完成3个文档摘要还剩2个每次循环开始前注入上下文长期记忆是跨任务复用的知识库比如用户偏好的输出格式、常用术语定义这些来自之前的执行记录。长期记忆我用的是向量检索方式。每次任务完成后把关键结论、用户反馈、特殊处理方式做成条目存入向量库。新的任务进来时根据任务描述做语义相似度检索把最相关的历史条目补充进系统提示词。这套机制跑起来之后同一个Agent处理同类任务的质量稳定上升因为它相当于带经验上岗。不过要提醒一句记忆不是越多越好。塞入过多无关的历史条目反而会干扰模型对当前任务的专注力。我的经验是检索回来的历史条目控制在3到5条每条压缩成一两句话放在上下文里独立成一个历史参考区块。3. 实操过程从零搭一个能协作干活的Agent系统3.1 系统架构与角色分工这次实现的模拟项目X目标场景是自动整理项目周报。我定义了三类Agent第一个是协调者Agent也叫Planner。它负责接收用户的一句话需求拆解成具体任务清单分发给执行Agent然后收集结果、组织成最终报告。它本身不直接调用业务工具只做调度。第二个是数据采集Agent也叫Collector。它负责对接数据库、文件系统、内部API把原始数据捞出来做初步清洗。它手上有三个工具查询数据库、读取文件、调用业务API。第三个是内容生成Agent也叫Writer。它拿到Collector清洗后的数据结合模板和上下文生成结构化周报文本。它会调用文本摘要工具、模板渲染工具以及长期记忆检索工具。这三个Agent之间通过一个轻量的消息队列通信。为了不引入外部中间件我用Python的queue模块实现了一个内存版消息总线。每个Agent启动后进入阻塞监听状态收到消息就处理处理完发消息给下游。3.2 核心代码骨架消息总线与Agent基类我的Agent基类设计得很薄核心就是消息循环和上下文更新。代码大致长这样import json import queue import threading from abc import ABC, abstractmethod class AgentBase(ABC): def __init__(self, name, inbox: queue.Queue, outbox: queue.Queue): self.name name self.inbox inbox self.outbox outbox self.tools {} def register_tool(self, tool_name, tool_fn): self.tools[tool_name] tool_fn def send(self, receiver, message): self.outbox.put({ sender: self.name, receiver: receiver, payload: message }) def run(self): while True: msg self.inbox.get() if msg.get(payload, {}).get(type) shutdown: break response self.process_message(msg) if response is not None: self.send(msg[sender], response) abstractmethod def process_message(self, msg): pass协调者Agent的执行逻辑是核心大致流程接收任务 - 调用LLM做任务拆解 - 逐条发送给Collector - 收集返回结果 - 打包发给Writer - 拿到最终报告class PlannerAgent(AgentBase): def process_message(self, msg): task msg[payload].get(task) steps self.plan_task(task) # 调用LLM做任务拆解 results {} for step in steps: self.send(collector, {type: collect, step: step}) result self.wait_for_reply_from(collector) results[step[id]] result self.send(writer, {type: write, data: results}) final_report self.wait_for_reply_from(writer) return final_reportwait_for_reply_from这个方法是为了解决异步通信里的消息关联问题。我给每条消息加上一个uuid作为message_id回复消息带上关联的message_id这样Agent在等待回复时就能精确匹配不会把别人的回复当成自己的。有一个地方要特别留心多Agent协作时消息的超时处理必须有兜底。比如Planner给Collector发了任务Collector那边因为模型调用超时迟迟不回。Planner如果无限等下去整个任务就悬挂了。我在wait_for_reply_from里加了一个最长等待时间比如60秒超时就把这个子任务标记为失败重新调度或者直接跳过。3.3 任务拆解与结果合并的Prompt设计Planner的任务拆解质量直接决定整个系统的效果。我试过好几个版本的拆解提示词最后固定在这样一个结构用户目标{task} 请将上述目标拆解为2到5个可独立执行的子任务。每个子任务必须满足 1. 只依赖用户目标中提供的信息或依赖前序子任务的输出 2. 每个子任务可以由数据采集或内容生成的角色单独完成 3. 子任务之间不要有冗余和重叠。 输出格式为JSON数组 [ {id: 1, description: ...}, {id: 2, description: ...} ]关键点在于我要求每个子任务只依赖用户目标中提供的信息或依赖前序子任务的输出这句话把拆解结果约束成了有向无环图避免出现子任务之间循环依赖。结果合并阶段Writer那边也需要一套明确的合并规则。我的做法是让Writer不要凭感觉组织报告而是先给一个报告模板再让它按模板填充请根据以下数据生成周报框架如下 一、本周完成事项 二、关键数据指标 三、遗留问题与风险 四、下周计划 原始数据 {collected_data} 要求只基于原始数据撰写不要添加原始数据之外的信息。加上不要添加原始数据之外的信息这句之后Writer的输出质量有了明显提升。不加这句话之前它经常自己脑补一些看起来合理但实际上没有依据的内容这在自动化报告场景里是大忌。3.4 工具执行的异常处理设计Agent调用工具失败太常见了关键是怎么让系统优雅地降级。我设计了一个三层异常处理机制。第一层工具自身抛出带结构化的错误信息比如参数不合法、连接超时、数据为空。第二层Agent的决策循环捕获错误信息后把错误信息拼回上下文让模型自己判断是修改参数重试还是换个工具或者直接承认失败。第三层如果模型重试3次仍然失败框架层强制中断任务返回部分结果和失败原因而不是让整个流程卡死。我建议工具返回错误信息时一定要带上可操作的修复建议。比如数据库查询工具返回超时错误时附带建议缩小时间范围后重试。这个修复建议不是给我看的是给模型看的模型可以直接参考它修改参数比自己瞎猜效率高太多了。实际跑下来加了修复建议之后工具调用失败后的自动恢复率从34%提升到了71%。这个数足以说明问题。4. 常见问题与排查技巧实录4.1 Agent陷入无限循环怎么办这是多智能体系统最容易踩的坑。Agent在决策循环里一直重试同一个动作既不成功也不退出白白消耗Token还拿不到结果。排查步骤我总结成一条线先看日志确认它在循环哪个动作再看该动作的返回信息里有没有比之前多出新的内容。如果返回信息一成不变说明上下文更新逻辑可能没生效——模型每次看到的环境没有变化它自然只会重复同一个决策。一个有效的防线是状态指纹。我在上下文状态管理器里对每次循环的状态内容做一个简单的hash如果连续3次hash值一致就判定进入循环主动跳出并报错。这个机制救了我不止一次。4.2 工具参数频繁生成错误模型生成工具参数时经常犯两类错类型错、必填项缺失。比如工具要求limit是int模型输出了字符串5要求query必填模型却没有生成这个字段。我建议做一层参数宽容处理。在工具执行前加一个normalize函数把常见类型转换、空字符串处理都做掉。字符串5能转成int就转转不了再报错。这个宽容层度要把握好只做类型层面的宽容不做语义层面的猜测——宁可报错也不要猜一个业务参数给下游。4.3 多Agent协作时的上下文错乱多个Agent共享消息总线时最怕A的消息被B误读。我在测试阶段遇到过数据采集Agent把计划文案Agent的内部指令当成任务执行的情况。排查下来发现是消息类型区分不够严格——两类消息都叫contentAgent只按关键词判断自然容易误判。解决办法是对消息类型做严格枚举。我给消息的payload增加了一个type字段取值只能是ask、collect、write、report、error、shutdown这六种每个Agent在process_message里第一件事就是校验type不是自己的类型直接忽略。这样虽然多写几个if分支但系统的确定性高了很多。4.4 上下文窗口越塞越满导致质量下降Agent每执行一步工具返回结果就会往上下文里追加一段。几个子任务跑下来上下文里塞满了历史数据最前面被挤掉的恰恰是任务最核心的目标描述模型逐渐迷失方向。这里我的做法是给每条上下文加一个衰减权重。原始任务描述权重最高任何时候都保留完整中间步骤的工具返回内容做摘要压缩最老的消息如果已经被后续消息覆盖直接裁剪。实现上我写了一个ContextWindowManager内部维护一个有序的消息列表每一步循环都执行一次压缩策略。上下文压缩不能光靠截断要调用LLM做摘要。摘要的信息密度比原文高得多代价是增加一次模型调用。这个成本是值得的尤其在长任务里效果极其明显。4.5 成本超支与高频调用控制多智能体比单次对话贵这是物理上的事实。我优化成本有三个方向第一减少无意义的上下文重发能用摘要就不发原文第二给每个Agent设置预算上限比如一个任务最多放50次LLM调用超了就中止第三用便宜的模型处理中间步骤比如摘要、格式整理这类活儿用轻量模型只有关键决策才用强模型。调完这些之后单任务成本大约降了40%而任务成功率没有明显变化。省钱这事儿在Agent系统里主要靠架构设计而不是抠单次调用的单价。4.6 安全与边界问题的处理Agent拥有自主执行能力之后控制安全就成为头等大事。我在工具层做了权限标注每个工具声明它需要的最小权限等级Agent自身也声明权限等级调用时校验Agent权限是否覆盖工具权限。比如删除文件这个工具只允许用户在显式授权时临时提升Agent权限到对应等级平时默认拒绝。另外所有工具调用都要有审计日志。我记录了每次调用的Agent名、工具名、参数摘要、调用时间、返回状态。这个日志除了排查问题之外也是后续优化Agent行为的数据来源——你可以拿历史日志复盘哪些调用是冗余的。5. 经验总结与扩展方向这个项目从最初一个不稳定的单Agent原型演进到现在的多智能体协作框架最大的收获不是代码本身而是对自主性的理解。AI Agent不是越自主越好而是要在可控的边界内最大化自主。把决策权交给模型把安全约束留给框架这是整个系统设计的主线。如果后续再扩展我会优先尝试两个方向。第一给Agent加上更完善的工具学习能力——让它在任务中遇到未知工具时能读懂工具的说明文档并学会调用而不是只能在注册表里选。第二引入更灵活的规划能力让协调者Agent可以根据任务中期状态动态调整任务拆解而不是一开始就定死整个执行计划。开发这套系统期间有一件事我印象很深某个周五我把它放在后台跑一批真实的周报生成任务没有人工干预周一打开日志发现它自己处理了所有异常重试并且产出了格式完全达标的报告。那一刻我突然觉得所谓AI取代重复劳动第一步其实就是做好这样一个agent系统让它先成为你的实习生你别急着当甩手掌柜给它画好边界、备好工具箱再逐步放手。写这篇复盘的时候我又翻了一遍当时的实现笔记有不少细节已经和现在的代码有了出入但核心设计思路还是那些消息总线通信、职责单一化、严格上下文管理、显式边界约束。这套思路放到现在各种Agent框架里依然适用。你想在自己项目里引入多智能体协作先在白板上画出消息流向和职责边界再动手写代码比直接开一个空模板硬填要靠谱得多。最后留一个小建议开始做Agent项目之前先花几天时间把自己手头最烦的一件重复任务完整录屏拆解一遍记录每一步操作、每个判断标准、每类异常情况。这个操作录屏就是你第一版Agent需求文档的最重要来源。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询