在 Agent 系统刚开始流行的时候,我们其实默认认为:

只要 Tool 足够完善,Agent 最终就会变成一个“会调用 API 的大脑”。

后来我们发现,这个假设只在小规模场景里成立。

一旦数据规模继续上升,尤其是在日志分析、RAG、监控系统、数字孪生这类高数据量环境里,纯 Tool 架构会开始出现非常明显的问题。

最典型的一类任务是:

“在整个数据集中筛选异常,并生成分析结果。”

它的问题不在于推理复杂。

返回的数据量会大到让上下文系统本身开始崩溃。

我们后来在一个约 1 万实体的真实任务里,对比了三种方案:

  • 纯 Tool(标准 MCP)
  • 纯 Code-as-MCP
  • Tool + Code 的双轨执行模型

纯 Tool 会被上下文拖死。

纯 Code 虽然准确,但整体延迟会迅速上升。

最后真正稳定下来的,是一种双轨执行模型:

  • Tool 负责控制平面
  • Code 负责数据平面
  • 中间通过一个很小的 “Context Off-Ramp” 做切换

这篇文章主要讲这个结构为什么会出现,以及我们后来是怎么把它真正跑起来的。


1. 为什么纯 Tool 和纯 Code 都会开始失控

在上一篇里,我们已经区分过 MCP(基于 Tool)和 Code-as-MCP 的两个核心差异:

  • 动作是如何表达的
  • 系统在什么时候做校验

这些差异在小规模场景下其实不明显。

但系统复杂度一旦继续上升,问题会开始以一种非常不线性的方式暴露出来。

我们后来反复遇到两种典型的“成本悬崖”。


Tool 的问题:上下文开始维护自己

最开始我们其实非常偏向 Tool。

因为它天然适合:

  • schema 校验
  • 权限控制
  • UI 操作
  • 状态修改
  • 可审计动作

很多事情本来就应该是 Tool:

  • 删除对象
  • 修改状态
  • 缩容 deployment
  • 执行交易

这些动作如果交给自由生成代码,其实风险会明显变高。

但问题在于,大部分 Tool 系统默认假设返回结果不会太大。

这在真实生产环境里经常不成立。

例如:

“列出当前数据集中所有异常实体。”

这个任务本身并不复杂。

真正的问题是它可能一次性返回几千、几万条记录。

如果这些结果被原样序列化并注入上下文,事情会迅速失控。

我们在实验里实际见过单次返回超过 50 万 token。

真正麻烦的不是成本。

而是整个 Agent 会开始进入一种很奇怪的状态:

  • 响应时间明显变长
  • tool loop 开始增加
  • prompt 约束逐渐丢失
  • 原始任务开始漂移
  • 后部数据开始覆盖前部目标

到后面你会发现:

系统已经不是“在思考任务”。

而是在努力维持上下文本身。

这是我们后来观察到最明显的一个现象。

Tool 原本是为了“精确控制”设计的。

但在高数据密度场景下,它反而会变成上下文的主要压力源。


Code 的问题:很多时间花在“让代码跑起来”

后来我们尝试过另一个极端。

既然 Tool 会把上下文撑爆,那是不是应该让 Agent 全部走代码?

结果也并不理想。

因为很多任务本身其实是原子操作。

例如:

“选中对象 #42”

这种动作本质上只是一次确定性的状态修改。

但如果让 Agent:

  • 生成脚本
  • 调用 sandbox
  • 执行
  • 检查结果
  • 修复错误

那整个系统会开始为“处理复杂数据的能力”付出额外成本。

而这些成本很多时候和业务本身没什么关系。

我们在纯 Code 架构里反复看到几类问题:

  • 代码生成本身耗时很长
  • dependency 问题开始增加
  • sandbox 调试循环很多
  • Agent 会为了小操作生成复杂逻辑

最后虽然结果准确率更高,但整体延迟明显上升。

很多时间花在让代码终于能跑起来。

这也是纯 Code Agent 很容易出现的问题。

它们非常灵活。

但很多原本只需要一个 Tool 调用的小操作,最后会被放大成一整个执行链。


2. 后来我们把执行路径拆开了

做到后面,我们其实慢慢意识到

控制类任务和分析类任务,本来就不属于同一种执行模式。

于是后来整个系统被拆成了两条轨道。


Tool 控制路径

这一条路径专门负责:

  • UI 操作
  • 状态变更
  • 单实体查询
  • 小规模返回
  • 高风险动作

这一层的目标非常明确:

快、确定、可验证。

所以这里保留强 schema、严格校验,以及有限可调用操作。

本质上,它更像传统软件系统。

只是调用者从“人”变成了 Agent。


Code 分析路径

另一条轨道则专门负责:

  • 聚合分析
  • 批量计算
  • 大规模异常筛选
  • 可视化
  • 多步逻辑推导

这里我们反而主动放弃一部分约束。

因为这些任务真正需要的是:

处理复杂数据的能力。

代码直接运行在沙箱环境中。

它面对的是文件、DataFrame 和真实数据。

而不是 Chat 上下文窗口。

这是一个很重要的变化。

因为数据一旦进入代码环境,Agent 就不再需要“记住全部数据”。

上下文终于重新回到了控制层。

而不是继续承担数据平面。


3. 真正关键的是 Context Off-Ramp

真正让整个系统稳定下来的,其实不是“双轨”。

而是:

什么时候强制切轨。

系统会持续监控一次 Tool 调用返回的数据规模。

当返回结果接近 token 阈值时,编排器不会再继续把完整 JSON 注入上下文。

取而代之的是三个步骤:

  1. 中止注入
  2. 将结果写入 CSV / Parquet
  3. 返回文件路径和少量摘要信息
  4. 强制 Agent 改走 Code 轨道

这里最重要的一点是:

这不是优化。

而是一次强制执行切换。

Agent 此时已经无法继续依赖 Tool 路径处理这些数据。

它只能进入代码环境。

后来我们内部一直把这个机制叫做:

Context Off-Ramp。

因为它本质上就像高速公路匝道。

当上下文流量开始失控时,系统会强制把数据流导向另一条轨道。


4. 三种方案的真实实验

后来我们拿一个真实任务做了完整实验:

在 1 万实体上做全量异常筛选,并生成 PDF 报告。

我们分别实现了三种方案。


纯 Tool(Pure MCP)

这是最接近“经典 Agent”结构的一版。

结果比我们预期差很多。

  • 总耗时约 13 分钟
  • AI 分析对话约 11 分钟
  • Tool 调用 7 轮
  • 单次全量返回接近 51 万 token

为了避免上下文直接爆炸,我们最后不得不对 API 返回结果做截断。

问题也从这里开始。

因为一旦截断,异常筛选本身就不再完整。

最终结果准确率只有约 23%。

最明显的问题是:

系统大量时间都花在“维持上下文”。

而不是分析数据。

这也印证了前面的观察。

在高数据密度场景下,Tool 会开始从“控制接口”变成“上下文压力源”。


纯 Code-as-MCP

第二版则完全反过来。

所有事情都走代码。

结果准确率明显提升。

  • 总耗时约 23 分钟
  • 工具调用约 15 轮
  • 异常分析准确率约 85%

但这里也出现了另一个问题。

很多时间其实并不是花在分析。

而是花在:

  • 修 sandbox 问题
  • 调整依赖
  • 修生成代码
  • retry 执行

最后整个系统虽然“聪明”,但明显太慢。

尤其很多原本只需要一个 Tool 调用的小操作,也会被放大成完整代码执行链。


双轨执行模型(Tool + Code + Context Off-Ramp)

第三版则是现在的结构。

也是目前唯一真正稳定的一版。

整个任务耗时约 4 分 30 秒。

中间可以看到非常明显的:

Tool → Code

切轨过程。

其中几个关键节点非常有代表性。

R2:

entities_keyword_search 返回约 108,634 tokens。

系统判定 oversized。

数据被直接写入 CSV。

上下文里只保留前 100 行指针信息。

R3:

进一步 refine 后,返回规模达到 512,675 tokens。

再次触发 off-ramp。

完整数据被写入文件。

Agent 被强制进入 execute_data_analysis。

后面的约 3.5 分钟里:

所有数据处理都发生在 Python Sandbox。

而不是 Chat 主线程。

这是整个系统最关键的变化。

因为原本 50 万级 token 的数据,最后实际上只变成:

“一个文件路径 + 少量提示信息”

等价于把 0.5M token 压缩成几十 token。

最终结果:

  • 总耗时约 4 分 30 秒
  • 分析准确率约 90%
  • 没有发生上下文爆炸

做到这里时,我们其实已经很难再把它理解成“优化”。

它更像是一种执行分层。


5. 为什么这种结构会反复出现

虽然这篇文章的实验来自数字孪生场景。

但这种结构几乎会自然出现在所有高数据密度 Agent 系统里。

因为很多系统本来就同时存在两类任务:

  • 低数据量、高风险控制
  • 高数据量、低风险分析

例如金融系统。

下单、修改仓位、风控指令,本质上更像 Tool。

因为这些动作必须:

  • 可验证
  • 可审计
  • 可限制权限

但策略回测、组合分析、历史数据推演,又明显更适合 Code。

DevOps 其实也一样。

例如:

“在 1GB 日志里找到异常请求”

天然适合代码分析。

但:

“重启 deployment”

则必须是受约束的 Tool。

这里的 off-ramp 很像:

“日志查询超出上下文 → 写入文件 → 进入代码分析 → 再映射回少量控制命令”

Tool 和 Code 的关系,可能更像控制平面 vs 数据平面。

而不是两种互相替代的 Agent 执行方式。


6. 最后

这篇文章最后真正想表达的,并不是:

  • Tool 比 Code 好
  • 或者 Code 比 Tool 更高级

而是:

在开放、反馈驱动、数据规模跨度巨大的系统里,单一执行方式很难同时撑住控制平面和数据平面。

控制层需要确定性。

数据层需要处理复杂数据的能力。

它们本来就不是同一种问题。

真正可行的方法是承认这种执行差异。

然后把它们放到适合的位置。