简单的链执行预设序列,而代理则引入动态行为。代理使用大型语言模型(LLM)不仅处理信息,更是决定下一步做什么。它对问题进行推理,选择工具,观察结果,并迭代直到达成目标。然而,这个推理过程并非随意;它遵循特定的模式或框架,这些模式或框架被称为代理架构。这些架构构建了代理的“思维过程”,影响着它如何分解问题、与工具互动以及综合信息。理解这些架构对于为特定任务选择或设计合适的代理非常重要。让我们看看在LangChain中常被实现和讨论的三种有影响力的代理架构:ReAct、Self-Ask和规划与执行。ReAct:推理与行动交织ReAct架构,即“推理+行动”的缩写,促进了推理与行动之间的紧密结合。代理不是预先生成一个完整计划,而是将内部推理(Thought)步骤与外部交互(Action和Observation)步骤交织进行。思考(Thought): 代理首先分析当前情况和总体目标。它口头表达一个推理步骤,决定下一步需要采取什么行动来取得进展。这个思维过程通常由大型语言模型明确生成。行动(Action): 基于思考,代理选择一个工具并为其指定输入。观察(Observation): 代理执行行动(调用工具)并收到一个观察结果(工具的输出或结果)。重复(Repeat): 代理将观察结果整合到其理解中,并以新的思考再次开始循环,基于之前的步骤和最终目标决定后续行动。这个循环持续到代理认为它有足够信息给出最终答案。digraph ReAct_Flow { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; Start [label="用户查询", shape=ellipse, style=filled, fillcolor="#a5d8ff"]; Thought1 [label="思考:\n分析查询,\n决定下一步", fillcolor="#fffec9"]; Action1 [label="行动:\n选择工具,\n指定输入", fillcolor="#ffd8a8"]; Observation1 [label="观察:\n收到工具输出", fillcolor="#e9ecef"]; Thought2 [label="思考:\n分析观察结果,\n决定下一步", fillcolor="#fffec9"; style=dashed]; Action2 [label="行动:\n...", fillcolor="#ffd8a8"; style=dashed]; Observation2 [label="观察:\n...", fillcolor="#e9ecef"; style=dashed]; FinalAnswer [label="最终答案", shape=ellipse, style=filled, fillcolor="#b2f2bb"]; Start -> Thought1; Thought1 -> Action1; Action1 -> Observation1 [label="执行工具"]; Observation1 -> Thought2 [label="整合结果"]; Thought2 -> Action2; Action2 -> Observation2 [label="执行工具"]; Observation2 -> FinalAnswer [label="综合结果"]; // 简化结尾 }ReAct循环将内部推理(思考)与外部交互(行动/观察)交织进行,直到形成最终答案。优点:适应性: ReAct代理可以根据从工具收到的观察结果动态调整其策略。如果工具失败或提供意外信息,代理可以对失败进行推理并尝试不同方法。透明度: 明确的“思考”步骤使代理的推理过程更具可解释性,有助于调试和理解其行为。工具使用: 它自然适合于需要与多个工具交互或从外部源收集复杂信息的任务。局限性:冗长与延迟: 生成明确的思考会增加大型语言模型的调用次数,可能导致更高的延迟和成本。潜在循环: 设计不当的提示或意外的工具输出有时可能使代理陷入重复的推理循环。ReAct是一种强大的通用架构,当解决方案路径最初不清晰且需要根据中间结果进行试探和调整时特别有效。LangChain提供了实例化ReAct代理的便捷方式,通常只需一个大型语言模型、一套工具和一个基本提示模板。Self-Ask与搜索:问题分解Self-Ask架构侧重于将复杂问题分解为更简单的子问题,这些子问题通常可以使用专用工具(最常见的是搜索引擎)来回答。核心思想是迭代分解和信息收集。初始问题: 代理从主要用户查询开始。识别子问题: 大型语言模型判断是否需要更具体的信息来回答主要查询。如果需要,它会提出一个更简单、后续的问题。使用工具(搜索): 代理使用一个指定的工具(例如搜索API包装器)来寻找子问题的答案。整合答案: 大型语言模型将子问题的答案整合到其知识库中。重复或回答: 如果需要,代理会提出另一个后续问题(步骤2)。如果它有足够信息,它会将收集到的事实综合成对原始查询的最终答案。digraph SelfAsk_Flow { rankdir=TD; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; Start [label="用户查询", shape=ellipse, style=filled, fillcolor="#a5d8ff"]; IdentifySQ1 [label="识别子问题 1", fillcolor="#fffec9"]; UseTool1 [label="使用工具(例如搜索)\n输入:子问题 1", fillcolor="#ffd8a8"]; Result1 [label="结果 1", fillcolor="#e9ecef"]; IdentifySQ2 [label="识别子问题 2\n(基于查询 + 结果 1)", fillcolor="#fffec9"; style=dashed]; UseTool2 [label="使用工具(例如搜索)\n输入:子问题 2", fillcolor="#ffd8a8"; style=dashed]; Result2 [label="结果 2", fillcolor="#e9ecef"; style=dashed]; Synthesize [label="综合最终答案\n(使用结果 1, 2, ...)", fillcolor="#d0bfff"]; FinalAnswer [label="最终答案", shape=ellipse, style=filled, fillcolor="#b2f2bb"]; Start -> IdentifySQ1; IdentifySQ1 -> UseTool1; UseTool1 -> Result1 [label="获取答案"]; Result1 -> IdentifySQ2 [label="整合"]; IdentifySQ2 -> UseTool2; UseTool2 -> Result2 [label="获取答案"]; Result2 -> Synthesize [label="整合"]; // 简化流程以显示多次整合 // 如果不再需要子问题的替代路径 Result1 -> Synthesize [label="整合并决定完成", style=dotted]; Synthesize -> FinalAnswer; }Self-Ask过程将主问题分解为子问题,使用工具(通常是搜索)来回答它们,并整合结果。优点:事实查找: 擅长回答需要从外部知识源检索和组合多条事实信息的复杂问题。结构化分解: 强制将问题清晰分解为可管理的部分。减少幻觉: 通过依靠外部工具提供子问题的事实答案,它可以降低大型语言模型生成错误信息的可能性。局限性:工具依赖: 严重依赖于指定工具(通常是搜索)的有效性。推理范围有限: 主要侧重于信息检索;可能不太适合需要复杂计算、创意生成或规划的任务。Self-Ask对于构建基于外部数据的问答系统特别有用。LangChain通过特定的提示策略和优化用于迭代分解的工具配置来支持这种模式。规划与执行:规划与执行分离规划与执行架构在规划阶段和执行阶段之间引入了明确的分离。这种方法对于需要预先确定一系列行动的复杂任务通常是有益的。规划: 根据用户的目标,一个专门的“规划器”组件(通常由大型语言模型驱动)分析请求并生成一个分步计划。每个步骤通常描述一个要采取的行动。执行: 一个“执行器”组件接收生成的计划并按顺序执行每个步骤。执行器可能涉及只专注于执行特定步骤的更简单的大型语言模型调用,或者它可以直接调用计划步骤中指定的工具。一个步骤的结果通常会输入到下一个步骤。digraph PlanExecute_Flow { rankdir=TD; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; Start [label="用户目标", shape=ellipse, style=filled, fillcolor="#a5d8ff"]; Planner [label="规划器(LLM):\n生成分步计划", fillcolor="#eebefa"]; Plan [label="计划:\n1. 行动 A(工具 X)\n2. 行动 B\n3. 行动 C(工具 Y)\n...", shape=note, fillcolor="#fcc2d7"]; Executor [label="执行器", shape= Mdiamond, fillcolor="#bac8ff"]; Step1 [label="执行步骤 1:\n行动 A(工具 X)", fillcolor="#a5d8ff"]; Step2 [label="执行步骤 2:\n行动 B", fillcolor="#a5d8ff"]; Step3 [label="执行步骤 3:\n行动 C(工具 Y)", fillcolor="#a5d8ff"; style=dashed]; FinalResult [label="最终结果", shape=ellipse, style=filled, fillcolor="#b2f2bb"]; Start -> Planner; Planner -> Plan [label="输出"]; Plan -> Executor [label="输入"]; Executor -> Step1 [label="处理步骤 1"]; Step1 -> Step2 [label="步骤 1 的结果"]; Step2 -> Step3 [label="步骤 2 的结果"]; Step3 -> FinalResult [label="步骤 N 的结果"]; // 简化结尾 }规划与执行架构将计划生成(规划器)与分步执行(执行器)分离。优点:结构化任务: 非常适合具有清晰、逻辑操作序列的任务。可预测性: 计划是预先生成的,使代理的总体方法更具可预测性(尽管执行细节可能有所不同)。状态管理: 由于计划提供了清晰的结构,因此更容易管理步骤之间的状态。效率: 与ReAct相比,可能需要更少的高级推理大型语言模型调用,因为主要推理发生在规划阶段。执行步骤可能使用更简单的逻辑或集中的大型语言模型调用。局限性:僵硬性: 与ReAct相比,对执行过程中意外结果的适应性较差。如果某个步骤失败或产生意想不到的结果,代理可能难以偏离原始计划,除非有复杂的重新规划机制。规划复杂性: 为高度复杂或模糊的任务生成正确且全面的计划对于规划器大型语言模型可能具有挑战性。规划与执行代理在处理流程可以预先合理确定的多步骤流程时很有效。LangChain支持这种模式,通常涉及一个规划组件,结合一个负责执行计划步骤的代理或链。选择合适的架构没有单一的“最佳”代理架构;最佳选择很大程度上视任务性质而定:当任务需要与工具进行大量交互、基于中间结果的动态调整,以及解决方案路径预先不明显时,使用ReAct。推理步骤的可解释性通常是一个优点。对于需要分解复杂查询并从网络等外部知识源检索事实信息的问答任务,使用Self-Ask(带搜索)。对于具有明确、顺序步骤、计划可以预先可靠生成且执行期间适应性不那么重要的任务,使用规划与执行。在实践中,这些架构代表了基本模式。高级实现可能会混合元素,例如将重新规划整合到规划与执行中,或在更大计划的特定步骤中使用ReAct式推理。你对大型语言模型的选择、工具的质量以及提示的精心设计仍然是影响任何代理成功的重要因素,无论选择何种架构。随着你构建更复杂的代理,尝试这些不同的推理框架对于实现有效性能将非常重要。