1. 为什么“重写地基”比“堆功能”更值得聊
做 Agent 开发的人大概都有过这种体验:第一版跑得挺爽,工具调用、多轮对话、记忆存储全都塞进去,Demo 演示的时候行云流水。等到真正要接第二个模型、第三个工具、第四种编排逻辑的时候,代码开始变成一团乱麻——模型调用层里混着业务逻辑,工具注册表里藏着状态管理,多智能体之间的消息传递靠一堆 if-else 硬编码。这时候你才意识到,问题不在于功能不够,而在于地基没打牢。
Orkas 这次做的事情,就是把这个地基重新挖开、重新浇筑。它不是加了一个新功能,也不是换了一个新模型接口,而是把整个 Agent 运行时的核心抽象重新设计了一遍。关键词里的Orkas、Agent、模型调用、多智能体编排、工具生态,这五个词基本勾勒出了这次重构的全部战场:模型调用要统一、编排要解耦、工具要可插拔、多智能体要能协作、整个运行时要有清晰的边界。
这篇文章适合谁看?如果你正在从零搭一个 Agent 框架,或者你手里的 Agent 项目已经进入了“改一处崩三处”的阶段,再或者你单纯想搞清楚一个生产级 Agent 框架的底层应该长什么样,那这篇内容应该能给你一些可以直接抄作业的思路。我不会只讲“做了什么”,更会讲“为什么这么做”以及“不这么做会踩什么坑”。
2. 重构前的 Orkas 到底卡在哪里
2.1 旧架构的三个致命耦合
Orkas 在重构之前,核心问题可以归结为三个层面的耦合。第一个是模型调用与业务逻辑的耦合。早期的实现里,调用一次模型和处理模型返回的结果是写在一起的,换一个模型提供商就意味着要改一大片业务代码。第二个是工具注册与执行上下文的耦合。工具的定义、参数校验、执行、结果回传,全都挂在同一个对象上,想单独测试一个工具几乎不可能。第三个是多智能体之间的通信耦合。Agent A 要调用 Agent B,得直接持有 B 的引用,消息格式也是内部私有协议,外部想接进来一个用其他框架写的 Agent,成本极高。
这三个耦合带来的直接后果就是:扩展成本随功能数量指数上升。你加第一个工具的时候觉得挺方便,加第十个工具的时候发现注册表已经变成了一个上千行的巨型文件。你接第一个模型的时候觉得封装得挺好,接第三个模型的时候发现每个模型的流式输出格式都不一样,适配层写得像打补丁。
2.2 一个具体的崩溃场景
举个实际会遇到的例子。假设你有一个 Orkas Agent,它需要调用一个搜索工具、一个代码执行工具,同时还要和一个专门做数据分析的子 Agent 协作。在旧架构下,这个场景的代码大概长这样:主 Agent 的run方法里,先判断要不要调搜索,调完搜索把结果拼进 prompt,再判断要不要调代码执行,代码执行的结果又要塞回上下文,然后还要手动构造一个消息发给子 Agent,等子 Agent 返回后再解析。这一整套流程全写在一个方法里,任何一步出问题,排查起来都要从头读一遍。
更麻烦的是,当你想把搜索工具换成另一个提供商,或者想把子 Agent 换成用不同框架实现的版本,你会发现改动量远超预期。这就是典型的“地基不稳,上层建筑越高越危险”。
2.3 重构的核心目标:四个解耦
Orkas 这次重构的目标非常明确,就是四个解耦:模型调用与业务逻辑解耦、工具定义与执行解耦、编排逻辑与运行时解耦、智能体之间的通信解耦。这四个解耦不是拍脑袋定的,而是从上面那些具体崩溃场景反推出来的。每一个解耦都对应着一个明确的接口边界,边界之内可以自由替换实现,边界之外只依赖抽象。
提示:如果你正在评估自己的 Agent 项目要不要重构,可以先问自己一个问题——换一个模型提供商需要改几个文件?如果超过三个,那基本可以考虑动手了。
3. 模型调用层:统一抽象到底该怎么设计
3.1 为什么不能直接封装成一个大接口
很多人设计模型调用层的时候,第一反应是定义一个ModelProvider接口,里面放一个chat方法,所有模型都实现这个接口。这个思路方向是对的,但问题在于粒度太粗。不同模型的能力差异很大:有的支持流式输出,有的支持函数调用,有的支持多模态输入,有的支持并行工具调用。如果你只定义一个chat方法,那这些差异要么被抹平(导致能力丢失),要么被塞进一个巨大的参数对象里(导致接口膨胀)。
Orkas 的做法是把模型调用拆成三层:能力声明层、请求构造层、响应解析层。能力声明层负责告诉上层“我这个模型支持什么”,请求构造层负责把统一的内部请求转换成特定模型的 API 格式,响应解析层负责把特定模型的返回转换成统一的内部响应。这三层各自独立,换模型的时候只需要换实现,不需要动上层逻辑。
3.2 统一消息格式的设计取舍
内部消息格式的设计是模型调用层的核心。Orkas 选择了一种基于角色和内容块的消息格式,而不是简单的role + content字符串。为什么?因为现代模型的输出越来越复杂,一个响应里可能同时包含文本、工具调用请求、推理过程标记。如果用纯字符串,这些信息要么丢失,要么需要额外的解析步骤。
具体来说,一条内部消息由role和content blocks组成,每个 block 有自己的类型:text、tool_call、tool_result、reasoning。这样设计的好处是,无论底层模型返回什么格式,解析层都可以把它映射成这几种 block 的组合。上层业务逻辑只需要处理 block 列表,不需要关心底层是哪个模型。
# 内部消息格式示意 message = { "role": "assistant", "content": [ {"type": "text", "text": "我来帮你查一下"}, {"type": "tool_call", "name": "search", "arguments": {"query": "Orkas 重构"}} ] }3.3 流式输出的统一处理
流式输出是模型调用层最容易出问题的地方。不同模型的流式协议差异极大:有的按 token 返回,有的按事件返回,有的把工具调用拆成多个 chunk。Orkas 的解决方案是定义一个统一的流式事件类型,解析层负责把各家的流式协议转换成这个统一事件流。
统一事件类型包括:text_delta、tool_call_start、tool_call_delta、tool_call_end、reasoning_delta、done。上层只需要监听这些事件,不需要关心底层是 SSE 还是 WebSocket,也不需要关心工具调用的参数是分几次传过来的。
注意:流式工具调用的参数拼接是一个经典坑点。有些模型会把 JSON 参数分成多个 chunk 返回,你必须等到
tool_call_end才能拿到完整参数。提前解析会得到不完整的 JSON,导致工具调用失败。
3.4 模型能力协商机制
Orkas 在模型调用层还引入了一个能力协商机制。每个模型实现都需要声明自己支持的能力,比如supports_streaming、supports_tool_call、supports_parallel_tool_call、supports_vision。上层在构造请求之前,会先检查当前模型是否支持所需能力,如果不支持,要么降级处理,要么抛出明确的错误。
这个机制的价值在于把运行时错误提前到了请求构造阶段。以前你可能要等到模型返回了一个奇怪的错误才知道它不支持并行工具调用,现在在发请求之前就能知道,排查成本大幅降低。
4. 工具生态:从“注册表”到“可插拔运行时”
4.1 工具定义的标准化
旧架构里,工具就是一个函数加上一堆装饰器,参数校验靠函数签名,描述靠 docstring。这种方式在工具数量少的时候没问题,但工具一多就乱了:有的工具是同步的,有的是异步的,有的需要访问外部资源,有的需要注入上下文。Orkas 重构后,工具定义变成了一个显式的 schema 对象,包含名称、描述、参数 schema、执行函数、所需能力、超时设置等字段。
tool_schema = { "name": "search", "description": "搜索互联网获取最新信息", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"} }, "required": ["query"] }, "execute": search_function, "timeout": 30, "requires": ["network"] }这种显式 schema 的好处是,工具的定义和实现完全分离。你可以把工具 schema 序列化成 JSON 存到数据库里,也可以从远程加载工具定义。工具的执行环境可以是一个本地函数,也可以是一个远程服务,只要它符合 schema 约定。
4.2 工具执行的沙箱与隔离
工具执行是 Agent 安全风险最高的环节。一个恶意工具或者一个有 bug 的工具,可能把整个 Agent 运行时搞崩。Orkas 在重构中引入了工具执行沙箱的概念,每个工具的执行都在一个受控的环境中进行,有独立的超时控制、资源限制和错误隔离。
具体实现上,同步工具和异步工具走不同的执行路径。同步工具会被放到一个独立的线程池里执行,异步工具则在事件循环中执行,但两者都有统一的超时包装。如果工具执行超时或者抛出异常,沙箱会捕获并返回一个标准化的错误结果,而不是让异常向上传播。
提示:工具执行的超时设置不要一刀切。搜索类工具可能需要 30 秒,而本地计算类工具 5 秒就够了。按工具类型设置不同的超时,可以避免一个慢工具拖垮整个 Agent 的响应速度。
4.3 工具结果的标准化回传
工具执行完之后,结果怎么回传给模型,也是一个容易出问题的地方。不同模型对工具结果的格式要求不一样:有的要求 JSON 字符串,有的要求特定结构的对象。Orkas 的做法是定义一个统一的工具结果格式,然后在模型调用层的响应构造阶段,由各模型实现负责转换成自己需要的格式。
统一工具结果格式包含:tool_name、success、result、error、execution_time。这样设计的好处是,工具执行层不需要关心模型差异,模型调用层也不需要关心工具的具体实现。
4.4 工具生态的扩展性设计
Orkas 的工具生态设计还有一个关键点:工具可以组合。一个工具可以调用另一个工具,形成工具链。这在旧架构下是很难做到的,因为工具之间没有统一的调用约定。重构后,工具执行器本身就是一个可以被其他工具调用的对象,只要传入符合 schema 的参数,就可以触发另一个工具的执行。
这种组合能力让工具生态的扩展性大幅提升。你可以把常用的工具组合封装成一个高层工具,对外暴露更简单的接口。比如“搜索并总结”可以是一个组合工具,内部先调搜索工具,再调总结工具,但对外只暴露一个search_and_summarize接口。
5. 多智能体编排:消息驱动与角色解耦
5.1 从“直接调用”到“消息传递”
旧架构下,Agent A 调用 Agent B 的方式是直接持有 B 的引用,然后调用 B 的方法。这种方式的问题在于,A 和 B 的生命周期被绑定了,B 挂了 A 也会受影响,而且 A 和 B 必须在同一个进程里。Orkas 重构后,Agent 之间的通信变成了消息驱动:A 把消息发到一个消息通道,B 从通道里取消息并处理,处理完再把结果发回另一个通道。
这种设计的好处是,Agent 之间完全解耦。A 不需要知道 B 在哪里、用什么语言写的、是不是在同一个进程里。只要消息格式符合约定,任何实现了消息处理接口的实体都可以是一个 Agent。
5.2 编排逻辑的声明式表达
多智能体编排的另一个难点是编排逻辑本身。旧架构下,编排逻辑是硬编码在代码里的:先调 A,再调 B,如果 A 的结果满足某个条件就调 C,否则调 D。这种硬编码的编排逻辑很难修改,也很难可视化。
Orkas 重构后,编排逻辑变成了声明式的。你可以用一个编排描述文件来定义 Agent 之间的调用关系、条件分支、循环等。运行时根据这个描述文件来调度 Agent 的执行。这种方式的好处是,编排逻辑和 Agent 实现完全分离,修改编排不需要改 Agent 代码,甚至可以在运行时动态调整。
# 编排描述示意 steps: - agent: researcher input: "{{user_query}}" output: research_result - agent: analyst input: "{{research_result}}" output: analysis_result condition: "{{research_result.success}}" - agent: writer input: "{{analysis_result}}" output: final_answer5.3 多智能体之间的状态共享
多智能体协作的一个核心问题是状态共享。Agent A 产生的中间结果,Agent B 需要用到,这个状态放在哪里?旧架构下,状态通常放在一个全局字典里,谁都可以读写,很容易出现竞态条件和数据污染。Orkas 重构后,状态管理变成了显式的上下文对象,每个 Agent 执行时都会拿到一个上下文,上下文里包含了它需要读取的状态和它可以写入的状态。
上下文对象的设计遵循最小权限原则:Agent 只能读取它被授权读取的状态,只能写入它被授权写入的状态。这样既保证了状态的安全性,也让状态流转变得可追踪。
5.4 并发与并行的处理
多智能体编排绕不开并发问题。多个 Agent 同时执行时,如何保证状态一致性?Orkas 的做法是基于消息队列的串行化:所有对共享状态的修改都通过消息队列串行执行,Agent 之间的并行只发生在计算层面,不发生在状态修改层面。这样既利用了并行的计算能力,又避免了状态竞态。
注意:并发 Agent 的数量不是越多越好。每个 Agent 都有自己的上下文和工具调用开销,并发数过高会导致资源竞争和响应延迟。建议根据实际硬件资源和任务特性,把并发数控制在一个合理范围内,比如 4 到 8 个。
6. 重构后的运行时:边界清晰带来的实际收益
6.1 可测试性的提升
重构之后最直观的收益是可测试性。模型调用层可以单独测试,用一个 mock 模型实现就可以验证上层逻辑。工具执行层可以单独测试,每个工具都可以独立运行和验证。编排层可以单独测试,用一个 mock Agent 就可以验证编排逻辑是否正确。这种分层测试的能力,在旧架构下几乎不可能实现。
6.2 可观测性的增强
第二个收益是可观测性。重构后的 Orkas 在每个关键节点都埋了事件:模型请求发出、模型响应返回、工具调用开始、工具调用结束、Agent 消息发送、Agent 消息接收。这些事件可以被统一收集和分析,形成完整的调用链路。排查问题时,你可以清楚地看到一次请求经过了哪些环节、每个环节花了多少时间、哪个环节出了错。
6.3 可扩展性的质变
第三个收益是可扩展性。重构后,增加一个新的模型提供商只需要实现模型调用层的接口,增加一个新的工具只需要定义工具 schema,增加一个新的 Agent 只需要实现消息处理接口。这三种扩展互不影响,可以独立进行。这种扩展性上的质变,是这次重构最大的价值。
| 维度 | 重构前 | 重构后 |
|---|---|---|
| 换模型 | 改多个业务文件 | 只改模型实现层 |
| 加工具 | 改注册表大文件 | 新增工具 schema |
| 加 Agent | 改编排硬编码 | 新增编排描述 |
| 测试 | 只能端到端测 | 分层独立测试 |
| 排查 | 读完整调用栈 | 看事件链路 |
6.4 性能上的实际变化
重构在性能上也有实际变化。旧架构下,模型调用、工具执行、编排逻辑都在同一个执行流里,任何一个环节阻塞都会影响整体。重构后,模型调用和工具执行可以并行,编排逻辑可以异步调度,整体吞吐量有明显提升。当然,这种提升不是免费的,消息传递和上下文管理本身也有开销,但在 Agent 场景下,模型调用的延迟远大于消息传递的开销,所以整体收益是正的。
7. 实操中踩过的坑与排查技巧
7.1 流式工具调用的参数拼接
这是我在实操中遇到的最频繁的问题。前面提到过,有些模型会把工具调用的 JSON 参数分成多个 chunk 返回。如果你在tool_call_delta阶段就尝试解析参数,会得到不完整的 JSON,导致解析失败。正确的做法是维护一个参数缓冲区,每次 delta 到达时追加到缓冲区,等到tool_call_end时再统一解析。
提示:参数缓冲区要按工具调用 ID 区分。并行工具调用时,多个工具调用的 delta 可能交错到达,如果不按 ID 区分,参数会串到一起。
7.2 工具执行超时的连锁反应
工具执行超时如果没有被正确隔离,会引发连锁反应。一个工具超时导致整个 Agent 卡住,后续所有请求都排队等待。Orkas 的解决方案是给每个工具执行都包一层超时控制,超时后立即返回错误结果,不阻塞后续流程。同时,超时工具会被标记,如果同一个工具连续多次超时,会被暂时禁用,避免反复拖慢整体响应。
7.3 多智能体消息循环
多智能体编排中,消息循环是一个隐蔽的坑。Agent A 发消息给 Agent B,Agent B 处理后又发消息给 Agent A,如果 A 的处理逻辑又触发了对 B 的调用,就会形成无限循环。Orkas 在编排层引入了调用深度限制和循环检测:每个消息都带一个调用链 ID,如果同一个调用链 ID 出现的次数超过阈值,就强制终止并返回错误。
7.4 上下文膨胀问题
Agent 执行过程中,上下文会不断膨胀。每一轮对话、每一次工具调用、每一个中间结果都会往上下文里塞内容。如果不加控制,上下文会迅速超出模型的窗口限制。Orkas 的做法是上下文压缩策略:当上下文接近窗口限制时,自动对历史消息进行摘要,只保留关键信息。摘要的粒度可以配置,比如保留最近 N 轮完整对话,更早的对话只保留摘要。
7.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决思路 |
|---|---|---|---|
| 工具调用参数解析失败 | 流式参数未拼接完整 | 检查是否在 end 事件前解析 | 维护参数缓冲区,end 时统一解析 |
| Agent 响应卡住 | 工具执行超时未隔离 | 检查工具超时设置 | 加超时包装,超时返回错误 |
| 多智能体死循环 | 消息循环未检测 | 检查调用链 ID | 加调用深度限制和循环检测 |
| 上下文超限 | 上下文未压缩 | 检查上下文长度 | 加摘要压缩策略 |
| 模型能力不匹配 | 未做能力协商 | 检查模型能力声明 | 请求前做能力检查,不支持则降级 |
8. 这套重构思路还能怎么用
Orkas 这次重构的核心思路,其实不局限于 Agent 框架。任何需要多组件协作、多提供商适配、动态编排的系统,都可以参考这套分层解耦的设计。比如工作流引擎、数据处理管道、甚至微服务网关,都能从中找到可借鉴的模式。
我个人在实际操作中的体会是,重构的时机比重构的方案更重要。太早重构,你还没有足够的场景来验证抽象是否合理;太晚重构,技术债已经压得你喘不过气。一个比较合适的信号是:当你发现加一个新功能需要改动的文件数量超过三个,或者修一个 bug 需要读超过五百行代码的时候,就该考虑动手了。
最后再分享一个小技巧:重构的时候不要试图一次到位。先把最痛的耦合点解开,跑通一个最小闭环,再逐步扩展。Orkas 这次重构也是分阶段进行的,先解耦模型调用,再解耦工具执行,最后解耦多智能体编排。每个阶段都有可运行的版本,这样风险可控,也更容易验证效果。