- SignalDesk2小时前
原文: What is Codemode | Armin Ronacher's Thoughts and Writings [!tip] 省流 什么是 Codemode 写于 2026 年 10 月 06 日 一年多以前,我在这里写过几篇文章,建议大家不要把自定义工具(或 MCP 服务器)加载进上下文,而是多写脚本就够了。其中最重要的是 《代码即一切》 ,以及 《MCP 需要代码》 。到了 Pi 1.0,我们通过 Codemode 加上了对 MCP 的支持——某种意义上这是水到渠成,但对一些人来说或许也有些意外。所以我想借这篇博客,聊聊这件事究竟意味着什么,分享一些新的思考。 什么是工具 当 Pi 这样的 harness(运行框架)为 LLM 提供可调用的工具时,它其实是提供了一些工具定义,再由服务端把这些定义翻译成某种 token 结构。而模型是否被“鼓励”去调用某个工具,是强化学习过程的结果。如果你想了解更多,可以看看我 之前写过的一篇 。 我们之所以强烈偏向 CLI 和 bash,原因之一是它让调用可以轻松组合,也因为模型在训练时就已经学会了文件系统如何运作。所以当它调用 echo foo > /tmp/test.txt 这样的工具时,模型同时也学到:这次调用之后, /tmp 下就多了一个叫 test.txt 的文件。 但 bash 有一个根本性的局限:它只能组合那些“能运行的程序”。而有些东西并不是程序,却是 LLM 的原生工具,而且它们多少必须是原生工具。 最明显的例子是 read 或 view_image 。如果多模态模型需要读一张图片,它没法用 cat 来做这件事,因为 harness 必须把真正的图像负载注入到 LLM 的协议里。 另一个相当生动的例子是子智能体(sub agent)。要创建并编排子智能体,很难绕开 harness 提供的工具。理论上,智能体可以提供一个 CLI 工具,通过环境变量和 Unix socket 与外层 harness 通信,但这个过程相当粗糙。而且它还有另一个问题——代码在哪里运行。 大脑与双手 为了更好地理解这一点,有必要多想一层:所有这些零碎的部件究竟在哪里运行。通常确实涉及两套不同的系统。第一套是大脑,也就是 harness:它运行在一台机器上,是可信的。第二套 往往 是同一台机器,但它才是工具真正执行的地方:双手。在 Pi 里我们现在称之为执行环境(execution environment),但你可以把它理解为所有操作的目标。 对我们来说至关重要的是:harness 这个“大脑”,与运行 bash、执行工具的目标环境之间,存在一条分界线。 把它一分为二会带来一些非常重要的后果。首先,这意味着它们运行在不同的文件系统上,拥有不同的信任等级。比如你若使用 像 Gondolin 这样的沙箱方案 ,你的 bash 相关操作会被妥善沙箱化,但 harness 本身不会。 编排 harness 这就引出了 Codemode 真正在做的事:它让 LLM 能够在 harness 一侧(而非执行环境一侧)表达和编排复杂操作。Codemode 运行在 harness 之中,有它自己的沙箱。就 Pi 而言,它跑在 WASM 运行时里的 QuickJS 上,并带有刻意的限制:没有网络、没有文件系统、没有定时器、内存受限。唯一的出路就是调用更多工具。你也不难想象,Codemode 同样可以运行 Scheme 或别的语言。 如果你不熟悉 Codemode,它基本上就是“在某种语言内部发起工具调用”的一种方式,在我们这里就是 JavaScript。这样你就能组合这些调用,而不必让它们都经过 LLM 的上下文。命名上的功劳归于我们在 Cloudflare 的朋友,是 他们提出了这个词 。 举例来说,如果你在 LLM 里以普通工具调用的方式发起一次 bash 调用,我们只会把末尾 2000 行塞进上下文;如果智能体想要更多,它得自己去翻溢出文件。但如果智能体是通过 Codemode 发起这次调用,那么 Codemode 那一侧会以结构化的方式拿到更大的输出。 最重要的是,由于 Codemode 是 JavaScript,智能体可以表达并发操作和基本工作流。现在你常看到智能体这样用它:先从某个工具的响应里试探 5 到 10 条,看看数据长什么样,然后写一个 Codemode 脚本来处理接下来的 n 条。 Codemode 还允许你往会话记录(transcript)里扔状态!也就是说,一次 Codemode 调用可以把数据存起来,会话中的下一次调用再把它读回来。记住:这发生在 harness 主机上,而不是沙箱里。 对 Pi 来说,Codemode 还允许发起一些在 Pi 传统界面里天然毫无意义的调用。比如你想用图像模型生成图片,或者想用一次性分类模型给一些文本分类——这些 Pi API 通过 Codemode 暴露出来,但不会做成常规工具,因为做成常规工具只会白白浪费上下文。 它长什么样 既然我们已经聊了这么多,也许值得再具体一点。下面让我们一起走一遍我最近在 Pi 会话中使用 Codemode 的几次调用。请注意,这些代码没有一行是人写的,全部来自 Pi 的真实会话,只是为方便阅读重新缩进过。智能体会自动开始使用 Codemode,要么是因为这类任务模型本来就会自然地选中这个工具,要么是因为用户要求它这么做。 请注意,Codemode 在 Pi 中默认只在启用 MCP 时才开启,但你可以通过设置里的 "defaultTools": ["+codemode"] 打开它。直接让 Pi 帮你启用就行。 生成图片 先从最简单的图像生成开始。图像生成是 Pi 在 AI SDK 核心里支持的功能,但它并不是智能体能用的工具。过去想用图像模型,唯一的办法是写一个专门的扩展,或者让智能体自己跑 node 并调用内部图像 API。但既然我们在 Codemode 里暴露了不少内部模型 API,智能体也就能直接用上它了: const [painter] = await models.getAvailableOfType("image"); const result = await models.generateImages(painter, { input: [{ type: "text", text: "A cute little puppy sitting on a grassy " + "lawn, soft natural light, photorealistic" }], }); if (result.stopReason !== "stop") return result.errorMessage; for (const block of result.output) { if (block.type === "image") image(block); else text(block.text); } 注意,调用 image() 会把图片作为图像内容回传给 LLM。在 harness 一侧,它既直接喂给智能体,也会作为临时产物落盘——以防智能体之后想把这张图片再交给 bash 使用。 给东西分类 分类模型(比如 Jev )也是类似情况。它们同样很难通过常规工具融入智能体的工作流。但与其专门做一个定制工具,Codemode 干脆让智能体直接伸手进 AI SDK 里调用它们。下面这段就是用它批量处理 GitHub issue、做一轮快速情感分析的例子: const jev = await models.getModelOfType("classifier", "typesafe", "jev-latest"); const r = await tools.bash({ command: "gh issue list --state open --limit 100 " + "--json number,title,body,comments", }); const issues = JSON.parse(r.output); const results = await Promise.all(issues.map(async (issue) => { const res = await models.classify(jev, { state: { title: issue.title, body: (issue.body || "").slice(0, 4000), comments: issue.comments.slice(-5).map(c => c.body.slice(0, 800)), }, questions: { sentiment: { type: "choice", instructions: "What is the overall sentiment of the author towards pi?", criteria: { positive: "Appreciative, happy, constructive praise", neutral: "Matter-of-fact report or request without emotion", negative: "Frustrated, annoyed, upset, or angry", }, }, frustration: { type: "score", instructions: "How frustrated is the reporter?", criteria: ["not at all", "mildly", "clearly frustrated", "very angry"], }, kind: { type: "choice", instructions: "What kind of issue is this?", criteria: { bug: "Bug report or regression", feature: "Feature request or enhancement", question: "Question or support request", other: "Docs, discussion, meta, spam", }, }, }, }); if (res.stopReason !== "stop") { return { n: issue.number, title: issue.title, error: res.errorMessage }; } return { n: issue.number, title: issue.title, ...res.answers }; })); store("sentiment_results", results); return results .filter(r => !r.error) .sort((a, b) => b.frustration.score - a.frustration.score) .slice(0, 12) .map(r =>
#${r.n} ${r.frustration.score.toFixed(2)} [${r.kind.choice}] ${r.title}); 注意上面这个例子里我们还调用了 store() ,它会把这次执行的结果倾倒进会话记录。之后某次 Codemode 调用如果需要,就能把这个结果读回来。 这里的 Promise.all 没问题,因为 Pi 自己就把并发的工具执行总数限制为四个,其余的都进了队列。 一个更大胆的例子是用 Jev 驱动一个游戏引擎来做调试: 用 Codemode + Jev 调试游戏 (点击了解更多详细信息) 调用 MCP 服务器 最后,Codemode 显然也非常适合调用 MCP 服务器。而且由于我们实际上不会把任何 MCP 工具暴露给 LLM,智能体会先使用给定的 API,在 Codemode 内部发起一次工具搜索,看看连上的服务器都能做些什么。这种渐进式发现(progressive discovery)让整套 MCP 机制在今天的许多场景下已经足够好用。 比如下面这里,你可以看到智能体直接就奔着 Sentry MCP 去了,甚至都没先搜索工具——想必是因为它在 RL 过程中已经知道 Sentry MCP 长什么样。但它至少是从我们注入系统提示的内容里得知“有 Sentry 服务器可用”的,所以算不上完全靠猜。 const orgs = await tools.mcp__sentry__find_organizations({}); const { organizations } = orgs.structuredContent; const results = await Promise.allSettled(organizations.map(org => tools.mcp__sentry__find_projects({ organizationSlug: org.slug, regionUrl: org.regionUrl, }) )); return organizations.map((org, i) => { const r = results[i]; if (r.status !== "fulfilled") return { org: org.slug, error: String(r.reason) }; if (r.value.isError) return { org: org.slug, error: r.value.content }; return { org: org.slug, projects: r.value.structuredContent.projects.map(p => p.slug), }; }); 现代 MCP 是一场苦战 我实在不想在这里谈太多 MCP,但 MCP 实际上是一个从 Codemode 中获益极大的协议。问题部分在于,实践中的 MCP 往往面向那些(还?)没有用上 Codemode 的 harness。不过风向正在转变。在此期间,一个临时的权宜之计就是 Clou- 情报分类:服务器与云资源
- 分类依据:内容涉及服务器、云资源或网络线路
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/10/6 23:28:04
- 暂无回复