@TDataScience:跟随@neural_avb的全方位深度解析,了解“递归语言模型(RLM)是什么、为何它们会在长上下文基准测试中持续胜出……”

X AI KOLs Following 新闻

摘要

一篇关于递归语言模型(RLM)的教育性深度文章,解释了RLM是什么、为何它们能在长上下文基准测试中胜出,以及它们与现有智能体框架(如ReAct或CodeAct)的不同之处,并通过一个简单的案例研究进行说明。

跟随@neural_avb的全方位深度解析,了解“递归语言模型(RLM)是什么、为何它们目前在所有长上下文基准测试中都能胜出,以及它们与现有智能体框架设计有何不同”。https://t.co/PfcAX3bimE
查看原文
查看缓存全文

缓存时间: 2026/05/24 02:15

跟着@neural_avb的全面深度剖析,一起来学习“什么是递归语言模型(RLMs),为什么它们现在在长上下文基准测试中全面领先,以及它们与现有代理化方案设计的区别。“ https://t.co/PfcAX3bimE


递归语言模型:一篇全面深度剖析 | 数据科学之路

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

你将了解什么是递归语言模型(RLMs),为什么它们现在在长上下文基准测试中全面获胜,以及它们与现有代理化方案设计的区别。而我们将通过放大一个简单的案例研究来学习这一切。

过去一个月,我花了不少时间实现RLMs、运行基准测试,并为此制作了一个50分钟的教程视频。在此过程中,我在YouTube和X上回答了100多个关于RLMs的问题。本文是我在回答这些问题时所学到的总结,以及RLMs中那些让我“啊哈!”的特定细微差别。

注:除非特别说明,本文中所有图片均由作者制作,免费授权。

递归语言模型对许多读者来说难以理解的主要原因在于,其中一些想法与现有方法(如ReAct、CodeAct、普通子代理等)相比,实际上是相当反直觉的。理解RLMs的最佳方法是先理解其他方法失败之处,并意识到代理化方案中缺失的那一块。

关键思想是:通过引用传递上下文,而不是复制它。

1. 在我运行过的所有复杂实验中……

……最有启发性的是这个有点傻的实验:我让一个RLM执行: “生成50个水果名称,并计算每个单词中字母R的个数,以字典形式返回。” 以及一个更高级的变体(我们称之为问题2): “生成一个包含不同类别(水果、国家、动物)的字典。对于每个类别,生成50个名称并计算其中的R个数,以嵌套字典形式返回。”

对于问题1,预期输出类似于:
{"strawberry": 3, "berry": 2, ... "grape": 1}

对于问题2,预期输出类似于:
{"fruits": {"strawberry": 3, "berry": 2, ... "grape": 1, ...}, "countries": {"united states of america": 1, "russia": 1, ...}, "animals": {"kangaroo": 1, "tiger": 1", ... "deer": 1, ...}}

我知道这是个傻问题,但RLM解决它的方式与其他架构(如ReAct或CodeAct)有着根本的不同。

理解每种方法如何解决这个玩具问题,就是你欣赏RLM之美所需的一切。

我们开始吧!

2. 代理化方案概览

2.1 直接生成

第一种方法就是直接生成。LLM“思考”用户的请求,然后自回归地生成一个字典。没有方案,没有脚手架,只是在循环中直接进行下一个标记预测。

这种方法的问题:

  • LLM无法验证结果在数学上是否正确。
  • LLM很可能会出错,因为从根本上说,字母计数不是一个“下一个词预测”问题。
  • 即使底层LLM很聪明,产生幻觉或错误的概率也极高。

2.2 ReAct(推理与行动)

ReAct是一种推理与行动循环:LLM先思考问题(思维链),然后生成工具调用。基本上,在系统提示中,我们传递一个“函数名”列表以及如何调用它们的说明。例如,你可以给LLM一个简单的工具,比如: def count_alphabet_in_word(word: string, alphabet: string) -> int

利用上述思路,ReAct智能体将能够执行以下操作:

  • 生成一个水果名称列表。
  • 使用工具将每个水果名称传入,并接收输出的整数。
  • 从其输出记忆中重建每个水果对应计数的字典并返回。
  • 这样一次交互的堆栈跟踪如下:

``# 用户 生成一个包含50种水果及其中’r’个数的字典

助手

50个水果名称是:strawberry, berry, grape, …

助手

count_alphabet_in_word(“strawberry”, “r”)

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

3

助手

count_alphabet_in_word(“berry”, “r”)

工具调用已执行!

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

2 . . .

助手

现在我的消息历史中有了所有需要的信息,让我构造那个字典 {“strawberry”: 3, “berry”: 2, ….}``

你看到问题了吗?首先,你需要预先为这个特定用例定义一个函数count_alphabet_in_word。如果你没有定义函数,智能体就会退回到旧方法(即直接生成字母计数)!这保证了LLM对输出有一些提示,但LLM仍然需要逐词从其消息历史中生成标记。

LLM仍然需要记住每个单词的计数,并逐字从记忆中复现。在此阶段仍可能发生传输错误。 当扩展到问题2的多类别设置时,这个问题会复合。 LLM必须重复一长串函数调用,并记住每次调用的结果,然后逐标记生成答案。

作为一名开发者,ReAct是很好的,如果你正在开发窄域应用,希望智能体能够访问特定工具(网页搜索、文档搜索、计算器、终端访问、文件编辑、差异应用等),但你很少会开发一个通用智能体并针对这类小众技能进行优化。基本上,对于通用智能体,只有那些通用工具才是有用的。你不会写count_alphabet_in_word这样的工具,除非你明确知道用户会需要它。 如果LLM能够自己创建工具呢?

2.3 CodeAct

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

这意味着你(人类)不再需要编写精确的工具了。你只需赋予LLM编写任意Python代码并在沙盒终端环境中执行的能力,然后读取结果并生成输出。

过程大致如下: ``# 用户 生成一个包含50种水果及其中’r’个数的字典

助手

好的,让我们为此编写一些Python代码。 python -c ’ fruits = [ “strawberry”, “berry”, “grape”, …. ] count_r = {k: sum(1 for c in fruit if c == “r”) for k, f in fruits} print(“水果数量:”, len(fruits)) print(“计数:”, count_r) ’

工具输出(终端输出)

水果数量:50 计数:{“strawberry”: 3, “berry”: 2 ….}

助手

好的,我已读取终端输出,让我重新写下来以返回输出 {“strawberry”: 3, “berry”: 2, ….}``

CodeAct的工作方式如下:

  • CodeAct读取完整的用户消息(就像我们之前讨论的其他方法一样)。
  • LLM思考、编写并运行代码,或执行bash命令!
  • LLM将代码的输出加载到其上下文窗口中。
  • 根据读取的内容生成结果。

CodeAct同样容易受到我们在ReAct中讨论过的传输错误的影响。因为LLM仍然需要从其记忆中逐字复现答案。CodeAct相对于ReAct的优势在于,你(人类)不需要预先配置智能体可用的工具。智能体创建自己的工具(可执行命令)。

我对ReAct与CodeAct的经验法则:

  • 当你处理窄域产品且确切知道AI需要哪些工具来解决问题时,使用ReAct。
  • 当领域是通用的时,使用CodeAct。
  • 请记住,CodeAct的运行速度总是比ReAct慢,因为LLM需要花时间思考和制作自己的工具(而在ReAct中,工具是由用户传递给它的)。

再次强调,当扩展到问题2的多类别设置时,这个问题会复合。 问题2的难点在于AI需要跟踪太多的内部状态。它必须记住3个不同类别(水果、国家、动物)中的150个不同名称,以及每个单词中’r’的个数。 如果你能分而治之这三个类别呢?也就是说,让一个智能体处理水果,一个处理国家,一个处理动物?

2.4 CodeAct + 子代理

现在我们开始讨论一些强大的东西!

  • 子代理架构相当简单。有一个主智能体,它可以启动较小的智能体来执行子任务。
  • 每个子代理也是一个CodeAct智能体,执行分配给它的任何任务并将输出返回给主智能体。
  • 主智能体将这些输出直接加载到上下文中,并执行下一步行动。

理解我上面所说的所有内容对于理解RLM架构(稍后会提到)至关重要。

关于子代理及其有用性的更多细节:

  • 通常,子代理不与主智能体共享任何内部状态/上下文(但也有像“分叉子代理”这样的子代理设计能够共享)。
  • 子代理在完成子任务时采取的任何内部步骤(消息跟踪或工具调用跟踪)对主智能体都是隐藏的。
  • 子代理架构的好处在于主智能体不会遭受上下文腐烂,因为它不需要担心子代理的内部工作。完全的黑箱。

我们已经知道子代理架构可以轻松解决问题1(使用num_subagent = 0,即普通的CodeAct),所以让我们看看它在问题2上如何工作。

``# 用户 生成一个包含不同类别(水果、国家、动物)的字典。对于每个类别,生成50个名称并计算其中的R个数,以嵌套字典形式返回。

助手

让我们调用一些子代理并分配任务 call_subagent(“返回一个包含50种水果名称及其中r个数的字典”)

子代理(一个新的代码-动作模块)

{“strawberry”: 3, “berry”: 2 ….}

助手

call_subagent(“返回一个包含50个国家名称及其中r个数的字典”)

子代理

{“france”: 1, “russia”: 1 ….}

助手

call_subagent(“返回一个包含50种动物名称及其中r个数的字典”)

子代理

{“kangaroo”: 1, “deer”: 1 ….}

助手

我已收到所有子代理的响应,现在我将编写最终的JSON { “fruits”: {“strawberry”: 3, “berry”: 2, ….}, “countries”: {“france”: 1, “russia”: 1 ….}, “animals”: {“kangaroo”: 1, “deer”: 1 ….} }``

我们取得了不错的进展。CodeAct + 子代理可以编写任意代码来完成任意任务,但它仍然必须:

  • 将整个用户提示读取到其上下文窗口中。
  • 将整个子代理输出读取到其上下文窗口中。
  • 自回归地编写最终输出(在处理先前工具调用和子代理返回的信息之后)。

这里的难点有两个:

  1. LLM需要记住所有过去的工具调用结果。
  2. LLM需要在输出时将结果以正确的格式原样复述出来。

如果我们允许LLM将结果写入中间文件,这样它就不会遗忘了呢?

2.5 CodeAct + 子代理 + 文件系统

这是最强大的架构之一!你赋予LLM访问特殊工具——write_fileread_file的能力。你指示智能体使用这些工具(或直接在bash终端中使用>操作符)将中间结果写入持久化的文件系统。这有助于智能体检查进度,以便在需要时随时加载旧状态!

拥有文件系统访问权限有几个注意事项:

  • 更多的工具调用/读取操作。
  • 更容易记住事物,不会与现实脱节。
  • 传输问题仍然存在:LLM最终需要读取文件并逐字复现(假设这是一个严格的要求)。

所有这些解决方案缺少的是一个简单的特性:通过引用传递。 这是一个古老的编程概念:不是在模块之间(或者在当前场景下,智能体之间)来回传递变量的副本,而是传递对变量的引用。这就是RLM所做的。

3. 递归语言模型

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

根据RLM论文(https://arxiv.org/abs/2512.24601),以下是解释RLM工作原理的4个要点:

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

让我们逐一分解每个概念。

3.1 REPL

REPL是读取-求值-输出循环。 可以把它想象成一个Jupyter笔记本。

  • 你可以访问一个名为context的Python变量,其中保存了用户的查询。
  • 你可以编写命令来查看这个上下文。例如,每当LLM发出一个print语句时,实时的Python内核就会打印出该表达式。
  • LLM可以迭代地读取输出以将新信息加载到其上下文中,然后决定下一步行动。
  • REPL还可以在隔离的沙盒中运行,并具有可配置的文件系统权限,这样LLM就无法影响用户的实际文件。这主要是出于安全考虑。

以下是RLM运行“开始”时的示例:

  • 在任何LLM被调用之前,我们将启动一个Python沙盒环境。你可以通过在Deno.js中运行一个pyodide实例来实现。
  • Python运行时初始化一个名为“context”的特殊变量,其中包含用户的提示。
  • 我们传递给LLM的不是上下文的内容,而仅仅是一个事实:它可以访问一个REPL,并且其中有一个名为context的变量。LLM可以在REPL中运行print(context)来查看提示。

以下是一个示例轨迹:

# 系统 你可以访问一个REPL Python环境。你的任务存储在一个名为`context`的变量中。你可以发出print语句。Print会显示变量的截断部分(最多200个词)。找出任务内容。在你的代码块中使用repl … 格式。 准备好回答时,使用 FINAL(answer) 提交结果。

助手

让我打印出上下文以了解我的任务 repl print(context)

REPL输出(执行助手代码)

“生成一个包含50种水果名称及其中r个数的字典”``

用户提示进入LLM上下文窗口的方式并不是由我们直接传递的!而是LLM主动决定从环境中读取它。在我们的例子中,用户的提示简单而短小。但请记住,用户提示可以是任意长的。例如,在我测试的一个案例中,我输入了300期Lex Fridman播客的完整转录文本,作为一个包含近1000万标记的字符串。REPL环境中的print语句并不会返回完整的输出转储!相反,它将输出截断为固定长度并返回。

即使RLM试图用感官信息过载自己,我们通过截断终端输出明确阻止了RLM这样做。LLM还可以有意识地探索提示的切片:

# 助手 repl print(context[:200]) ``

REPL输出

** 前200个字符 **

助手

repl print(context[300:600])

REPL输出

** 第300到第600个切片 ** ``

3.2 程序化探索

相似文章

@neural_avb: https://x.com/neural_avb/status/2063907440509571354

X AI KOLs Timeline

探索递归语言模型(RLM)中一个常见的失败模式,其中自由文本子代理响应会导致问题,并提出一种使用结构化输出提高可靠性的解决方案,通过NarrativeQA中的长上下文问答示例进行说明。

强化递归语言模型(18分钟阅读)

TLDR AI

本文探讨了利用强化学习微调小型(4B)递归语言模型(RLM)从科学文档中选取证据,结果表明经过强化学习训练的4B模型在模型大小和成本仅为其一小部分的情况下,达到了与Claude Sonnet 4.6相当的性能。