Reflection Agents

反射代理

反射是一种提示策略,用于提高代理和类似 AI 系统的质量和成功率。这篇文章概述了如何使用 LangGraph 构建 3 种反射技术,包括 Reflexion 和语言代理树搜索的实现。

阅读 6 分钟

反射是一种提示策略,用于提高代理和类似 AI 系统的质量和成功率。它涉及提示 LLM 反思和批判其过去的行为,有时还会结合其他外部信息,例如工具观察。

人们喜欢谈论“系统 1”和“系统 2”的思维方式,其中系统 1 是反应性或本能的,而系统 2 则更加有条理和反思性。正确应用时,反射可以帮助 LLM 系统摆脱纯粹的系统 1“思维”模式,更接近于表现出类似系统 2 的行为。

System 1 and 2: Thinking fast? Slow down.

反射需要时间!本文中的所有方法都以牺牲一些额外的计算为代价来换取更好的输出质量。虽然这可能不适用于低延迟应用程序,但对于响应质量比速度更重要的知识密集型任务来说,它值得的。

下面概述了三个示例

基本反射

链接:(PythonYouTube

这个简单的示例组合了两个 LLM 调用:一个生成器和一个反射器。生成器尝试直接响应用户的请求。反射器被提示扮演教师的角色,并对初始响应提供建设性批评。

循环执行固定次数,并返回最终生成的输出。

简单反射循环

我们可以在下面的 LangGraph 中定义循环

from langgraph.graph import MessageGraph

builder = MessageGraph()
builder.add_node("generate", generation_node)
builder.add_node("reflect", reflection_node)
builder.set_entry_point("generate")


def should_continue(state: List[BaseMessage]):
    if len(state) > 6:
        return END
    return "reflect"


builder.add_conditional_edges("generate", should_continue)
builder.add_edge("reflect", "generate")
graph = builder.compile()

MessageGraph表示一个有状态的图,其中“状态”只是一个消息列表。每次调用生成器或反射器节点时,它都会将一条消息追加到状态的末尾。最终结果从生成器节点返回。

这种简单的反射类型有时可以通过让 LLM 多次尝试改进其输出,以及让反射节点在批判输出时采用不同的角色来提高性能。

然而,由于反射步骤没有建立在任何外部过程中,因此最终结果可能不会比原始结果好多少。让我们探索一些可以改善这种情况的其他技术。

Reflexion

链接:(PythonYouTube

Reflexion由 Shinn 等人提出,是一种旨在通过语言反馈和自我反思进行学习的架构。在 Reflexion 中,actor 代理明确地批判每个响应,并将其批评建立在外部数据的基础上。它被迫生成引用并明确列出生成响应中多余和缺失的方面。这使得反思的内容更有建设性,并且更好地引导生成器响应反馈。

链接的示例中,我们在固定数量的步骤后停止,尽管您也可以将此决策卸载到反射 LLM 调用中。

代理循环概述如下所示

Reflexion Actor 概述

对于每个步骤,响应者负责生成响应,以及以搜索查询形式的其他操作。然后提示修订者反思当前状态。逻辑可以在 LangGraph 中定义如下

from langgraph.graph import END, MessageGraph

MAX_ITERATIONS = 5
builder = MessageGraph()
builder.add_node("draft", first_responder.respond)
builder.add_node("execute_tools", execute_tools)
builder.add_node("revise", revisor.respond)
# draft -> execute_tools
builder.add_edge("draft", "execute_tools")
# execute_tools -> revise
builder.add_edge("execute_tools", "revise")

# Define looping logic:
def event_loop(state: List[BaseMessage]) -> str:
    # in our case, we'll just stop after N plans
    num_iterations = _get_num_iterations(state)
    if num_iterations > MAX_ITERATIONS:
        return END
    return "execute_tools"


# revise -> execute_tools OR end
builder.add_conditional_edges("revise", event_loop)
builder.set_entry_point("draft")
graph = builder.compile()

该代理可以有效地利用显式反射和基于网络的引用来提高最终响应的质量。然而,它只追求一条固定的轨迹,因此如果它犯了错误,该错误可能会影响后续决策。

链接:(PythonYouTube

语言代理树搜索(LATS),由 Zhou 等人提出,是一种通用的 LLM 代理搜索算法,它结合了反射/评估和搜索(特别是蒙特卡洛树搜索)来实现比 ReACT、Reflexion 甚至思想树等类似技术更好的整体任务性能。它采用了标准的强化学习 (RL) 任务框架,用对 LLM 的调用替换了 RL 代理、价值函数和优化器。这旨在帮助代理适应和解决复杂任务,避免陷入重复循环。

搜索过程在下面的图表中概述

搜索有四个主要步骤

  1. 选择:根据下面步骤 (2) 中的聚合奖励选择最佳的下一步操作。要么响应(如果找到解决方案或达到最大搜索深度),要么继续搜索。
  2. 扩展和模拟:生成 N(在本例中为 5)个潜在的操作,并并行执行它们。
  3. 反射+评估:观察这些操作的结果,并根据反射(以及可能存在的外部反馈)对决策进行评分
  4. 反向传播:根据结果更新根轨迹的分数。

如果代理具有紧密的反馈循环(通过高质量的环境奖励或可靠的反射分数),则搜索能够准确地区分不同的动作轨迹并选择最佳路径。然后可以将最终轨迹保存到外部存储器(或用于模型微调)以在将来改进模型。

“选择”步骤选择具有最高上限置信度 (UCT) 的节点,该节点仅平衡预期奖励(第一项)和探索新路径的激励(第二项)。

\( UCT = \frac{\text{value}}{\text{visits}} + c \sqrt{\frac{\ln(\text{parent.visits})}{\text{visits}}}\)

查看代码以了解其实现方式。在我们的 LangGraph 实现中,我们将生成+反射步骤放在单个节点中,并在每个循环中检查树状态以查看任务是否已解决。简化的图定义如下所示

from langgraph.graph import END, StateGraph

class Node:
    def __init__(
        self,
        messages: List[BaseMessage],
        reflection: Reflection,
        parent: Optional[Node] = None,
    ):
        self.messages = messages
        self.parent = parent
        self.children = []
        self.value = 0
        self.visits = 0
    # Additional methods are defined here. Check the code for more!

class TreeState(TypedDict):
    # The full tree
    root: Node
    # The original input
    input: str

def should_loop(state: TreeState):
    """Determine whether to continue the tree search."""
    root = state["root"]
    if root.is_solved:
        return END
    if root.height > 5:
        return END
    return "expand"


builder = StateGraph(TreeState)
builder.add_node("start", generate_initial_response)
builder.add_node("expand", expand)
builder.set_entry_point("start")


builder.add_conditional_edges(
    "start",
    # Either expand/rollout or finish
    should_loop,
)
builder.add_conditional_edges(
    "expand",
    # Either continue to rollout or finish
    should_loop,
)

graph = builder.compile()

LATS 图

创建基本轮廓后,很容易扩展到其他任务!例如,此技术非常适合代码生成任务,其中代理可以编写明确的单元测试并根据测试质量对轨迹进行评分。

LATS 统一了其他代理架构(例如 Reflexion、思想树和计划和执行代理)的推理、规划和反射组件。LATS 还来自反射和基于环境的反馈的反向传播,以改进搜索过程。虽然它可能对奖励分数敏感,但通用算法可以灵活地应用于各种任务。

LATS 与其他代理架构的比较

视频教程

结论

感谢您的阅读!所有这些示例都可以在LangGraph存储库中找到,我们很快就会将其移植到LangGraphJS(也许在你阅读这篇文章时)。

上面提到的所有技术都利用额外的 LLM 推理来增加生成更高质量输出或正确响应更复杂推理任务的可能性。虽然这需要额外的时间,但在输出质量比响应时间更重要的情况下,以及如果将轨迹保存到内存(或作为微调数据),则可以更新模型以避免将来重复错误。