LangGraph详解:图结构、状态、路由机制及生产环境应用模板
LangGraph如何将智能体建模为具有类型化状态、归约器、检查点、路由机制、工具以及ReAct循环的循环图,并提供用于实际应用的模板。
来源: Mastering LangGraph: The Complete Technical Guide to Building Production-Grade Agentic AI Systems
第一波大语言模型应用仅仅是简单的信息交换:发送提示语,接收回复。而智能体则是另一种类型的程序。它们会经过多步推理,根据目前已掌握的信息决定行动方案,并在现实世界中采取相应行动。这要求有基于循环、分支和内存构建的基础设施,而非从输入到输出的直线流程。
LangGraph就是这样的一个框架。它专为需要一个或多个由大语言模型驱动的智能体协同工作的状态型应用而设计。与线性链不同,它允许你描述包含循环的有向图,实现条件化路由,保持持久状态,并协调工具调用。这些并非仅仅是便利功能——那些必须应对不确定性、从错误中恢复并在多次尝试中不断优化工作的智能体,恰恰需要所有这些功能。
接下来的内容将介绍该框架的内部运作机制、设计选择的重要性以及各种模式适用的场景。读完之后,您应该能够自信地做出架构决策,调试那些行为异常的智能体,并根据自身需求扩展该框架。
图作为基本构建单元
早期的编排工具将工作流建模为线性链或最多是有向无环图。这种方式适用于可预测的任务,但当智能体需要迭代、回退或即时选择不同路径时就会失效。LangGraph则基于相反的假设,将带有循环的有向图作为其核心元素。
为何线性链不够用
链与图的区别在于实用性而非表面形式。编写代码时,链假设你知道操作的确切顺序;而图则承认系统中的执行路径会受运行时状态、中间结果以及无人能预测的条件影响。
想象一个正在调研某公司的智能体。它从财务数据开始分析。之后是获取新闻、查阅分析师报告还是阅读竞争对手的公开文件,都取决于财务数据所揭示的信息。这种条件性的重复行为需要循环和分支结构,而链无法表达这两者中的任何一种。
常见的智能体模式是一个循环:思考该做什么,执行动作,观察结果,然后重复此过程直至达成目标。而对于基于链的工具而言,只有两种不佳的选择:要么将循环展开为固定步数,这会丧失灵活性;要么在框架之外自行编写控制流,这又会牺牲组合性。在LangGraph中,循环本身即为一等概念,通过带有条件边的循环图来表达。
节点
节点代表计算过程中的一个独立步骤。它接收当前状态,执行某些操作并返回更新结果。这些操作可以是用于做出决策的LLM调用、用于操作外部系统的工具调用、数据转换,或是某种路由逻辑。
节点是具有特定结构的普通 Python 函数:输入为当前状态,输出为更新后的字典。这种函数式接口使得节点易于组合和测试。节点从不直接修改状态,而是报告所需的更改,随后 LangGraph 会根据你定义的规则将这些更改合并起来。
节点可以简单到仅执行一次大型语言模型调用,也可以复杂到包含多步骤逻辑。有些节点会调用其他整个图结构,从而实现分层式智能体;还有些节点以异步方式运行,或封装带有指数退避机制的重试逻辑。该框架并不关心节点内部的复杂程度。
边
边决定了控制权下一步将流向何处,共有两种类型,其语义截然不同。
- 静态边具有确定性。当节点A处理完成时,控制权总会传递给节点B。它们的行为类似于普通的函数调用,适用于那些事先已知的流程。例如,在研究任务初始化之后,接下来总是进行数据收集。
- 条件边会根据状态动态选择路径。一旦数据收集结束,可能需要判断是否有足够的数据开始分析,或者是否需要重试失败的API调用。这类决策无法在设计阶段确定,因此由路由函数在运行时做出判断。
路由函数会检查当前状态,并返回一个字符串来指定应采用的路径。框架会为这些字符串与目标节点建立映射关系。这样一来,就可以构建决策树、重试逻辑、错误处理机制以及自适应流程,同时让路由过程更加明确且易于测试。
作为共享内存的状态
状态管理是LangGraph设计中尤为有趣的部分。状态对象是一种在图中传递的共享结构。每个节点都会读取当前状态,而对其进行的任何更新都会在下一个节点执行之前被合并进来。因此,每一步都是在之前步骤所获取的信息基础上进行的。
你可以使用带类型的字典或Pydantic模型来定义状态,这不仅能确保类型安全,还能为各个节点之间提供清晰的接口规范。该架构明确了存在的字段、它们的类型以及更新是如何被合并的。最后这一点很容易被忽视,但实际上非常重要。
当某个节点返回更新数据时,LangGraph需要决定如何将其与现有数据合并。对于字符串或数字,替换旧值是显而易见的。但对于列表或字典则不然:是应该将返回的列表追加到现有列表中,还是替换旧列表,又或是逐个元素合并?归约函数负责回答这些问题。
归约函数
归约函数是一种控制更新数据如何与现有状态合并的函数。它将LangGraph的状态从一个简单的容器转变为能够处理累积模式的结构。
默认情况下,新值会直接替换旧值。这对大多数字段来说没问题,但对于需要累积数据的情况则不合适。想象一个在运行过程中收集问题的错误列表,你并不希望每个新节点都覆盖之前的记录,而是希望新错误能与旧错误一起保留。add_messages reducer就是基于这一理念设计的,而且这种模式也可用于其他类型的数据。
你可以为任何字段编写自定义reducer。它的输入是状态中已有的值以及新传入的值,输出则是该字段处理后的最终值。对于错误列表,它会将多个列表拼接起来;对于字典,它可能会深度合并嵌套结构;而对于计数器,则会直接累加数值。
reducer是通过Python的Annotated类型提示来定义的,因此数据结构描述依然保持声明式风格:
from typing import TypedDict, Annotated
def append_lists(existing: list, new: list) -> list:
return existing + new
class AgentState(TypedDict):
query: str # Simple replacement
results: Annotated[list, append_lists] # Custom accumulation
iteration: int # Simple replacement
这样能使合并行为更加明确且集中,而非分散在各个节点中。
检查点机制
生产环境中的代理程序通常会运行很长时间,会进行成本较高的 API 调用,或处理那些绝不能因临时故障而丢失的操作。检查点机制正是为了解决这些问题。启用该机制后,LangGraph 会在每个节点处理完毕后立即保存完整状态。如果进程崩溃或内存不足,执行可以从上一个检查点继续,而无需重新开始。原本脆弱的顺序脚本 thus 变成了可恢复的工作流。
检查点机制还使得人工干预成为可能。执行过程可以在选定位置暂停,以便人工审查或提供输入,待其回应后再继续执行。由于检查点记录了暂停时的确切状态,因此即便中间间隔数天也无需担心。
其背后的存储支持插件式扩展。内存中的检查点机制适用于开发阶段,SQLite可实现本地数据持久化,而Redis则适合分布式系统。图结构保持不变,只有检查点实现方式会改变。
条件路由
路由正是让系统行为真正具备动态性的关键所在。根据已积累的状态在运行时选择下一步操作,这正是将僵化的自动化流程与适应性强的智能体区分开来的核心。
路由函数与边缘映射
路由函数是一个小型 Python 函数,它根据状态返回一个字符串。当执行流程到达条件分支时,LangGraph 会调用该函数,获取返回的字符串,然后在映射表中找到对应的节点,并将控制权交予该节点。由于路由是通过字符串而非直接的节点引用来实现的,因此你可以修改映射表而不必改动函数,或者更换不同的路由函数而不改变图的结构。
典型的路由决策问题包括:是否有足够的数据继续处理、某项操作是否成功或需要重试、是否已达到重试次数限制,以及分析结果的可靠性是否足够高以采取相应行动。该函数能够查看所有状态信息,因此可以比较多个字段、进行计算、参考外部条件或实现业务规则编码。唯一的要求是它必须是确定性的且纯函数,即对于相同的输入状态始终给出相同的输出结果。
避免无限循环
循环虽然能实现强大的迭代功能,但也可能引发无限循环。陷入重试状态的智能体会无止境地消耗资源,LangGraph提供了几种方法来防止这种情况发生。
最简单的方法是在状态中设置计数器。路由函数会将其与最大值进行比较,一旦超过限制就会将控制权发送到终止节点。几乎所有的生产级代理都采用这种模式。更精细的选项包括收敛检测,即当迭代间的变化幅度低于特定阈值时停止运行;以及基于超时的终止机制,路由功能会考虑已经过的时间。
关键在于,防止循环是路由功能的工作,而非框架本身的限制。LangGraph提供了构建适合您具体场景的防护机制的所需组件,而非强制实施统一的规则。
工具:从语言到行动
只有当大型语言模型能够接入外部系统时,代理才能真正发挥作用。工具正是将语言理解转化为现实世界行动的途径。
执行模式
在LangGraph中,工具是一种能够在流程之外执行操作的节点。它可能调用API、查询数据库、运行代码或搜索网络。它会从状态中获取参数,执行相应操作,然后将结果作为状态更新返回。
当这种模式与大语言模型的判断能力结合时,其效力会大幅提升。大语言模型节点会读取状态并决定调用哪个工具,路由函数则将控制权传递给对应的工具节点。工具运行后将结果写入状态,控制权再返回给大语言模型,后者会对结果进行解读并选择下一步行动。如此循环直至目标达成。
现代模型支持函数调用,这意味着它们会发出包含工具名称及其参数的结构化请求。大语言模型节点会从模型的响应中解析此类请求,并将选定的工具记录到状态中。路由函数则读取这一选择,将流程导向正确的工具节点。
处理工具错误
外部系统可能会故障、超时、返回异常数据,从而打破原有的假设。工具节点需要提前做好应对准备。
- 捕获并报告。 捕获异常,将其记录下来,并在状态中返回错误详情,而非让异常导致整个代理程序崩溃。这样大型语言模型就能了解问题所在,尝试使用其他参数重新处理或采取其他解决方案。
- 超时处理。 将操作封装起来,使其在达到最大时长后触发异常。单个响应缓慢的 API 不应导致整个代理程序停滞。
- 验证。 解析响应内容,并根据预设的架构进行校验。异常数据应触发错误处理流程,而非被存储下来,以免进一步影响后续的节点处理。
ReAct循环
ReAct即“推理与行动”的缩写,是一种基础智能体架构,LangGraph能够自然地体现这一架构,通过它可以看出其他概念是如何相互关联的。
循环的运行方式
它从一个推理节点开始。大型语言模型会分析当前状态并决定下一步该做什么:调用工具、请求更多信息、给出最终答案或要求人工输入。此时模型的能力就显得至关重要,因为它能够权衡复杂的背景信息并做出精准的决策。
如果系统决定采取行动,控制权就会转移到执行选定操作的动作节点,无论是调用 API、进行计算还是查询数据库。该动作节点始终以确定且有限的方式执行请求并返回结果。
接下来是观察节点,它将结果添加到状态中,并为大型语言模型做好准备。它可能会对数据进行格式化、过滤掉噪声,或计算出有助于更直观理解结果的衍生指标。之后循环会带着更丰富的状态回到推理阶段。大型语言模型会了解已发生的情况并选择下一步行动,如此持续直至拥有足够的信息来完成任务。
知道何时停止
决定何时结束是该循环中至关重要的部分,实践中会出现多种相应的模式。
- 目标达成。最理想的情况是:大语言模型宣布任务已完成并给出最终答案,路由函数检测到这一点后便转向终止节点。
- 迭代次数限制。无论是否达到目标,当经过最大次数的“推理-行动”循环后,循环就会结束,因此智能体最终总会停止运行。
- 置信度阈值。如果模型达到足够的确定性,比如95%,它就可以提前停止,而无需继续收集回报越来越低的资料。
- 致命错误。当关键操作持续失败或智能体遇到无法处理的情况时,它应该通过错误报告结束运行,而非毫无意义地继续下去。
多个智能体协同工作
有些任务更适合由一组专业代理共同完成,而非由一个单一的代理处理。图表能让几种协作模式一目了然。
层级结构
在层级设计中,高级规划器会将任务分解并分配给各专业代理。它负责把握整体目标并协调各方,但不执行具体工作。从实现方式来看,某些节点会调用其他已编译的图表。规划节点可能会分析一项复杂的任务,将其分解,然后为每一部分运行相应的专业图表。处理结果会反馈给规划器,由其整合为最终输出。
其主要优势在于职责分离。每个专业代理都可以独立开发、测试和优化,而规划器只需知道如何协调它们,无需了解其具体工作原理。
对等节点
智能体也可以以平等的身份协作,各自贡献自己的专业技能。一个软件智能体团队可能包含负责架构、实现、测试和文档编制的独立智能体,它们通过共享的消息总线进行交流。系统通过一个所有智能体都能读写的全局状态来构建。路由功能会根据当前状态及所需技能决定下一个由谁来执行任务。每个智能体完成自己的工作后,会附上关于其所处理内容的元数据并交还控制权,随后路由器会选择下一个合适的智能体。
同行协作模式能够带来多元化的观点,让智能体互相发现错误,其中一个智能体可以指出另一个智能体推理中的缺陷。
辩论与共识
对于重要决策,多个智能体可以独立解决问题,然后再通过讨论达成一致。这种方法借鉴了宪法式人工智能的理念,将意见分歧视为一种优质信号。三个智能体分析某一情况,各自提出解决方案。由一名协调者轮流主持辩论,每个智能体都要阐述自己的推理过程、批评其他智能体的观点,并回应具体的批评。经过几轮讨论后,通过共识步骤将讨论内容整合为最终决策,必要时还会对最具说服力的论据给予更高权重。
这种做法能够揭示那些单个智能体可能忽略的幻觉与推理错误,而当多个智能体从不同角度审视问题时,这些错误就会被发现。
智能体内部的检索
检索增强生成技术让智能体能够访问庞大的知识库和领域资料,而图结构则使其与这种技术天然契合。
按需检索
传统的RAG会在任务开始时一次性获取信息。而在代理型RAG中,检索变成了代理在发现信息缺口时随时调用的工具。代理会从自身的基础知识出发进行推理。当遇到无法回答的问题或需要更多上下文时,它会编写检索查询,调用该工具,并利用新获取的信息继续处理任务。
这种方式相比预先加载信息有两大优势:首先成本更低,因为代理只获取所需信息;其次更准确,因为查询内容是根据代理当前的理解来生成的。
由状态驱动的查询
真正的价值体现在查询能够反映迄今为止收集到的所有信息,而不仅仅是最初的问题。以企业调查为例,它可能从一项全面的概览查询开始。查询结果提到了某次近期收购,这便引出了针对该交易的针对性查询。随后又发现了相关的监管问题,进而需要查询相关规则。每一次查询都建立在前一次的基础上,这样就能进行有针对性的调查,而非零散无序的信息收集。
多种数据来源
智能体通常会参考具有不同延迟、成本和可靠性的数据来源:内部文档、外部API、网络搜索、专业数据库等。LangGraph支持多源策略。智能体可以先检查快速的本地缓存,若不行则转向API,只有在万不得已时才会使用成本较高的网络搜索。或者它也可以同时查询多个来源,并让大语言模型协调那些可能存在冲突的答案。
路由逻辑可以根据问题类型选择数据来源。事实类查询会指向结构化数据库,概念类问题则通过向量搜索在文档中查找,近期事件通过网页搜索获取,而历史类问题则从精心整理的知识库中检索。
使其具备生产环境适用性
使用LangGraph很容易构建概念验证版本的任务代理。而投入生产环境则需要满足可靠性、可观测性及运维方面的要求。
各层面的错误处理
生产代理在多个层面处理故障。工具节点会捕获预期出现的错误,并以状态更新的形式返回这些错误,而非异常。路由功能会检测错误状况,并通过指数退避策略设置重试路径。该架构还包含专门的错误处理节点,它们会分析累积的错误,决定是进行重试、回退还是优雅终止操作。对于关键操作,断路器可在多次失败后暂停尝试,从而避免故障级联,同时减轻出现故障的服务的负担。
查看发生的情况
当出现问题时,你需要知道智能体做了什么以及原因何在。LangGraph可与LangSmith等平台集成,提供每个节点执行过程、状态转换以及LLM调用的详细追踪信息。节点应输出包含足够上下文的结构化日志,以解释其决策依据。时间数据可显示瓶颈,令牌计数有助于成本核算,而用户交互跟踪则能为评估提供依据。从检查点重新运行尤其方便——你可以加载出出错时的确切状态,逐步排查根本原因。
控制成本
在大多数智能体中,LLM调用是成本的主要组成部分,不过有一些方法可以在不影响质量的前提下降低成本。
- 模型选择。仅在需要复杂推理时使用GPT-4这类强大模型;对于简单的分类或提取任务,则交给更快、更便宜的模型处理,可通过任务分析逻辑来选择合适的模型。
设计指南
部署LangGraph智能体有助于识别出哪些设计模式能让系统更加稳定,而非沦为脆弱的原型。
状态架构
架构是基础。应使用 Pydantic 模型而非松散的字典,以便在早期发现错误;同时需为每个字段记录其含义与用途。可选字段能让不同节点逐步贡献数据,而必填字段则能确保关键数据存在。区分式联合类型可用于处理根据不同路径呈现不同形态的状态。应避免过深的嵌套,以免难以访问字段。尽可能简化结构,使用能体现关联关系的命名方式,并将元数据与业务数据分开,从而使控制信息保持独立。
节点边界
优秀的节点能够出色地完成单一任务。这使它们能够在不同图中重复使用,同时也更易于测试和维护。工具节点应仅与外部系统交互,而不应包含业务逻辑。转换节点负责执行转换操作而不做决策;决策节点则负责路由处理而不执行具体操作。应避免隐藏的副作用:节点应尽可能保持纯粹,通过明确的方式返回每一次状态变化,而非修改全局变量。
图的结构
该结构应反映智能体的推理方式。首先是一个设置阶段,用于检查输入并准备上下文。接着可以并行收集数据。之后添加分析节点,再添加生成输出的综合节点,以及用于清理工作的最终处理节点。在下一步取决于先前发现结果的真实决策点处设置条件边,而在流程为确定性时则使用静态边。每个图应保持同一抽象层次。如果其中某部分实现了复杂的子流程,应将其提取为独立的图,并以节点的形式调用。
测试
图结构可以在多个层面进行测试。通过传递模拟状态并检查更新结果来对节点进行单元测试;通过输入不同状态并检查返回的路径来测试路由功能;使用已知输入对整个图结构进行集成测试,以验证最终状态。通过模拟大语言模型的响应可使测试更快且结果可预测,而使用记录的真实响应则能为测试提供更贴近实际的场景。基于属性的测试能够验证在广泛输入范围内的行为表现。务必彻底覆盖关键路径,尤其是错误路径,因为这些路径在实际运行中很难触发,但一旦出现却至关重要。
判断是否需要它
LangGraph功能强大,但并非所有大语言模型应用都需要使用它。
当代理需要通过迭代方式推理,不断收集和分析信息并决定是否继续深入探索时,此方法适用。此外,在控制流依赖于运行时状态的情况下——比如重试、错误恢复或自适应工作流,当多个工具或系统之间存在复杂依赖关系,状态需要在不同步骤或会话之间保持一致,执行过程必须为人类操作暂停和恢复,或是多个代理需要协作或形成层级结构时,此方法也同样适用。
当任务仅为简单的提示与响应,没有循环或分支,流程是线性的且可预先知晓,工作内容可由一系列确定性步骤构成,无需在单次运行后保留任何数据,或者应用最多仅调用一两个简单API时,使用更简单的方法就足够了。 在这类情况下,使用LangChain的链式结构或直接调用API能生成更清晰、更易于维护的代码,相比强行将问题转化为图结构更为合适。
逐步扩展
许多应用都是从简单开始逐渐变得复杂的。合理的系统设计本就应如此。可以先从链式结构开始,但需保持提示、工具和业务逻辑的模块化与松耦合,以便日后轻松转换为图结构。一旦链式结构包含五到七个步骤,或者加入了第二个条件分支,就应在复杂度失控之前考虑进行迁移。
高级技术
在基础功能之外,还有一些技术能够带来更多可能性。
并行执行。那些彼此的输出无关的节点可以同时运行。例如,调用不同 API 的数据收集节点在并行运行时能缩短总耗时,框架会自动收集并合并这些结果。此时需要明确的并行连接,并且必须确保这些节点真正独立,不存在通过共享资源或全局状态而产生的隐式耦合。
流式处理。对于需要长时间运行的任务,展示中间结果能提升用户体验。节点可以发送部分状态更新,让观察者立即看到最新情况。这非常适合报告生成,因为内容可以边编写边显示;也适用于搜索,因为结果可以在找到时立即呈现。
子图。复杂的智能体可以从分层分解中获益。子图会被单独编译,然后作为父图中的节点被调用,它们接收父图的部分状态,并返回需要合并回父图的更新信息。不同的工程师可以负责不同的子图,独立地部署和测试它们,同时可以在多个父图中重用常见的子图。
动态图结构变化。大多数图在设计阶段是固定的,但某些高级场景会在运行时修改图结构。规划智能体在分析任务后可能会构建自定义图,即时创建节点和边。这种方式会使行为更难理解,因此应谨慎使用。不过它确实能让元智能体为新型任务设计和运行自定义工作流。
总结观点
LangGraph代表了一种日益成熟的代理系统工具集。它的优势不在于复杂性,而在于其基本组件与代理行为方式的契合度:代理能够通过循环进行推理、行动和观察,依据积累的知识做出决策,协调各种工具,并在出现问题时进行调整。你可以直接使用这些模式,而无需从头构建。
要获得良好的效果,关键在于掌握状态管理、条件路由和工具集成这些架构模式,而不仅仅是将其视为功能。优秀的应用会展现出精心设计的状态结构、明确的节点职责、周全的错误处理机制以及完善的可观测性。该框架提供的是工具而非解决方案,如何有效运用这些工具取决于你的领域知识与判断力,因此建议从简单入手,尽早验证假设,让实际需求而非想象中的需求来塑造架构。
相关阅读
- 智能体还是工作流?企业级人工智能中推理与执行的分离 —— 利用智能体处理意图识别与信息检索,用确定性工作流处理事务处理:RAG、LangGraph、MCP、审批流程及SQS如何共同构成更安全的架构。
- 多智能体韩剧推荐系统:BERTopic、RAG与互反排名融合技术 —— 这种注重用户偏好的韩剧推荐系统如何结合BERTopic标签、ChromaDB检索功能、对话智能体、查询扩展技术以及互反排名融合方法,再由大语言模型进行最终排序。