ARTICLE DETAIL

资讯详情

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

Agent运行时重构实录:从单体执行器到可恢复的立体架构

Agent运行时重构实录:从单体执行器到可恢复的立体架构 凌晨三点值班群里的告警声把我从半睡半醒中拽了出来。Orkas 这条老链路在周五晚上迎来了流量高峰几百个并发 Agent 任务把执行队列堵得死死的——不是下游接口慢也不是模型响应慢是 Agent 自己能同时跑的任务数到了上限。监控图上任务堆积的曲线几乎垂直向上而 CPU 和内存还躺得很平。这个晚上之后我们团队开了一次持续三个多小时的会结论只有一个Orkas 的地基确实该重写了。我在这支团队里负责大模型应用的底层运行时Orkas 是我们自研的 Agent 编排框架。早期版本上线得很顺业务方接进来之后很多复杂的多步任务总算有了可以落地的承载层。但随着 Agent 的场景从demo 演示切换到生产环境持续跑数据问题开始像冰山一样浮出水面。这次重构前后花了大概四个月期间踩了很多坑也推翻了好几版设计。文章会从旧架构的问题讲起再拆一拆新地基的设计思路特别是 Agent 并发、记忆、调试和安全这几个最让人头疼的部分最后说一下迁移过程中的实战经验。如果你也在维护 Agent 框架或者正准备从单体脚本往真正的运行时架构迁移这篇应该能对你有用。1. 旧版 Orkas 的单体模型把 Agent 卡在了哪里1.1 先从一次线上告警说起重构这件事从来不是技术洁癖驱动的。Orkas 第一代版本其实就是一套单体 Agent 执行器用户请求进来框架根据大模型输出的计划按顺序执行工具调用把结果塞回上下文再让大模型继续推理。听起来很顺但在生产环境里跑你会发现所有 Agent 任务都是一个线程里长跑的老牛。那天夜里堆积的任务主要是两类一类是数据标注辅助每个任务要翻十几张表、调四五个接口另一类是长文档分析Agent 要反复读取分段、做摘要、对比结论。这些任务单看都不难但并发上来之后有个致命问题——旧版 Orkas 把会话和执行线程绑死在了一起。一个 Agent 会话占住一个线程从第一轮推理一直到任务结束期间这个线程就是它一个人的。线程一耗尽后面排队的任务就只能等。你以为加线程池就能解决我试过把核心线程从 64 加到 256Java 进程的内存和上下文切换开销直接起飞而且任务依然堆积。因为问题不在线程数量而在结构Agent 的每一步都在等待等模型、等工具、等外部系统等待期间线程就这么干耗着。传统 IO 密集型的线程池优化手段碰到了单会话长运行这种模式就失效了。1.2 四个硬伤的本质剖析旧版还有另外三个问题和并发堆积并称四座大山。第一是状态管理混乱。每个 Agent 会话的全部上下文被装进一个巨大对象里用户的原始意图、历史消息、工具返回、中间结果全部塞在一起连个大模型输入窗口都装不下。更麻烦的是这个对象随着会话不断变大每轮推理的 token 成本也跟着涨。后来运营同学反馈费用高得离谱我们一查发现不少 Agent 会话聊到第 20 轮时居然还在把第 1 轮的工具输出原封不动地往模型里塞。第二是重启即失忆。只要进程一重启、一发布所有进行中的 Agent 会话全部断掉。业务方打电话过来问我这个任务跑了一个小时了怎么没结果我们只能答服务升级了重新跑一次吧。这在离线场景还能忍但如果你想把 Agent 接到客户服务、运维助手这种不得中断的流程里根本没法交代。第三是无法观测。旧版日志就是按轮次打的推理、工具调用、结果返回都混在一起。任务卡住时我们只能对着日志猜它到底是在等模型还是在等工具这一步输出怎么这么长上下文被什么污染了这种状态下任何优化和排障都像蒙着眼睛修电路。1.3 重构前的共识不解决状态问题调优都是打补丁开了几次设计会之后团队达成一个共识并发问题、记忆问题、调试问题的根其实都在状态管理上。只要 Agent 的执行过程不能持久化、不能恢复、不能被外部系统安全地观察和干预后面做再多的并发优化都是空中楼阁。所以整个重构的主线就这样定了下来先把 Agent 的执行过程和它的状态彻底解耦把运行时拆成可以独立扩展、独立恢复的层次。只有地基打得足够干净才能谈并发、谈记忆、谈安全。2. 新地基的核心设计把 Agent 运行时分成立体三层2.1 编排器、执行器、状态仓库各管什么新版本的 Orkas 不再是一个巨大的单体执行器而是拆成了三个职责清晰的部分编排器、执行器、状态仓库。这三个词听着普通但各自承担的东西完全变了。编排器Orchestrator不再管工具的细节。它的工作只有一个根据大模型给出的计划决定下一步应该做什么、由谁来做什么。举个例子Agent 在做跨系统数据核对这个任务时大模型可能会这样规划先从 CRM 拉订单列表再从财务系统拉对账单然后两边比对那么编排器就负责把每一步拆成节点建立依赖关系并在合适的时机触发执行。执行器Executor则非常笨。它只做一件事拿到一个具体的执行命令去调用对应的工具把结果原样返回。执行器不关心整个任务长什么样不关心这轮执行完了下一步是继续还是终止。这样设计有一个直接的好处执行器可以像无状态函数一样水平扩展。哪台机器空闲任务就派到哪台机器上跑。状态仓库StateStore是新版 Orkas 的心脏。每个 Agent 会话的状态不再住在内存里指望进程别挂而是以增量事件的形式持久化到独立的存储层。每一轮推理的输入输出、每一步工具的返回结果、编排器每次决策的依据全部落盘。取用的时候按需加载而不是一股脑全塞给模型。这条改造之后重启即失忆的问题从根上消失了。2.2 必变项与变项分离Agent 终于可以被恢复这个设计我们内部聊了很多轮最后落到一个原则把Agent 一步步往下走这件事拆成必变项和变项两个集合。必变项是说不管这个 Agent 要完成什么业务目标每一轮都一定需要的东西当前计划、当前执行到的节点编号、上下文的一段紧凑摘要、还有关键节点的输出引用。变项则是这一步额外要用的数据比如工具 A 返回了一份大表格这份表格只对当前这一步有用就存在执行器层如果后续步骤确实还要用再通过显式的上下文写入把它提升到状态仓库。这样做最大的收益是恢复一个 Agent 变得像续播视频一样简单。崩溃了、重启了、调度到另一台机器上了只要读取这个会话最新的必变项摘要就能接着往下跑。我们压测过一个长时间任务人为杀掉进程再拉起来Agent 能在十几秒内恢复到接近中断点的状态然后继续执行。这在旧架构里是没法想象的。2.3 节点不等于步骤重新定义 Agent 的执行轨迹这里有一个容易混淆的点。旧版说起 Agent 执行轨迹基本就等于大模型的一轮对话输出。新版我们把这两件事拆开了。一个 Agent 会话被建模成一张执行拓扑节点类型包括计划节点、工具节点、子任务节点、终止节点和条件节点。工具节点负责调用外部系统子任务节点负责把某一段逻辑下发给并行的下级 Agent条件节点读取某些上下文后决定走向。大模型的每次推理只是生成了一份可能的节点拓扑真正执行过程是编排器按这个拓扑逐个触发节点。节点不等于步骤这件事在业务上带来一个很实际的改变同一个计划可以反复执行但不需要重新推理。比如一个日报生成 Agent它的拓扑基本每天一样查数据、渲染表格、归档、推送。旧版每天都要把整套计划交给大模型重新生成一遍新版只需要第一次交给大模型之后就可以把这份拓扑缓存起来直接跑。算下来每天在这种重复任务上省掉的 token 调用至少有 30%而且执行稳定性也高了——大模型不会再在今天到底要不要附带趋势描述这种小事上摇摆。3. 并发与调度Agent 怎么扛住多路并发3.1 协作式时间片一个 Agent 进程内部怎么让位热词里大家都在问AI Agent 怎么扛并发这个词问得很准。Agent 并发的难处和普通高并发接口完全不一样。普通接口的并发是一千个请求同时进来各算各的互不干扰Agent 的并发是一千个长跑任务每个都在等模型、等工具、等外部系统但谁也不能把谁踢下线。新版 Orkas 在单进程内引入了协作式时间片机制。每个 Agent 会话的执行过程被拆成一步步推进而不是一口气跑完。每执行完一个节点会话就把执行权交出来由调度器决定下一步给谁跑。这相当于把 Agent 从霸道总裁改成了开会轮流发言保证再多的会话挤在一台机器上也不会出现一个超长任务独占线程的情况。具体落地上我们给每个执行任务设置了时间片上限默认 500ms。时间片到了如果这一步还没执行完比如说工具还在等响应整个这一步会被挂起让出执行权。当工具响应回来调度器再把对应的会话恢复从挂起点继续跑。实测下来单机并发会话数从旧版的 42 个提升到了 380 多个单任务的平均响应时间反而更稳定了。3.2 跨会话的并发控制全局时钟加挂起表时间片解决的是单机内部的问题但生产中你还会遇到一个更微妙的场景多个 Agent 会话在同时操作同一个外部资源。比如 20 个 Agent 同时在写同一张报表同时刷新同一个缓存同时调同一个被限流的 API。如果每个会话都按自己的节奏横冲直撞外部系统很快就会被打挂。我们在新版调度器里加了两个基础设施。第一个叫全局时钟。每个接入 Orkas 的外部资源都有一个资源令牌Agent 要操作资源前必须先申请令牌拿到之后才能执行用完立即释放。第二个叫挂起表。申请不到令牌的会话不是硬等而是进入挂起状态由调度器在令牌释放时统一唤醒。这样做的效果是把并发变成协作避免形成对下游系统的瞬时冲击。这套机制在生产环境里效果很明显。之前有个 Agent 批量生成合同每天早上九点半开始跑总是把合同服务怼到超时。接上资源令牌之后同时写同一个合同模板的 Agent 数量被限制到 5 个剩下的排队轮转合同服务再也没被打挂过整个批次的完成时间反而因为它不用重试而缩短了。3.3 会话结束时那些还在跑的 Agent 任务怎么办并发控制里最容易被忽略的是取消与清理。一个 Agent 会话在跑的过程中用户可能取消了任务或者上级流程判定这个分支已经不需要了。这时候还在执行器里跑的节点任务必须能被安全地终止或取消。我们专门定义了completion语义会话进入完成态之前调度器会先清点一遍所有未完成的节点。可以取消的直接取消已经在外部系统产生副作用的比如已经调用了写接口不能粗暴取消而是标记为待确认在最终清理报告里列出结果。等全部节点都处理完毕会话的上下文才真正落盘归档内存态随之释放。这个设计看起来简单实际上避免了很多隐患。旧版任务结束就是线程结束后台的异步调用经常没人管导致外部系统收到请求却拿不到结果留下大量脏数据。新版要求每个会话有始有终所有副作用的处理都有记录可追溯。4. 记忆与感知的拆分把会记和会看分开来搞4.1 三类记忆分开存连总结也分两级Agent 的记忆这个词不同人理解完全不一样。有人以为是把所有聊天记录丢回大模型就叫记忆有人以为是搞个向量库就可以。在 Orkas 新架构里我们把记忆按用途分成了三类用三条独立的通道存储会话记忆本次会话内的短期上下文包括用户最新的意图、当前执行计划、最近几轮的关键输出。这部分默认保留最近 N 轮更早的内容被摘要化。业务记忆跟具体任务强相关的数据比如上个月的销售报表路径某个门店的收货人默认地址。这些不是通用知识是业务系统里查出来的、会在当前任务中反复用到的数据。长期记忆跨会话沉淀的用户偏好、行为模式、决策历史比如这个用户每次审批偏好走加急“这个客户反应不喜欢邮件正文太长”。另外我们把总结也做了两级。会话记忆的总结是轻总结每 5 轮做一次只要保住关键数字和结论业务记忆的总结是重总结在会话结束或任务关键节点更新要保存完整路径和依据。这样做的原因是轻总结太重会把上下文越滚越大重总结太轻会导致后续流程找不到原始数据。4.2 感知引擎独立出来系统事件不再卡主流程老版 Orkas 的 Agent 是戳一下动一下用户发消息Agent 才去处理。但新的业务场景里Agent 需要自己看到变化然后行动——比如文件系统里新增了一个文件、定时任务提醒该汇报了、某个接口推送了一条待办。这些外部事件如果都靠用户消息触达Agent 就退化成了问答机器。新版里我们把感知能力抽成了一个独立的感知引擎它和编排器并行运行。感知引擎持续监听各类事件源把事件转换成统一格式的感知记录再根据预设的优先级规则决定是否打断当前 Agent 的执行。普通事件只进入待处理队列高优先级事件才会触发会话挂起、切换。这样一个 Agent 既能专心做手头任务又不会对重要变化视而不见。这个拆分在实现上给了我们很大空间。多路 Agent 的并发调度、执行器与感知引擎的资源分配都因此解耦了。如果感知逻辑还留在主执行线程里遇上文件监听阻塞或者消息队列卡顿整个 Agent 都会跟着停住——现在感知引擎单独挂掉也只是丢几条感知记录主任务不受影响。4.3 持久化与冷热归档当前的粗暴方案记忆系统上线之后第一个被问的问题是存储成本扛得住吗。Agent 的记忆如果不分冷热一股脑存着成本确实吓人。我们的方案很粗暴但很有效按最后访问时间做分层归档。会话进行中记忆全部放在内存里的高速缓存区读写都不落盘追求速度。会话结束但还在热期比如 7 天内可能被追问写入热存储比如 Redis 或类似 KV 库支持快速检索。热期过后摘要写入冷存储原始细节只保留必要字段。需要完整记忆时再去冷存储解压。这套冷热分层的方案跑了一个多月存储成本比原来全量存原始记录下降了 70%而记忆召回率几乎没有下降。原因是大部分跨会话访问都集中在高频摘要上完整原始记录的调用频率非常低。还有一个额外好处因为冷存储里的数据是经过摘要处理的泄露敏感细节的概率也低了。5. 调试与可观测性要在 Agent 头上装行车记录仪5.1 全量 Trace虽然贵但值得调试 Agent 的难度和调试普通程序完全不是一个量级。普通程序每一步都是确定的断点打下去变量值一目了然Agent 不一样你看到它调了个工具、拿到个结果但你不知道大模型是怎么想的为什么决定调这个工具而不是那个工具为什么在这个问题上重复循环三次。新版 Orkas 把全量 Trace做到了运行时层面。每一次大模型推理的请求和响应、每一个节点的触发和结果、每一次上下文的读写变更、每一次记忆的检索命中全部记录成结构化的 trace 事件。别小看这一点遇到线上问题你可以直接按会话 ID 拉出整条 trace精确回放那个 Agent 在每一步做了什么、看到了什么。有同事问成本怎么办。我们的答案是先保证能排查问题再谈省钱。全量 trace 在没出问题时确实浪费但一旦出了线上事故能省下的排查时间就不是钱能衡量的。我们现在保留 7 天的原始 trace归档 30 天的浓缩 trace生产环境一直开着没有关。5.2 断点重放把一次会话当电影倒着看Trace 解决了看到过程的问题但还差一步——干预过程。Agent 跑歪了你看到第 15 步开始偏离预期但没有办法回到第 14 步结束时改一下上下文再重来。旧版只能让整个会话推倒重跑。新版加了一个重放模式从任意一个 trace 断点处恢复执行并且允许你修改上下文再继续。这个功能听起来很常规实际做的时候要处理很多细节。修改上下文后后续节点的依赖关系可能要重新计算已经写入过外部系统的副作用不能简单重放否则会重复执行写操作。我们的解决方法是重放模式下外部写入操作默认标记为演练模式真实执行前需要二次确认。这样调试人员可以放心地在测试环境里把 Agent 重放一百遍不用担心污染业务数据。我自己的习惯是新接一个业务方的复杂 Agent 需求时先拿历史 session 的 trace 做一次断点重放看看哪些步骤容易出问题、哪些上下文容易膨胀。这个习惯帮我提前避免了很多线上问题比事后告警再排查要省心得多。5.3 上下文安全性Debug 也要被审计说到调试很容易踩到一个安全盲区。trace 里装的是 Agent 的全量上下文这些上下文往往包含真实的业务数据、用户信息、甚至密钥片段。如果调试工具的访问权限控制不严任何能查 trace 的工程师都能顺藤摸瓜看到敏感数据。我们团队的做法是给 trace 和断点重放都加了一层脱敏与审计。脱敏是自动的trace 在写入时会按预置规则对疑似敏感字段做掩码替换比如邮箱、手机号、长文本里的身份证号模式。审计是指每一次断点重放、每一次 trace 查看都会记录操作人、时间、范围和目的。这既是为了合规也是为了出现问题时有据可查。调试能力越强越要管住这扇门。6. Agent 安全的运行时边界6.1 内存上下文的分级不是只靠大模型内容审核Agent 的安全大家第一反应是给大模型加内容审核但其实运行时的安全边界更重要。上下文在 Agent 内部流转时不同来源的数据信任级别是不一样的。用户在对话框里写的文字、外部工具返回的数据、长期记忆里沉淀的历史记录它们的可信度和敏感度完全不同。新版 Orkas 在内存上下文层面引入了分级标记机制。每一条进入上下文的记录都附带一个数据源标签和敏感级别。编排器在处理上下文时会按级别决定哪些内容可以进入大模型推理窗口哪些内容只能在执行器内部流转、不能反馈给用户。比如工具返回里含有别的用户的信息这种数据就绝不会出现在 Agent 的对外回复里。这套机制不能替代业务侧的合规审查但它给业务方提供了一个强约束的底座。接入方只需要在注册工具时声明数据级别Orkas 就会在运行时强制拦截不合规的数据流而不是等到模型输出后才发现泄露。6.2 工具调用链路的四道检查另一个安全重点是工具调用。Agent 的能力来自工具风险也来自工具。新版 Orkas 在工具调用链路上加了四道检查所有工具调用都要经过这四关第一关是权限检查。这个 Agent 会话当前的身份是否有权限调用这个工具不同用户绑定的工具权限是独立的不会有 A 用户的 Agent 顺手调了 B 用户才有权限的接口。第二关是参数校验。工具调用的参数会做一次结构化和合规校验防止 Agent 因为推理不稳定而构造出非法或危险参数。第三关是依赖检查。如果这个工具调用会对某个外部资源产生副作用调度器会检查资源锁是否匹配、是否在允许的副作用范围内。第四关是行为审计。通过前三关的调用会被完整记录包括调用前上下文、调用参数、调用结果。事后有任何安全问题都能追溯到具体某一次调用。这四道检查每道都不重但合在一起能把绝大多数Agent 乱调工具的风险挡在门外。6.3 多 Agent 间的隔离与最小权限多 Agent 协作是 Agent 框架绕不开的方向。但多个 Agent 在一起跑安全问题会更复杂。我们的原则是默认隔离按需授权。每个 Agent 会话默认只有自己命名空间下的资源可见不能看到其他会话的上下文和记忆。如果业务上确实需要子任务把结果回传给父任务那必须通过显式的消息通道传递而且要过一遍结果脱敏检查。内存上下文、存储空间、对外服务的权限全部按最小权限原则赋权。宁可多配置几行授权规则也不要让整个系统处于所有 Agent 什么东西都能看到的状态。7. 从零到一的迁移经验最怕表面重构7.1 第一步不是写代码而是冻结状态模型最后这部分讲讲迁移。很多人重构是先画架构图、再写新代码然后找一个晚上切流量。我们一开始也这么想但很快意识到这样做的风险极大。Orkas 底层是承载线上真实业务的状态模型一变所有接入方的数据格式、任务类型都可能受影响。我们的做法是重构第一个里程碑不是代码而是状态模型的冻结。花了两周时间把所有接入方的使用方式盘一遍整理出旧版会话里所有可能出现的数据结构、所有工具调用的输入输出格式、所有异常分支。然后把这个模型用清晰的文档定义出来明确哪些是必变项、哪些是变项、哪些字段在迁移时要保留兼容层。状态模型定了后面的代码才敢动。7.2 灰度双跑 30 天我们到底在对比什么新版地基搭好之后我们没有直接宣布完成而是搞了一场持续 30 天的灰度双跑。所有新增流量走新架构同时选了一批高频业务线把旧流量也同样跑一遍两边结果做对比。我们对比的不是跑不跑得通而是三件事第一是结果一致性。同一个输入任务新旧架构跑出来的最终输出是否一致如果不一致差异在哪个节点这能检验新架构在状态流转上有没有逻辑漏点。第二是恢复能力。模拟各种故障场景进程杀掉、依赖接口超时、网络抖动看新架构能不能按预期恢复会话。旧架构在这个测试里几乎全军覆没新版有 95% 以上的恢复成功率。第三是对外表现。接入方的感知有没有变化工具调用次数、token 消耗、响应时长是否在可接受范围内如果业务方发现新版明显变贵了或者变慢了架构再漂亮也白搭。双跑期间我们发现了一堆小问题比如有些工具返回的字段在旧版里被隐式转换过新版严格执行类型校验后直接报警再比如某些超长上下文在新版存储层要分片写入聚合读取时偶发顺序问题。这些问题如果不双跑根本不可能提前发现。7.3 给后来人的一些提醒最后说几条实操层面的经验吧都是我们踩坑踩出来的。第一先做 Trace 再做状态模型。Trace 能让你看清线上 Agent 真正的运行轨迹这是设计状态模型的依据。没看清现状就开始画图基本是在自嗨。第二迁移期间不要移除旧的兼容层。哪怕新版确认稳定了也至少保留一个更新周期的兼容适配器。业务方不可能按你的节奏升级给他们留缓冲其实也是给自己留后路。第三别用流量高峰日做迁移。我们本来想赶在月底前切换后来硬生生拖到月初流量低谷期才动。这种底层重构级别的变更一定要选一个即使出问题也来得及回滚的时间窗口。第四团队里必须有一个懂全部旧代码的人全程盯迁移。新版代码写得再干净也总有边缘行为是从旧实现里继承下来的。这个人不需要写新代码但他的旧代码记忆能帮你解释很多古怪的线上行为。我一直觉得Agent 框架现在最缺的不是模型能力和工具数量而是稳定的执行地基。模型今天能用 GPT明天可能换别的工具今天接这个明天接那个只有用来承载 Agent 推理、并发、记忆、追溯的那套运行时机制才是长期沉淀下来的价值。Orkas 这次底层重构与其说是把代码翻新了一遍不如说是我们终于想明白了 Agent 运行时到底该长什么样。骨架立住了后面加功能、接场景心里才有底。
返回列表