ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent系统重构实战:从编排模型到工具调用的稳定地基搭建

AI Agent系统重构实战:从编排模型到工具调用的稳定地基搭建 从年初接手 Orkas 的维护到现在我最大的感受就是一个 Agent 项目能跑起来不难但想让它稳定地扛住真实业务地基必须得扎实。Orkas 是我们团队内部一套面向多智能体编排与执行的框架最早是几个人用脚本拼出来的原型后来逐步加了工具调用、短期记忆、并发调度功能是越堆越多可代码结构越来越像一座违章建筑。这次我们花了将近两个月做了一次彻底的底层重构把执行内核、编排模型和资源管理层全部推倒重来。这篇文章不聊口号只讲我在这次重构里踩过的坑、做过的取舍以及那些日常文档里很少写清楚的细节。如果你正在做 Agent 开发或者你手里有一套已经跑起来但越改越难受的框架这篇内容应该能帮上忙。我会从为什么必须重写说起再把分层设计、编排模型、工具接入、并发与安全的落地方式拆开讲最后附上重构期间最典型的一批问题和排查思路。1. 为什么要重写地基老架构的失控现场1.1 从单体脚本到“三不管地带”Orkas 最早的设计非常简单一个Agent类内部循环里维护一个消息列表每次迭代把用户输入和历史消息拼进 prompt调用大模型接口解析返回结果如果是工具调用就走工具函数。这套逻辑在 demo 阶段很爽半小时就能跑通一个 agent。但等我们开始往里面加多 Agent 协作、持久化记忆、流式输出、权限校验之后问题就暴露了。最典型的情况是状态管理。老架构里每个 Agent 实例自己维护一份messages列表而协作场景中 A 要把上下文传给 B就会发生多个实例共同修改同一份列表的尴尬操作。有人用浅拷贝有人直接传引用结果就是同一个 user message 在不同 Agent 里被重复追加模型上下文被无意义的信息塞满token 成本直接翻倍。工具调用的返回结果也经常漏挂到正确的消息序列里模型经常陷入“调用了工具但看不到结果”的幻觉循环。这个阶段我把它叫作“三不管地带”谁都可以写状态谁都不为状态负责出了问题只能靠反复看日志猜测。代码里到处都是临时补丁今天为超时加个 sleep明天为字段缺失加个默认值。每次有新需求进来光是在调用链里找到该改的位置就要花半天。1.2 并发场景像拆炸弹真正逼我们下决心重写的是一次线上并发压测。业务方要求同一时刻最多跑 50 个 Agent 会话每个会话可能派生 3~5 个子 Agent。老架构在单会话下勉强能跑一旦并发上来就开始暴露连环问题全局唯一的工具注册表被并发写坏、模型客户端连接池被耗尽、共享内存字典里出现错乱的 session 归属。更麻烦的是错误处理。老代码里大量使用try...except包裹整段执行逻辑一个子 Agent 超时就会把整个链路上抛。后来实测发现很多报错根本不是模型的问题而是我们自己的状态没清理干净。比如一次任务结束后缓存里的中间结果没删除下一次任务带着脏数据继续跑输出质量明显劣化但表面上看哪一环都是正常的。这类问题最难排查因为你无法通过单步调试复现只能从日志里一点点往回找。说白了老架构的核心问题不是某个 bug而是它没有一个清晰的执行边界。谁在编排、谁在执行、谁在保管状态这三件事纠缠在一起导致任何局部修改都可能引发全局震荡。重构的第一目标就是把这三件事切开。1.3 重写的边界全部推翻还是保留局部做重写决策时团队里有个争论是一口气全换还是保留一部分能用的模块。我的观点是要分三层看。模型调用、prompt 模板、少量稳定的工具函数这些属于“资源层”可以保留并标准化接口编排逻辑、状态流转、并发调度这些属于“控制层”必须重写外部依赖如向量库、Redis、消息队列这些是基础设施不涉及重构但需要重新梳理接缝。这个判断的核心原则是重构不是炫技而是把走不通的路重新修直。凡是老代码里已经被多个业务方依赖、且行为稳定的部分保留下来能大幅降低回归风险凡是每天都让人觉得别扭、改一处崩三处的部分别犹豫直接推倒。2. 新地基的三大设计判断2.1 三层分离编排、执行、资源的边界新架构里我们把系统拆成三层控制层Orchestration、执行层Execution、资源层Resource。控制层只负责“决策”下一步该选哪个 Agent、当前任务要不要拆解、子任务之间的依赖关系是什么。控制层不直接调用大模型也不直接读写业务状态它只产生指令比如“调用 Agent B 处理数据分析子任务”。执行层负责“干活”接收控制层的指令加载对应 Agent 的配置组装 prompt调用模型执行工具函数把结果按统一格式写回。执行层不关心高层决策它只保证一件事——给定输入和工具环境稳定地产出结构化结果。资源层负责“提供能力”模型客户端、工具注册中心、记忆存储、文件系统、外部服务连接全都收归资源层管理。资源层对上层暴露的是接口而不是具体对象。比如工具注册中心暴露register_tool和invoke_tool上层不直接 import 某个工具的类。这套分层带来的直接好处是我们可以在不触碰编排逻辑的情况下替换底层的模型供应商也可以在不动执行层的前提下增加一种新的编排策略。每个层的职责在代码审查时一眼就能看出是否越界团队的协作效率提升非常明显。2.2 统一消息契约一切皆 Event老架构里最痛苦的就是消息格式不统一。同一个任务上下文在 A 模块里是个dict在 B 模块里被包了一层对象到 C 模块又变成 JSON 字符串。为了兼容到处都在做格式转换一转换就出错。新架构强制规定了两种标准消息UserMessage和AgentMessage所有模块之间只允许传递这两种类型。AgentMessage带上了role、content、tool_calls、tool_results、metadata这些字段metadata 里可以挂 trace_id、session_id、创建时间、来源节点。这个设计我把“一切皆 Event”作为原则就是每个 Agent 的执行生命周期都会对外发布事件任务开始、模型请求发出、工具调用中、工具返回、任务完成、任务失败。所有下游系统——日志、监控、审计、缓存失效——都通过订阅事件来响应而不是轮询状态。这样做的好处是执行链路完全可观测任何一个卡点都能从事件流里定位。2.3 状态管理内存分级与持久化边界Agent 的状态管理是这次重构里最容易翻车的地方。我们最终采用了三级记忆模型工作记忆当前任务上下文放内存、会话记忆同一用户会话的摘要与关键事实放 Redis、长期记忆跨会话的领域知识与用户偏好放向量库。关键取舍在于“什么进工作记忆、什么进会话记忆”。我们的规则是原始对话消息只留在工作记忆里当上下文长度超过阈值时用模型做一次摘要把摘要和关键的实体、决策点写入会话记忆原始消息降级为可丢弃。这样既保证了模型上下文的质量又不会因为无限堆消息导致 token 爆炸。状态持久化的边界也很重要。凡是能从事件流重建的状态不持久化只有跨会话必须保留的事实类信息才写入 Redis 或向量库。这个原则让我们避免了“什么都要存、什么都存不对”的窘境。3. 核心环节的落地实现3.1 编排引擎用有限状态机替代自由脚本老架构的编排逻辑是“自由脚本”式的——在代码里直接写if ... then Agent A else Agent B。业务一复杂脚本就成了面条代码。新架构把编排转成了有限状态机FSM每个 Agent 的执行流程被定义为一系列状态PENDING、PLANNING、EXECUTING、WAITING_TOOL、COMPLETED、FAILED。每个状态对应一个处理器状态的迁移由事件触发。比如收到ToolResultReceived事件WAITING_TOOL状态的 Agent 自动回到EXECUTING。这样做最大的价值是超时和重试变得极其自然任何状态都可以配置最大停留时长超时就触发Timeout事件进入FAILED或者根据策略重试。我特别推荐用状态机去建模 Agent 的长链条执行。人肉写几十个if分支的“思维链编排”维护起来太痛苦了。状态机让每个执行阶段的边界变得明确也方便了可视化监控——线上可以直接看到每个 Agent 现在卡在哪个状态。3.2 工具注册中心与 MCP 接入工具调用是 Agent 项目的命门。老架构里工具就是一个函数Agent 直接调用。新架构把工具抽象成了三层定义层描述工具的名称、参数 schema、用途说明、注册层工具的管理与检索、执行层参数校验、调用、超时、重试、结果格式化。参数校验这一步特别容易被忽略。我们遇到过模型生成的工具参数里带空字符串、参数类型错误、缺少必填字段等问题。新架构统一用 JSON Schema 校验入参不合格就返回结构化错误信息并把它作为 tool_result 反馈给模型让模型自行修正。实测下来这一步能把工具调用的成功率从 78% 拉到 95% 以上。MCP 是我们接入外部工具的主通道也就是 Model Context Protocol。如果你还没接触过可以把它理解成工具调用的通用协议层远端服务把自己的工具暴露成 MCP ServerAgent 通过 MCP Client 调用。我们内部的工具注册中心把 MCP Server 注册为一个“工具组”在 Agent 的配置里通过白名单决定暴露哪些工具组。这样外部服务接入时不需要改动 Agent 的任何代码只要注册一个 MCP Server 并在配置里声明即可。3.3 并发调度与沙盒安全并发是 Agent 落地最绕不开的话题。AI Agent 跟普通 Web 服务不一样单个 Agent 的执行时长可能从几秒到几分钟中间还夹杂着多次模型调用和工具调用如果每个会话简单地占一个线程几十个会话就能把进程打垮。我们采用的方案是asyncio 事件循环 任务队列。每个 Agent 会话是一个异步任务模型调用和工具调用都是 IO 密集操作天然适合异步。调度器维护一个任务队列按优先级和资源配额分发。资源配额的关键参数是“同时进行的模型请求数”和“同时进行的工具调用数”这两个参数要独立控制不然模型没被限流工具调用却把下游打挂了。安全方面我们给工具调用加了三层防护。第一层是白名单校验每个 Agent 只能调用配置里允许的工具未注册的工具直接拒绝。第二层是内容过滤工具入参和出参都要过敏感信息检测防止用户数据落入不必要的外部服务。第三层是资源限额每个工具调用的最大执行时间、最大输出体积、最大重试次数都有硬限制。特别要注意的是即使内部工具也要设限额因为模型生成的参数可能让工具陷入死循环或产生超大输出。3.4 可观测性每一条消息都有迹可循重构之后我们做的第一件事就是给所有执行过程接入全链路追踪。每个会话从创建开始就生成一个trace_id每个子 Agent、每次模型调用、每次工具调用都挂上这个 trace_id。日志系统里可以一键检索某次任务的全部执行轨迹。这里有个经验日志要打印决策依据而不是只打印结果。比如一个 Agent 决定不调用工具而是直接回答日志里要记下“当前上下文的关键判断依据是什么、模型返回了什么信号”。这些信息对排查线上问题极其重要。因为 Agent 的行为有随机性同一个问题两次执行可能走完全不同的路径没有决策日志出了问题根本无法定位。我们还做了一个简易的执行回放面板根据事件流把一次任务的执行过程渲染成一个时间线哪一步耗时高、哪一步失败重试、哪一步跳过了模型调用直接走缓存一眼就能看清。这个工具在重构后的联调阶段帮了大忙很多问题不需要翻日志直接看时间线就明白了。4. 常见问题与排查技巧实录4.1 模型陷入工具调用死循环一个问题是被线上告警逼出来的Agent 调用一个查询工具工具返回“未找到数据”Agent 又用同样的参数再调用一次反复三次之后才放弃。原因在于工具返回的错误信息太笼统模型无法从中学习到“应该换一种查询方式”。解决办法有两层。第一层是工具侧返回信息里加上改进建议比如“当前查询条件过于严格建议扩大时间范围”。第二层是编排侧为同一个工具设置单轮最大调用次数比如 3 次超过就触发ToolOverflow事件强制 Agent 转入反思状态重新规划。实测下来第二层是兜底的关键因为指望模型每次都自觉是不现实的。4.2 共享缓存导致上下文污染这是重构后让我印象最深的一次故障。生产环境里两个用户同时咨询类似问题其中一个用户的回答里出现了另一个用户的名字。排查后发现是向量存储的会话隔离出了问题新会话在构建上下文时取到的session_id还是旧会话的导致把旧会话的长期记忆注入了新会话。根因是缓存 key 的粒度太粗。修复思路很清晰所有会话级缓存 key 必须带上 session_id 作为前缀而且不能只在前缀上做拼接要在读写两侧都做校验。另外缓存里凡是涉及用户标识的字段都要做脱敏和归属校验。现在我们的规则是临时性数据不落缓存必要缓存一律两段式 keycache_key session_id。4.3 工具调用超时但任务不结束还有一个高频问题某个外部 HTTP 工具超时了但我们没有正确传播超时状态导致 Agent 一直挂在WAITING_TOOL状态直到整个任务被外部超时机制杀掉。这个问题的技术根源很典型——我们最初用asyncio.wait配合统一的超时时间但对不同工具有不同的合理超时窗口。比如内部数据库查询 5 秒就够了外部报表服务却可能需要 30 秒。解决方案是让每个工具在注册时声明自己的timeout_hint注册中心在执行时按工具维度设置超时同时在调度器里配置一个“全局执行护栏”——单次 Agent 任务的总时长上限 150 秒超过就会被强制中断并返回结构化错误。4.4 排查技巧速查现象优先排查方向常用检查手段模型输出质量突然下降上下文是否混入脏历史检查 trace_id 对应的消息序列工具调用频繁失败参数校验是否漏装查看注册中心参数 schema 日志并发一高就延迟暴涨模型请求限流失效监控同一时刻的模型并发数子 Agent 结果丢失事件消费逻辑重复检查事件流里的消费组 offset任务卡死不动状态机缺少超时迁移查看各状态最大停留时长配置这个速查表是我们在重构后的一个月里反复修改才稳定下来的。排查 Agent 系统的问题时我最大的体会是不要先怀疑模型先怀疑状态。大多数“模型变傻了”的假象背后都是上下文被污染、工具结果没正确回传、或者缓存命中了错误的数据。5. 重构后的数据与体验重构上线三周后的数据我觉得能说明一些问题。在同样的并发压力下单任务执行耗时的 P95 从之前的 46 秒降到了 21 秒主要原因是消除了无效的重复模型调用和排队等待。工具调用成功率从 78% 提升到 95% 左右模型陷入循环导致的废 token 消耗下降了约 40%。最直观的改动是以前每天能收到七八个线上异常告警现在一周都未必有一个。但比数据更重要的是开发体验的改善。重构之后新接入一个外部工具的时间从半天压缩到四十分钟因为不需要再追着代码库改动 Agent 本体只要注册一个 MCP Server 加一条白名单配置就行。团队里新来的同事看代码的入门时间也从一周缩短到两天分层清晰之后新人只需要理解控制层和执行层的交互契约就能独立接需求。如果你也在做 Agent 相关的基础设施我的建议是别让“能跑”变成“只能这么跑”。当你在同一个地方完成三次补丁式修复时就应该停下来认真考虑重构的事。重构不是对过去代码的否定而是对系统边界的一次重新确认——知道什么该强耦合什么该松绑定什么该交给抽象层什么必须暴露细节。这些判断来自于一次次线上事故和一行行日志的复盘写不出漂亮的 PPT但能让你的 Agent 系统真正站得住。
返回列表