6.5.1 LangGraph 的结构梳理
LangGraph 作为 LangChain 生态系统的重要扩展,代表了智能体框架设计的一个全新方向。与前面介绍的基于“对话”的框架(如 AutoGen 和 CAMEL)不同,LangGraph 将智能体的执行流程建模为一种状态机(State Machine) ,并将其表示为有向图(Directed Graph) 。在这种范式中,图的节点(Nodes) 代表一个具体的计算步骤(如调用 LLM、执行工具),而边(Edges) 则定义了从一个节点到另一个节点的跳转逻辑。这种设计的革命性之处在于它天然支持循环,使得构建能够进行迭代、反思和自我修正的复杂智能体工作流变得前所未有的直观和简单。
要理解 LangGraph,我们需要先掌握它的三个基本构成要素。
首先,是全局状态(State) 。整个图的执行过程都围绕一个共享的状态对象进行。这个状态通常被定义为一个 Python 的 TypedDict,它可以包含任何你需要追踪的信息,如对话历史、中间结果、迭代次数等。所有的节点都能读取和更新这个中心状态。
from typing import TypedDict, List
# 定义全局状态的数据结构
class AgentState(TypedDict):
messages: List[str] # 对话历史
current_task: str # 当前任务
final_answer: str # 最终答案
# ... 任何其他需要追踪的状态Copy to clipboardErrorCopied
其次,是节点(Nodes) 。每个节点都是一个接收当前状态作为输入、并返回一个更新后的状态作为输出的 Python 函数。节点是执行具体工作的单元。
# 定义一个“规划者”节点函数
def planner_node(state: AgentState) -> AgentState:
"""根据当前任务制定计划,并更新状态。"""
current_task = state["current_task"]
# ... 调用LLM生成计划 ...
plan = f"为任务 '{current_task}' 生成的计划..."
# 将新消息追加到状态中
state["messages"].append(plan)
return state
# 定义一个“执行者”节点函数
def executor_node(state: AgentState) -> AgentState:
"""执行最新计划,并更新状态。"""
latest_plan = state["messages"][-1]
# ... 执行计划并获得结果 ...
result = f"执行计划 '{latest_plan}' 的结果..."
state["messages"].append(result)
return stateCopy to clipboardErrorCopied
最后,是边(Edges) 。边负责连接节点,定义工作流的方向。最简单的边是常规边,它指定了一个节点的输出总是流向另一个固定的节点。而 LangGraph 最强大的功能在于条件边(Conditional Edges) 。它通过一个函数来判断当前的状态,然后动态地决定下一步应该跳转到哪个节点。这正是实现循环和复杂逻辑分支的关键。
def should_continue(state: AgentState) -> str:
"""条件函数:根据状态决定下一步路由。"""
# 假设如果消息少于3条,则需要继续规划
if len(state["messages"]) < 3:
# 返回的字符串需要与添加条件边时定义的键匹配
return "continue_to_planner"
else:
state["final_answer"] = state["messages"][-1]
return "end_workflow"Copy to clipboardErrorCopied
在定义了状态、节点和边之后,我们可以像搭积木一样将它们组装成一个可执行的工作流。
from langgraph.graph import StateGraph, END
# 初始化一个状态图,并绑定我们定义的状态结构
workflow = StateGraph(AgentState)
# 将节点函数添加到图中
workflow.add_node("planner", planner_node)
workflow.add_node("executor", executor_node)
# 设置图的入口点
workflow.set_entry_point("planner")
# 添加常规边,连接 planner 和 executor
workflow.add_edge("planner", "executor")
# 添加条件边,实现动态路由
workflow.add_conditional_edges(
# 起始节点
"executor",
# 判断函数
should_continue,
# 路由映射:将判断函数的返回值映射到目标节点
{
"continue_to_planner": "planner", # 如果返回"continue_to_planner",则跳回planner节点
"end_workflow": END # 如果返回"end_workflow",则结束流程
}
)
# 编译图,生成可执行的应用
app = workflow.compile()
# 运行图
inputs = {"current_task": "分析最近的AI行业新闻", "messages": []}
for event in app.stream(inputs):
print(event)Copy to clipboardErrorCopied
6.5.2 三步问答助手
在理解了 LangGraph 的核心概念之后,我们将通过一个实战案例来巩固所学。我们将构建一个简化的问答对话助手,它会遵循一个清晰、固定的三步流程来回答用户的问题:
- 理解 (Understand) :首先,分析用户的查询意图。
- 搜索 (Search) :然后,模拟搜索与意图相关的信息。
- 回答 (Answer) :最后,基于意图和搜索到的信息,生成最终答案。
这个案例将清晰地展示如何定义状态、创建节点以及将它们线性地连接成一个完整的工作流。我们将代码分解为四个核心步骤:定义状态、创建节点、构建图、以及运行应用。
(1)定义全局状态
首先,我们需要定义一个贯穿整个工作流的全局状态。这是一个共享的数据结构,它在图的每个节点之间传递,作为工作流的持久化上下文。 每个节点都可以读取该结构中的数据,并对其进行更新。
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
class SearchState(TypedDict):
messages: Annotated[list, add_messages]
user_query: str # 经过LLM理解后的用户需求总结
search_query: str # 优化后用于Tavily API的搜索查询
search_results: str # Tavily搜索返回的结果
final_answer: str # 最终生成的答案
step: str # 标记当前步骤Copy to clipboardErrorCopied
我们创建了 SearchState 这个 TypedDict,为状态对象定义了一个清晰的数据模式(Schema)。一个关键的设计是同时包含了 user_query 和 search_query 字段。这允许智能体先将用户的自然语言提问,优化成更适合搜索引擎的精炼关键词,从而显著提升搜索结果的质量。
(2)定义工作流节点
定义好状态结构后,下一步是创建构成我们工作流的各个节点。在 LangGraph 中,每个节点都是一个执行具体任务的 Python 函数。这些函数接收当前的状态对象作为输入,并返回一个包含更新后字段的字典。
在开始定义节点之前,我们先完成项目的初始化设置,包括加载环境变量和实例化大语言模型。
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from tavily import TavilyClient
# 加载 .env 文件中的环境变量
load_dotenv()
# 初始化模型
# 我们将使用这个 llm 实例来驱动所有节点的智能
llm = ChatOpenAI(
model=os.getenv("LLM_MODEL_ID", "gpt-4o-mini"),
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"),
temperature=0.7
)
# 初始化Tavily客户端
tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))Copy to clipboardErrorCopied
现在,我们来逐一创建三个核心节点。
(1) 理解与查询节点
此节点是工作流的第一步,此节点的职责是理解用户意图,并为其生成一个最优化的搜索查询。
def understand_query_node(state: SearchState) -> dict:
"""步骤1:理解用户查询并生成搜索关键词"""
user_message = state["messages"][-1].content
understand_prompt = f"""分析用户的查询:"{user_message}"
请完成两个任务:
1. 简洁总结用户想要了解什么
2. 生成最适合搜索引擎的关键词(中英文均可,要精准)
格式:
理解:[用户需求总结]
搜索词:[最佳搜索关键词]"""
response = llm.invoke([SystemMessage(content=understand_prompt)])
response_text = response.content
# 解析LLM的输出,提取搜索关键词
search_query = user_message # 默认使用原始查询
if "搜索词:" in response_text:
search_query = response_text.split("搜索词:")[1].strip()
return {
"user_query": response_text,
"search_query": search_query,
"step": "understood",
"messages": [AIMessage(content=f"我将为您搜索:{search_query}")]
}Copy to clipboardErrorCopied
该节点通过一个结构化的提示,要求 LLM 同时完成“意图理解”和“关键词生成”两个任务,并将解析出的专用搜索关键词更新到状态的 search_query 字段中,为下一步的精确搜索做好准备。
(2)搜索节点
该节点负责执行智能体的“工具使用”能力,它将调用 Tavily API 进行真实的互联网搜索,并具备基础的错误处理功能。
def tavily_search_node(state: SearchState) -> dict:
"""步骤2:使用Tavily API进行真实搜索"""
search_query = state["search_query"]
try:
print(f"🔍 正在搜索: {search_query}")
response = tavily_client.search(
query=search_query, search_depth="basic", max_results=5, include_answer=True
)
# ... (处理和格式化搜索结果) ...
search_results = ... # 格式化后的结果字符串
return {
"search_results": search_results,
"step": "searched",
"messages": [AIMessage(content="✅ 搜索完成!正在整理答案...")]
}
except Exception as e:
# ... (处理错误) ...
return {
"search_results": f"搜索失败:{e}",
"step": "search_failed",
"messages": [AIMessage(content="❌ 搜索遇到问题...")]
}Copy to clipboardErrorCopied
此节点通过 tavily_client.search 发起真实的 API 调用。它被包裹在 try...except 块中,用于捕获可能的异常。如果搜索失败,它会更新 step 状态为 "search_failed",这个状态将被下一个节点用来触发备用方案。
(3)回答节点
最后的回答节点能够根据上一步的搜索是否成功,来选择不同的回答策略,具备了一定的弹性。
def generate_answer_node(state: SearchState) -> dict:
"""步骤3:基于搜索结果生成最终答案"""
if state["step"] == "search_failed":
# 如果搜索失败,执行回退策略,基于LLM自身知识回答
fallback_prompt = f"搜索API暂时不可用,请基于您的知识回答用户的问题:\n用户问题:{state['user_query']}"
response = llm.invoke([SystemMessage(content=fallback_prompt)])
else:
# 搜索成功,基于搜索结果生成答案
answer_prompt = f"""基于以下搜索结果为用户提供完整、准确的答案:
用户问题:{state['user_query']}
搜索结果:\n{state['search_results']}
请综合搜索结果,提供准确、有用的回答..."""
response = llm.invoke([SystemMessage(content=answer_prompt)])
return {
"final_answer": response.content,
"step": "completed",
"messages": [AIMessage(content=response.content)]
}Copy to clipboardErrorCopied
该节点通过检查 state["step"] 的值来执行条件逻辑。如果搜索失败,它会利用 LLM 的内部知识回答并告知用户情况。如果搜索成功,它则会使用包含实时搜索结果的提示,来生成一个有时效性且有据可依的回答。
(4)构建图
我们将所有节点连接起来。
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
def create_search_assistant():
workflow = StateGraph(SearchState)
# 添加节点
workflow.add_node("understand", understand_query_node)
workflow.add_node("search", tavily_search_node)
workflow.add_node("answer", generate_answer_node)
# 设置线性流程
workflow.add_edge(START, "understand")
workflow.add_edge("understand", "search")
workflow.add_edge("search", "answer")
workflow.add_edge("answer", END)
# 编译图
memory = InMemorySaver()
app = workflow.compile(checkpointer=memory)
return appCopy to clipboardErrorCopied
(5)运行案例展示
运行此脚本后,您可以提出一些需要实时信息的问题,例如我们第一章中的案例:明天我要去北京,天气怎么样?有合适的景点吗
您会看到终端清晰地展示出智能体的“思考”过程:
🔍 智能搜索助手启动!
我会使用Tavily API为您搜索最新、最准确的信息
支持各种问题:新闻、技术、知识问答等
(输入 'quit' 退出)
🤔 您想了解什么: 明天我要去北京,天气怎么样?有合适的景点吗
============================================================
🧠 理解阶段: 我理解您的需求:理解:用户想了解明天北京的天气情况以及合适的景点推荐。
搜索词:北京 明天 天气 景点推荐 Beijing weather tomorrow attractions
🔍 正在搜索: 北京 明天 天气 景点推荐 Beijing weather tomorrow attractions
🔍 搜索阶段: ✅ 搜索完成!找到了相关信息,正在为您整理答案...
💡 最终回答:
明天(2025年9月17日)北京的天气预报显示,预计将是多云,气温范围在17°C(62°F)到25°C(77°F)之间。这种温和的天气非常适合户外活动。
### 合适的景点推荐:
1. **长城**:作为中国最著名的历史遗址之一,长城是必游之地。你可以选择八达岭或慕田峪这些较为受欢迎的段落进行游览。
2. **故宫**:故宫是明清两代的皇宫,拥有丰富的历史和文化,适合对中国历史感兴趣的游客。
3. **天安门广场**:这是中国的象征之一,广场上有许多重要的建筑和纪念碑,适合拍照留念。
4. **颐和园**:一个非常美丽的皇家园林,适合漫步和欣赏自然风光,尤其是湖泊和古建筑。
5. **798艺术区**:如果你对现代艺术感兴趣,798艺术区是一个集艺术、文化和创意于一体的地方,适合探索和拍摄。
### 小贴士:
- 由于明天天气良好,建议提前规划出行路线,并准备一些水和小吃,以便在游览时保持充足的体力。
- 由于天气变化可能会影响游览体验,建议查看实时天气更新。
希望这些信息能帮助你安排一个愉快的北京之旅!如果你需要更多关于景点的信息或者旅行建议,欢迎随时询问。
============================================================
🤔 您想了解什么:Copy to clipboardErrorCopied
并且他是一个可以持续交互的助手,你也可以继续向他发问。
6.5.3 LangGraph 的优势与局限性分析
任何技术框架都有其特定的适用场景和设计权衡。在本节中,我们将客观地分析 LangGraph 的核心优势及其在实际应用中可能面临的局限性。
(1)优势
- 如我们的智能搜索助手案例所示,LangGraph 将一个完整的实时问答流程,显式地定义为一个由状态、节点和边构成的“流程图”。这种设计的最大优势是高度的可控性与可预测性。开发者可以精确地规划智能体的每一步行为,这对于构建需要高可靠性和可审计性的生产级应用至关重要。其最强大的特性在于对循环(Cycles)的原生支持。通过条件边,我们可以轻松构建“反思-修正”循环,例如在我们的案例中,如果搜索失败,可以设计一个回退到备用方案的路径。这是构建能够自我优化和具备容错能力的智能体的关键。
- 此外,由于每个节点都是一个独立的 Python 函数,这带来了高度的模块化。同时,在流程中插入一个等待人类审核的节点也变得非常直接,为实现可靠的“人机协作”(Human-in-the-loop)提供了坚实的基础。
(2)局限性
- 与基于对话的框架相比,LangGraph 需要开发者编写更多的前期代码(Boilerplate) 。定义状态、节点、边等一系列操作,使得对于简单任务而言,开发过程显得更为繁琐。开发者需要更多地思考“如何控制流程(how)”,而不仅仅是“做什么(what)”。由于工作流是预先定义的,LangGraph 的行为虽然可控,但也缺少了对话式智能体那种动态的、 “涌现”式的交互。它的强项在于执行一个确定的、可靠的流程,而非模拟开放式的、不可预测的社会性协作。
- 调试过程同样存在挑战。虽然流程比对话历史更清晰,但问题可能出在多个环节:某个节点内部的逻辑错误、在节点间传递的状态数据发生异变,或是边跳转的条件判断失误。这要求开发者对整个图的运行机制有全局性的理解。
6.6 本章小结
本章我们感受了目前最前沿的一些智能体框架,通过案例的形式进行实操体验。
我们看到,每一个框架都有自己实现智能体构建的思路:
- AutoGen 将复杂的协作抽象为一场由多角色参与的、可自动进行的“群聊”,其核心在于“以对话驱动协作”。
- AgentScope 则着眼于工业级应用的健壮性与可扩展性,为构建高并发、分布式的多智能体系统提供了坚实的工程基础。
- CAMEL 以其轻量级的“角色扮演”和“引导性提示”范式,展示了如何用最少的代码激发两个专家智能体之间深度、自主的协作。
- LangGraph 则回归到更底层的“状态机”模型,通过显式的图结构赋予开发者对工作流的精确控制,尤其是其循环能力,为构建可反思、可修正的智能体铺平了道路。
通过对这些框架的深入分析,我们可以提炼出一个设计的权衡: “涌现式协作”与“显式控制”之间的选择。AutoGen 和 CAMEL 更多地依赖于定义智能体的“角色”和“目标”,让复杂的协作行为从简单的对话规则中“涌现”出来,这种方式更贴近人类的交互模式,但有时难以预测和调试。而 LangGraph 要求开发者明确地定义每一个步骤和跳转条件,牺牲了一部分“涌现”的惊喜,换来了高度的可靠性、可控性和可观测性。同时,AgentScope 则揭示了第二个同样重要的维度:工程化。无论我们选择哪种协作范式,要将其从实验原型推向生产应用,都必须面对并发、容错、分布式部署等工程挑战。AgentScope 正是为解决这些问题而生,它代表了从“能运行”到“能稳定服务”的关键跨越。
总而言之,智能体并非只有一种构建方式。深入理解本章探讨的框架设计哲学,能让我们不仅仅成为更优秀的“工具使用者”,更能理解框架设计中的各种优劣与权衡。
在下一章中,我们将进入本教程的核心内容,从零开始,亲手构建一个属于我们自己的智能体框架,将所有理论与实践融会贯通。
习题
-
本章介绍了四个各具特色的智能体框架:
AutoGen、AgentScope、CAMEL和LangGraph。请分析:- 在6.1.2节的表6.1中,对比了这四个框架的多个维度。请选择其中两个你最熟悉的框架,从"协作模式"、"控制方式"、"适用场景"三个维度进一步深入对比。
- 本章提到了"涌现式协作"与"显式控制"之间的权衡,如何理解这两种设计哲学的含义。
-
在6.2节的
AutoGen案例中,我们构建了一个"软件开发团队"。请基于此案例进行扩展思考:提示:这是一道动手实践题,建议实际操作
- 当前的团队使用
RoundRobinGroupChat(轮询群聊)模式,智能体按固定顺序发言。如果需求变更,工程师的代码需要返回给产品经理重新审核,应该如何修改协作流程?请设计一个支持"动态回退"的机制。 - 在案例中,我们通过
System Message为每个智能体定义了角色和职责。请尝试为这个团队添加一个新角色"测试工程师"(Quality Assurance),并设计其系统消息,使其能够在代码审查后执行自动化测试。 AutoGen的对话式协作存在可能的不稳定性,可能导致对话偏离主题或陷入循环。请思考:如何设计一套"对话质量监控"机制,在检测到异常时及时干预?
- 当前的团队使用
-
在6.3节的
AgentScope案例中,我们实现了一个"三国狼人杀"游戏。请深入分析:- 案例中使用了
MsgHub(消息中心)来管理智能体间的通信。请解释消息驱动架构相比传统函数调用的优势是什么?在什么场景下这种架构特别有价值? - 游戏中使用了结构化输出(如
DiscussionModelCN、WitchActionModelCN)来约束智能体行为。请设计一个新的游戏角色"猎人",并定义其对应的结构化输出模型,包括字段定义和验证规则。 AgentScope支持分布式部署,这意味着不同的智能体可以运行在不同的服务器上。请思考:在"三国狼人杀"这样的实时游戏场景中,分布式部署会带来哪些技术挑战?如何保证消息的顺序性和一致性?
- 案例中使用了
-
在6.4节的
CAMEL案例中,我们让心理学家和作家协作创作电子书。- 在案例中,协作会在检测到
<CAMEL_TASK_DONE>标志时强制终止。但如果两个智能体意见分歧(一位认为可以终止,一位认为不应该终止),无法达成一致怎么办?请设计一个"冲突解决"的兼容机制。 CAMEL最初设计用于双智能体协作,但现在已经扩展支持多智能体。请查阅CAMEL的最新文档,了解其多智能体协作模块workforce,并结合架构图说明其与AutoGen的群聊模式有何不同。
- 在案例中,协作会在检测到
-
在6.5节的
LangGraph案例中,我们构建了一个"三步问答助手"。请分析:LangGraph将智能体流程建模为状态机和有向图。请画出案例中"理解-搜索-回答"流程的图结构,标注节点、边和状态转换条件。- 当前的助手是一个线性流程。请扩展这个案例,添加一个"反思"节点:如果生成的答案质量低(例如过于简短或缺乏细节),系统应该重新搜索或重新生成答案。请设计这个循环机制的条件边逻辑。
LangGraph的优势在于对循环的原生支持。请设计一个更复杂的应用场景,充分利用这一特性:例如"代码生成-测试-修复"循环、"论文写作-审阅-修改"循环等。要求画出完整的图结构并说明关键节点的功能。
-
框架选型是智能体产品开发过程中的关键决策之一。假设你是一家
AI公司的技术架构师,公司计划开发以下三个智能体产品应用,请为每个应用选择最合适的框架(AutoGen、AgentScope、CAMEL、LangGraph或不借助框架从零开发),并详细说明理由:应用A:智能客服系统,需要处理大量并发用户请求(每秒1000+),要求响应时间低于2秒,系统需要7×24小时稳定运行,并支持水平扩展。
应用B:科研论文辅助写作平台,需要一个"研究员智能体"和一个"写作智能体"深度协作,共同完成文献综述、实验设计、数据分析和论文撰写。要求智能体能够进行多轮深度讨论,自主推进任务。
应用C:金融风控审批系统,需要按照严格的流程处理贷款申请:资料审核 → 风险评估 → 额度计算 → 合规检查 → 人工复核 → 最终决策。每个环节都有明确的判断标准和分支逻辑,要求流程可追溯、可审计。