关于 rlm
rlm 是用于借助 PredictRLM 构建可调用预配置 Agent 的技能,面向需要类型化输入输出、沙盒代码执行和结构化结果的开发者,而非聊天式回复。
主要功能
- 01用 DSPy 签名定义 Agent 契约
- 02在沙盒中迭代执行 Python
- 03调用专注任务的子 LLM 预测
- 04接入技能与宿主工具
- 05校验输出并返回轨迹
适用场景
- 01构建分析大型文件输入的 Agent
- 02结合模型推理与确定性代码
- 03连接宿主侧 API 或数据库
- 04向应用代码返回结构化结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trampoline-ai/ predict-rlm --skill rlm安装后在 Claude Code 中输入/rlm确认已加载。
详细介绍
创建一个 PredictRLM
检查 Skill 更新
在规划或构建之前,检查已安装的 rlm skill 是否有更新:
npx skills update rlm
将 npx 替换为 pnpx、bunx 或当前使用的等效软件包运行器。
更新后,请重新阅读该 skill,再继续操作。
RLM 是一个可调用的预配置 Agent。它会自主探索上下文,在沙盒 REPL 中编写并执行代码、调用工具、检查结果,并不断迭代直至完成任务。与聊天 Agent 不同,RLM 是一个函数——你可以定义它的输入、输出和工具,然后在代码中调用它。它返回结构化数据,而不是聊天消息。
心智模型
调用方提供一个 DSPy 签名。其输入和输出字段构成函数契约,文档字符串则是操作流程。每次调用时:
- PredictRLM 准备类型化输入,并获取一个沙盒执行会话。默认后端是通过 Deno 和 Pyodide/WASM 运行的有状态 Python REPL。
- 外层 LM 读取签名和策略,然后编写 Python 代码来检查输入、保留中间状态、分支、重试和验证。Python 变量会在本次调用的多次迭代之间保留。
- 沙盒代码可以使用
await predict(...)执行专门的子 LM 感知或提取任务。每次predict()调用都有自己的上下文窗口,并可使用类型化 DSPy 签名、Pydantic 架构和dspy.Image。 - 沙盒代码还可以使用
Skill对象提供的软件包和模块,调用宿主端工具来实现 WASM 之外的功能,并访问明确允许的网络域名。 - 工作完成后,外层 LM 会调用
SUBMIT(...)。PredictRLM 会验证声明的输出,将文件输出同步到宿主,并返回包含输出字段和结构化trace的dspy.Prediction。
这种分工是有意设计的:外层 LM 负责规划和执行代码;子 LM 负责范围明确的理解任务;确定性库和工具则负责更适合它们的工作。大量上下文会保留在文件、沙盒变量和专用子调用中,而不是不断累积在一个提示词里。
application call
-> DSPy signature: inputs + strategy + outputs
-> outer LM <-> stateful sandbox REPL
|-> predict() -> sub-LM
|-> skill packages and modules
`-> host-side tools
-> validated dspy.Prediction + trace
核心 API
一个最简的类型化 RLM 包含一个签名、一个已配置的 PredictRLM,以及常规的同步或异步调用:
签名类的文档字符串就是传递给外层 LM 的模型指令提示词。它不只是面向开发者的文档:应在其中写明 RLM 的工作流、决策规则、验证步骤和完成标准。修改文档字符串会改变模型的运行时行为。
import dspy
from pydantic import BaseModel, Field
from predict_rlm import CtxStr, File, PredictRLM
from predict_rlm.skills import pdf
class Analysis(BaseModel):
summary: str = Field(description="Grounded summary of the documents")
risks: list[str] = Field(description="Material risks supported by the documents")
class AnalyzeDocuments(dspy.Signature):
"""Inspect the documents, apply the criteria, and return a grounded analysis.
Survey the files first. Extract only relevant evidence with focused
predict() calls, verify important claims, then submit the typed result.
"""
documents: list[File] = dspy.InputField(desc="Documents to inspect")
criteria: CtxStr = dspy.InputField(desc="Criteria the outer LM must see in full")
analysis: Analysis = dspy.OutputField(desc="Evidence-grounded analysis")
rlm = PredictRLM(
AnalyzeDocuments,
lm="openai/gpt-5.4",
sub_lm="openai/gpt-5.1",
skills=[pdf],
max_iterations=30,
)
result = await rlm.acall(
documents=[File(path="report.pdf")],
criteria="Cover obligations, deadlines, and material risks.",
)
print(result.analysis.summary)
使用 rlm(...) 进行同步调用,使用 await rlm.acall(...) 进行异步调用。两者都会返回一个 dspy.Prediction;每个已声明的输出都可通过 result.analysis 这类属性访问。
主要构造参数包括:
signature— DSPy 签名类或紧凑字符串签名;lm— 编写代码的外层 LM;sub_lm— 内置predict()工具背后的 LM;skills— 可复用的指令、PyPI 软件包、沙盒模块和工具;tools— 在沙盒中公开的同步或异步宿主端可调用对象;allowed_domains— 沙盒网络允许列表,默认为空;max_iterations、max_llm_calls和max_output_chars— 执行预算;output_dir— 用于收集已声明File输出的宿主根目录;verbose和debug— 便于人类阅读的运行输出和生命周期诊断信息。
对于大型文件输入和生成的构件,请使用 File 或 list[File]。对于直接字符串输入(例如评分标准),如果其完整运行时值必须既添加到外层 LM 提示词中,又公开为 Python 变量,请使用 CtxStr。当沙盒需要可复用的指令、软件包、模块或打包工具时,请使用 Skill。对于特定的宿主端操作,例如经过身份验证的 API、数据库、本机库或宿主文件系统访问,请直接使用 tools=。
运行时内核与扩展模型
大多数 RLM 只应使用签名、File、CtxStr、Workspace、技能和宿主工具。只有当工作流需要新的类型化边界、资源生命周期、执行底层或对正确性至关重要的事件流时,才扩展内核。不要仅仅因为某个签名使用了 Pydantic 模型,就采用自定义适配器。
构造时,PredictRLM 会将直接选项和 RuntimeContribution 模块工厂解析为一个不可变的 RuntimeSpec:其中包含指令、适配器、工具、软件包、唯一的一个执行后端、事件接收器、验证器和工具操作。模块工厂在构造时运行一次。之后,每次调用都会创建新的 RunContext,用于存放可变状态、已准备的输入、绑定、输出预留、清理回调和证据状态。
内核生命周期与所有权
construction
direct options + RuntimeContribution modules
-> immutable RuntimeSpec
invocation
prepare typed inputs and output requirements
-> compile path and artifact claims
-> open adapter-owned resources
-> validate requirements and acquire one ExecutionSession
-> bind inputs and reserve outputs
-> apply invocation-local prompt contributions
-> run generated-code attempts
-> after_execution durability hooks
-> materialize submitted outputs while the session is active
-> finalize input adapters in reverse order
-> finalize and release the kernel-owned session
-> run remaining LIFO RunContext cleanup
所有权边界十分严格:
- 适配器须在获取会话前声明所需能力和目标位置。
ExecutionBackend创建调用范围内的ExecutionSession。- 适配器可以使用受支持的后端或会话能力,但不得获取、定稿或释放会话。
- 可变的提供方客户端、租约、基线和重试状态应属于本次调用的
PreparedInput或RunContext,而不是共享的适配器。 - 沙箱目标位置重叠时,生成的代码尚未运行就会报错。
- 无论成功、失败、设置失败还是取消,适配器资源都会定稿。即使适配器出错,框架拥有的会话定稿操作仍会执行。
选择最小的扩展点
使用能够表达需求的最小边界:
- 签名文档字符串或
CtxStr— 外层 LM 必须读取的任务策略或特定调用条件。 Skill— 可复用的外层 LM 指令,以及沙箱软件包、可导入的 Python 模块或捆绑的宿主工具。tools=— 范围明确的同步或异步宿主可调用对象。用于原生库、凭据、API、数据库和宿主文件系统访问。在传输边界返回可规范化的普通类 JSON 数据或 Pydantic 值。File、Workspace或现有适配器 — 普通复制文件、生成文件或可变目录。优先使用这些内置类型,而不是新建类型。- 使用
PreparedInput.path()、.paths()或.glob()的InputAdapter— 自定义类型化输入可先物化为宿主路径。内核负责目标位置规范化、重叠检查、复制或显式挂载、后端要求以及模型可见的沙箱路径。 - 完整的
InputAdapter生命周期 — 外部资源需要提供方租约、实时句柄、逐次尝试同步,或无法用宿主路径表示的自定义会话能力。 OutputAdapter— 自定义输出需要预留目标位置,并在会话仍处于活动状态时进行提供方专属的物化。- 通过
modules=使用RuntimeContribution— 多个宿主端扩展应组合在一起,作为可复用的构造时单元。 - 通过
events=使用EventSink— 消费方需要运行期间有序的生命周期事件。使用prediction.trace获取已完成的执行轨迹,使用prediction.evidence获取单独记录的完整生命周期日志。 - 通过
execution=使用ExecutionBackend— 仅用于全新的沙箱或执行底层。对于维护中的后端,请使用sandbox_backend="jspi"或"sbx"。
interpreter= 是面向旧版 CodeInterpreter 形集成的兼容桥接方式,而不是根扩展契约。新的原生会话集成必须实现 ExecutionBackend 并使用 execution=。
输入适配器
InputAdapter[T] 通过 value_type 选择签名字段,并将调用方的值转换为模型可见的数据和运行时要求。适配器实例是在构造时创建的对象,可能会服务于并发调用,因此应保持不可变或确保并发安全。
使用当前的生命周期名称和职责:
prepare(field, value, ctx) -> PreparedInput在获取后端之前运行。它不会接收后端或会话。返回声明式路径、制品、沙箱根目录预留、元数据、要求和输入说明。open(field, prepared, ctx, backend)可在所有输入准备完毕、会话创建之前,获取适配器自有的客户端或租约。将句柄以适配器自有的键存储在ctx.state中。bind(field, prepared, ctx, session) -> BoundInput在获取后运行。仅在默认制品挂载不够用时使用;返回生成代码可见的最终值和绑定。append_prompt(prompt, field, prepared, ctx) -> str可在绑定后,向外层操作和强制提取提示词添加本次调用专属的上下文。不得修改规范的PredictRLM.signature。after_execution(field, prepared, ctx, session, result, error)在每次生成代码尝试完成后运行。用于持久化或同步工作,包括在该次尝试抛出错误之前所做的更改。finalize(field, prepared, ctx, session, error)执行最终保存并释放适配器自有资源。已打开的适配器按逆序各 finalize 一次;如果获取失败,session为None。
对于常见的路径场景,只需实现 prepare():
class S3FileAdapter(InputAdapter[S3File]):
name = "s3-file"
value_type = S3File
async def prepare(self, field, value, ctx) -> PreparedInput:
local_path = await materialize_s3(value.uri, ctx=ctx)
return PreparedInput.path(
local_path,
instructions=(f"{field.name} is available at its sandbox path.",),
)
可通过 PredictRLM(MySignature, adapters=[S3FileAdapter()]) 直接注册一次性适配器;如果适配器与工具、包、事件或执行后端相关,也可以通过运行时模块提供。
复制是可移植的默认方式。只有在必须实时查看目录时才设置 mode="mount";不支持的后端会报错,而不会静默复制。
.paths() 提供显式列表;.glob() 在主机端执行确定性选择,并检查目录遍历和符号链接逃逸。
对于仅通过 ID、URI 或主机路径即可完整描述的资源,在 PreparedInput.artifacts 中返回一个 Artifact,并使用默认的 bind() 调用 session.mount(artifact)。对于由提供方管理的实时句柄,在 open() 中获取句柄,将其保存在 ctx.state 中,并在 bind() 中使用所选会话实现的能力协议。
不要使用已移除的生命周期名称:输入的 prepare_session() 已改为 open(),输入的 mount() 已改为 bind(),而 MountedInput 已改为 BoundInput。prepare_session() 仍是输出适配器的方法。保留的 ctx_str 适配器名称只能由 CtxStrInputAdapter 子类替换。
输出适配器
OutputAdapter[T] 负责自定义的类型化输出边界:
prepare_session()提供获取前的策略和要求;reserve()在会话获取后、代码执行前预留目标位置;materialize()在会话仍处于活动状态时,将提交的值转换为面向调用方的值。
输出适配器没有输入适配器那样的 finalize() 钩子。在本地清理部分失败,并使用 ctx.add_cleanup() 注册可能在会话结束后仍需执行的提供方清理操作。如果输出占用沙箱路径,应将其放在预留制品的 metadata["sandbox_path"] 中;内核会在执行前拒绝与输入及其他输出重叠的路径。
运行时模块
使用无参数模块工厂来打包相关的主机端扩展:
from predict_rlm import CallableTool, RuntimeContribution
def document_runtime() -> RuntimeContribution:
return RuntimeContribution(
instructions=("Treat document IDs as opaque provider references.",),
adapters=(DocumentReferenceAdapter(),),
tools=(
CallableTool(
name="fetch_metadata",
function=fetch_metadata,
description="Fetch provider metadata for one document ID.",
schema={
"type": "object",
"properties": {"document_id": {"type": "string"}},
"required": ["document_id"],
},
),
),
packages=("pure-python-package",),
events=(DocumentAuditSink(),),
)
rlm = PredictRLM(MySignature, modules=[document_runtime])
PredictRLM(modules=...) 用于组合主机端运行时行为。
Skill.modules 则不同:它会将 Python 文件复制到沙箱中,以便生成的代码导入这些文件。扩展包会去重;适配器、工具或工具操作名称重复时会报错;解析后的配置必须且只能选择一个执行后端。
执行、事件、钩子和门控
ExecutionBackend.start(spec, ctx)会返回一个异步上下文管理器,并在其中生成一个作用域限于单次调用的ExecutionSession。该会话实现代码执行、软件包安装、制品挂载与收集、取消和最终处理。可选的能力协议会添加主机目录挂载或可变目录收集等操作。EventSink实现异步emit、flush和close,并带有strict标志。对于失败时不应导致运行失败的监控,应使用strict=False。只有在持久、完整的证据属于正确性要求时,才使用strict=True;严格事件接收器失败可能会阻止成功发布。- DSPy 回调报告 RLM 的高层迭代进度。
EventSink报告有序的内核生命周期证据。两者用途不同。 prediction.trace和prediction.evidence是相互独立的对象。应分别通过to_exportable_json()导出。创建调用后发生的失败会携带exception.evidence;也可能提供轨迹exception.trace。不要读取trace.evidence,也不要声明名为evidence的输出,因为该名称保留给运行时日志。RuntimeHook(target=..., phases={"before", "after", "error"})会在沙盒中插桩一个点分函数,并将经过净化的RuntimeHookEvent值发送到on_runtime_hook_event。PredictRLM v1 中,运行时钩子需要 SBX 后端;不要围绕它们设计可移植扩展。submit_confirmation是有效SUBMIT后的门控:返回反馈以继续 RLM 循环,或返回None/""以接受提交。
实现内核扩展前,请先阅读 docs/custom-path-inputs.md 了解基于路径的输入,阅读 docs/custom-adapters.md 了解生命周期模式,阅读 docs/api.md 了解准确的方法签名,并阅读 docs/observability.md 了解事件接收器的保证。从 predict_rlm 导入公开契约;不要使用已移除的兼容垫片模块,例如 predict_rlm.adapters、predict_rlm.artifacts、predict_rlm.events、predict_rlm.execution 或 predict_rlm.kernel。
应针对成功、生成代码失败、设置失败、取消和并发调用测试扩展。如果扩展管理可变的外部状态,请验证其按 LIFO 顺序清理,且不同调用之间不会泄漏状态。
此技能的构建内容
此技能会创建或扩展 PredictRLM 周边的可复用软件包:架构、DSPy 签名和策略、能力定义、服务封装器以及冒烟测试。此处不要添加 RLM-GEPA 优化接线;基础 RLM 正常工作且存在优化目标后,应使用单独的 rlm-gepa 技能。
分两个阶段进行:
- 规划 — 与用户一起定义 RLM、研究可行性,并制定具体的实施计划。
- 构建 — 实施已批准的计划,然后对生成的软件包进行冒烟测试。
阶段 1:规划
保持以下顺序。这样可以避免构建出看似合理的 RLM,却存在不可用的边界、缺失的运行时能力或无法证伪的输出。
步骤 1:目标定义
了解用户希望构建什么。
询问:
- 期望的结果是什么?怎样才算成功?
- 输入材料是什么:文档、代码、数据、API 还是有状态系统?
- 输出应该是什么:结构化数据、已修改的文件、电子表格还是其他制品?
然后验证 RLM 是否适用。符合以下一项或多项需求时,RLM 是合适的选择:
- 有选择地探索大型输入,例如文档、数据集或代码库;
- 借助工具完成多步骤工作,例如提取 → 转换 → 验证;
- 执行会修改文件或其他受控状态边界的操作;
- 针对大量条目并行调用子 LM;
- 执行文件到文件的转换,例如将 PDF 转换为电子表格,或将文档转换为报告。
如果单次 LM 调用或确定性脚本更合适,请说明并改为提出该方案。
步骤 2:输入设计
在定义实现文件之前,先定义所有输入。
对于每项输入,确定其名称、类型、来源、描述,以及外层 LM 是否需要立即获取其完整值。
- 对于大型文件,使用
File或list[File]。输入会被复制到沙箱中的/sandbox/input/<field>/;RLM 会按需访问这些文件。 - 对于元数据和配置,使用普通的
str、基本类型或精简的 Pydantic 模型。 - 仅当外层 LM 必须完整看到某个小型到中型字符串(例如评分标准或任务指令)时,才使用
CtxStr。CtxStr仅用于输入,且只能作为基于类的 DSPy 签名中的直接字段注解。 - 如果使用
File引用并进行针对性提取就能控制上下文规模,就不要将原始文档内容或大型表格直接放入提示词。
继续之前,请确认输入边界。
步骤 3:输出设计
在选择软件包或编写提示词之前,先定义结构化输出。
对于每个输出字段,确定其名称、类型、描述,以及它属于基本类型、Pydantic 模型、File 还是 list[File]。
尽量设计具体且可观察的字段。对于含义不明显的模型字段,使用 Pydantic 的 Field(description=...)。只对调用方需要的数据建模;不要暴露内部 ID 或中间推理过程。
询问用户会先检查什么、哪些计算值重要,以及是否需要输出文件。继续之前,请确认架构。
步骤 4:研究可行性
自主开展研究,然后给出清晰的可行性评估。
- 查找领域库和现有项目模式。
- 检查沙箱兼容性。默认执行环境是 WASM 中的 Pyodide:纯 Python wheel 和使用 Pyodide 构建的软件包可以运行;原生二进制文件和普通 C 扩展无法运行。
- 确认网络需求,并列出
allowed_domains的确切域名。 - 确认主机侧需求。将原生库、需要身份验证的 API、数据库访问、大量主机文件系统操作和不受支持的软件包封装在有类型定义的主机侧工具中。
- 检查内置的
pdf、spreadsheet或docx技能是否已能满足任务需求。 - 在设计完整的运行时扩展之前,检查该边界是否已适配
File、Workspace、CtxStr、现有适配器或PreparedInput路径声明。
将无法在 Pyodide 中运行的软件包视为设计决策,而不是实现阶段的意外:使用主机侧工具或更改方案。
步骤 5:能力设计
选择能够支持工作流运行的最小能力范围。
内置技能
说明需要哪些内置技能及其原因:
pdf:用于 PDF 渲染、文本提取、修改和涂黑;spreadsheet:用于 Excel 工作簿、公式和格式设置;docx:用于读取和编写 Word 文档。
自定义技能
仅当某项能力可在不同 RLM 之间复用时,才创建 Skill。技能用于教模型如何熟练使用某项功能,例如检查和修改 PDF、处理电子表格或查询案卷系统。将其附加到需要同一能力的其他 RLM 时,技能说明应保持原样且仍然有用。
将特定工作流的说明放在 DSPy 签名文档字符串中。签名文档字符串是外层 LM 的指令提示词,因此应负责当前 RLM 的目标、针对特定输入的流程、业务规则、输出要求、验证步骤和完成标准。
请按以下方式判断:
- 如果指南说明如何使用可复用能力,就放入
Skill。 - 如果指南说明此 RLM 必须做什么,就放入签名文档字符串。
- 如果文本提及此 RLM 的字段、输出架构、一次性策略或工作流阶段,几乎可以确定它只适用于签名。
不要仅仅为了缩短冗长的签名而创建技能,也不要在两处重复相同的指南。一次性的宿主可调用对象应直接放在 tools= 中;只有当该可调用对象及其操作指南构成其他 RLM 也能复用的能力时,才将其打包到技能中。
对于每项真正可复用的技能,请指定:
name— 简洁的能力标识符;instructions— 独立于当前 RLM 的通用操作指南;packages— 该能力所需且与 Pyodide 兼容的沙盒软件包;modules— 映射为可复用、可导入沙盒模块的宿主路径;tools— 属于该可复用能力的宿主可调用对象。
instructions 应是一份操作手册,而不是简短的软件包说明或函数列表。实用的技能通常包括:
- 何时使用该能力,以及如何选择不同方法;
- 检查、修改或生成产物的有序工作流;
- 针对模型常见操作错误的可运行代码示例;
- 在
SUBMIT前要遵守的正确性不变量和检查项; - 已知故障模式、沙盒限制和恢复指南。
内置技能内容有意设计得较为详实:PDF 技能说明了视觉渲染与文本提取的区别,以及并行调用 predict();DOCX 技能涵盖文档结构、编辑模式、格式陷阱和限制;电子表格技能涵盖公式策略、重新计算、渲染、样式保留和输出验证。
以下是一个可复用的 HTML 能力示例,规模较小,但体现了预期的结构:
from predict_rlm import Skill
html_skill = Skill(
name="html",
instructions="""Use BeautifulSoup to inspect and extract static HTML documents.
## Workflow
1. Read the source without modifying it and record its path and byte size.
2. Parse with `html.parser`. Remove scripts, styles, and `noscript` blocks, but
preserve headings, lists, links, tables, labels, and form controls.
3. Inventory the semantic structure before extracting: title, headings, main
content container, tables, links, and repeated navigation/footer regions.
4. Use deterministic selectors and extraction first. Use `predict()` only for
interpretation of one bounded, cleaned section with a typed result; never
send an entire large page when a section or table is sufficient.
## Parse and inventory
from bs4 import BeautifulSoup
with open(path, "r", encoding="utf-8", errors="replace") as handle:
soup = BeautifulSoup(handle.read(), "html.parser")
for node in soup(["script", "style", "noscript"]):
node.decompose()
root = soup.find("main") or soup.find("article") or soup.body or soup
title = soup.title.get_text(" ", strip=True) if soup.title else None
headings = [
{
"level": node.name,
"text": node.get_text(" ", strip=True),
"id": node.get("id"),
}
for node in root.find_all(["h1", "h2", "h3", "h4", "h5", "h6"])
]
## Extract links and tables
links = [
{"text": a.get_text(" ", strip=True), "href": a.get("href")}
for a in root.find_all("a", href=True)
]
tables = []
for table in root.find_all("table"):
rows = [
[cell.get_text(" ", strip=True) for cell in row.find_all(["th", "td"])]
for row in table.find_all("tr")
]
tables.append(rows)
## Correctness and verification
- Preserve document order, exact link targets, table row order, element IDs,
and the nearest heading as source context. Do not invent text hidden behind
missing JavaScript data.
- Check that the selected root is nonempty, expected headings and tables were
found, and extracted row/link counts match the parsed elements.
- Keep raw source text for quoted evidence. Re-open and verify important quotes
or values against the original element before submitting.
## Failure modes
- Static HTML may omit content rendered by JavaScript. Report that limitation
and use a host-side browser tool when rendered DOM access is required.
- `errors="replace"` can hide encoding damage; flag replacement characters in
relevant text and retry with an identified encoding when necessary.
- `rowspan` and `colspan` require explicit normalization when table geometry
matters. Do not pretend a flattened row list preserves merged-cell layout.
""",
packages=["beautifulsoup4"],
)
此示例具备可复用性,因为它讲解了 HTML 处理方法,但未提及任何特定 RLM 的字段、输出架构、业务规则或完成策略。
宿主端工具
对于每个工具,都要定义类型化签名、实用的文档字符串、返回值结构,以及必须在宿主端运行的原因。返回纯 JSON 类数据或 Pydantic 模型;PredictRLM 会在 Pydantic 值跨越沙盒边界之前,将其(包括嵌套值)规范化为映射和列表。
优先使用范围明确、带类型的工具。工具是系统边界,不是任意访问宿主的逃生通道。
内核扩展
如果工作流需要自定义运行时边界,请在计划中明确说明,而不是等到实现时才发现:
- 面向调用方的签名类型和模型可见值;
- 为什么内置文件、工作区、技能或宿主工具无法满足需求;
- 上文内核决策顺序中最小的扩展点;
- 所有沙盒路径、复制与挂载行为,以及后端要求;
- 适配器拥有的资源、调用局部状态、持久化点,以及成功、失败、取消和部分设置情况下的清理方式;
- 该扩展是否可在 JSPI 和 SBX 之间移植;
- 多个组件需要组合时所用的
RuntimeContribution组合边界;以及 - 有针对性的生命周期和并发测试。
第 6 步:策略与架构
编写应放入 DSPy 签名文档字符串中的策略。这是 RLM 的行动手册:
- 调查并了解可用输入;
- 使用文件技能、
predict()和宿主工具收集证据; - 处理、交叉核对并综合证据;
- 输出声明的数据,并将所有输出文件写入其输出位置。
当工作属于具有共享状态且迭代预算合理的连贯工作流时,选择一个 RLM。只有当各阶段确实具备不同能力、会生成供后续阶段使用的输出产物,或需要不同的模型/迭代配置时,才串联多个 RLM。对于每条链,在实现前定义类型化阶段边界和 DAG。
根据工作负载选择初始 max_iterations、max_llm_calls 和 max_output_chars。记录所需的 allowed_domains、提取是否需要功能足够强大的 sub_lm,以及调用方是否需要 output_dir 来存放生成的文件。
可行性检查清单
确定计划前,请验证:
- 每个沙盒程序包都兼容 Pyodide,或已由宿主工具替代。
- 网络域名明确且尽可能少。
- 宿主端工具覆盖所有不受支持的宿主能力。
- 迭代和 LM 调用预算对该工作流而言合理。
- 大型输入以
File引用或按需获取的元数据形式保留。 - 输出架构足够具体,可以进行验证。
- 任何串联阶段都有必要且经过类型定义的边界。
计划输出
提交一份计划,包含:
- 概述和 RLM 适用性判断。
- 交付范围和文件清单。
- 完整的输入和输出架构。
- 完整的签名策略。
- 技能和宿主工具契约。
- 服务架构;如有串联阶段,还需包含类型化 DAG。
- 可行性约束、替代方案和初始预算。
- 冒烟测试用例和精确的验证命令。
第 2 阶段:构建
使用最精简且易于维护的程序包布局,实现已批准的设计。不要凭空搭建基准测试或 RLM-GEPA 优化。在用户提供示例并有评分需求后,才添加评估层;优化接入请使用 rlm-gepa。
默认程序包布局
my_rlm/
├── pyproject.toml
├── src/
│ └── my_rlm/
│ ├── __init__.py
│ ├── schema.py # Pydantic inputs and outputs
│ ├── signature.py # DSPy signature and strategy
│ ├── service.py # PredictRLM wiring
│ ├── skills.py # Only when custom skills are needed
│ └── tools.py # Only when host-side tools are needed
创建 schema.py、signature.py、service.py 和程序包导出文件。仅在所选边界有需要时才添加文件。
依赖项
在目标仓库中安装 predict-rlm,但不要硬编码版本:
uv add predict-rlm
仅在使用内置文档或电子表格技能时添加 examples 附加项,并且只在运行位置添加任何领域依赖项。仅包含 Agent 的项目不要添加 GEPA 或可视化附加项。
架构
from pydantic import BaseModel, Field
class KeyDate(BaseModel):
name: str = Field(description="What deadline or effective date this is")
date: str = Field(description="ISO-8601 calendar date")
class DocumentAnalysis(BaseModel):
summary: str = Field(description="Grounded Markdown summary")
key_dates: list[KeyDate] = Field(
default_factory=list,
description="Dates supported by the input documents",
)
签名与策略
将签名文档字符串视为 RLM 的可执行策略,而非一句话摘要。完善的指令应定义来源权威性、信息发现、受限的工具使用、中间状态、证据规则、验证、从信息缺失或冲突中恢复的方式、输出契约以及停止条件。
import dspy
from predict_rlm import CtxStr, File
from .schema import DocumentAnalysis
class AnalyzeDocuments(dspy.Signature):
"""Analyze a related document set into a grounded, structured report.
Read `criteria` first and treat the supplied documents as the sole authority
for document facts.
## Method
1. Inventory every file before deep reading. Record its exact name, format,
page or sheet count, likely role, and which criteria it may answer.
2. Inspect each file with deterministic, format-appropriate tools. Search
and extract structure to narrow the work; render pages or use focused
`predict()` calls only on bounded sections that need interpretation.
Never send the whole document corpus through one giant sub-LM call.
3. Keep a compact evidence map in Python state with the source file, page,
sheet, section, or other locator for each relevant fact. Retain evidence,
not repeated full-document text.
4. Reconcile overlapping sources. Prefer explicit and more current evidence,
deduplicate repeated facts, and preserve meaningful disagreements.
## Evidence rules
- Every conclusion and key date must trace to a source locator.
- Never turn missing, ambiguous, or contradictory evidence into a confident
claim. Mark it unresolved and explain what is missing.
- Use `criteria` to control emphasis and format, but never let it justify an
unsupported fact or silently exclude a supplied file.
## Verification and stopping
Before submitting, confirm that every file was inspected, every criterion is
answered or explicitly unresolved, important facts were cross-checked, and
the result satisfies the declared schema. Stop only after those checks pass.
## Output contract
Return one `DocumentAnalysis`. Its summary must be a standalone grounded
report that follows `criteria`; `key_dates` must contain only supported dates
with their meaning and normalized value. Use an empty list when no key date
is supported.
"""
documents: list[File] = dspy.InputField(desc="Documents to inspect")
criteria: CtxStr = dspy.InputField(desc="Rubric to apply in full")
analysis: DocumentAnalysis = dspy.OutputField(desc="Grounded result")
调用方为 criteria 传入普通字符串;CtxStr 会将其同时设为沙盒变量和完整提示词附录。如果 RLM 可以从沙盒中检查该值,则改用普通的 str。
服务
import dspy
from predict_rlm import File, PredictRLM
from predict_rlm.skills import pdf
from .signature import AnalyzeDocuments
class DocumentAnalyzer(dspy.Module):
def __init__(
self,
*,
lm: dspy.LM | str | None = None,
sub_lm: dspy.LM | str | None = None,
max_iterations: int = 30,
verbose: bool = False,
debug: bool = False,
) -> None:
self.predictor = PredictRLM(
AnalyzeDocuments,
lm=lm,
sub_lm=sub_lm,
max_iterations=max_iterations,
verbose=verbose,
debug=debug,
skills=[pdf],
)
async def aforward(self, documents: list[File], criteria: str):
return await self.predictor.acall(documents=documents, criteria=criteria)
如果可调用对象的名称合适,请使用 tools=[callable];若要公开指定的工具名称,请使用 tools={"name": callable}。使用 skills=[...] 添加可复用的沙盒能力。如果 File 或 list[File] 类型的输出必须收集到调用方指定的根目录下,请向 PredictRLM 传入 output_dir。
运行时参考
核心构造函数
PredictRLM(
signature,
lm=None,
sub_lm=None,
max_iterations=30,
max_llm_calls=50,
max_output_chars=50_000,
verbose=True,
tools=None,
skills=None,
adapters=(),
execution=None,
modules=(),
events=(),
allowed_domains=None,
output_dir=None,
interpreter=None,
runtime_hooks=None,
submit_confirmation=None,
debug=False,
)
lm 用于驱动外层代码生成;sub_lm 为沙盒中的 predict() 调用提供服务。两者都接受 DSPy LM 或模型字符串。predict() 始终可用。
内核扩展契约已在上文说明。对于新的、原生支持会话的后端,请使用 execution=;对于 CodeInterpreter 形式的兼容性集成,请使用 interpreter=。请查阅 docs/api.md 获取准确签名,不要猜测生命周期参数。
文件、技能和工具
-
File(path="report.pdf")表示一个输入文件;File.from_dir(path)会创建一个按顺序排列的递归文件引用列表。 -
输入文件会复制到
/sandbox/input/<field>/。文件输出会写入/sandbox/output/<field>/,并在执行后同步回来。 -
Skill(name, instructions, packages, modules, tools)将可复用的能力打包在一起。技能工具名称冲突会报错。 -
pdf、spreadsheet和docx是内置技能:from predict_rlm.skills import docx, pdf, spreadsheet -
主机工具在 WASM 环境之外运行。请确保其文档字符串、参数和返回数据足够清晰,以便生成的代码能够安全调用。
沙盒限制
- 在沙盒技能中使用纯 Python 或兼容 Pyodide 的软件包。
- 不要依赖子进程、本机二进制文件或常规 C 扩展。
- 默认禁止网络访问;若沙盒必须访问网络服务,请设置最精简的
allowed_domains列表。 - 对于本机代码、凭据、数据库和主机文件,请使用主机端工具。