T
traeai
登录
返回首页
Towards Data Science

递归语言模型:全面深入解析

8.5Score
递归语言模型:全面深入解析

TL;DR · AI 摘要

递归语言模型(RLMs)通过引用传递上下文而非复制,显著提升了长文本处理能力。

核心要点

  • RLMs通过引用传递上下文解决长文本处理问题
  • ReAct等方法需要预先定义函数并逐字生成结果
  • RLMs在长上下文基准测试中表现优于现有方法

结构提纲

按章节快速跳转。

  1. 介绍Recursive Language Models的概念及其重要性。

  2. 通过水果名称统计实验展示RLMs与其他方法的差异。

  3. 比较直接生成、ReAct和RLMs三种方法的优缺点。

  4. 总结RLMs在长上下文任务中的优势及未来应用前景。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • Recursive Language Models
    • 核心机制
      • 引用传递上下文
      • 避免重复计算
    • 方法对比
      • 直接生成
      • ReAct
      • RLMs
    • 应用场景
      • 长文本处理
      • 复杂任务分解

金句 / Highlights

值得收藏与分享的关键句。

  • Recursive Language Models之所以对很多受众来说难以理解,是因为其中的一些想法与现有方法相比相当反直觉。

    第2段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 理解每种方法如何解决这个玩具问题,就是欣赏RLMs之美的全部所需。

    第3段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • LLM仍然必须记住每个单词的计数,并从记忆中逐字复现。

    第4段

    ⬇︎ 下载 PNG𝕏 分享到 X
#语言模型#深度学习#AI
打开原文

标题:递归语言模型:一站式深度解析

URL 来源:https://towardsdatascience.com/recursive-language-models-one-example-deep-dive-that-explains-everything/

发布时间:2026-05-16T13:00:00+00:00

Markdown 内容: 你将了解什么是递归语言模型(RLM),为何它们当前能横扫所有长上下文基准测试,并理解它们与现有智能体架构设计的区别!

我们将通过放大剖析一个简单案例来掌握其精髓。

上个月我投入了大量时间实现 RLM、运行基准测试,并制作了长达 50 分钟的教学视频。在此期间,我回复了 YouTube 和 X 平台上关于 RLM 的 100 多个问题。本文即是我在答疑过程中的心得总结,其中包含那些让我豁然开朗的 RLM 精妙细节。

_注:除非特别说明,本文所有图片均由作者制作,采用自由许可协议。_

图片 1

递归语言模型让许多读者感到难以理解的主要原因在于,相较于现有方法(如 ReAct、CodeAct、基础子智能体等),其部分理念实际上相当反直觉。理解 RLM 的最佳方式是先认清其他方法的局限性,发现智能体架构中缺失的关键要素。

通过引用传递上下文,而非复制上下文。

1. 在所有复杂实验中...

...最具启发性的是这个看似简单的实验:我要求 RLM 执行以下任务:

“生成 50 个水果名称并统计每个名称中字母 R 的数量,以字典形式返回。”

以及一个更复杂的变体(我们称之为问题 2):

“生成包含不同类别(水果、国家、动物)的字典。为每个类别生成 50 个对应名称并统计字母 R 的数量,以嵌套字典形式返回。”

对于问题 1,预期输出类似: {"草莓": 3, "浆果": 2, ... "葡萄": 1} 对于问题 2,则类似:

code
{ 
  "水果": {"草莓": 3, "浆果": 2, ... "葡萄": 1, ...}, 
  "国家": {"美利坚合众国": 1, "俄罗斯": 1, ...},
  "动物": {"袋鼠": 1, "老虎": 1, ... "鹿": 1, ...} 
}

我知道这看似简单,但 RLM 解决该问题的方式与 ReAct 或 CodeAct 等架构有本质区别。

而理解每种方法如何解决这个示例问题,正是领悟 RLM 精妙之处的关键。

让我们开始吧!

2. 智能体技术生态

2.1 直接生成

第一种方法是直接生成。LLM 对用户请求进行“思考”后,以自回归方式生成字典。

无需架构支撑,无需框架辅助,仅通过循环进行直接的下一个词元预测。

这种方法的问题:

  • LLM 无法验证数学准确性
  • LLM 很可能出错,因为字母计数本质上不属于“下一个词预测”问题
  • 即使底层 LLM 很智能,出现幻觉或错误的概率仍然极高

2.2 ReAct(推理与行动)

ReAct 是推理-行动循环框架,LLM 先对问题进行思考(思维链),随后生成工具调用指令。具体来说,我们在系统提示中提供“函数名称”列表及调用说明。

例如,你可以为 LLM 提供这样一个简单工具: def count_alphabets_in_word(单词: 字符串, 字母: 字符串) -> 整型

基于此设计,ReAct 智能体将能执行以下操作:

  • 生成水果名称列表
  • 使用工具传递每个水果名称并获取输出整数值
  • 根据输出记忆重建字典,记录每个水果的计数结果并返回
  • 此类交互的堆栈轨迹如下所示:
code
# 用户
生成包含 50 个水果及其字母'r'数量的字典

# 助手
$思考% 50 个水果名称:草莓、浆果、葡萄... $/思考%

# 助手
count_alphabets_in_word("草莓", "r")

# 工具输出(执行我们的函数) 
3
 
# 助手
count_alphabets_in_word("浆果", "r")  ## 工具调用执行!

# 工具输出(执行我们的函数)  
2
. . . 

# 助手
$思考% 我现在消息历史中已具备所有必要信息,
现在来构建字典 $/思考% 
{ "草莓": 3, "浆果": 2, .... }

现在你发现问题所在了吧?首先,你需要针对这个特定场景预先定义 count_alphabet_in_r 函数。如果未定义函数,智能体就会退回到原始方式(即直接生成字母计数)!

这虽然能确保 LLM 对输出结果有一定认知,但 LLM 仍需要从其消息历史中逐个词元地生成内容。

LLM 仍需记忆每个单词的计数,并逐字复现。在此阶段仍可能发生传输错误。

当扩展到问题 2 的多类别场景时,问题会进一步加剧。 LLM 必须重复执行长序列的函数调用,记住每个环节的结果,并逐个词元生成答案。

对于开发者而言,如果你正在开发垂直应用,希望智能体能调用特定工具(网络搜索、文档检索、计算器、终端访问、文件编辑、差异应用等),ReAct 非常适用。但你很少会开发通用智能体并针对此类特定技能进行优化。

本质上,对通用智能体而言,只有那些通用工具才具有价值。除非明确知晓用户需求,否则你不会编写 count_alphabet_in_r 这类特定工具。

如果 LLM 能自主创建工具会怎样?

2.3 CodeAct

CodeAct 允许 LLM 编写并执行代码。

这意味着你(人类)不再需要编写具体的工具。你可以让大语言模型具备编写任意 Python 代码的能力,并在沙盒终端环境中执行代码、读取结果并生成输出。

具体流程如下:

code
# 用户
生成包含50种水果及其名称中'r'字母数量的字典

# 助手
$think% 好的,让我为此编写一些Python代码 $/think% 

python -c ' 
fruits = [ 'strawberry', 'berry' 'grape', .... ] 
count_r = { k: sum(1 for c in fruit if c == 'r') for k, f in fruits } 
print("Number of fruits: ", len(fruits)) print("Counts: " , count_r) ' 

# 工具输出(终端输出)
Number of fruits: 50 
Counts are: {"strawberry": 3, "berry": 2 ....} 

# 助手
$think% 我已读取终端输出,
现在重新整理输出结果 $/think% 
{ "strawberry": 3, "berry": 2, .... }

CodeAct 的工作原理如下:

  • CodeAct 读取完整的用户消息(与我们之前讨论的其他方法相同)
  • 大语言模型进行思考、编写并运行代码,或执行 bash 命令!
  • 大语言模型将代码输出加载到其上下文窗口中
  • 根据读取的内容生成最终结果

> CodeAct 容易受到我们在 ReAct 中讨论过的传输错误影响。因为大语言模型仍然需要逐字复述记忆中的答案。CodeAct(相对于 ReAct)的优势在于,你(人类)无需为智能体预配置可用工具。智能体可以创建自己的工具(可执行命令)。

>

> 我

ReAct 与 CodeAct 的选择原则:

  • 当处理特定领域产品且明确知道 AI 需要哪些工具来解决问题时,使用 ReAct
  • 当处理通用领域时,使用 CodeAct
  • 请记住,CodeAct 始终比 ReAct 运行更慢,因为大语言模型需要花费时间思考并构建工具(而在 ReAct 中工具是由用户直接提供的)

当扩展到问题2的多类别场景时,这个问题会进一步加剧。 问题2的难点在于 AI 需要跟踪过多的内部状态。它必须记住3个不同类别(水果、国家、动物)的150个不同名称,以及每个单词中'r'的数量。

如果能够对这三个类别进行分治处理会怎样?即让一个智能体处理水果,一个处理国家,另一个处理动物?

2.4 CodeAct + 子智能体

现在我们开始讨论真正强大的架构!

  • 子智能体架构相当简单。存在一个主智能体,它可以启动较小的智能体来执行子任务
  • 每个子智能体同样是 CodeAct 智能体,执行被分配的任务并将输出返回给主智能体
  • 主智能体将这些输出直接加载到上下文中,并执行下一个操作单元

理解上述所有内容对于理解 RLM 架构(稍后会介绍)至关重要

关于子智能体及其优势的更多细节:

  • 通常,子智能体与主智能体不共享任何内部状态/上下文(也存在如“分叉子智能体”这类会共享的设计)
  • 子智能体为完成子任务所采取的所有内部步骤(消息轨迹或工具调用轨迹)对主智能体都是隐藏的
  • 子智能体架构的优势在于主智能体不会受上下文衰减影响,因为它无需关心子智能体的内部运作。完全的黑箱操作

我们已经知道子智能体架构可以轻松解决 num_subagent = 0(原始 CodeAct)时的问题1,现在让我们看看它如何处理问题2:

code
# 用户
生成包含不同类别(水果、国家、动物)的字典。每个类别生成50个名称,统计每个名称中'R'的数量,返回嵌套字典

# 助手
$think% 让我们调用子智能体来分配任务 $/think% 

call_subagent("返回包含50种水果名称及其'r'数量的字典") 

# 子智能体(新的 CodeAct 模块)
{"strawberry": 3, "berry": 2 ....} 

# 助手
call_subagent("返回包含50个国家名称及其'r'数量的字典") 

# 子智能体
{"france": 1, "russia": 1 ....} 

# 助手
call_subagent("返回包含50种动物名称及其'r'数量的字典") 

# 子智能体
{"kangaroo": 1, "deer": 1 ....} 

# 助手
$think% 我已收到所有子智能体的响应,现在编写最终JSON $/think% 
{ 
"fruits": { "strawberry": 3, "berry": 2, .... }, 
"countries": { "france": 1, "russia": 1 .... } 
"animals": { "kangaroo": 1, "deer": 1 .... } 
}

我们取得了很棒的进展。CodeAct + 子智能体可以编写任意代码处理任意任务,但它仍然必须:

  • 将整个用户提示读入上下文窗口
  • 将所有子智能体输出读入上下文窗口
  • 自回归地编写最终输出(在处理过往工具调用和子智能体返回的信息后)

面临的挑战有两个方面:

  1. 大语言模型需要记住所有过往的工具调用结果
  2. 大语言模型需要在输出时以正确格式复述结果

如果允许大语言模型将结果写入中间文件,是不是就不会遗忘了?

2.5 CodeAct + 子智能体 + 文件系统

这是最强大的架构之一!

你为大语言模型提供特殊工具——write_fileread_file

你指导智能体使用这些工具(或直接在 bash 终端中使用 > 操作符)将中间结果写入持久文件系统。这有助于智能体建立进度检查点,以便在需要时加载旧状态!

使用文件系统需要注意以下几点:

  • 需要更多的工具调用/读取操作
  • 更容易记住信息而不脱离实际情况
  • 传输问题仍然存在:大语言模型最终需要读取文件并逐字复述(如果这是严格需求的话)

所有这些解决方案都缺少一个简单的特性:

按引用传递

这是一个古老的编程概念——不在模块(或本例中的智能体)之间来回传递变量副本,而是传递变量的引用。

这正是RLM的实现原理。

3. 递归语言模型

RLM是一种通过特定方式调用LLM以完成任务的脚手架系统。请记住,脚手架是一个外部系统,通过特定方式提示LLM执行操作、管理其上下文,并逐步完成更复杂的任务。

图片2

摘自RLM论文 (https://arxiv.org/abs/2512.24601)

以下四点阐述了RLM的工作原理:

  • 语言模型通过外部可编程环境或_REPL_与任意长度的提示词进行交互。打印输出会在脚手架层被截断
  • LLM可以编写代码以_编程方式探索_并创建提示词的_转换_
  • 可以_递归调用子智能体_完成较小子任务。子智能体的响应不会自动加载到父智能体的上下文中,而是_以符号或变量形式返回到父级REPL_
  • RLM智能体可通过两种方式返回响应:(a) 像普通LLM那样自回归生成答案,(b) 将答案构建到Python_变量_中并返回该变量

让我们逐步解析每个概念。

3.1 REPL环境

REPL是读取-求值-输出循环的缩写。可以将其视为Jupyter notebook:

  • 可以访问名为context的_Python变量_,其中存储着用户查询
  • 可以编写命令查看此上下文。例如当LLM执行print语句时,实时Python内核会输出表达式结果
  • LLM可以迭代读取输出,将新信息加载到其上下文中,进而决定后续操作
  • REPL可在具有可配置文件系统权限的隔离沙箱中运行,因此LLM不会影响用户的实际文件。这主要是出于安全考虑

以下是RLM运行的"启动"示例:

  • 在调用任何LLM之前,我们将启动Python沙箱环境_(可通过在Deno.js中运行pyodice实例实现)_
  • Python运行时初始化时会创建包含用户提示词的特定变量"context"
  • 输入LLM的并非上下文内容,而是告知其可访问REPL环境且其中存在context变量。LLM可通过在REPL中运行print(context)查看提示词

运行轨迹示例如下:

code
# 系统指令
您可访问REPL Python环境
您的任务存储在`context`变量中
可执行print语句
打印显示变量的截断内容(最多200词)
请查明任务内容
在```repl ... 代码块中生成代码
准备就绪后使用 FINAL(answer) 提交结果

# 助手
$think% 让我打印上下文以了解任务内容 $/think%

print(context)

code

# REPL输出(执行助手代码)
"生成包含50种水果名称的字典
并统计每个名称中字母r的出现次数"

用户提示词进入LLM上下文窗口的方式并非通过直接传递!而是LLM主动从环境中读取。

本例中用户提示词简短明了,但请注意用户提示词长度可以是任意的。例如在我的某个测试案例中,输入了包含近1000万token的300期Lex Fridman播客完整文本。

REPL环境中的print语句不会返回完整输出!而是将输出截断至固定长度返回。

即使RLM试图用感官信息过载自身,我们也会通过截断终端输出来明确阻止此行为

LLM也可以主动探索提示词的片段:

code
# 助手

print(context[:200])

code

# REPL输出
** 前200个字符 **

# 助手  

print(context[300:600])

code

# REPL输出
** 第300-600个字符片段 **

3.2 程序化探索

LLM还可以执行正则表达式、查找及其他转换代码来提取信息并存储到变量中。请注意,变量在多次执行调用间持续存在,这正是REPL的特性——它是一个持久的Python运行时(可类比Jupyter Notebook/ipykernel的工作原理)

code
x = re.match(....)
y = context[30:90].split(",")
print(len(y))
  • LLM的提示词包含探索提示空间并思考如何整理数据以完成任务的指令
  • 这类似于数据科学家在处理新获取的房价数据集CSV时,会在Jupyter notebook中打印各种信息以_理解_所处理的内容
  • 在探索过程中,LLM还可以在Python运行时中创建包含重要数据转换的新变量!
  • 请牢记,Python变量在不同的REPL执行调用间持续存在。我反复提及Jupyter Notebook示例是因为必须建立这种关联。LLM每次编写和执行代码块,就相当于我们人类编写代码块并执行单元格!

以下是RLM分析Lex Fridman播客文字记录的示例:

这个新的RLM轨迹让我震惊!我会在YT教程中把这个作为主要示例。

我输入了一个包含320期Lex Fridman播客文字记录的CSV文件,要求它找出前10位机器学习嘉宾对AGI的看法。

上下文包含... pic.twitter.com/P3SOtFJC24

— AVB (@neural_avb) 2026年2月16日

上下文的探索或转换示例可能包括:

  • LLM提取底层CSV结构并将数据放入pandas dataframe以便后续处理
  • LLM从markdown文件中提取特定章节并创建子章节标题->子章节文本的字典
  • LLM使用正则表达式或查找语句在上下文中搜索关键词(基础关键词搜索)
  • 探索阶段的核心是将完整提示提炼为更小、更有用的变量

不过对于我们的问题1来说,任务相对简单,因此LLM的探索任务也比较容易。

code
# 助手

print(context)

code

# REPL输出
生成包含50种水果名称的字典
并统计每个名称中字母r的数量

# 助手
$think%好的,我需要生成50个水果名称$/think%

FRUIT_NAMES = ['grape', 'strawberry', 'berry', ....] print(len(FRUIT_NAMES)) # 检查是否正好50个

code

# REPL输出
52

# 助手
$think% 不小心多加了2个
让我取前50个再检查一次
$/think%

FRUIT_NAMES_50 = FRUIT_NAMES[:50] # 只取前50个 assert len(FRUIT_NAMES_50) == 50 print("成功: ", len(FRUIT_NAMES_50)

code

[[REPL]] 成功: 50

# 助手
$think% 很好,现在计算r的数量 $/think%

fruit_r_count = { fruit: sum([ 'r' in f for f in fruit.lower() ]) } for fruit in FRUIT_NAMES_50 }

FINAL(fruit_r_count)

code

如你所见:

  • LLM可以根据REPL输出自动调整。在第二次尝试时,它已经能够访问原始的52个FRUIT_NAMES变量,因此可以重用该变量并将其切片为FRUIT_NAMES_50变量!
  • 如果assert语句失败,LLM将收到REPL错误并修复代码!
  • LLM完全不需要读取fruit_r_count字典!它可以直接将其传回给用户。
  • FINAL(.)函数直接将REPL中表达式的结果返回给scaffold的输出!

这是我们首次讨论智能体能够在不满足以下条件的情况下将输出返回给用户的路径: (a) 无需将整个字典读入上下文 (b) 无需逐个令牌生成字典 (c) 完全不使用文件系统_(理论上CodeAct本可以将字典写入文件系统并让用户从那里读取)_

因此,RLM的输出不受LLM上下文长度的限制。它们可以返回任意长的输出,只要Python变量能够容纳。

3.3 递归子代理

我们已经讨论了RLM的一些很酷的功能,但还没涉及到递归部分。

在RLM中,递归性与子代理类似,但在子代理之间信息共享的方式上存在根本性差异。

  • RLM在其REPL中可以访问一个特殊函数llm_query
  • llm_query输入单个字符串
  • llm_query调用一个全新的REPL环境(完全重置),并将上下文设置为父LM传递给llm_query的内容
  • 这个子RLM必须解决问题并使用FINAL将结果发送回来
  • 子RLM的输出不会自动加载到父RLM的上下文中。相反,它只是Python REPL中的另一个表达式!

为了理解这一切,让我们看看上面的问题2。

code
# 系统
 .... # 所有内容如前,外加
你被赋予一个新函数llm_query
llm_query调用新的子代理来执行任务
你的子代理非常强大,能够很好地处理长任务!
要调用子代理,只需运行llm_query(子上下文)

# 助手

print(context)

code

# REPL输出
生成不同类别的字典:水果、国家、动物
为每个类别生成50个名称,并统计每个名称中R的数量
以嵌套字典形式返回

# 助手
$think% 很好,我将启动子代理来
获取每个类别的R计数并完成工作 $/think%

然后执行以下代码:

code
FRUIT_DICT = llm_query("生成包含50种水果的字典
                        并统计r出现的次数") 
COUNTRY_DICT = llm_query("生成包含50个国家的字典
                        并统计r出现的次数") 
ANIMAL_DICT = llm_query("生成包含50种动物的字典
                        并统计r出现的次数") 

for dictionary in [FRUIT_DICT, COUNTRY_DICT, ANIMAL_DICT]: 
      assert isinstance(dictionary, dict) and len(dictionary) == 50 

answer = { 
    "fruits": FRUIT_DICT, 
    "countries": COUNTRY_DICT, 
    "animals": ANIMAL_DICT 
} 

FINAL(answer)

在实际的RLM实现中,我们将能够并行化这些调用。多个子代理在正交任务上并行工作不仅非常酷,而且实际上能极快地完成大量工作。

注意刚才发生了什么。

  • LLM 指派了 3 个子代理分别负责管理水果、国家和动物
  • 这些子代理(如前所述)将在各自的本地 REPL 中通过调用 FINAL 返回答案
  • 输出结果会直接存入主代理 REPL 的 FRUIT_DICTANIMAL_DICTCOUNTRY_DICT 字典中
  • 子代理的输出是输入到 REPL 中,而非直接加载到 LLM 的上下文里(这与 CodeAct 或 ReAct 子代理的工作方式不同)。要查看子代理的输出,主代理需要通过 print 语句主动查看。

主代理甚至不需要:

  • 将整个子代理输出加载到上下文中
  • 读取任何水果名称
  • 凭记忆逐词生成最终输出
  • 它通过递归调用构建关键符号,组合出了一个答案,并将最终输出作为组合结果交付。
Image 3

基于 Deno 和 Pyodide 的基础 RLM 架构

3.4 RLM 的输出空间

  • RLM 可以选择两种方式返回其最终输出。
  • 其一,可以将答案组合到 Python 变量中并返回(如上例所示)
  • 或者,它可以像普通 LLM 一样自回归地生成响应

在以下情况中,输出是自回归生成的:

code
# Assistant
print(context) 

# REPL Output
Capital of France? 

# Assistant
FINAL('Capital of France is Paris')

而在上述情况中,输出是在 Python 中计算得出的,并返回了该变量的内容。

code
# Assistant
print(context) 

# REPL Output
Today's date?

# Assistant

from datetime import date today = date.today() FINAL(today)

code

这两种生成答案的模式为 RLM 开启了巨大机遇:

  1. 它们可以通过正则表达式以编程方式探索,并使用常规 Python 执行查找操作
  2. 它们可以创建小变量来保存工作进度(由于处于 REPL 环境中,旧的工作成果不会丢失)
  3. 它们可以递归调用代理进行总结。
  4. 子代理可以是并行或顺序的。LLM 会智能决定这一点。RLM 可能希望顺序调用子代理的一个原因是,如果需要对需要先前信息的长上下文文本进行运行中的总结。
  5. 它们也可以使用外部工具,但你必须通过沙盒层(例如 Deno)暴露这些工具。

要更直观地了解 RLM 的工作原理、如何从零开始实现它们,并查看一些解决实际问题的真实轨迹,请观看本视频教程:

请接受 cookie 以访问此内容

查看我开源的 RLM 实现;它带有一个用于递归追踪的 TUI 日志查看器。

https://github.com/avbiswas/fast-rlm

以下是我用于 RLM 实现的完整系统提示。这将揭示很多信息!

点击此处查看完整系统提示(因其较长而隐藏)。你可以在 RLM 论文(下文有链接)中找到作者推荐的提示。此处的提示改编自论文中的提示,并增加了一些少量示例和指令,以减少开源模型上的失败状态(已在 Minimax-M2.7、GLM-5.1 上测试)。

code
你的任务是根据相关上下文回答查询。你可以在一个 REPL 环境中交互式地访问、转换和分析此上下文,该环境可以递归地查询子 LLM,强烈建议你尽可能多地使用此功能。你将反复被查询,直到提供最终答案。

用户将提供关于你上下文的信息。
此元数据将包括上下文类型、总字符数等。

REPL 环境初始化时包含:

1. 一个 `context` 变量,其中包含关于你查询的极其重要的信息。你应该检查 `context` 变量的内容以了解你正在处理的内容。在回答查询时,请确保充分查阅它。

2. 一个 `llm_query` 函数,允许你在 REPL 环境中查询一个 LLM(可处理约 10 万字符)。此函数是异步的,因此你必须使用 `await llm_query(...)`。返回值是子代理传递给 FINAL 的实际 Python 对象(例如列表、字典、字符串等)。

请勿将结果包装在 eval() 或 json.loads() 中;直接使用它。也就是说,你必须使用 Python 来尽可能减少 LLM 可见的字符数量。

3. 一个全局函数 FINAL,你可以用它来以字符串或任何原生数据类型的 Python 变量形式返回你的答案(使用字典、列表、基本类型等)

** 理解用户询问的详细程度 **
用户是否在询问确切细节?如果是,你应该极其详尽。用户是否要求快速响应?如果是,则优先考虑速度。如果你调用递归子代理,请确保告知他们用户的原始意图,如果这对他们了解相关的话。

你可以通过编写 Python 代码与 Python REPL 交互。

1. 能够使用 `print()` 语句查看你的 REPL 代码的输出并继续你的推理。

2. 当返回结果时,`print()` 语句会截断输出。

这个 Python REPL 环境是你访问上下文的主要方法。读取上下文的分片,并采取行动。

你可以写注释,但这不是必需的,因为用户不会阅读它们。所以跳过写注释或写非常简短的注释。

** 如何控制子代理行为 **
- 当调用 `llm_query` 时,有时作为父代理,你最好读取从数据中挑选的实际上下文。在这种情况下,指示你的子代理通过切分重要部分并使用 FINAL 逐字返回来具体操作。无需自回归地生成总结性答案。
  • 在其他情况下,当您需要让大语言模型调用对信息进行总结或转述时,它们需要通过自回归生成的方式探索上下文来给出答案,因此您可以在任务提示中指导它们这样做。
  • 默认情况下,代理会自行规划并决定如何完成任务!
  • 清晰地说明您期望的返回输出格式(列表?字典?字符串?转述内容?要点形式?原文段落?)有助于您的子代理工作!
  • 如果您收到了关于用户/上级期望数据格式的明确指示,必须遵循这些指示

重要提示 这是一个多轮交互环境。您不需要在首次尝试时就使用 FINAL 返回答案。在返回答案之前,建议先打印出来检查格式是否正确和功能是否正常。这是一个迭代式环境,应尽可能使用 print() 语句,而不是过度自信地急于在一轮中完成回答。

从子代理返回响应时,最好先暂停并检查答案,然后再继续下一步。这对于单个子代理、并行子代理或在 for 循环中运行的子代理序列都适用。

您的 REPL 环境类似于 Jupyter Notebook,因此之前执行的代码和变量都会保留在 Python 运行时中。这意味着您不需要重写旧代码。请务必小心,切勿意外删除重要变量,特别是 context 变量,因为这是不可逆的操作。

您只能看到 REPL 环境中的截断输出,因此应该对需要分析的变量使用查询 LLM 函数。当需要分析上下文的语义时,这个函数尤其有用。要让子代理分析变量,只需通过 llm_query() 传递任务描述和上下文。

您可以使用变量作为缓冲区来构建最终答案。变量可以通过您自己对上下文的操作来构建,或者直接使用 llm_query() 的输出。

在回答查询之前,请确保显式查看 REPL 中的尽可能多的上下文。一个示例策略是:先查看上下文并制定分块策略,然后将上下文智能分块,针对每个分块用特定问题查询 LLM 并将答案保存到缓冲区,最后用所有缓冲区查询 LLM 生成最终答案。

您可以使用 REPL 环境来帮助理解上下文,特别是当上下文很大时。请记住,您的子 LLM 功能强大——它们的上下文窗口可容纳约 50 万字符,因此不要害怕向它们输入大量上下文。例如,一个可行的策略是每次子 LLM 查询输入 10 个文档。分析输入数据,看看是否只需几次子 LLM 调用就能容纳!

调用 llm_query() 时,必须在添加的任何上下文开头给出指令。如果只将上下文传递给子代理而不附带任何指令,它将无法执行任务!

因此,请确保明确说明需要子代理执行的任务,以保证它们正常工作。

提供更多指令来帮助它们,比如说明数据是字典、列表或其他有助于它们更轻松理解任务的发现。清晰度很重要!

当您想在 REPL 环境中执行 Python 代码时,请用三重反引号包裹代码,并加上 repl 语言标识符。例如,假设我们要让递归模型在上下文中搜索魔法数字(假设上下文是字符串),且上下文非常长,因此我们想要分块处理:

* 运行缓慢 *

  • 程序运行缓慢的最大原因是顺序执行子代理
  • 并行运行的子代理通常能快 10 倍完成
  • 您的智能和思考能力的价值体现在如何设计方法以最大化子代理的并行化(使用 asyncio.gather(*tasks))
repl
chunk = context[: 10000]
answer = await llm_query(f"上下文中的魔法数字是什么?这是分块内容:{chunk}")
print(answer)

举例来说,假设您要回答关于一本书的问题。您可以逐段迭代分块上下文,对每个分块查询 LLM,并在缓冲区中跟踪相关信息。

repl
query = "在《哈利波特与魔法石》中,格兰芬多赢得学院杯是因为他们领先吗?"
for i, section in enumerate(context):
    if i == len(context) - 1:
        buffer = await llm_query(f"您正在阅读书籍的最后章节。目前您已知:{buffers}。从这最后章节中收集信息来回答{query}。这是该章节内容:{section}")
        print(f"通过迭代阅读全书,答案是:{buffer}")
    else:
        buffer = await llm_query(f"您正在迭代阅读书籍,当前处于第{i}节/共{len(context)}节。收集信息以帮助回答{query}。这是该章节内容:{section}")
        print(f"阅读第{i}节/共{len(context)}节后,您已跟踪到:{buffer}")

再举一个例子,当上下文非常长时(例如超过 50 万字符),一个简单但可行的策略是基于上下文块长度组合它们,并递归地对分块查询 LLM。例如,如果上下文是 List[str],我们对每个分块提出相同的查询。您还可以使用 asyncio.gather 并行运行这些查询:

repl
import asyncio
repl
query = '一个男人因他的书《了不起的盖茨比》而闻名。他有多少份工作?'
# 假设我们的上下文约100万字符,我们希望每个子LLM查询约10万字符,因此将其分成5个块
chunk_size = len(context) // 10
tasks = []
for i in range(10):
    if i < 9:
        chunk_str = "\\n".join(context[i * chunk_size: (i + 1) * chunk_size])
    else:
        chunk_str = "\\n".join(context[i * chunk_size:])
    
    task = llm_query(f"尝试回答以下查询:{query}。以下是文档:\\n{chunk_str}。仅当你能基于证据确信答案时才回答。")
    tasks.append(task)

answers = await asyncio.gather(*tasks)
for i, answer in enumerate(answers):
    print(f"我从第{i}个块得到了答案:{answer}")

final_answer = await llm_query(f"汇总每个块的所有答案,回答关于工作总数的原始查询:{query}\\n\\n答案:\\n" + "\\n".join(answers))

作为最后一个示例,在分析上下文并发现其通过Markdown标题分隔后,我们可以通过按标题分块上下文,并迭代查询LLM来维护缓冲区状态。请注意这种模式速度较慢,因此仅在绝对必要时使用:

repl
# 发现上下文通过Markdown标题分隔后,我们可以分块、总结并回答
import re
sections = re.split(r'### (.+)', context["content"])
buffers = []
for i in range(1, len(sections), 2):
    header = sections[i]
    info = sections[i + 1]
    summary = await llm_query(f"总结这个{header}部分:{info}")
    buffers.append(f"{header}: {summary}")

final_answer = await llm_query(f"基于这些总结回答原始查询:{query}\\n\\n总结:\\n" + "\\n".join(buffers))

在下一步中,我们可以返回FINAL(final_answer)。 重要提示:当你完成迭代过程后,你必须在完成任务的FINAL函数中提供最终答案,而不是在代码中。除非你已完成任务,否则不要使用这些标签。你有两个选择:

  1. 使用FINAL("你的最终答案")直接提供答案
  2. 你必须在FINAL中返回一个有效的Python字面量,如字符串、整数、浮点数等。不能返回函数或未终止的字符串。
  3. 使用FINAL(variable_name)返回你在REPL环境中创建的变量作为最终输出

当你使用FINAL时,不能使用字符串引号如FINAL("variable_name")。相反,你应该直接将变量名传入FINAL,如FINAL(variable_name)。FINAL("variable_name")将向用户返回字符串"variable_name",而不是该变量的内容,这在100%的情况下会导致错误——因此请小心这一点。

仔细逐步思考,制定计划,并立即在你的响应中执行该计划——不要只说"我将做这个"或"我将做那个"。尽可能多地输出到REPL环境和递归LLM。记住在你的最终答案中明确回答原始查询。

  • 不良做法 *

如果你尝试通过多个工具调用来读取所有上下文,然后尝试通过重新生成上下文和输出来拼凑答案——这是低智能的表现。我们期望你认真思考并生成智能的Python代码来更好地操作数据。

  • 知道何时停止 *

你采取的每一步时间都在流逝。用户在你采取的每一步中等待。我们希望尽可能快。如果你已经尝试过但无法完成任务,要么调用更多子代理,要么返回你不知道。

你不应该运行多个print()语句只是为了构建输出。如果上下文太大,使用带有llm_query的子代理。如果上下文是结构化的,编写Python代码提取更容易操作的结构。如果上下文很小(未被截断),你可以完全读取它。如果需要,你可以递归地缩短上下文。

在生成代码之前你必须思考和计划。你的预期响应应如下所示:

repl
你的工作Python代码
FINAL(...) 

不要输出多个代码块。你的所有代码必须包含在单个``repl ... ``块中。

code

你可以在此处研究完整论文:[https://arxiv.org/abs/2512.24601](https://arxiv.org/abs/2512.24601)

或通过AI:[https://paperbreakdown.com/abs/2512.24601](https://paperbreakdown.com/abs/2512.24601)

## 4. 为什么这种方法效果这么好?

1.   **专注注意力**:RLM不是关注巨大输入中的所有令牌对,而是允许模型定位特定部分加载到上下文中。**RLM通过选择加载上下文,而不是像ReAct或CodeAct那样强制加载。**通过结合来自提示多个不同部分的信息。

2.   **多步推理**:许多任务本质上是递归的(多跳问答、代码库搜索、多文档摘要)。RLM原生匹配多任务结构**RLM可以通过简单地打印上下文的各种切片并将其加载到上下文中来迭代优化其计划。**

3.   **对噪声的鲁棒性**:当99%的输入无关时,递归搜索避免了"注意力稀释"。智能模型将智能加载可能提供最多信息的提示部分。**精选要加载到内存中的上下文是智能的标志!**

4.   **结果是可组合的变量**:子代理的答案不会直接加载到LLM的上下文中;它们作为符号返回到Python REPL中,代理可以选择查看结果或直接使用它们。**它们可以直接从子代理响应中组合结果,而无需完全读取它们**

5.   **任意长的输出**:记住,RLM不需要自回归生成答案;它们可以在Python变量中构建答案——这意味着理论上模型可以返回无限长的输出。**摘要任务在很大程度上仍然是自回归的。**

6.   **成本节约**:由于模型自主决定何时读取信息及何时进行递归处理,通常只需为实际所需资源付费,无需承担全量扫描的开销。RLM 论文展示了相比其他方法,这类实验的运行成本可以大幅降低。提示输入令牌的成本极低!且根据问题类型,完成令牌的成本也保持在较低水平。

7.   **子代理仍可受益于 KV 缓存**:子代理逐步执行任务,其系统提示和历史消息保持不变。90% 的情况下都能命中 KV 缓存,因此运行成本显著降低。**子代理遵循简单的 用户->助手->用户->助手 消息模板,可立即享受 KV 缓存优势!**

8.   **关注点分离**:根语言模型充当“规划者/协调者”,而子代理语言模型则作为“执行者/工作者”处理具体任务。您还可以选择不同模型来承担这些不同角色!**可自由定制各模型职责。实际上,您可以将 RLM 扩展为让特定类型的模型处理特定子问题**

优秀的代码模型天然适合作为 RLM 的驱动核心。目前已有研究者在 RLM 框架上训练模型,预计这一技术将持续优化!

感谢阅读!

**我的 Patreon:**

[https://www.patreon.com/NeuralBreakdownwithAVB](https://www.patreon.com/NeuralBreakdownwithAVB)

**我的 YouTube 频道:**

[https://www.youtube.com/@avb_fj](https://www.youtube.com/@avb_fj)

**在 Twitter 上关注我:**

[https://x.com/neural_avb](https://x.com/neural_avb)

**我正在构建 Paper Breakdown——一个研究论文学习平台**

[https://paperbreakdown.com](https://paperbreakdown.com/)

**阅读我的文章:**

[https://towardsdatascience.com/author/neural-avb/](https://towardsdatascience.com/author/neural-avb/)

AI 可能会生成不准确的信息,请核实重要内容

递归语言模型:全面深入解析 | Towards Data Science | traeai