2026/9/28 22:19:36

Agent-Native架构实战:从工具封装到事件总线,打造AI Agent可用的系统

Agent-Native架构实战:从工具封装到事件总线,打造AI Agent可用的系统 1. agent-native到底在说什么从“人操作软件”到“Agent操作一切”过去两年我一直在做AI应用相关的架构设计经历了从“给LLM写Prompt”到“给LLM套工作流”再到“让LLM自己调工具”的三个阶段。现在圈子里最热的一个词变成了agent-native很多人一听到就头大觉得又是一波概念炒作。但我个人认为这个词背后确实有一个实打实的架构转向而且它直接影响你接下来怎么写代码、怎么设计系统、怎么养团队。先说结论agent-native的核心思想是把AI Agent从“应用的一个附属功能”提升为“系统的一等公民”。在传统架构里用户打开网页、点按钮、填表单软件是人操作的工具。但在agent-native架构里用户可能根本不打开你的网页而是让一个Agent带着自然语言指令过来直接操作你的系统完成目标。你的系统要做的不是“给用户看”而是“给Agent用”。这听起来只是一句话但真正落地时从API设计到鉴权方式、从数据模型到日志体系全部要换一套思路。我遇到过很多团队嘴上说在做agent-native实际就是把业务接口挂到了大模型的function calling上让模型能调几个工具就算“Agent化”。这其实是“agent-compatible”只是兼容了Agent并没有为Agent重构系统。真正的agent-native要求你在设计系统时就把“Agent如何接入”“Agent如何协作”“Agent如何被审计”当作核心考量系统从骨子里就是为自动化和智能体协同准备的。这个转变之所以现在才火有几个现实推力。第一模型的能力阈值跨过了“能稳定调用工具”的门槛function calling已经不是Demo级效果而是能跑生产第二MCP这类协议的普及让“模型访问外部数据”有了统一标准第三多Agent协作场景开始出现系统不再只服务一个Agent而是一群Agent。这三件事凑在一起“面向Agent设计系统”就成了刚需。这篇文章我主要面向两类人一类是后端工程师和架构师正在思考怎么把现有业务改造成“能被Agent调用”的形态另一类是AI应用开发者想从“写单个Agent”升级到“设计和多个Agent协作的平台”。我不会讲太多概念重点放在架构拆解和实操经验上。2. agent-native架构的核心要素身份、工具、记忆、通信把一个系统做成agent-native不是简单加一层API。我拆了几个关键要素每个都是在实战中绕不开的。2.1 身份与权限Agent不是“谁”而是“角色”第一个最容易踩的坑就是Agent的“身份”怎么处理。用户登录系统有账号体系Agent呢很多团队直接把Agent请求伪装成某个用户的请求结果权限瞬间失控——Agent本来只需要读数据结果因为绑了管理员账号把删除接口也调了。我的建议是引入“服务身份”和“代理身份”两层模型。服务身份是Agent自己的凭证它说明“我是哪个Agent属于哪个组织能访问哪些资源范围”代理身份则是“我代表哪个用户/哪个任务来执行”。这两层在鉴权时必须分开校验。比如订单查询Agent它的服务身份决定了它能连哪个订单库而它代理的具体用户则决定了它只能看该用户的订单而不是全量订单。实际操作中可以用JWT同时携带两个claimagent_id和acting_user_id。中间件先校验agent_id对应的权限范围再叠加acting_user_id的行级权限过滤。这套设计我用了大半年基本没有出现过越权事故。2.2 工具契约你的API是“给人看的”还是“给模型看的”传统API的设计目标是“语义清晰、路径直观”比如POST /orders/create加一大段JSON请求体。但给Agent用的API重点变成“描述准确、参数明确、错误可理解”。同一个接口给模型看的时候需要额外的机器可读描述告诉模型这个工具是干嘛的、参数什么格式、什么情况会失败。这里有一个非常容易忽视的细节工具描述里不该写“稍微”“大概”“等”这类模糊词。模型是字面理解你写“数量稍微多一点”它可能真的给你传一个超出预期的值。你的描述必须精确到“参数范围”“单位”“默认值”“边界条件”。比如一个退款工具不是写“退款金额”而是写“退款金额单位分必须大于0且不超过订单实付金额币种与订单一致”。另外工具的返回结构也要为模型设计。不要返回一个巨大的JSON对象让模型自己找重点。最好的做法是返回一个精简后的结果摘要再加一个details_url让模型按需去查详细信息。这相当于你在帮模型省token、省推理时间。2.3 记忆与上下文Session不能只存对话要存“任务现场”Agent-native系统里“记忆”比在单Agent应用里复杂得多。单个Agent的对话上下文好做就是一个会话列表。但多Agent协作时一个任务可能被多个Agent接力处理前面Agent的工作结论、状态快照、中间产物都需要被后续Agent读取。我推荐“任务现场”这个设计模式每个任务绑定一个现场对象里面包含任务目标、当前状态、已完成步骤、关键决策记录、相关实体ID列表。Agent在工作时读取现场干活时更新现场交接时传递现场。会话消息只是“过程日志”现场才是“工作状态”。这样即使Agent中途换模型、换实例任务都可以从现场恢复而不是从聊天记录里重新猜。2.4 通信方式别用HTTP硬轮询事件总线是标配多个Agent协作时通信方式非常关键。一开始我图省事让Agent A直接调用Agent B的HTTP接口结果是耦合严重——A要等B处理完才能继续超时、重试、负载全部乱套。后来改成事件总线模型每个Agent不直接调用另一个Agent而是往总线上发事件订阅相关事件的Agent自动响应。比如订单Agent发了order.paid事件仓库Agent收到后自动创建拣货单风控Agent收到后自动开始审核。Agent之间完全解耦每个Agent都可以独立伸缩。实现上可以用Redis Stream或者Kafka事件量不大时Redis Stream非常顺手运维成本低延迟也低。3. 落地实操从一个最小可用的agent-native服务说起概念讲再多不如一个能跑的例子。我下面拆解一个我实际搭过的“项目助理系统”它解决的核心场景是项目在推进过程中会有大量信息散布在文档、群聊、工单里系统用一个协调Agent加若干个专业Agent自动完成信息收集、建任务、跟进进度、输出周报。这个系统麻雀虽小但五脏俱全覆盖了agent-native的大部分关键点。3.1 第一步定义系统里的Agent角色不要一上来就写代码先把角色画清楚。我们这个系统里设了四个AgentAgent角色职责需要的工具权限范围协调Agent接收用户目标拆解任务分发给专业Agent任务分发、进度查询、结果汇总只读项目元数据可写任务列表文档Agent检索项目文档提炼信息文档检索、文档摘要只读文档库工单Agent处理用户反馈和工单流转工单查询、工单状态更新读写工单模块周报Agent汇总各方进展生成周报读取任务、读取文档摘要只读全部可写周报库这里的关键是权限边界先定死后面写工具函数时才有章可循。3.2 第二步把业务能力封装成Agent可调用的工具工具封装的核心是把“业务操作”翻译成“Agent可理解的服务”。我用的方式是为每个工具写一份“契约”工具名称、一句话描述、入参JSON Schema、出参结构、错误码。这个契约不是给人看的文档而是会直接填入Agent的system prompt或者注册到MCP协议的工具列表里。以“创建任务”工具为例入参Schema大概是这样的{ name: create_project_task, description: 在指定项目中创建一条新任务。项目ID必须是已存在的项目。截止时间使用ISO格式如2025-06-30T18:00:00Z。优先级只能是low/medium/high。, parameters: { type: object, properties: { project_id: {type: string, description: 目标项目的UUID}, title: {type: string, description: 任务标题不超过100字}, description: {type: string, description: 任务详细说明可以为空}, due_time: {type: string, format: date-time}, priority: {type: string, enum: [low, medium, high]} }, required: [project_id, title] } }写这种描述时有个小技巧把常见的边界条件直接写进description里不要指望模型“常识性理解”。你告诉它截止时间是ISO格式它就会传标准格式你不说它可能传个“明天下午3点”你的解析层就要做一堆兼容。3.3 第三步Session管理与任务现场用户和协调Agent的每一次交互都开一个SessionSession绑定一个“任务现场”对象。现场的字段设计如下class TaskScene(BaseModel): task_id: str user_goal: str status: str # running / waiting / done / failed assigned_agents: list[str] pending_steps: list[str] completed_steps: list[str] key_entities: dict # 例如 {doc_id: ..., ticket_id: ...} last_decision: str # 最近一次关键决策记录每次Agent做完一步都通过工具更新现场。比如文档Agent检索完资料后协调Agent会把“关键实体”和“已完成步骤”写回现场。这样如果调用链中断比如模型超时、网络抖动恢复时只要读现场就知道干到哪了不用重新问“刚才要我干嘛”。我强烈建议现场对象用Redis存TTL设成任务最大可能时长读写在毫秒级代价极低。3.4 第四步让Agent能“看得见”自己的行为轨迹这是agent-native里我花时间最多的一块可观测性。传统应用看日志、看指标就行Agent系统的行为是“自然语言驱动的决策链”你如果不记录决策过程出了问题完全没法查。我给每个Agent加了一个“思考日志”和普通日志分开存。思考日志里记录的是本轮收到的用户指令/上游事件、工具调用记录调了哪个工具、传了什么参数、返回了什么、模型自身的推理摘要从哪一步得出了什么结论。这些记录单独存到ES或ClickHouse用于事后审计和回放。有一次周报Agent生成了一份错误周报把另一个项目的任务也汇总进去了。当时就是靠思考日志回放发现是文档Agent在检索时没有加项目隔离条件把所有项目的文档都搜了回来。如果只查普通应用日志这种问题根本定位不出来。3.5 第五步用事件总线把Agent们串起来这个系统里协调Agent并不是直接调用其他Agent的代码而是发事件。比如用户说“整理一下本周项目进展”协调Agent先发一个task.decomposed事件文档Agent和工单Agent各自订阅这个事件去干活干完后发doc.summary.ready和ticket.stats.ready事件周报Agent等这两个事件都到了才开始汇总。用Redis Stream实现时我给每个Agent配一个独立的消费组互不干扰。事件里面带上scene_id关联到任务现场下游Agent处理时先去读现场获取上下文干完再更新现场并发布新事件。这套模型跑起来后最明显的好处是我可以随时加一个新Agent进来。比如后来加了“竞品监控Agent”它订阅project.updated事件每次项目信息变化就去爬竞品动态然后发一个competitor.alert事件。协调Agent收到后决定要不要通知用户。整个接入过程没有改任何已有Agent的代码。4. 常见问题与排查经验实录agent-native系统的坑很多时候跟传统后端完全不一样。我把实际踩过的坑和排查思路整理一下都是搜索引擎不太容易直接找到的实战经验。4.1 模型上下文窗口被“撑爆”不是模型问题是设计问题很多人在多Agent协作时习惯把历史消息全量传给模型没跑几个来回就报token超限。其实这是把“人类看聊天记录”的习惯带到了Agent上。正确做法是做“上下文浓缩”。每个Agent在处理事件时只关注当前步骤需要的输入而不是整个任务的全部历史。我是通过现场对象来实现的把过去对话压缩成“用户目标、当前状态、已完成步骤、关键实体”这几个字段作为上下文传入模型。结果一样能干活token消耗降到原来的十分之一。如果某个Agent确实需要长文档背景不要直接把全文塞给它先用文档Agent做一个摘要提取再把摘要放进去。授人以鱼不如授人以渔在Agent协作里摘要提取是必备的预处理步骤。4.2 工具循环调用导致费用飙升必须设硬性上限Agent在循环里反复调同一个工具或者几个Agent互相触发停不下来这种事故我见过不止一次。有一次竞品监控Agent和文档Agent互相发事件整整刷了一晚上把API额度烧掉大半。现在的做法是在三个层面设防单Agent单轮任务工具调用次数上限比如协调Agent一个任务最多调30次工具超出强制停止并询问上级Agent或用户。事件循环深度限制事件总线上的每条消息带上trace_id和depth字段一个链路深度超过10就直接丢弃并告警。费用预算硬门槛给每个Agent挂一个计数器按Token和工具调用量估算费用每天超过一个阈值就熔断需要人工解除。这些限制不会影响正常流程但能挡住绝大多数失控场景。设置的时候宁可保守一点出问题再放宽不要一开始就没有约束。4.3 Agent“一本正经地胡说八道”工具返回结构要倒逼模型说真话模型在无法确定时倾向于“编一个看似合理的答案”。尤其在设工具返回信息不足时它宁可猜也不承认不知道。解决思路是让工具的返回结果里显式包含“置信度”和“数据时间点”。比如工单Agent查询“最近的用户反馈”返回里加一个data_as_of字段说明数据截至什么时间。文档Agent做摘要时如果原文档本身就互相矛盾返回里加一个conflicts_detected字段提醒下游不要盲目采信。这个改动之后编译内容明显变少因为模型被“逼着”正视信息缺失。4.4 多Agent并发写同一份数据分布式锁不可少多个Agent同时操作同一个业务对象时很容易出现互相覆盖。比如两个Agent同时更新一个任务的负责人后写的覆盖先写的。方案就是引入业务对象级别的分布式锁。用Redis的SET NX EX实现即可锁的key为业务对象ID持有锁的Agent完成后释放。锁超时时间要设短一点避免Agent卡住导致其他人拿不到锁。实测下来10秒足够既不影响并发又能防止长时间加锁。4.5 调试Agent比调试代码难一个量级建立“沙箱重放”机制普通代码出bug打断点看调用栈就能解决。Agent出问题你根本不知道它在想什么。我建议搭一个“沙箱重放”环境核心思路是每次生产环境的Agent交互把“任务现场快照”和“工具调用轨迹”存下来。出问题时把这两个数据拉回本地沙箱用一个固定版本的模型重新跑一遍就能复现问题。这个机制的关键在于工具的幂等性。如果工具本身有副作用比如“发邮件”“改状态”沙箱里要用mock版本替代。所以Agent调用的工具要设计成“可注入”生产环境注入真实实现沙箱注入mock实现。我试过几个方案最顺手的是在工具注册表里做环境切换一套代码两套配置。5. 工程化落地与工具选型建议最后聊一下真实项目里的工程化决策从语言、框架、基础设施到团队协作方式给你们一些可以直接参考的选择。5.1 技术栈选型先定事件总线再定Agent框架很多团队一上来先选Agent框架比如LangChain、CrewAI、AutoGen然后再想配套设施。我的建议恰恰相反先选事件总线和存储再选Agent框架最后再选模型。因为agent-native架构里Agent框架只是最上面一层“思考壳子”底下的数据流、事件流、状态存储才是系统的筋骨。我自己常用的一套组合是事件总线Redis Stream单机或Kafka跨机房、高吞吐状态存储Redis现场对象 PostgreSQL业务实体工具注册自建一个基于JSON Schema的注册中心简单可控Agent运行时轻量自封装或者用LangChain的Tool Calling能力但不引入太重的工作流引擎模型优先选支持稳定function calling的模型比如最新的GPT-4o系列、Claude系列或者国产的Kimi、Qwen等那为什么不推荐重型Agent框架呢不是说框架不好而是很多框架把“定义Agent的方式”绑定了你的业务结构。当你的业务需要深度定制状态管理、权限模型和事件交互时框架的抽象反而成了一道枷锁。我的经验是除了一些非常标准的检索问答场景真到了多Agent协作自研一个轻量运行时整个也就几千行代码往往比不断跟框架斗智斗勇要舒服得多。5.2 模型选择别只看准确率要看工具调用的纪律性不同模型的function calling“纪律性”差异巨大。有些模型你给它5个工具它非要变着法子绕开工具直接给答案有些模型工具参数格式写错还一本正经地继续。所以在选型时不要只盯着基准测试分数要做专门的工具调用压力测试给模型一个复杂任务要求在单轮多次调用不同工具观察它是否按照规定顺序、正确参数完成还是出现跳步、参数幻觉、多次重试。我实测过几个主流模型不同厂商差异非常明显。这个测试集最好沉淀成团队内部基线每次模型版本升级都跑一遍。5.3 团队协作让“Agent行为测试”成为CI的一部分agent-native开发最头疼的一点是你改了一个Agent的prompt或工具描述不知道会影响到哪个下游Agent。所以我强烈建议把Agent行为测试纳入CI流程。具体做法是维护一组“场景剧本”每个剧本是一段用户意图序列附带期望的Agent行为轨迹应该调用哪些工具、不应该调用哪些工具、最终输出应该包含哪些关键信息。每次代码/提示词变更都跑一遍剧本。跑完后人工检查diff看哪些Agent的行为被意外改变了。我印象很深的一次某次只是优化了一条工具描述文本结果下游周报Agent突然频繁调用文档Agent导致周报生成变慢。就是因为描述里多写了“可以获取更多信息”这类模糊引导词。如果当时没有跑回归剧本这个问题到了线上才暴露排查成本会高很多。5.4 安全与合规Agent的“最小权限”原则要落实到数据行最后说安全。Agent系统因为有了“自主决策”的空间权限管理比人直接操作系统严格得多。核心原则是“最小权限行为审计”Agent能调的工具只给最少必需的那几个能见的数据只给符合任务范围的行级数据。不要什么事都塞给Agent一个“超级接口”让它自己判断。我之前讲过agent-native系统的审计日志是另一个重点。每个Agent的行为都要能追溯到一次具体的用户请求、一个具体的决策链路。这不只是为了事后查问题更是为了在权限争议、合规审查时拿得出证据。所以从一开始就要把“记录决策链”当作系统刚需来做而不是事后补。写在后面做了这么久agent-native架构我的体会是这波AI技术迭代比以往都快但架构设计的“守恒定律”没变——你提前做好的解耦、封装、可观测性都会在后期加倍回报你。agent-native不是要给每个系统强塞一个Agent而是让你在做架构决策时多问一句“如果以后有个Agent要来用我的系统它的体验是什么”。冲着这个问题去做设计系统自然就耐用了。最后分享一个日常小技巧给Agent的工具列表里总会有一个不大起眼的“记录笔记”工具让Agent在任务中途把重要的假设、疑问、临时结论写下来。这个工具看起来没什么技术含量但在长链路任务里它极大地提高了Agent的稳定性和可恢复性。很多看似是“模型不够聪明”的问题其实只是因为缺少一个让它“把话说出来”的出口。这大概就是agent-native最朴素的真谛。