@hicaru/pi-rlm
v0.3.16
Published
Save 99% tokens, Recursive Language Model (RLM) for the Pi
Maintainers
Readme
pi-rlm — 为 Pi 编程代理提供的递归语言模型 (Recursive Language Models)
递归语言模型 (RLMs) 作为 Pi 扩展原生实现 —— 完全本地。
递归语言模型 (RLM) 是一种与任务无关的推理范式,其中根语言模型通过对输入进行编程式的检查、分解并递归调用自身,从而在近乎无限的上下文中进行编排。RLM 将典型的 llm.completion(prompt, model) 调用替换为 rlm.completion(prompt, model) 调用:提示词/上下文作为 REPL 环境中的一个变量进行卸载,模型与其进行交互,并且模型可以将子 LLM 和子 RLM 调用作为代码中的普通函数启动。
这是对 CodeAct 风格框架的一种尝试 —— 每个语言模型都能访问代码环境,子 (R)LM 调用是函数,而上下文/提示词是代码中的对象 —— 从而脱离了 JSON 工具调用 (tool-calling) 标准。以此方式构建的系统本身就是一个依赖于递归子 LLM 调用的语言模型,因此得名。
pi-rlm 将该范式原生引入 Pi:
- 根编排器模型逐轮驱动一个持久化的 Python REPL。
- 长上下文工作通过
llm_query/llm_query_batched委派给廉价的工作模型。 - 困难的子问题通过
rlm_query递归到子 RLM 中(设有深度限制)。子 RLM 继承父级的context——已加载的文件以及通过add_context()追加的来源——因此可以在相同的路径上使用相同的检索原语。 继承不消耗额外的 token:内容存放在沙箱中,模型只看到一行大小信息。 - 所有内容均在进程内运行 —— 唯一的外部进程是一个本地的
python3worker。
这是 RLM 方法的 Pi 插件重新实现(参见 RLM 论文)。 它不是那个 Python 库。
工作原理
pi 进程 (TypeScript)
├─ /rlm ──► 引擎逐轮驱动 SMART (根) 模型 (编写 ```repl``` Python)
│ │ 每轮:解析 repl 块 ──► 在沙箱中运行 ──► 将 stdout 反馈回去
│ ▼
├─ bridge ── llm_query / llm_query_batched ──► WORKER 模型 (serverless, 进程内)
│ rlm_query ──► 递归子 RLM (自有沙箱), 设有深度限制
├─ AgentTree ──► 编辑器上方的实时 agent/subagent 树 (角色, 深度, 成本, token)
└─ PythonSandbox ── `python3 worker.py` ──[基于 stdio 的 JSONL, 双向]── 持久化 REPL- 无需服务器,无需 socket,无需 Docker。 唯一的外部进程是一个本地
python3沙箱。 当沙箱代码调用llm_query时,worker 在 stdout 上写入请求并在 stdin 上阻塞; Pi 在进程内提供服务并将回复写回。供应商 API 密钥绝不会进入沙箱。 - 沙箱公开了
context,llm_query,llm_query_batched,rlm_query,rlm_query_batched,SHOW_VARS(),ask_user_question()以及一个answer字典。 模型通过设置answer["ready"] = True来提交最终结果。
安装
pi-rlm 是一个 Pi 包。Pi 提供了 @earendil-works/pi-* 和 typebox peer
依赖;请不要在该包中安装它们的独立副本。要求 PATH 中有 python3 (仅限标准库)。
开发时的推荐本地安装方式:
pi install /path/to/this-repo/pi-plugin/rlm已发布的 npm 包安装方式:
npm publish # 例如 as @<you>/pi-rlm
pi install npm:@<you>/pi-rlmGit 安装要求包清单位于安装的仓库根目录下。 对于像这样一个 monorepo 子目录,请优先使用上述的本地路径或 npm 流程。
如果您之前直接复制了扩展文件夹,请将其删除,以免遮蔽 (shadow) 该包:
rm -rf ~/.pi/agent/extensions/rlm然后运行 /reload 或重启 Pi。使用 pi list 验证该包是否出现在
settings.packages 中,并检查 /rlm, /rlm-config 和 /rlm-stop 是否出现在 [Extensions] 下。
命令
| 命令 | 快捷键 | 描述 |
|---|---|---|
| /rlm | Ctrl+Shift+R | 切换持久化 RLM 模式 (通过 RLM 引擎路由普通提示词) |
| /rlm-stop | | 终止正在运行的任务 |
| /rlm-config | | 选择 smart + worker 模型并调整运行设置 |
在任务激活期间,一个实时树会显示根编排器和每个子 LLM /
递归子节点的状态、模型、成本、token 和持续时间。最终答案将以 markdown 形式发布
到聊天中;任何代码修改将作为 diff 收集并通过弹出窗口进行审核 (除非开启了 yolo)。
沙箱 API
这些函数被注入到 REPL 内部模型的 Python 命名空间中:
| 函数 | 签名 | 描述 |
|---|---|---|
| context | list[dict] | 打包为 [{"path","content","tokens"}, ...] 的仓库 —— 完整的代码库 |
| llm_query | (prompt) -> str | 单次子 LLM 调用(配置的 RLM LLM) |
| llm_query_batched | (prompts) -> list[str] | 并发子 LLM 调用 (池上限) |
| llm_query_chunked | (text, prompt) -> list[str] | 将大文本拆分为不超过上限的块并通过子 LLM 处理 |
| rlm_query | (prompt, paths=None) -> str | 具有自有沙箱的递归子 RLM (设有深度限制)。继承父级的 context;paths 按前缀缩小范围 |
| rlm_query_batched | (prompts, paths=None) -> list[str] | 并发递归子 RLM,共享同一个 paths 切片 |
| ask_user_question | (questions) -> list[dict] | 向用户提出结构化问题 (仅限深度 0) |
| SHOW_VARS | () -> str | 列出当前定义的变量及其类型 |
| answer | dict | 设置 answer["content"]=...; answer["ready"]=True 以结束 |
设置 (/rlm-config)
| 设置 | 默认值 | 含义 |
|---|---|---|
| Smart model | Pi 的当前活动模型 | 根编排器 |
| Worker model | 最便宜的可用模型 | 响应 llm_query |
| Max recursion depth | 4 | 超过此深度的 rlm_query 将回退到 llm_query |
| Max iterations | 30 | 引擎完成前的最大轮数 |
| Budget ceiling | none | 当美元支出超过此值时停止整个树 |
| Max consecutive errors | 5 | 在 N 轮连续错误后停止 |
| REPL block timeout | 120s | 每个 repl 块的墙上时钟时间 (worker 中的 SIGALRM) |
| Max concurrent sub-calls | 4 | *_batched 的池大小 |
| Orchestrator addendum | on | “委派,而非自行解决”的引导 |
| Trajectory compaction | on (0.85) | 当历史记录接近上下文窗口时进行总结 |
| yolo | off | 立即应用建议的修改,跳过审核弹出窗 |
| askUserQuestion | on | 向模型公开 ask_user_question() |
并发注意: 每个
rlm_query子节点都会启动自己的python3worker (冷启动约 50–150 毫秒)。 最坏情况下的并发解释器数量 ≈maxConcurrentSubcalls^(depth−1);在 默认设置下 (深度 4, 并发 4),极端情况下为 4³ = 64。预算和错误 上限 (见上文) 无论扇出 (fan-out) 如何都会限制总支出。
采样与可复现性
r3 基准测试表明:最大的能力杠杆不是模型,而是采样。仅 temperature 0 就让 OOLONG 从
71% (pooled) / 40% (flips) 提升到 91.7–100% all-stable,总计仅花费 $1.74。这些旋钮在
rlm.json (~/.pi/agent/rlm.json) 和 /rlm-config 面板中都是一等公民:
| 字段 | 位置 | 默认值 | 作用范围 |
|---|---|---|---|
| rootSampling.maxTokens | rlm.json, 面板 | 16384 | 根模型每轮的输出上限 (包括 finalize) |
| rootSampling.temperature | rlm.json, 面板 | 提供商默认 | 根轮次与 finalize 的采样温度;0 = 确定性 |
| smartReasoning | rlm.json, 面板 | 无 | 根模型的思考强度 |
| subSampling.maxTokens | rlm.json, 面板 | 8192 | 每个叶子子调用 (llm_query, llm_batch, map_files) 的输出上限 |
| subSampling.temperature | rlm.json, 面板 | 提供商默认 | 叶子调用的采样温度 |
| enableVerificationNudge | rlm.json | 关 | 当根节点过早以“裸数字”定稿时,给予一次带提示的重做 |
可复现性配方 (经 r3 验证):
{
"config": {
"rootSampling": { "maxTokens": 8192, "temperature": 0, "reasoning": "high" }
}
}思考 token 与答案共享补全预算——开启 thinking 时请保持充裕的 maxTokens
(基准测试将其翻倍至 8192;若过紧,引擎会在第 0 轮警告一次)。
作用边界: rlm.json 的采样适用于 RLM 模式运行、rlm() 委托以及任意深度的子递归
(同一个引擎函数)。Pi 原生代理循环遵循 Pi 自身的会话设置——rlm.json 不会触及它。
模型能力: reasoning 要求模型的注册表条目为 reasoning: true。其他情况会在发送给
提供商之前丢弃该级别 (pi-ai 会将不支持的级别钳制为 off);能力来自注册表,而非配置。
运行日志
- 密钥隔离:供应商密钥仅存在于 TypeScript (
AuthStorage) 中;沙箱 接收提示词并返回文本 —— 绝不接触密钥。 - 环境清理:在 worker 启动前会剥离敏感环境变量 (API 密钥, token)。
worker 无法从
os.environ读取供应商凭据。 - 并非安全沙箱:Python worker 公开了
__import__和open。模型编写的 代码可以导入网络模块、读写本地文件,并向 stdout 写入符合协议格式的 JSON。 此层级信任根模型的代码;stdio 协议隔离的是供应商密钥和 进程生命周期,而非对抗性代码的隔离。以后可以在不改变协议的情况下, 通过设置添加更强的沙箱 (Docker, seccomp)。 - 限制内置函数:禁用
eval/exec/compile/input/globals/locals;每块 SIGALRM 超时 + 父进程监视器 (挂起时 SIGKILL);预算 / token / 超时 / 连续错误上限。 - 信任:本地安装需要 Pi 项目信任。
基准测试
针对 OpenRouter 聊天模型对真实引擎进行端到端测试 —— 精简套件(needle 多针召回、
codeqa 代码库问答、coding 修复任务;每个模型 7 个任务 × 2 轮)。确定性评分
(召回率 / 标准答案包含 / 正则),无 LLM 评审。
最新结果 —— 小型模型(≤32B 参数,付费额度):
| 模型 | 参数 | 得分 | 准确率 | 每任务延迟 |
|------|------|------|--------|------------|
| qwen/qwen3-30b-a3b-instruct-2507 | MoE 30B / 3B 激活 | 14/14 | 100% | ~15s |
| google/gemma-3-27b-it | dense 27B | 12/14 | 86% | ~26s |
| mistralai/mistral-small-3.2-24b-instruct | dense 24B | 12/14 | 86% | ~28s |
逐任务原始数据(正确性、召回率、延迟、token、成本)位于 bench/runs/bench-<ts>.jsonl
—— 每个任务一行 JSONL,作为历史记录提交。
运行基准测试
export OPENROUTER_API_KEY=sk-or-... # 必需 —— 密钥仅通过环境变量传递
bun run bench # 精简套件:needle + codeqa + coding
bun run bench --suite needle --limit 1 # 单个套件,仅第一个任务
bun run bench --model openrouter/qwen/qwen3-30b-a3b-instruct-2507
bun run bench --list # 仅列出任务,无需引擎和密钥
bun run bench --suite paper # paper 套件:s_niah, oolong, browsecomp, codeqa_lb(需下载数据集)套件:all(精简版,默认) · needle · codeqa · coding · paper · s_niah ·
oolong · browsecomp · codeqa_lb。
项目布局
src/
sandbox/ py/ (worker.py · guards · retrieval · tasks) · sandbox.ts · interrupts · protocol · sandbox-manager · context-file
bridge/ model.ts (single completion) · subcall-handlers.ts (the one llm/rlm impl) · ask-user · library
core/ engine.ts (the loop) · iteration · limits · resource-limits · answer · compaction · history · types
prompts/ glossary (shared REPL vocabulary) · system (headless) · native · user
text/ parsing (repl blocks) · tokens · preview
tool/ repl-tool · repl-result · repl-render · rlm-tool · rlm-events · rlm-aggregator · subcall-store · background-tasks
config/ defaults · settings (rlm.json persistence + validation)
context/ native walker + anydoc document conversion + add_context
ui/ status · model-picker · config-panel · intro · theme
commands/ rlm · rlm-config
mode/ rlm-mode (controller) · worker-model (cheapest pick) · native-guards
util/ errors · concurrency · trace
test/ phase suites · native-smoke · native-mode · helpers测试
运行时为 Bun (bun install, bun run … —— 绝不要使用 npm/pnpm/yarn)。
bun run test/phase1.ts # 沙箱:执行, 持久化, 密钥隔离, 超时终止
bun run test/phase4.ts # 递归深度限制逻辑 (不消耗 token)
bun run test/phase5.ts # 实时 agent 树渲染 (不消耗 token)
RLM_TEST_LIVE=1 bun run test/phase2.ts # 通过沙箱进行真实的 llm_query
RLM_TEST_LIVE=1 bun run test/phase3.ts # 在文件上下文中进行真实的端到端 /rlm 运行
RLM_TEST_LIVE=1 bun run test/phase4.ts # 引擎解决 20 个文档的“大海捞针”测试背景
基于 RLM 论文 中的方法,为 Pi 原生重新实现。
如果您在研究中使用此项目,请引用原始 RLM 工作:
@misc{zhang2026recursivelanguagemodels,
title={Recursive Language Models},
author={Alex L. Zhang and Tim Kraska and Omar Khattab},
year={2026},
eprint={2512.24601},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2512.24601},
}