反射:言语强化学习的语言代理

原论文:Reflexion: Language Agents with Verbal Reinforcement Learning

摘要

大型语言模型( Large Language Models,LLMs )作为目标驱动的智能体被越来越多地用于与外部环境(例如,游戏、编译器、API等)进行交互。然而,由于传统的强化学习方法需要大量的训练样本和昂贵的模型微调,这些语言智能体快速有效地从试错中学习仍然具有挑战性。我们提出了一种新的框架Reflex,它不是通过更新权重来加强语言代理,而是通过语言反馈来加强语言代理。具体来说,反应主体会对任务反馈信号进行言语反思,然后在情景记忆缓冲器中保持自己的反射性文本,以便在随后的试次中诱发更好的决策。反射足够灵活,可以包括各种类型(标量值或自由形式的语言)

1. 引言

最近的工作如ReAct[ 30 ]、SayCan[ 1 ]、Toolformer[ 22 ]、HuggingGPT[ 23 ]、生成式Agent[ 19 ]、WebGPT[ 17 ]等已经证明了构建在大型语言模型(large language model,LLM)内核之上的自主决策Agent的可行性。这些方法使用LLM生成可用于API调用并在环境中执行的文本和"动作"。由于它们依赖于具有大量参数的大规模模型,因此这些方法迄今为止仅限于使用语境样本作为教授代理的方式,因为更多的传统优化方案,如梯度下降的强化学习需要大量的计算和时间。

在这篇文章中,我们提出了一种替代方法,称为"反射" (Reflex),它使用语言强化来帮助代理人从先前的失败中学习。回应将来自环境的二元或标量反馈以文本摘要的形式转换为言语反馈,并在下一集作为LLM施动者的附加语境添加。这种自我反思反馈作为一种"语义"梯度信号,通过为智能体提供一个具体的改进方向,帮助其从先前的错误中学习,从而更好地完成任务。这就类似于人类如何迭代地学习以少数次的方式完成复杂的任务--通过反思他们之前的失败,以便为下一次尝试形成改进的攻击计划。例如,在图1中,反射代理通过尝试、错误和自我反思来学习优化自己的行为,以解决决策、编程和推理任务。

生成有用的反思性反馈是具有挑战性的,因为它需要很好地理解模型在哪里犯了错误(即信用分配问题),以及生成包含可操作的改进见解的摘要的能力。我们探索了三种方法--简单的二进制环境反馈,针对常见失败案例的预定义启发式,以及自我评估,如使用LLMs (决策)或自我编写的单元测试(编程)进行二分类。在所有的实现中,评价信号被放大为自然语言经验总结,这些经验总结可以存储在长时记忆中。

与更传统的RL方法(如策略或基于值的学习)相比,Reflex有几个优点:1)它是轻量级的,不需要对LLM进行微调;2)它允许更细微的反馈形式(例如,有针对性地改变行动),而不是那些难以执行准确的信用分配的标量或向量奖励;3)它允许在过去的经验中使用更明确和可解释的情景记忆形式;4)它为未来的情节提供更明确的行动暗示。同时,它也存在依赖LLM的自我评价能力(或启发式)的力量和不具备成功的正式保证的缺点。然而,随着LLM能力的提高,我们只预期这种范式会随着时间的推移而变得更好。

我们在(1)决策任务上测试长轨迹上的顺序动作选择,(2)推理任务上测试知识密集型的单步生成改进,(3)编程任务上教会智能体有效地使用外部工具,如编译器和解释器。在所有三类任务中,我们都观察到Reflex Agent是更好的决策者、推理者和程序员。具体而言,在12个迭代学习步骤中,Reflex Agent在决策AlfWorld [ 24 ]任务上比强基线方法提高了22%,在HotPot QA [ 28 ]中的推理问题上提高了20%,在HumanEval [ 6 ]上的Python编程任务上提高了11%。

综上所述,我们的贡献有以下几点:

• 我们提出了一种新的"言语"强化范式Reflex,将政策参数化为代理人的记忆编码和LLM参数的选择。

• 我们在LLMs中探索了自我反思的这种涌现特性,并通过实验证明了自我反思对于学习复杂任务是非常有用的。

• 我们介绍了LeetcodeHardGym,一个由19种编程语言中的40个具有挑战性的Leetcode问题("硬水平")组成的代码生成RL训练环境。

• 我们展示了Reflex在多个任务上实现了对强基线的改进,并在各种代码生成基准上取得了最先进的结果。

2. 相关工作

upload successful

图1:反思任务包括决策4.1、编程4.3和推理4.2。

upload successful

推理和决策 Self-Refine [ 15 ]使用迭代的框架进行自我修正,通过自我评估来自主地改进生成。这些自我评价和自我完善的步骤是以给定的任务限制为条件的,例如"这一代人如何以更积极的方式写作"。自求精是有效的,但仅限于单代推理任务。Pryzant等人[ 21 ]进行了类似的语义提示书写优化,但也仅限于单代任务。Paul等人[ 20 ]微调评价模型,在轨迹中提供中间反馈,以提高推理反应。Xie等人[ 27 ]使用随机束搜索来执行一个更有效的决策搜索策略,该策略允许智能体由于其自我评价成分而使用预见优势。Yoran等人[ 31 ]和Nair等人[ 16 ]使用判定模型在几代人中进行推理。Kim等人[ 10 ]使用固定步数的重试模式,没有评估步。Goodman [ 9 ]执行了对上一代提出优化的定性评估步骤。在本文中,我们证明了这些概念中的一些可以通过自我反思来增强,以建立对自我反思经验的持久记忆,这使得代理可以识别自己的错误,并自我暗示随着时间的推移从错误中吸取教训。

编程 过去和最近的一些工作采用了测试驱动开发或代码调试实践的变体。AlphaCode [ 14 ]在评估了一组在隐藏测试用例上的生成。CodeT [ 5 ]使用自生成的单元测试,用于对生成的函数实现进行打分。自调试[ 7 ]使用了一个调试组件,该组件用于在给定代码执行环境反馈的情况下改进现有的实现。CodeRL [ 12 ]将问题设置在RL框架中,使用行动者评论家设置来调试程序,并给出执行环境的反馈。AlphaCode、Self-Debugging和CodeRL在修复复杂度较低的程序错误时是有效的,但它们依赖于无效的通过 @1 合格性的真实测试用例,并且没有使用自我反思来弥合错误识别和实现改进之间的鸿沟。CodeT不访问隐藏的测试用例,但没有实现自学习步骤来改进代码编写。

3. 反射性:通过言语反射进行强化

我们开发了一个Reflex的模块化公式,使用了三个不同的模型:一个Actor,记为MaM_a,它生成文本和动作;以Me为代表的Evaluator模型对MaM_a产生的输出进行打分;以及一个自我反思模型,记为MeM_e,它产生言语强化线索来辅助Actor进行自我提升。我们对每个模型进行了详细的描述,并随后在Reflex框架内阐明了它们的协同作用。

upload successful

图2:( a ) Reflex图。( b )反射增强算法

Actor Actor是建立在一个大语言模型( LLM )的基础上的,该模型专门提示生成以状态观测为条件的必要文本和动作。类似于传统的基于策略的RL设置,我们在t时刻从当前策略π θ中采样一个动作或生成,从环境中接收一个观测。我们探索了各种行动者模型,包括思想链(Chain of Thought)[ 26 ]和ReAct [ 30 ]。这些不同的生成模型允许我们在Reflex框架内探索文本和动作生成的不同方面,为它们的性能和有效性提供了有价值的见解。此外,我们还添加了一个记忆组件mem,为这个agent提供了额外的上下文。这种适应性是受Brooks等人[ 3 ]的启发,他们提出了一个策略迭代算法ap

Evaluator Reflex框架中的Evaluator组件在评估Actor产生的输出的质量中起着至关重要的作用。它将生成的轨迹作为输入,并计算在给定任务环境下反映其性能的奖励分数。定义适用于语义空间的有效值和奖励函数是困难的,因此我们考察了Evaluator模型的几种变体。对于推理任务,我们探索了基于精确匹配(EM)评分的奖励函数,以确保生成的输出与期望解紧密对齐。在决策任务中,我们使用预先定义的启发式函数,这些函数是根据特定的评价标准定制的。此外,我们还尝试使用LLM本身的不同实例化作为评估器,为决策和编程任务生成奖励。这种对评价者设计的多方位方法允许我们检查不同的策略来对生成的输出进行评分,以了解它们在一系列任务中的有效性和适用性。

自反射 自我反思模型作为LLM的一个实例,通过产生言语上的自我反思,为未来的试验提供有价值的反馈,在反思框架中起着至关重要的作用。给定一个稀疏的奖励信号,例如一个二进制的成功状态(成功/失败),当前的轨迹和它的持久记忆,自反射模型会产生细致入微和特定的反馈。然后,这种比标量奖励更有信息的反馈存储在智能体的记忆(mem)中。例如,在多步决策任务中,当智能体接收到故障信号时,它可以推断一个特定的动作aia_i导致了随后的不正确动作ai+1a_{i+1}ai+2a_{i+2}。然后,代理可以口头陈述,它应该采取不同的行动,ai{a′}_i,这将导致ai+1{a′}_{i+1}ai+2{a′}_{i+2},并将这种经验存储在其记忆中。在随后的试次中,代理可以利用过去的经验,通过选择行动ai{a′}_i来调整自己在tt时刻的决策方式。这种尝试、错误、自我反思和持久记忆的迭代过程使智能体能够利用信息性的反馈信号迅速提高其在各种环境中的决策能力。

记忆 Reflex过程的核心成分是短期和长期记忆的概念。在推理时,行动者将其决策条件限定在短时记忆和长时记忆上,类似于人类记忆细粒度近期细节的方式,同时也会回忆从长时记忆中提炼出来的重要经验。在强化学习装置中,轨迹历史作为短时记忆,自反射模型的输出存储在长时记忆中。这两个记忆成分共同提供了特定的情境,但同时也受到了几个试次的经验教训的影响,这也是Reflex Agent相对于其他LLM动作选择作品的一个关键优势。

Reflexion过程

在过过程1中,反射被形式化为一个迭代优化过程。在第一个试次中,Actor通过与环境交互产生一个轨迹τ0τ_0。然后,Evaluator产生一个得分r0r_0r0r_0计算为rt=Me(τ0)r_t = M_e(τ_0)rtr_t只是对试验tt的一个标量奖励,它随着特定任务性能的增加而增加。在第一次试次后,为了将r0r_0放大为LLM可以改进的反馈形式,自我反思模型对{τ0τ_0r0r_0}集合进行分析,产生摘要sr0sr_0,存储在记忆记忆中,sr0sr_0为试次tt的言语经验反馈。Actor、Evaluator和自反思模型通过循环往复的试验共同工作,直到Evaluator认为τtτ_t是正确的。如3所述,Reflexion的记忆成分对其有效性至关重要。在每次试验后,ttsrtsr_t,被加入到记忆中。在实际应用中,我们通过最大数量的存储经验,ΩΩ (通常设置为1 ~ 3)来约束mem,以遵守最大上下文LLM限制。

4. 实验

我们在决策、推理和代码生成任务上评估了各种自然语言RL设置。具体来说,我们在HotPotQA [ 28 ],AlfWorld [ 24 ]中常见的家庭环境中的多步任务,以及HumanEval [ 6 ],MBPP [ 2 ]中的解释器和编译器,以及一个新的基准测试程序LeetcodeHard上挑战一个代理执行基于搜索的问答任务。最值得注意的是,在AlfWorld上,Reflex在强基线上的性能提升了22 %,在HotPotQA上提升了20 %,在HumanEval上提升了11 %。

4.1 序列决策:ALFWorld

AlfWorld是一套基于文本的环境,它基于TextWorld [ 8 ],在各种交互环境中挑战Agent解决多步骤任务。遵循Yao等[ 30 ]的方法,我们在134个AlfWorld环境中跨6个不同的任务运行该代理,包括寻找隐藏对象(例如,在抽屉里找到一个铲刀)、移动对象(例如,将一把小刀移到切割板上)和用其他对象操作对象(例如,在冰箱里冷却一个番茄)。我们使用ReAct [ 30 ]作为动作生成器,因为Yao等人[ 30 ]在使用显式中间思想的长轨迹决策中显示出了成功。AlfWorld任务自然需要一个自我评价步骤,因为只有当任务完成时,环境才能发出信号。为了实现完全自主的行为,我们实现了两种自我评估技术:使用LLM的自然语言分类和手写启发式。启发式是简单的:如果智能体执行相同的动作并收到相同的响应超过3个周期,或者如果当前环境中采取的动作数量超过30 (低效的规划),我们进行自我反思。在基线运行中,如果建议进行自我反思,则跳过自我反思过程,重新设置环境,开始新的试次。在Reflex运行中,代理使用自我反思来发现其错误,更新其记忆,重置环境,并开始新的尝试。为了避免可能超过最大限制的很长的提示窗口,我们将代理的记忆截断到最后3次自我反射(经验)。

为了避免句法错误,我们为agent提供了两个特定领域的小样本轨迹。对于LLM,我们使用与Yao等[ 30 ]相同的小样本轨道例子和GPT-3。附录中包括AlfWorld任务、ReAct小样本提示和Reflex示例。

结果 ReAct+Reflexion 在134个任务中完成了130个任务,使用简单的启发式方法来检测幻觉和低效的计划,明显优于ReAct。进一步,ReAct+Reflexion 通过连续12个试次的学习来解决额外任务。在ReAct-only方法中,我们发现在进行到6至7尝试时,性能增加就停止了。

upload successful 图3:( a ) AlfWorld在134个任务中的表现,显示了使用启发式( Heuristic )和二分类( GPT )的自我评估技术解决任务的累积比例。( b )按故障原因对AlfWorld轨迹进行分类。

分析 在基线失败的AlfWorld轨迹中,一个常见的错误是当代理认为自己拥有一个物品,但实际上并不拥有该物品。代理在一个较长的轨迹中执行若干动作,并且不能回溯其动作以发现错误。Reflexion通过自我反思将长期的、失败的轨迹提炼为相关经验,并在未来作为"自我暗示",几乎可以消除所有这些情况。在AlfWorld中,长时记忆帮助施动者的情况主要有两种:1)长轨迹中的早期错误很容易被识别。代理人可以提出新的行动选择,甚至是新的长期计划。2)检查物品的表面/容器太多。代理可以利用其在多个试验中的经验记忆来彻底搜索一个房间。在3中,学习曲线表明学习过程发生在多个经验上,这意味着代理人成功地平衡了案例1和2,表现在前两个试验之间的改进的立即尖峰,然后在接下来的11个试验中稳步增加,以达到近乎完美的性能。另一方面,3显示了一个ReAct-only agent收敛于22%的幻觉率,并且没有长期恢复的迹象。

4.2 推理:HotpotQa

HotPotQA [ 28 ]是一个基于维基百科的数据集,包含113k个问答对,这些问答对要求代理人解析多个支持文档的内容和推理。为了测试推理能力的提高,我们实现了Reflexion+Chain-of-Thought (CoT) [ 26 ],用于逐步QAQ→A和Q,CgtAC_{gt}→A的实现,其中QQ是问题,CgtC_{gt}是来自数据集的背景真值,AA是最终答案。由于CoT不是一种多步决策技术,因此我们给Agent赋予CgtC_{gt},以便我们可以在提供的文本的大段上隔离推理行为。为了测试需要推理和行动选择的整体问答能力,我们实现了一个Reflexion+ReAct [ 30 ]代理,该代理可以使用维基百科API检索相关上下文,并使用逐步显式思维推断答案。对于CoT的实现,我们使用6-shot提示;对于ReAct,我们使用2-shot提示,而对于Self-Reflexion,我们使用2-shot提示。所有的例子都可以在附录中找到。

鲁棒地评估自然语言答案是NLP中一个长期存在的问题。因此,在试验之间,我们使用精确匹配答案评分使用环境给代理一个二进制成功信号。每次试次结束后,采用自反射循环放大二进制信号,类似于AlfWorld中的决策设置4.1,内存大小为3次经验。

结果 在几个学习步骤中,Reflexion 优于所有基线方法,具有显著的优势。此外,ReAct-only,CoT-only和CoT(GT)-only的实现在任何任务上都不能概率性地改进,这意味着从任何基线方法的第一个试验中没有失败的任务能够在随后的试验中使用0.7的温度来解决。在RReflexion 运行中,我们允许代理收集经验并重新尝试失败的任务,直到它在特定任务上产生连续3次失败的尝试。自然地,由于CoT(GT)能够接触到问题的基本真值背景,因此获得了更高的正确率分数。尽管如此,CoT(GT)智能体仍有39 %的问题无法正确推理出正确答案,但Reflex帮助智能体在没有获得真实答案的情况下改正其错误,使其准确率提高了14%。

upload successful

图4:思想链(COT)与ReAct。在100个HotPotQA问题上,Reflexion提高了搜索、信息检索和推理能力。(a) Reflexion ReAct vs Reflexion CoT (b)只进行推理的Reflexion CoT(GT) (c) Reflexion vs情景记忆消融。

4.3 编程

我们在MBPP[ 2 ],HumanEval [ 6 ]和我们的新数据集LeetcodeHardGym上评估了在Python和Rust代码上编写的基线和Reflex方法。MBPP和HumanEval度量函数给出了人体生成精度的自然语言描述。我们使用基准语言编译器,MultiPL-E [ 4 ]将Human Eval和MBPP的子集翻译成Rust语言。MultiPL-E是一个小型编译器的集合,可用于将Python基准题翻译成18种其他语言。我们包括Rust代码生成的实验,以证明Reflexion代码生成的实现是语言无关的,可以用于解释和编译语言。最后,我们介绍了一个新的基准测试程序LeetcodeHardGym,它是一个交互式编程训练器,包含40个Leetcode硬评分问题,这些问题在2022年10月8日之后发布,这也是GPT-4的预训练截止日期[ 18 ]。

编程的任务提供了一个独特的机会,可以使用更接地气的自我评估实践,如自我生成的单元测试套件。因此,我们基于Reflexion的编程任务实现可以满足pass@1的准确性报告。为了生成测试集,我们使用思维链提示[ 26 ]来产生多样化的、广泛的测试以及相应的自然语言描述。然后,我们通过尝试为每个测试构造一个有效的抽象语法树(AST)来过滤语法上有效的测试语句。最后,从生成的单元测试集合中采样n个测试,生成一个测试集T,记为{ t0,t1,..,tn }。我们将n设置为最多6个单元测试。除了单元测试套件组件外,Reflexion编程代理的学习循环设置与最大内存限制为1经验的推理和决策代理完全相同。

upload successful

表1:各种模型-策略-语言组合的Pass@1准确率。基本策略是单个代码生成样本。所有基于指令的模型都遵循零样本代码生成。

upload successful 表2:HumanEval和MBPP的总体精度和测试生成性能。对于Rust问题,从HumanEval Python翻译成Rust问题的MultiPL-E[ 4 ]中,HumanEval是最难的50个问题。TP:单元测试合格,解决方案合格;FN:单元测试失败,解决方案通过;FP:单元测试通过,解决方案失败;TN:单元测试失败,解决方案失败。

结果 除了MBPP Python 1外,Reflex在Python和Rust的所有基准上都超过了所有基线精度,并制定了新的最先进的标准。我们进一步研究了Reflexion在MBPP Python上的性能劣势。

分析 我们承认,自我反思的代码生成代理势必会影响他们编写多样化、综合测试的能力。因此,在模型生成片状测试集的情况下,有可能所有的测试都传递了错误的解决方案,并导致代码补全上的假阳性标签[ 11 ]。另一方面,如果模型产生了错误编写的测试套件,则有可能导致部分测试在正确的解决方案上失败,从而导致以假阴性代码补全为条件的自我反思的产生。考虑到Reflexion的实现,假阴性比假阳性更受欢迎,因为代理可以使用自我反思来识别错误的测试,并提示自己保持原始代码的完整性。另一方面,如果一个无效的测试集返回一个假阳性完成(内部测试用例全部通过,但执行不正确),则代理将过早地报告一个无效的提交。在2中,测量了各种条件,以分析超过pass@1精度的性能。之前,我们在MBPP Python上展示了Reflexion相对于基准GPT-4的劣势。在2中,我们观察到由内部测试执行产生的假阳性标签之间的显著差异,P(未通过@1次生成正确|通过)。它表示一个提交通过了所有的单元测试之后失败的概率。对于HumanEval和MBPP Python,基线pass@1准确率比较接近,分别为82%和80%。然而,MBPP Python的假阳性测试执行率为16.3%,而HumanEval Python的假阳性测试执行率仅为1.4%,总体准确率为91% (1)。

upload successful 表3:以GPT-4为基准模型的Reflexion方法在Human Eval Rust - 50最难问题上各种折衷方法的Pass@1精度

消融研究 我们在50个最难的Human Eval Rust问题的子集上测试了测试生成的Reflexion和自反思合作的复合方法。我们的Rust编译环境提供了冗长的错误日志和有用的调试提示,因此为妥协方法提供了一个很好的场地。首先,我们省略了内部测试的生成和执行步骤,即在没有当前实现的指导下测试代理进行自我反思。3的准确率低于52% vs 60% (基线),这表明在没有单元测试的情况下,代理无法确定当前的实现是否正确。因此,代理必须参与运行的所有迭代,而没有提前返回的选项,对实现进行有害的编辑。

接下来,我们通过省略单元测试集评估失败后的自然语言解释步骤来检验自我反思的贡献。直观地说,这挑战了代理在所有失败的单元测试中结合错误识别和实现改进的任务。有趣的是,被妥协的代理并没有在基线运行时提高性能。我们观察到,测试生成和代码编译步骤能够捕捉到语法和逻辑错误,但实现修复并不能反映这些迹象。这些实证结果表明,最近提出的一些没有自我反思的盲目试错调试技术的工作,对于诸如在Rust中编写复杂程序等更难的任务是无效的。

5. 局限性

Reflexion 是一种使用自然语言进行策略优化的优化技术。策略优化是一种通过经验来改进行动选择的有力方法,但它仍然可能屈从于非最优的局部极小值解。在这项研究中,我们将长期记忆限制在具有最大容量的滑动窗口中,但我们鼓励未来的工作用更高级的结构来扩展Reflexion 的记忆组件,例如向量嵌入数据库或传统的SQL数据库。具体到代码生成,测试驱动开发在指定精确的输入输出映射方面存在许多实际限制,例如非确定性的生成器函数、与API交互的不纯函数、根据硬件规格改变输出的函数,或者调用可能难以预测的并行或并发行为的函数。

6. 更广泛的影响

大型语言模型越来越多地被用来与外界环境(例如互联网、软件、机器人等。)和人类进行交互。我们的工作具有加强和增强这些代理的潜力,以提高自动化程度和工作效率,但同时也放大了这些代理被误用时的风险。我们相信,这一方向的研究将需要在安全和伦理方面付出更多的努力。

另一方面,强化学习受到其黑盒策略和优化设置的影响,其中可解释性和对齐一直是具有挑战性的。我们提出的"口头"强化学习可能会解决一些问题,并使自主代理更具可解释性和可诊断性。例如,在人类难以理解的工具使用情况下,可以在使用工具之前监测自我反思,以确保正确的意图。

7. 结论

在这项工作中,我们提出了一种利用言语强化的方法--Reflexion,它可以教代理从过去的错误中学习。我们的经验表明,通过利用自我反思,Reflexion 代理显著优于目前广泛使用的决策方法。在未来的工作中,Reflexion 可以用来使用在传统RL环境中被深入研究的更先进的技术,例如自然语言中的值学习或非策略探索技术。

8. 重复性

我们强烈建议其他人在运行自主代码编写实验时使用孤立的执行环境,因为生成的代码在执行之前没有经过验证。