Code Mode 的核心机制与执行流程
视频首先通过一个具体的代码仓库审计任务来引入 Pi Agent 1.0 新增的 Code Mode。假设任务是让一个 Coding Agent 检查 Pi 的 monorepo(代码仓库),找出所有直接依赖 pi-codemode 的软件包。这个任务看似简单,但传统模式下至少包含五个步骤:找到所有 package.json 文件、读取并解析 JSON、筛选依赖,最后整理结果。
在普通的工具调用模式中,Pi 使用 bash 找到了 17 个文件。这份路径清单首先返回给模型,模型收到后并发调用 17 次 read 工具。17 份文件内容直接进入模型的上下文,由模型完成解析和筛选。整个过程包含 18 次顶层工具调用(1次bash + 17次read),但最终需要的结果只有两个软件包的名称。这种模式下,模型需要反复参与中间步骤的规划。
"整个过程包含18次的顶层工具调用 最终需要的只有两个软件包的名称"
Code Mode 提供了另一种执行方式。模型先生成一段 JavaScript 代码,由这段程序调用工具、等待结果、进行解析和筛选。最后,只有程序明确输出的内容才会返回给模型。这种机制减少了模型与工具之间的往返次数,像先列出文件、再批量读取、最后筛选这样的固定流程,可以在一次 Code Mode 调用中完成。工具仍然会执行,但中间步骤不必每次都让模型重新规划。
性能优势与上下文控制
Code Mode 的主要价值在于减少模型往返并严格控制进入上下文的數據量。在上述案例中,17 个文件可以全部交给脚本处理,模型只接受两个匹配项和必要的摘要。JavaScript 代码还可以利用并发循环、条件判断和错误分支等编程逻辑,从而更稳定地表达任务流程。
需要注意的是,Code Mode 并不保证所有任务都更快或消耗更少的 token。实际效果取决于该任务能否用程序稳定地表达。如果任务的每一步都需要模型重新理解自然语言,增加 JavaScript 这一层的意义可能并不太大。
"它也能控制进入上下文的数据量 17个文件可以全部交给脚本处理 模型只接受两个匹配项和必要的摘要"
在演示使用的 Pi 1.0.2 版本中,Code Mode 默认不会主动启用。如果只想在当前这次启动中使用它,可以运行以下命令明确选中工具:
pi --tools read,bash,codemode
这条命令明确选中了 read、bash 和 codemode 这三个工具,且只影响本次启动。如果需要持续使用,可以将 defaultTools 设置为包含 +codemode。配置可以写入用户级设置(影响之后启动的会话),也可以写入项目中的 .pi/settings.json(只影响到当前项目中的会话)。
配置模式与工具调用方式
Pi Agent 提供了两种 Code Mode 的工作模式:on 模式和 only 模式。
在默认的 on 模式下,模型会同时看到普通工具和 Code Mode。模型可以直接调用 read 工具进行简单操作,也可以在 JavaScript 中通过 tools.read() 来调用。这种方式使得在同一个会话中,批量任务可以使用 Code Mode,而后续的简单操作依然可以直接调用普通工具。
在 only 模式下,系统会隐藏普通工具的直接入口。模型只能看到 Code Mode,read、bash 等工具只能通过脚本以 tools.read()、tools.bash() 的方式调用。当前官方 Pi Agent 没有内置像 /codemode 这样的开关,如果需要在会话运行前修改可用的工具,可以通过扩展调用 setActiveTools() 来实现。
"默认的 on 模式会同时向模型提供普通工具和 Code mode...因此在同一个会话中 批量任务可以使用 Code mode 后续简单操作依然可以直接去调用这些普通的工具"
模型交给 Code Mode 的输入是一段原始的 JavaScript 代码。Pi 使用运行于 WebAssembly 上的 QuickJS 引擎来执行它。这个沙箱环境没有 Node API,没有文件系统、网络和定时器。脚本不能直接通过 import fs 或调用 fetch() 来实现资源访问。文件的读取必须通过 Pi 注入的 tools 代理来完成。
当脚本执行到 tools.read() 时,QuickJS 将工具名和参数交给桥接组件(bridge)。Pi 宿主检查当前会话是否允许调用该工具,随后执行读取操作并将结果送回给 QuickJS。JavaScript 再从 await 所在的位置继续向后运行。两边的分工非常清晰:沙箱里的 JavaScript 负责安排执行步骤和处理数据,而宿主的工具负责访问文件系统、Shell、网络和 MCP 等外部资源。
真实会话演示与执行细节
视频展示了一个真实的 Pi 命令行会话。启动命令选中了 read、bash 和 codemode,模型使用 DeepSeek v4.1 Flash。任务要求模型必须通过 Code Mode 扫描仓库,找出直接依赖 pi-codemode 的软件包。
模型生成的脚本首先调用一次 tools.bash(),列出排除了 node_modules 之后的 17 个 package.json。接着,脚本通过 Promise.allSettled() 并发调用 17 次 tools.read(),间接去调用 read 工具。每份文本在 QuickJS 内被解析为 JSON,再检查四类依赖字段。脚本最后只保留依赖 pi-codemode 的项目,按名称排序并通过 return 交付汇总结果。
运行开始后,Code Mode 先调用 bash 获取文件清单。宿主返回了 17 条路径,脚本随后并发发出 17 个 read 请求。这些调用都有真实的输入和输出:输入是文件路径,输出是对应的 package.json 内容。宿主记录每个嵌套调用的名称、状态和耗时。QuickJS 收到完整结果后,在沙箱中完成解析和筛选。整个过程一共执行 18 次宿主工具调用。
"模型提交一段程序 真正的工具调用发生在程序的执行期间"
对比普通模式的真实录制,同样执行了一次 bash 和 17 次 read。这些调用由模型直接发出,路径清单和文件内容也会返回给模型。而 Code Mode 把同样的 18 次操作放进了一次程序的调用中,由 JavaScript 完成发现、读取、解析和筛选。最后进入模型上下文的是扫描数量、失败数量和两个匹配的软件包:pi-coding-agent 和 pi-durable。两者都依赖 1.2.0 版本的 pi-codemode。模型接收的就是这两条可以直接回答给用户的结果,而非原始的 17 份清单。
错误处理与边界条件
批量任务必须考虑部分失败的情况。在第二个实验中,脚本同时读取一个存在的文件和一个不存在的文件。Promise.allSettled() 函数保留了成功结果,并把 ENOENT 错误码标识的错误作为单独的错误项交给脚本处理。整段执行仍然可以正常完成。
不同工具的失败语义可能不同:普通失败会让 Promise 进入 rejected 状态,而 bash 工具的非零退出码则会作为结构化的结果返回。脚本需要主动检查 exit_code,因此工具的 schema 和文档变得非常重要。
此外,沙箱不提供事务保证。脚本失败后,已经完成的工具调用是不会回滚的;仍在运行的调用会被取消;没有使用 await 等待的 Promise 也会被丢弃。 store() 只保存少量的 JSON 状态,并且只有脚本成功执行后写入的数据才会保留。
"沙箱也不提供事务保证 脚本失败后已经完成的工具调用是不会回滚的"
判断一个任务是否适合 Code Mode,可以先问三个问题: 1. 它是否包含可编程的重复步骤? 2. 工具是否返回稳定的结构化数据? 3. 外部的副作用是否有独立的权限审批或补偿的机制?
批量读取、并发查询、筛选汇总和多工具组合通常非常适合。但如果任务只有一次调用,或者每一步都需要模型重新理解自然语言时,增加 JavaScript 这一层的意义似乎并不太大。不可逆的操作也不能因为使用 Code Mode 就省略掉必要的保护。
结论
Pi Agent 1.0 的 Code Mode 通过引入 JavaScript 脚本作为中间层,重构了模型与工具之间的交互方式。其核心价值在于减少模型与工具间的往返次数以及严格控制进入大语言模型上下文的原始数据量。在涉及批量读取、并发处理和复杂筛选的任务中(如案例中的 18 次工具调用),Code Mode 允许模型生成代码由沙箱执行,最终仅将精简后的结果(如 2 个匹配包)返回给模型,从而显著降低 Token 消耗并提升执行稳定性。
然而,Code Mode 并非万能。它依赖于任务能否被稳定地编程表达,且沙箱环境受限(无 Node API、网络等),不提供事务回滚保证。因此,它最适合包含可编程重复步骤和结构化数据处理的批量任务;对于单次调用或高度依赖自然语言推理的步骤,其优势并不明显。用户需根据任务特性选择 on 或 only 模式,并妥善处理脚本中的错误分支与副作用。