本章涵盖以下内容:
- 智能体工作流与智能体概览
- LangGraph 基础与状态管理
- 从 LangChain 链迁移到智能体式工作流
大语言模型(LLM)正在推动新一代应用的发展,而这类应用所需要的,早已不只是简单的“提示—响应”式交互。随着应用变得越来越复杂,智能体式工作流(agentic workflows) 已经变得不可或缺——这是一种模式:由 LLM 使用预定义组件和显式状态管理来编排一个结构化、多步骤的流程。智能体式工作流遵循一套定义明确且一致的步骤序列。它们并不是在执行过程中动态调整自身行为,而是更强调可靠性、透明性与可控性。在这种方式下,LLM 的决策发生在明确规定的边界之内,从而确保流程中的每个阶段都保持结构化并且可复现。在本书后面的部分,我们还将继续探讨建立在这些原则之上的智能体架构,以实现更高程度的自主性与适应性。
5.1 理解智能体式工作流与智能体
由 LLM 驱动的智能体系统,通常遵循两种核心设计模式之一:智能体式工作流(agentic workflows) 和 智能体(agents) 。这两种模式分别决定了应用如何运作,如图 5.1 所示。由于这两个术语经常被混用——但它们之间又确实存在重要差异——因此,在继续深入之前,必须先准确理解这里所说的“智能体式工作流”和“智能体”分别是什么意思:
智能体式工作流(agentic workflow,或简称 workflow) —— 通过一套固定的、预先确定好的步骤序列来引导应用运行。LLM 被用于在预定义选项之间做选择,从而帮助系统完成任务并管理整体流程。
智能体(agent) —— 语言模型在这里的作用不只是执行任务:它们还会推理、做决策,并根据可用工具和不断演化的上下文,动态决定下一步该怎么做。这里所说的工具(tool),通常指的是一个能够返回数据或处理数据的函数。
图 5.1 工作流与智能体。工作流会使用 LLM 从一组固定选项中选择下一步,例如把请求路由到 SQL 数据库或 REST API,并结合相关结果来综合生成答案。而智能体则会动态地选择并组合工具,以达成其目标。
虽然这两种模式都依赖 LLM 来驱动应用行为,但工作流会保持一条结构化、可预测的路径,而智能体则能够根据新信息和目标变化进行实时适应。下面我们先进一步讨论工作流。
5.1.1 工作流
工作流会使用 LLM 从有限的一组选项中挑选下一步。它们通常会实现诸如 Controller-Worker 或 Router 这样的模式,如图 5.2 所示。
图 5.2 常见工作流模式。Controller-Worker 模式在控制器中使用 LLM,并通过按一定顺序把不同任务分配给 worker 来编排整个流程。Router 模式中,LLM 则只是根据上下文把任务导向合适的 worker。
在 Controller-Worker 模式中,控制器会按照某种顺序为 worker 派发任务。在 Router 模式中,LLM 只是简单地把任务定向到合适的处理器(或 worker)上。本章将聚焦于工作流,我们会把之前用 LangChain 构建的 Web 研究应用,改造为一个使用 LangGraph 构建的智能体式工作流。
5.1.2 智能体
基于 LLM 的智能体会使用语言模型来感知数据、对数据进行推理、决定采取什么动作,并最终实现目标。高级智能体能够保留过去交互的记忆,构建动态工作流,甚至还能从反馈中学习。与固定的“提示—响应”系统不同,智能体会基于实时数据和可用工具生成新的流程。在本书第 5 部分——即第 11 到第 14 章之间——我们会介绍多工具智能体,以及更复杂的多智能体系统。
5.1.3 什么时候使用基于智能体的架构
基于 LLM 的工作流与智能体这两个概念关系非常密切,并且经常相互重叠;二者之间并不存在一条绝对清晰的分界线。当你的应用需要把复杂任务拆分成更小的步骤、基于前一步结果做决策、访问外部工具或数据源,或者在长时间交互中维持上下文时,这两种方式都会特别有价值。只有当你的用例确实能从显式状态管理和动态控制中受益时,才最适合引入智能体式工作流或智能体——也就是说,要在它们带来的额外能力与额外复杂度之间做好权衡。
提示 如果你想更深入地理解工作流、智能体,以及什么时候该使用它们,我强烈推荐 Anthropic 的文章 “Building Effective Agents” (https://mng.bz/lZ0o)。
5.1.4 智能体开发框架
目前已有多种框架可用于构建基于智能体的系统,每种框架都有自己的关注重点与权衡取舍:
AutoGPT —— 强调完全自治、目标驱动的智能体,几乎无需人工监督,但在任务一致性方面可能面临挑战。
CrewAI —— 通过可创建并可在社区中共享的模板,支持构建协作式多智能体系统,适合面向专门化团队的场景。
LangGraph —— 支持通过基于图的执行方式构建有状态、可持久化的智能体式工作流。LangGraph 在处理复杂应用时非常强大,但可能需要更高的技术门槛。
LlamaIndex —— 在知识检索方面表现突出,但其覆盖范围相比更通用的智能体框架要窄一些。
Microsoft Autogen —— 支持高度可定制的多智能体对话,但学习曲线相对更陡。
Microsoft Semantic Kernel —— 更强调记忆与规划,并且与 Azure 服务集成良好。
n8n 与 LangFlow —— 提供可视化界面和大量集成能力,因此对非开发者也比较友好,但如果需要高级推理能力,往往还需要额外组件。
OpenAI Agent SDK 与 Google Agent Development Kit(ADK) —— 提供了更精简的 API,用于高效开发多智能体系统和智能体式工作流。
5.2 LangGraph 基础
LangGraph 构建在 LangChain 之上,用于管理更复杂的智能体式工作流,包括分支路径、有状态处理,以及步骤之间清晰的过渡关系。它是一个用于构建基于图结构的、有状态、多步骤 AI 应用的框架。在 LangGraph 中,节点(nodes) 表示单独的任务,例如生成文本、调用 API,或分析数据。边(edges) 定义连接这些任务的路径。状态(state) 则是在节点之间流动、并在每一步更新的信息。当你需要做决策、管理状态,或者处理复杂的智能体式工作流时,这种方式比传统链更合适。
注意 LangGraph 不是 LangChain 的替代品,而是它的扩展。你可以把 LangChain 理解为提供各种构建积木,而 LangGraph 则提供一张蓝图,用来把这些部件连接成一个复杂系统。LangChain 提供诸如 LLM、embeddings、retrievers 等组件,而 LangGraph 则帮助你把这些组件组织成一个结构化、有状态的工作流。
如果想高效使用 LangGraph,你需要先理解几个关键概念,包括图的核心组成部分(节点与边)、状态如何在图中流动,以及条件边如何控制图的行为。这些正是下一节将要展开的基础构件。
5.3 从 LangChain 链迁移到 LangGraph
LangChain 中那种简单、线性的链,对于直接、单一路径的任务是足够的,但当你的应用开始变复杂时,它就会显出局限。一个典型的 LangChain 写法往往像这样:
chain = (
prompt_template
| llm
| output_parser
)
当任务需要分裂成不同路径、需要根据新信息反复执行某些步骤,或者需要在多步过程中维护状态时,这种结构就会显得吃力。当多个过程需要并行发生时,它也同样不够理想。
LangGraph 通过提供更好的状态管理、条件分支以及对循环工作流的支持,解决了这些问题。借助显式状态管理,你可以在整个工作流中一致地定义并追踪数据,这对记忆和推理来说至关重要。条件分支允许智能体根据前一步的结果选择不同路径,从而让决策过程更自然。循环工作流则让智能体能够反复执行某项任务,直到满足特定条件,这对于不断优化结果非常有帮助。
LangGraph 也让复杂工作流更容易理解与调试。它的图结构让整个系统中的数据流向更清晰可见,因此当你需要追踪或修复问题时,会轻松很多。
这种基于图的方式非常适合多种用例,例如多步骤推理、任务规划、长对话中的上下文管理、研究任务协调,以及业务流程自动化。随着应用越来越复杂,LangGraph 这种基于智能体的架构所带来的好处会越来越明显。它提供了构建智能多步骤系统所需的控制力与灵活性,使这些系统能够自行适应并做出决策。
5.4 LangGraph 核心组件
LangGraph 为构建有状态、多步骤 AI 应用提供了一个强大的框架。图 5.3 展示了构成 LangGraph 应用基础的核心组件。
图 5.3 LangGraph 核心组件。一个强类型状态(本例中由 ResearchState 建模)会在整个工作流中流动。节点通常是 Python 函数(例如 def select_assistant),负责执行任务;边则在节点之间创建有向数据流,在某些情况下还会带有条件路径。
每个 LangGraph 应用的核心都是一个状态对象——在我们的例子里,它叫 ResearchState——它为整个工作流定义了一个清晰且强类型的状态。这个状态通常会被定义为 Python 的 TypedDict,从而确保在组件之间传递的数据有良好的结构,并能进行类型检查。
在 LangGraph 中,每个节点都充当一个处理单元。节点可以负责生成搜索查询、调用外部 API、摘要结果、转换数据等任务。这些节点通常都以 Python 函数的形式实现。节点之间的边,则决定了数据的有向流动,也就是规定了信息如何在图中穿行。
LangGraph 的一个强大特性,是它支持条件边(conditional edges) ,你可以基于运行时状态定义动态的执行路径。再结合入口点(entry points)和结束条件(end conditions),你就可以完全控制图从哪里开始、如何推进,以及何时结束。接下来的小节会逐步讲解如何定义并连接这些组件,从而使你能够构建出能够处理复杂工作流与自适应决策的系统。
5.4.1 StateGraph 结构
LangGraph 中一个核心工具是 StateGraph 类。你会用它来定义描述应用工作流的图。例如:
from langgraph.graph import StateGraph
from typing import TypedDict
class ResearchState(TypedDict): #1
input_query: str
intermediate_result: str
final_output: str
graph = StateGraph(ResearchState) #2
#1 Defines a state structure
#2 Creates a graph
5.4.2 状态管理与类型定义
状态管理是 LangGraph 应用的核心。与依赖隐式状态或弱类型状态的链式方法不同,LangGraph 强制使用显式的、强类型的状态,这使得工作流更加稳健、也更加可预测。下面是一个扩展版的 ResearchState,它包含了更多细节:
from typing import TypedDict, Optional, List
class ResearchState(TypedDict):
user_question: str
assistant_info: Optional[dict]
search_queries: Optional[List[dict]]
search_results: Optional[List[dict]]
research_summary: Optional[str]
final_report: Optional[str]
每个节点都会接收当前状态,并返回一些更新内容,这些更新会被合并进整体状态中:
def process_node(state: dict) -> dict:
result = do_something(state["input_data"]) #1
return {"output_data": result} #2
#1 Processes the state
#2 Returns the state update
5.4.3 节点函数与边的定义
节点代表处理步骤。每个节点都是一个函数:它接收当前状态,并返回状态更新。例如:
def generate_search_queries(state: dict) -> dict:
"""Generate search queries based on user question."""
question = state["user_question"]
queries = llm_generate_queries(question)
return {"search_queries": queries}
graph.add_node("generate_queries", generate_
↪search_queries) #1
#1 Adds a node to the graph
边则定义了节点之间允许发生的迁移。一个简单的线性边写法如下:
graph.add_edge("generate_queries", "perform_searches")
而条件边会使用一个函数,根据当前状态决定下一个节点是什么:
def should_refine_queries(state: dict) -> str:
if len(state["search_results"]) < 2:
return "refine_queries"
else:
return "summarize_results"
graph.add_conditional_edge("perform_searches", should_refine_queries)
5.4.4 入口点与结束条件
每张图都需要一个起点和清晰的结束条件。例如:
graph.set_entry_point("parse_question") #1
from langgraph.graph import END
graph.add_edge("write_final_report", END) #2
#1 Sets the entry point
#2 Defines the graph end
接下来,我们将进入一个 LangGraph 的实际应用示例。在那里,本节介绍的这些概念——强类型状态、节点函数、边、入口点和结束条件——会一起汇聚成一个真实工作流。
5.5 将 Web 研究助手改造成一个 AI 智能体
为了演示 LangGraph 是如何工作的,我会带你把第 4 章中那个最初基于 LangChain 构建的 Web 研究助手,改造成一个基于智能体的系统。升级之后,这个应用将能够评估来自网页结果摘要的相关性;如果其中少于 50% 是相关的,它就会把流程重新导回到“生成新的搜索查询”这一步。如果相关摘要足够多,应用就可以像往常一样继续写出最终报告。要在纯 LangChain 中实现这种程度的动态控制会非常复杂,这也正是为什么我们要转向使用 LangGraph 的智能体式方式。这个案例研究会逐步带你完成整个过程,并突出展示显式状态管理与模块化设计的好处。
5.5.1 原始 LangChain 实现概览
我们原来的 Web 研究助手使用的是 LangChain 的顺序链。整个流程包括以下几个步骤:
- 根据用户问题选择合适的研究助手
- 生成搜索查询
- 执行 Web 搜索并收集 URL
- 抓取并摘要每个搜索结果
- 编译最终研究报告
每一步的输出都会作为下一步的输入。你可以从下面这段原始实现代码摘录中看出这一点。
代码清单 5.1 Web 研究助手的原始实现
assistant_instructions_chain = (
{'user_question': RunnablePassthrough()}
| ASSISTANT_SELECTION_PROMPT_TEMPLATE
| get_llm()
| StrOutputParser()
| to_obj
)
web_searches_chain = (
# ...input processing...
| WEB_SEARCH_PROMPT_TEMPLATE
| get_llm()
| StrOutputParser()
| to_obj
)
web_research_chain = ( #1
assistant_instructions_chain
| web_searches_chain
| search_and_summarization_chain.map()
| RunnableLambda(lambda x: # ...process results...)
| RESEARCH_REPORT_PROMPT_TEMPLATE
| get_llm()
| StrOutputParser()
)
#1 Final research chain
这种方式虽然能工作,但也有很明显的局限:
- 流程是刚性的、线性的,很难根据中间结果动态调整。例如,如果你想实现这样一种条件流程:当摘要中少于 50% 相关时,系统应返回去重新生成搜索查询,那么用这种方式写起来会很笨重。
- 错误处理比较困难,因为没有显式状态,很难有效地追踪和管理失败。
- 状态没有被显式管理,这使得在多个步骤之间维护上下文变得复杂。
- 一旦流程复杂起来,调试会很困难,因为你不清楚到底是链中的哪一部分出错了,也不清楚原因是什么。
5.5.2 识别要转换的组件
要把这个 Web 研究助手迁移到 LangGraph,我们首先需要识别出那些将被转换为节点的关键组件。每个节点都负责流程中的某个特定部分:
Assistant Selector —— 根据用户问题决定使用哪一种研究助手
Query Generator —— 基于用户输入创建搜索查询
Web Searcher —— 根据生成的查询执行搜索并收集 URL
Content Summarizer —— 抓取网页内容并生成摘要
Relevance Evaluator —— 评估这些摘要是否足够相关,以决定是继续,还是需要重新生成新的搜索查询
Report Writer —— 使用那些相关摘要来编写最终研究报告
与简单的线性流程不同,这里引入了一个条件分支元素。在评估摘要相关性之后,流程有两种可能:如果有足够多的内容是相关的,就进入 Report Writer;如果相关内容不足,就返回 Query Generator 重新生成搜索查询。这个决策基于一个预定义阈值(例如:如果少于 50% 的摘要相关),而且可以重复最多三轮,以避免无限循环。
整个流程控制由一个条件路由函数 route_based_on_relevance 管理。它会检查搜索结果的相关性,以及当前的迭代次数。如果相关性不足,且尚未达到最大迭代次数,那么应用就会生成新的查询,并重复搜索与评估步骤。如果最大迭代次数已经达到,那么无论相关性是否足够,应用都会使用当前已有结果来编写报告。
对于每个组件,我们都要定义以下三件事:
- 输入状态 —— 节点运行所需的数据
- 处理逻辑 —— 节点执行的任务
- 状态更新 —— 节点返回给整体状态的更新信息
这种模块化、条件化的做法,使系统既灵活又具备适应性;而如果用 LangChain 的线性链来实现这些能力,就会显得非常笨重。接下来,我们就正式开始这个改造过程。
5.5.3 逐步转换过程
现在,我将带你一步步把一个 LangChain 应用转换成 LangGraph 应用。下面展示的是实际代码的一个简化版本,完整版本可在 GitHub 仓库中找到。
第 1 步:定义状态
第一步,是设计将会在整张图中流动的状态结构。一个定义良好的状态,有助于你在所有节点之间持续追踪数据。在我们的例子中,我们会用嵌套类型来建模一个复合状态,如代码清单 5.2 所示。这种状态结构会清楚地定义每个阶段可以访问哪些数据,从而减少歧义并简化调试。
代码清单 5.2 基于 LangGraph 的研究助手的状态类型
from typing import List, Dict, Any, TypedDict, Optional
class AssistantInfo(TypedDict): #1
assistant_type: str
assistant_instructions: str
user_question: str
class SearchQuery(TypedDict): #1
search_query: str
user_question: str
class SearchResult(TypedDict): #1
result_url: str
search_query: str
user_question: str
is_fallback: Optional[bool]
class SearchSummary(TypedDict): #1
summary: str
result_url: str
user_question: str
is_fallback: Optional[bool]
class ResearchReport(TypedDict): #1
report: str
class ResearchState(TypedDict): #2
user_question: str
assistant_info: Optional[AssistantInfo]
search_queries: Optional[List[SearchQuery]]
search_results: Optional[List[SearchResult]]
search_summaries: Optional[List[SearchSummary]]
research_summary: Optional[str]
final_report: Optional[str]
used_fallback_search: Optional[bool]
relevance_evaluation: Optional[Dict[str, Any]]
should_regenerate_queries: Optional[bool]
iteration_count: Optional[int]
#1 Typed dictionaries for state handling
#2 Graph state
第 2 步:把组件转换成节点函数
接下来,我们把每个组件转换成一个节点函数。每个函数都会接收当前状态、执行处理逻辑,并返回更新后的状态信息,如下一个代码清单所示。这里展示的是简化版,完整实现可以在 GitHub 仓库中查看。
代码清单 5.3 节点函数
def select_assistant(state: dict) -> dict:
"""Select the appropriate research assistant."""
user_question = state["user_question"]
# Use the LLM to select an assistant
prompt = ASSISTANT_SELECTION_PROMPT_TEMPLATE.format(
user_question=user_question
)
response = get_llm().invoke(prompt)
assistant_info = parse_assistant_info(
response.content) #1
return {"assistant_info": assistant_info} #2
def generate_search_queries(state: dict) -> dict:
"""Generate search queries based on the question."""
assistant_info = state["assistant_info"]
user_question = state["user_question"]
prompt = WEB_SEARCH_PROMPT_TEMPLATE.format( #3
assistant_instructions=assistant_info["assistant_instructions"],
user_question=user_question,
num_search_queries=3
)
response = get_llm().invoke(prompt)
search_queries = parse_search_queries(
response.content) #4
return {"search_queries": search_queries} #5
#1 Parses the response to extract assistant information
#2 Returns the state update
#3 Uses the LLM to create queries
#4 Parses the response to obtain search queries
#5 Returns the state update
其余节点函数也都遵循同样的模式,各自完成各自的任务。接下来,我们来定义整张图的结构。
第 3 步:定义图结构
当节点函数都准备好之后,我们就可以创建这张图,并定义节点之间如何连接,从而确定执行顺序与数据流向,如代码清单 5.4 所示。与简单的线性链不同,这个版本的图新增了一个“相关性评估”节点,以及一条条件边,用于根据搜索结果的相关性动态改变执行路径。
代码清单 5.4 图结构
from langgraph.graph import StateGraph, END
graph = StateGraph(ResearchState) #1
graph.add_node("select_assistant", #2
↪select_assistant) #2
graph.add_node("generate_search_queries", #2
↪generate_search_queries) #2
graph.add_node("perform_web_searches", #2
↪perform_web_searches) #2
graph.add_node("summarize_search_results", #2
↪summarize_search_results) #2
graph.add_node("evaluate_search_relevance", #2
↪evaluate_search_relevance) #2
graph.add_node("write_research_report", #2
↪write_research_report) #2
def route_based_on_relevance(state): #3
iteration_count = state.get("iteration_count", 0) + 1
state["iteration_count"] = iteration_count
if iteration_count >= 3:
return "write_research_report"
if state.get("should_regenerate_queries", False):
return "generate_search_queries"
return "write_research_report"
graph.add_edge("select_assistant", #4
↪"generate_search_queries") #4
graph.add_edge("generate_search_queries", #4
↪"perform_web_searches") #4
graph.add_edge("perform_web_searches", #4
↪"summarize_search_results") #4
graph.add_edge("summarize_search_results", #4
↪"evaluate_search_relevance") #4
graph.add_edge("write_research_report", END) #4
graph.add_conditional_edges( #5
"evaluate_search_relevance",
route_based_on_relevance,
{
"generate_search_queries": "generate_search_queries",
"write_research_report": "write_research_report"
}
)
graph.set_entry_point("select_assistant") #6
#1 Creates the graph
#2 Adds nodes
#3 Defines the conditional relevance evaluation
#4 Defines the edges between nodes
#5 Defines the conditional edge
#6 Sets the entry point
新增的 Relevance Evaluator 节点会检查,摘要结果中是否有足够比例的内容是相关的。如果少于 50% 的结果满足标准,那么图就会把流程重新导向 Query Generator,从而优化搜索。如果摘要已经足够,或者已经达到最多三轮迭代,系统就会继续前进,开始编写最终报告。这种条件流控制,相比 LangChain 那种刚性的线性链,是一个非常显著的增强,因为它使系统能够根据中间结果动态调整自身行为。
第 4 步:编译并运行图
定义完图之后,我们就可以像代码清单 5.5 那样,用一个初始状态来编译并运行它。这一步需要设置一个初始状态对象,并填好所有必需字段,其中还包括用于控制条件流程的额外参数,例如 should_regenerate_queries 和 iteration_count。
代码清单 5.5 运行图
app = graph.compile() #1
initial_state = { #2
"user_question": " What can you tell me about Astorga's roman spas?",
"assistant_info": None,
"search_queries": None,
"search_results": None,
"search_summaries": None,
"research_summary": None,
"final_report": None,
"used_fallback_search": False,
"relevance_evaluation": None,
"should_regenerate_queries": None,
"iteration_count": 0
}
result = app.invoke(initial_state) #3
final_report = result["final_report"] #4
#1 Compiles the graph
#2 Creates the initial state
#3 Runs the graph
#4 Extracts the final report
通过引入条件边和相关性评估,这个逐步转换过程把原本刚性、线性的链,变成了一个灵活、有状态、可适应的智能体式工作流。现在,系统可以评估自己的结果,如果需要还能够通过优化搜索查询来自我调整,并确保最终报告建立在足够相关的信息之上。若使用纯 LangChain 来实现这种适应性,会显得非常笨重;这也说明了为什么对于复杂的 LLM 应用,LangGraph 是更合理的选择。
5.5.4 代码对比与实现收益
这个案例研究展示了:与传统的 LangChain 链相比,LangGraph 在复杂的、多步骤 AI 应用中,如何显著增强灵活性、可控性与适应性。能够基于运行时评估结果实现条件流程控制,使 LangGraph 成为构建智能、具备上下文感知能力的智能体系统的一项强大工具。具体来说,LangGraph 方案带来了以下重要收益:
显式状态管理 —— 状态被清晰定义,并在每个节点之间传递,使数据处理透明且可靠。
模块化组件 —— 每个节点只负责单一任务,这使测试、调试和维护都更简单。
清晰的流程控制 —— 图结构把执行顺序和数据流向清楚地表达出来,使复杂流程更容易追踪和理解。
更容易调试 —— 节点和边都清晰定义后,你很容易定位出错位置,以及触发错误的数据是什么。
增强的错误处理能力 —— 每个节点都可以实现各自独立的错误处理策略,而不会影响系统其他部分。
条件流程控制 —— 基于相关性评估引入条件边之后,应用能够动态改变执行路径:如果结果不足,就继续优化搜索查询;如果结果足够,就进入报告编写。这种适应能力能让应用对中间结果做出智能响应,而这在纯 LangChain 中会很难实现。
未来可扩展性更强 —— 添加或修改节点时,对整体系统的改动非常小,因此升级和扩展新能力会更加平滑。
小结
- 智能体式工作流会按预定义步骤顺序执行。智能体则会根据中间结果或错误,动态选择工具并调整路径。
- LangGraph 使用有向图来构建工作流。节点表示处理函数,边定义迁移关系,条件边则根据运行时状态决定执行路径。研究助手就是一个例子:它可以基于前几次搜索得到的内容质量,决定是继续搜索更多来源,还是开始整理结果。
- 状态管理用于追踪工作流各步骤之间的数据。节点从类型化状态对象中读取和写入信息。对每个节点而言,状态是不可变的,但在整张图中会不断累积。
- 条件边根据运行时条件决定执行走向。比如,一个研究工作流可能会在检索内容不足时返回去继续搜索;如果已经获得了足够来源,则会继续进入综合整理阶段。
StateGraph类用于定义整个工作流结构。节点函数负责执行离散任务(如搜索、解析、摘要),边则按照你定义的逻辑将它们连接起来。- 把线性链转换成 LangGraph 图之后,原本耦合在一起的流程会被拆成离散节点。这会让调试、测试以及给工作流增加新能力都变得更简单。
- LangGraph 工作流会保留执行历史和中间状态。你可以检查推理路径、从某个检查点重新播放工作流,或者基于先前决策分叉执行。要在简单的 LangChain 链中实现这些能力是非常困难的。
- 你可以用 Python 的
TypedDict来定义状态,以获得强类型约束,例如:class ResearchState(TypedDict): question: str; search_queries: list[str]; results: list[dict]。这样可以确保节点之间流动的数据是经过类型检查的。 - 使用
graph = StateGraph(ResearchState)来创建图,其中ResearchState就是你定义的强类型状态。这会强制所有节点都接收并返回兼容的状态结构。 - 通过
graph.add_node("node_name", node_function)来添加节点,其中node_function是一个接收状态并返回状态更新的 Python 函数。节点函数在可能的情况下应尽量保持纯函数特性。 - 使用
graph.add_edge("source_node", "destination_node")来连接节点,构建有向数据流。这确定了节点之间的执行顺序。 - 通过
graph.set_entry_point("first_node")定义入口点,或者使用START常量来标记执行起点。第一个节点会接收初始状态。 - 通过
graph.add_edge("final_node", END)来标记终点,表示工作流结束。当执行到END时,系统会停止并返回最终状态。 - 在执行之前,需要先用
app = graph.compile()对图进行编译。这一步会校验图结构,并生成一个可执行应用。 - 节点函数接收当前状态作为输入,并返回部分状态更新,而不是整体替换整个状态。你只需返回想更新的字段,例如:
return {"search_queries": queries}。 - 可以通过
graph.add_conditional_edges("source_node", router_function, {"option1": "node1", "option2": "node2"})添加条件边。路由函数需要返回一个字符串,这个字符串必须匹配其中某个选项。 - 条件边的路由函数必须返回与后续节点名称匹配的字符串。可以使用
if逻辑来决定路由,例如:return "search_more" if len(results) < 3 else "write_report"。 - LangGraph 是对 LangChain 的扩展,而不是替代。你可以把 LangChain 中的组件(LLM、retriever、embedding 等)当作构建积木,在 LangGraph 节点中使用,从而构建复杂工作流。