大模型的上下文窗口一直在增长——128K、256K、甚至 1M tokens——但现实中的文档处理需求增长更快。一份年度财报、一套完整法规修订稿、一个大型代码仓库的跨模块审计,轻松就能超出任何单次调用的承载能力。Amazon Bedrock AgentCore 结合 Strands Agents SDK 提出了一种新思路:递归语言模型(Recursive Language Model, RLM),让模型在沙箱化的 Python 环境中反复调用自身,用持久化的工作记忆逐步消化任意长度的文档,不再有上限。
上下文窗口的真正瓶颈
传统做法面对超长文档有两条路:
- 截断或摘要:丢掉细节,分析结果天然不完整。
- 分片并行处理:各片独立推理,丢失跨章节的关联与因果链。
两条路都妥协了"全局理解"这个核心需求。你读一份 200 页的诉讼材料,第 180 页的关键证据只有和第 12 页的陈述对照才有意义——分片处理根本无法建立这种联系。
RLM 的思路不同:它不试图把整份文档塞进一次调用,而是让一个"编排者"在持久化的执行环境中反复调度子调用(sub-LLM),每次只处理一个片段,但把中间结论写入工作记忆,后续调用可以读取前序积累。这样既不超出单次上下文窗口,又保留了全局连贯性。
RLM 的运作机制
整个流程可以拆成三层:
- 编排层:一个 Agent(基于 Strands Agents SDK)负责规划——决定先读哪一段、什么时候汇总、什么时候做交叉验证。
- 执行层:Amazon Bedrock AgentCore Code Interpreter 提供沙箱化的 Python 运行环境,Agent 在这里写代码、读文件、维护状态。这个环境是持久化的,不是一次性 sandbox,前一轮写入的变量和文件下一轮还在。
- 推理层:每次 sub-LLM 调用只拿当前片段 + 工作记忆中的摘要进来,在单次上下文窗口内完成局部推理,结果再写回工作记忆。
关键在于"递归"二字:Agent 可以在 Code Interpreter 中写一段 Python,这段 Python 又调用 LLM 做推理,推理结果触发新的代码执行,代码执行又可能调度下一轮 LLM 调用——形成嵌套循环,直到 Agent 判断分析完成。
用 Strands Agents SDK 实现 RLM
下面是一个可改造运行的示例,展示如何用 Strands Agents SDK + Bedrock AgentCore Code Interpreter 构建一个最简 RLM 流程,对超长文档做迭代分析。
前置准备
# 安装 Strands Agents SDK
pip install strands-agents
# 确保 AWS 凭证已配置(Bedrock AgentCore 需要)
aws configure
# 或使用环境变量
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
核心代码
import boto3
from strands.agent import Agent
from strands.tools import code_interpreter
# 1. 创建带 Code Interpreter 工具的 Agent
# Code Interpreter 即 Bedrock AgentCore 提供的沙箱化持久执行环境
agent = Agent(
model_id="us.anthropic.claude-sonnet-4-20250514", # 或其他 Bedrock 模型
tools=[code_interpreter], # 注入 Code Interpreter 工具
)
# 2. 构造 RLM 提示词——让 Agent 自己编排分片读取与递归推理
rlm_prompt = """
你是一个递归语言模型(RLM)分析器。你的任务是分析一份超长文档,但你无法一次性读取全文。
策略:
1. 在 Code Interpreter 中用 Python 读取文档,每次只读一个章节(约 2000-4000 tokens)。
2. 每读完一个章节,将关键发现以 JSON 格式写入工作记忆文件 /workspace/memory.json。
3. 读取下一章节时,先加载已有的工作记忆,结合新内容做增量分析。
4. 当所有章节处理完毕,读取完整工作记忆,生成最终综合报告。
5. 如果发现前后章节存在矛盾或关联,立即在工作记忆中标注交叉引用。
文档路径:/workspace/document.txt
开始分析第一章,然后递归推进。
"""
# 3. 启动 RLM 流程
result = agent(rlm_prompt)
print(result.message)
Code Interpreter 内部的递归逻辑
Agent 在 Code Interpreter 中生成的 Python 大致会遵循这样的模式(以下为 Agent 可能自动生成的代码骨架,可手动预置到 /workspace 以引导方向):
# /workspace/rlm_runner.py — Agent 可在沙箱中执行此脚本
import json, os
MEMORY_PATH = "/workspace/memory.json"
DOC_PATH = "/workspace/document.txt"
def load_memory():
if os.path.exists(MEMORY_PATH):
with open(MEMORY_PATH) as f:
return json.load(f)
return {"chapters_processed": [], "findings": [], "cross_references": []}
def save_memory(memory):
with open(MEMORY_PATH, "w") as f:
json.dump(memory, f, ensure_ascii=False, indent=2)
def read_chapter(doc_lines, chapter_idx, lines_per_chapter=80):
"""按行数分片,每次取一个章节"""
start = chapter_idx * lines_per_chapter
end = start + lines_per_chapter
return "\n".join(doc_lines[start:end])
def main():
with open(DOC_PATH) as f:
doc_lines = f.readlines()
total_chapters = len(doc_lines) // 80 + 1
memory = load_memory()
for idx in range(total_chapters):
if idx in memory["chapters_processed"]:
continue # 跳过已处理章节(持久化记忆的威力)
chapter_text = read_chapter(doc_lines, idx)
# 这里 Agent 会调度一次 sub-LLM 调用,
# 输入 = 当前章节文本 + 工作记忆摘要
# 输出 = 该章节的结构化发现
# 实际调用由 Strands Agent 在运行时自动编排
memory["chapters_processed"].append(idx)
# memory["findings"].append(sub_llm_result) # 由 Agent 填入
save_memory(memory) # 每步持久化,中断后可恢复
# 最终汇总:读取完整记忆,生成综合报告
# 同样由 Agent 调度 sub-LLM 完成
if __name__ == "__main__":
main()
这段代码的核心设计意图:
- 分片读取:按固定行数切分,每次只喂给 sub-LLM 一个片段。
- 持久化工作记忆:
memory.json存在沙箱文件系统中,跨调用轮次保留。即使中途失败,重启后从chapters_processed判断进度,跳过已完成部分。 - 交叉引用空间:
cross_references字段专门存放跨章节关联,这是分片并行处理做不到的。
为什么 Code Interpreter 是关键基础设施
普通 Agent 框架的工具调用是无状态的——每次调用独立,前一轮的局部变量、临时文件全部消失。RLM 需要的是一个跨轮次持久化的执行环境,Bedrock AgentCore Code Interpreter 正好满足:
| 能力 | 普通 Tool Call | AgentCore Code Interpreter |
|---|---|---|
| 状态保持 | 无,每次重新初始化 | 文件系统 + 变量跨轮次保留 |
| 文件读写 | 仅通过 API 传递字符串 | 直接操作沙箱内文件系统 |
| 子进程调度 | 不支持 | 可在 Python 内部编排多步逻辑 |
| 安全边界 | 依赖外部过滤 | 沙箱化,网络与资源受限 |
这意味着 Agent 可以在 Code Interpreter 中写一个完整的分析脚本,脚本内部再按需调用 LLM——递归嵌套自然发生,而不是靠外部循环硬拼。
实践中的几个决策点
分片粒度怎么选? 太大可能撑爆单次 sub-LLM 的上下文窗口;太小则跨章节关联的密度不够,汇总时容易丢失线索。经验值:每个片段控制在模型上下文窗口的 30%-40%(留空间给工作记忆摘要和指令),例如对 128K 窗口的模型,每片 40K-50K tokens。
工作记忆的结构化程度? 纯文本摘要最简单,但后续 sub-LLM 解析成本高。建议用 JSON Schema 约束——每个章节提取固定字段(主题、关键实体、数值结论、与前文的矛盾点),这样后续读取只需少量 tokens 就能理解前序积累。
何时停止递归? 两类终止条件:一是所有章节已处理完毕,进入汇总轮;二是 Agent 在某轮判断"当前记忆已足够回答用户问题",提前终止。后者需要你在提示词中明确判断标准。
成本控制。 递归意味着多轮 sub-LLM 调用,tokens 总消耗可能达到文档长度的 2-3 倍(每片原文 + 记忆摘要重复进入)。对于成本敏感场景,可以在中间轮次使用更小/更便宜的模型做提取,最终汇总轮次用强模型做综合——这就是原文提到的 "sub-LLM" 策略的精髓。
上手清单
- 确认文档格式:纯文本最简单;PDF/Word 需要先在 Code Interpreter 中用 Python 库(
pdfplumber、python-docx)提取文本。 - 估算分片数量:文档 tokens 数 ÷ 目标片段大小,得到递归轮次上限,据此预估成本。
- 设计记忆 Schema:至少包含
chapters_processed(进度追踪)、findings(结构化提取)、cross_references(跨章节关联)三个顶层字段。 - 选择模型组合:提取轮用轻量模型(如 Haiku),汇总轮用强模型(如 Sonnet/Opus)。
- 设置中断恢复:每轮结束前必须
save_memory,确保任意轮次失败后可从断点续跑。 - 验证安全边界:Code Interpreter 沙箱已隔离网络和系统资源,但仍需检查文档中是否含敏感数据,确认符合合规要求后再上传。
上下文窗口的物理限制不会消失,但 RLM 模式把"一次塞完"的思路换成了"逐步消化、持久记忆、递归汇总"——用工程结构弥补模型能力的边界。Bedrock AgentCore Code Interpreter 提供了让这种结构落地的执行环境,Strands Agents SDK 让编排逻辑自然表达。下次面对一份超出窗口的文档,不用再截断或分片丢细节,试试让 Agent 自己递归地读完它。