上下文即代码:150 行的 IPython agent,上下文成本降到 30%,正确率不变

· a0gent

这个 harness 只有 150 行,没有工具调用协议,执行后端是 Docker 里的真 IPython。同任务、同模型下,它平均每个任务使用 2062 tokens 上下文,传统 CLI agent 要 6855,答案质量不变。为什么相差三倍多?我们用 336 个受控 trial(API 总成本约 $0.2)拆开成本。最反直觉的结果是,把数据表示成代码并不会让模型更懂。在受控对照下,这一层的收益恰好为零。实验代码与全部轨迹在 agentic-playground

什么是"上下文即代码"

先看实物。下面是实验里 agent 开始工作之前就"已经存在"的上下文(截取):

In [1]: import json, os
   ...: sorted(os.listdir('data'))
Out[1]: ['events.jsonl', 'projects.jsonl', 'tasks.jsonl', 'teams.jsonl', 'users.jsonl']

In [2]: def load(name):
   ...:     return [json.loads(l) for l in open(f'data/{name}.jsonl')]
   ...: teams, users, projects, tasks, events = (load(n) for n in
   ...:     ['teams', 'users', 'projects', 'tasks', 'events'])
   ...: len(teams), len(users), len(projects), len(tasks), len(events)
Out[2]: (30, 300, 80, 5000, 20000)

In [3]: sorted({t['status'] for t in tasks}), sorted({e['action'] for e in events})
Out[3]: (['blocked', 'done', 'in_progress', 'review', 'todo'],
         ['commented', 'created', 'reassigned', 'status_changed'])

关键在于,这段会话历史是伪造的。模型此刻还没说过一个字,但每个 Out 都是真的。harness 把这些 cell 交给真实 kernel 执行,再把输出拼进历史。变量 tasksevents 从此留在运行时命名空间里。agent 的第一条消息就可以直接写 [t for t in tasks if ...]。对模型而言,这不是一份描述数据的文档,而是一个可以直接续写的行为前缀。

如果你做 agent 开发,大概已经知道 CodeActsmolagentsCloudflare Code Mode 这类"代码作为行动空间"的工作。本文考察的是上下文。行动可以写成代码,上下文也可以是一段可续写、可执行的会话。我们把它的成本拆开来算。

实验设计:一把自由度阶梯

所有组使用同一套程序生成的项目管理数据集:xl 档有 300 个 users、5000 个 tasks、20000 个 events,约 3.5MB JSONL 全部落盘。上下文只给文件行数和每个文件末尾一两行,所有任务都需要多步交互。我们也固定了 8 个查询任务模板(过滤、聚合、多跳,以 gold 程序判分)和模型(通过 OpenRouter 调用 deepseek-v4-flash)。唯一改变的是 agent 与执行环境之间隔着多少层协议。

按自由度递减排:

Round 1 在两个更小的数据档先跑过一轮,结论方向相同,本文只列 Round 2 的数。

主结果:成本阶梯

ctx tokens ctx 中位数 out tokens 轮数 墙钟 成本/trial
A 传统 CLI 6855 7032 506 3.62 16.2s $0.000484
B0 会话不绑变量 7263 6926 438 3.62 15.8s $0.000470
B 伪造会话 4803 3540 298 2.62 9.9s $0.000358
D 裸 IPython 2062 2012 203 2.04 8.1s $0.000184
C 单发 854 850 363 1 8.5s $0.000113

五组正确率都是 100%,已贴近天花板。以下结论只讨论 token 成本,不讨论能力上限。

阶梯本身不意外,意外在每一级的成因。

逐层归因

表示形式:值 0%

B0 与 A 打平:轮数完全相同(3.62),ctx/out 差异在噪声内,两轮实验都复现了这个零结果。同样的数据、同样的 IPython 会话外观,只要示范不绑定变量,成本就与传统 CLI agent 没有区别。"表示成代码模型更懂"这一层,受控对照下收益为零。

差距来自行为。同一个任务(t6)中,B 的第一条 eval 直接写 [p for p in projects if ...],引用伪造历史里已 load 的变量,3 次调用完成。B0 的每条调用都重新用 [json.loads(l) for l in open(...)] 全量重载,共用了 4 次。

另一个发现解释了为什么 B0≈A。A 条件的 24 个 trial 共 63 次工具调用,全部是 bash 中的 python3 -c 多行脚本,内置 read 工具一次也没用。模型拿到原语工具后仍选择编程语言。A 实际上成了一个没有持久状态的 Python 条件。

工作习惯:ctx −30%,out −41%

相对 A,B 的 ctx 降 30%(中位数降 50%),output 降 41%,少一轮,墙钟时间降 39%。伪造历史让模型复用变量,并在一个 cell 中完成更多步骤。A 和 B0 没有持久状态,agent 会把上一步算出的任务 ID 集合整段作为字面量粘进下一条命令。这直接造成 B 的 output 比它们低四成。文档可以说明这些习惯,历史示范会让模型直接照做。

协议层:−57%

D 去掉了工具调用协议、tool schema、JSON 参数包装和 harness 封装,ctx 从 4803 降到 2062。B 要靠伪造维持"每条 assistant 消息都是 eval 调用"的形式,D 的消息格式本来就是这样。示范前缀因此可以直接作为合法对话前缀。

循环:−59%

C 连 agent 循环也去掉,只生成一次代码,ctx 降到 854。D 的 ctx 下界约等于轮数乘以前缀长度。这是保留多轮纠错能力的成本。任务可以一次命中时,循环就是额外开销。我们仍坚持执行代码,而不是让模型直接作答。Round 1 中,数据全量可见时,C 的模型在长上下文中直接数 distinct 评论者,答成 2,实际是 3。模型会数错,解释器不会。

示范与说教的边界

D 定稿前做了一轮提示词消融(各格 n=23–24):

变体 系统提示词 前缀 正确率 ctx out 轮数
v1 规则说教 完整规则,无注释通道 探索 95.8% 2080 248 2.12
v2 注释指令(定稿) 完整规则 + "思考写成 # 注释" 探索 100% 2062 203 2.04
v3 纯示范 一句话 探索+热身问答示范 39.1% 9747 1045 8.26
v4 示范+终止一句 一句话+ANSWER 说明 探索+示范 87.0% 2115 280 2.17

v3 想用示范取代全部规则,正确率降到 39.1%。模型把 16 算对后,把显示这个值当作回答结束,随后反复输出 16,最后输出散文并触发 SyntaxError。这很像人类在 IPython 里查看中间结果的习惯。终止协议属于会话的元规则,不在会话内容的分布中,一次示范无法稳定传达。v4 加回一句 ANSWER 说明,正确率回到 87%。剩下 3 个失败都是未经执行的猜值,说明"答案必须来自已执行输出"这条约束不可少。

边界很清楚。示范适合传递注释、批处理和变量复用等习惯,v3 中这些风格保持良好。规则适合传递终止协议和防猜约束等元规则。少掉其中任何一类,正确率都会下降。

同模态:编程语言的真正角色

编程语言的作用不是让模型更懂这种表示,这一层的收益为零。它让示范与行动处于同一模态:示范里的每一行都能执行,示范中绑定的变量也真实存在于运行时,因此模型可以直接续写示范。纯文本 skill 文档只能描述行为,不能成为行为前缀。

邻近工作主要讨论"行动"侧。CodeAct 与 smolagents 讨论代码行动能否减少步数,Cloudflare Code Mode 与 Anthropic 的 code execution with MCP 将工具封装为代码 API。近期 NVIDIA-labs 的 OO Agents(NOOA)从另一侧把角色、状态和能力收回到 Python 对象:模型面对的是对象的方法与活运行时状态,而非另一套框架专属的协议。这与本文的方向相邻,但侧重点不同:NOOA 提出 Agent 的编程模型;本文把上下文实现为可执行会话,并分别测量表示形式、工作习惯、协议层和循环的成本。拆开后,被讨论最多的表示形式反而是唯一没有收益的一层。

如果你在做 agent

  1. 不必改变数据表示形式。受控对照下,这一层没有收益。
  2. 要传递变量复用、批处理和注释思考等习惯,就提供一段伪造历史,而不是用文档说明。
  3. 协议层是本实验中节省最多的一项,ctx 降了 57%。可以考虑裸 completion 循环,让消息直接成为 cell。
  4. 任务能一次命中时,不要默认开启循环。循环为多轮纠错付费,本实验中再增加了 59% ctx。

局限

下一步

Round 3 已排期。它会把 MCP 工具分别以 tool-calling 和 Python API 暴露给同一个 agent,在 N+1 组合压力下对比 A 与 D,测量 Code Mode 类主张的成本。