从LangChain到LangGraph:构建可控AI智能体的图式思维与工程实践
1. 从 LangChain 到 LangGraph:为什么我们需要新的“大脑”?
如果你在过去一年里折腾过基于大语言模型的智能应用,那么“LangChain”这个名字对你来说一定不陌生。它就像一个功能强大的“瑞士军刀”,把提示词模板、记忆、工具调用、文档检索这些功能模块化,让我们能像搭积木一样快速构建起一个AI应用的原型。我最初用它来做一个简单的文档问答机器人,感觉确实方便,几行代码就能跑起来。但当我试图构建一个更复杂的、需要多步骤决策和状态流转的“智能体”时,问题就来了。
比如,我想做一个能自动处理用户工单的Agent。它需要先理解用户的问题,然后根据问题类型决定是去查知识库、调用某个API获取数据,还是需要向用户追问更多细节。在LangChain的框架里,我可以用
AgentExecutor
配合一堆工具来实现。但很快,代码就变成了一团乱麻:状态管理分散在各个回调函数里,步骤之间的依赖关系不清晰,一旦流程需要循环(比如用户回答不明确,需要再次追问),逻辑就变得异常复杂且难以调试。更头疼的是,当我想给这个Agent加上“长期记忆”,让它能记住和同一个用户的多次对话上下文时,LangChain提供的方案总感觉像是后期打上的补丁,不够优雅和可控。
这其实就是从“链式思维”到“图式思维”的跃迁。LangChain的核心是“链”,它定义了线性的、一步接一步的执行顺序,适合确定性的流程。但真正的智能体行为更像一张“图”,它有不同的节点(代表思考、决策、执行等动作),节点之间通过边连接,而边的走向取决于当前的状态。我们需要一个能清晰描述这种有状态、可分支、可循环的计算过程的框架。这就是LangGraph出现的背景。
它不是要取代LangChain,而是LangChain生态的自然进化。你可以把LangGraph理解为专门为构建复杂、可控的智能体而设计的“大脑皮层”。它引入了“状态”作为一等公民,用“图”来显式地定义智能体的工作流,使得整个决策逻辑变得可视化、可调试、可持久化。从工程实践的角度看,这意味着我们终于可以告别那些隐藏在
if-else
和回调地狱里的脆弱逻辑,用一种更声明式、更健壮的方式来构建真正具备自主能力的AI智能体。接下来,我会结合一个具体的工单处理Agent案例,带你一步步拆解如何用LangGraph构建一个可控、可观测、可扩展的智能体系统。
2. 核心概念拆解:State、Node、Edge与Graph
在动手写代码之前,我们必须先吃透LangGraph的几个核心概念。这就像学开车先要明白方向盘、油门和刹车是干嘛的,否则直接上路肯定手忙脚乱。
2.1 State:智能体的“记忆画布”
State是LangGraph中最重要的概念,它是一个贯穿整个图执行过程的、可变的字典。你可以把它想象成智能体的“短期工作记忆区”或一块共享的白板。所有节点(Node)都从这块白板上读取信息,处理完后,再把新的信息写回白板。
定义一个State,通常使用TypedDict(类型化字典)来明确其结构,这能极大提升代码的可读性和类型安全性。在我们的工单处理Agent例子中,State可能长这样:
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
import operator
class AgentState(TypedDict):
# 对话消息历史,LangGraph提供了add_messages操作符来简化列表追加
messages: Annotated[list, add_messages]
# 用户输入的原始问题
user_query: str
# 经过分类的问题类型,如“查询余额”、“故障申报”、“业务咨询”
query_type: str
# 从知识库或API获取到的中间结果
retrieved_info: str
# 最终要返回给用户的答案
final_answer: str
# 一个标志位,用于控制图的走向,比如“是否需要继续追问”
needs_clarification: bool
这里的关键是
Annotated
和
add_messages
。
add_messages
是一个“归约器”,它定义了当多个节点同时尝试修改
messages
列表时,如何合并这些修改(这里是追加)。这解决了并发操作状态时的冲突问题。其他字段如
query_type
,我们使用
operator.setitem
作为归约器,意味着后一个节点的写入会直接覆盖前一个节点的值。
定义State时,一定要想清楚每个字段的生命周期和更新规则,这是构建稳定Agent的基础。
2.2 Node与Edge:智能体的“思考单元”与“决策路径”
Node(节点)就是一个普通的Python函数(或可调用对象),它接收当前的State,执行一些操作,然后返回一个包含对State更新内容的字典。
def classify_query(state: AgentState) -> dict:
"""节点函数:分类用户问题"""
query = state[“user_query”]
# 这里可以调用一个LLM或一个简单的分类器
# 假设我们有一个分类函数
q_type = query_classifier(query)
return {“query_type”: q_type}
Edge(边)决定了执行完一个节点后,下一步该去哪个节点。LangGraph提供了几种边:
- 条件边 :根据State中的某个值,动态决定下一个节点。这是实现分支逻辑的核心。
- 普通边 :固定指向下一个节点。
- 入口边 :图的开始。
- 出口边 :图的结束。
条件边的定义非常直观,它就是一个返回下一个节点名称的函数:
def route_after_classification(state: AgentState) -> str:
"""条件边:根据问题类型路由到不同处理节点"""
q_type = state[“query_type”]
if q_type == “故障申报”:
return “handle_ticket”
elif q_type == “查询余额”:
return “query_balance”
else:
return “general_consultation”
2.3 Graph:将一切编织成工作流
最后,我们用
StateGraph
这个类,把State、Node和Edge组装起来,形成一个完整的、可执行的计算图。
from langgraph.graph import StateGraph, END
# 1. 创建图,并指定State的类型
workflow = StateGraph(AgentState)
# 2. 添加节点
workflow.add_node(“classify”, classify_query)
workflow.add_node(“handle_ticket”, handle_ticket_node)
workflow.add_node(“query_balance”, query_balance_node)
workflow.add_node(“general_consultation”, general_consultation_node)
# 3. 设置入口点
workflow.set_entry_point(“classify”)
# 4. 添加边(包括条件边)
workflow.add_conditional_edges(
“classify”,
route_after_classification, # 上面定义的条件路由函数
{
“handle_ticket”: “handle_ticket”,
“query_balance”: “query_balance”,
“general_consultation”: “general_consultation”
}
)
# 5. 为其他节点添加普通边,指向结束
workflow.add_edge(“handle_ticket”, END)
workflow.add_edge(“query_balance”, END)
workflow.add_edge(“general_consultation”, END)
# 6. 编译图,得到可执行对象
app = workflow.compile()
编译后的
app
就是一个可以调用的智能体了。你通过
app.invoke()
传入初始状态,它就会按照你定义的图逻辑自动执行。
这种声明式的编程方式,将业务逻辑(图结构)和执行引擎(LangGraph运行时)彻底解耦,使得代码的维护性和可测试性大大增强。
你可以轻易地将这个图可视化出来,一目了然地看到整个Agent的决策流程,这是用传统代码难以实现的。
3. 工程实践:构建一个工单处理智能体
现在,我们把理论付诸实践,构建一个相对完整的工单处理智能体。这个Agent需要完成以下功能:1)理解用户意图;2)根据意图采取不同行动(查知识库、调API、反问);3)管理多轮对话;4)给出最终答复。
3.1 定义状态与工具准备
首先,我们完善之前定义的State,并准备一些工具。工具就是Agent可以调用的外部函数,比如搜索、计算、调用API等。
from typing import TypedDict, Annotated, List
from langgraph.graph.message import add_messages
import operator
from langchain.tools import tool
from langchain_community.utilities import SQLDatabase
from some_api_client import TicketAPI
class AgentState(TypedDict):
messages: Annotated[List, add_messages]
user_query: str
query_type: str # “fault”, “query”, “consult”
needs_clarification: bool
clarification_question: str
retrieved_data: str
final_answer: str
step_history: List[str] # 记录执行步骤,用于调试
# 模拟工具定义
@tool
def search_knowledge_base(query: str) -> str:
"""在内部知识库中搜索相关问题解决方案"""
# 这里可以是向量数据库检索
return f“根据知识库,关于‘{query}’的解决方案是:重启服务。”
@tool
def query_user_account(user_id: str, query_type: str) -> str:
"""查询用户账户信息(余额、订单等)"""
# 模拟调用内部账户API
return f“用户{user_id}的{query_type}为:100元。”
@tool
def create_ticket(description: str, priority: str) -> str:
"""在工单系统中创建一条新工单"""
# 模拟调用工单系统API
ticket_id = “TICKET-2024-001”
return f“已创建工单 {ticket_id},优先级:{priority},描述:{description}”
tools = [search_knowledge_base, query_user_account, create_ticket]
3.2 构建图节点:分解智能体的思考过程
一个复杂的节点内部,可能包含LLM调用和工具使用。我们可以利用LangChain的
create_react_agent
或类似助手函数来构建单个节点的推理能力。但更LangGraph的方式是,将“思考是否用工具”和“使用工具”也建模为图的一部分。这里为了清晰,我们先在一个节点内完成小范围的推理。
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent
from langchain.agents.format_scratchpad import format_log_to_str
from langchain.tools.render import render_text_description
llm = ChatOpenAI(model=“gpt-4”, temperature=0)
def classify_and_route_node(state: AgentState) -> dict:
"""节点1:分析用户输入并分类路由"""
step_history = state.get(“step_history”, [])
step_history.append(“进入 classify_and_route_node”)
user_input = state[“user_query”]
# 使用LLM进行意图识别和分类
system_prompt = “””你是一个工单分类助手。请分析用户输入,判断其属于以下哪一类:
1. fault: 故障申报或问题投诉。
2. query: 查询信息,如余额、订单状态。
3. consult: 一般业务咨询。
4. unclear: 意图不明确,需要进一步澄清。
只返回类别关键词(fault/query/consult/unclear)。输入:{input}“””
response = llm.invoke(system_prompt.format(input=user_input))
q_type = response.content.strip().lower()
# 如果意图不明确,设置澄清标志和问题
needs_clarify = (q_type == “unclear”)
clarify_q = “”
if needs_clarify:
clarify_q = “您能具体描述一下您遇到的问题或想查询的信息吗?”
return {
“query_type”: q_type,
“needs_clarification”: needs_clarify,
“clarification_question”: clarify_q,
“step_history”: step_history
}
def handle_fault_node(state: AgentState) -> dict:
"""节点2:处理故障申报"""
step_history = state.get(“step_history”, [])
step_history.append(“进入 handle_fault_node”)
user_input = state[“user_query”]
# 首先尝试从知识库找答案
kb_result = search_knowledge_base.invoke(user_input)
# 让LLM判断知识库答案是否足够解决用户问题
judge_prompt = f“””用户问题:{user_input}
知识库检索结果:{kb_result}
请判断:
1. 如果知识库结果能直接解决问题,请直接总结一个友好的答复。
2. 如果不能,请生成一个用于创建工单的详细描述。
只输出最终答复或工单描述。“””
llm_decision = llm.invoke(judge_prompt)
decision_text = llm_decision.content
final_answer = decision_text
# 如果LLM的判断看起来像工单描述(这里用简单关键词判断),则创建工单
if “工单” in decision_text or “ticket” in decision_text.lower():
ticket_result = create_ticket.invoke(decision_text, “medium”)
final_answer = f“{decision_text}\n\n系统执行结果:{ticket_result}”
step_history.append(“故障处理完成”)
return {
“retrieved_data”: kb_result,
“final_answer”: final_answer,
“step_history”: step_history
}
def handle_query_node(state: AgentState) -> dict:
"""节点3:处理查询请求"""
step_history = state.get(“step_history”, [])
step_history.append(“进入 handle_query_node”)
user_input = state[“user_query”]
# 简单从输入中提取用户ID和查询类型(实际应用需更复杂的NLP)
# 这里仅为示例
assumed_user_id = “user123”
assumed_query = “余额”
api_result = query_user_account.invoke(f“{assumed_user_id}, {assumed_query}”)
final_answer = f“查询结果:{api_result}”
step_history.append(“信息查询完成”)
return {
“retrieved_data”: api_result,
“final_answer”: final_answer,
“step_history”: step_history
}
def ask_clarification_node(state: AgentState) -> dict:
"""节点4:向用户提出澄清问题"""
step_history = state.get(“step_history”, [])
step_history.append(“进入 ask_clarification_node”)
# 这个节点不修改最终答案,只是将澄清问题放入消息历史,并等待下一次输入
# 在实际流式应用中,这里会暂停图执行,等待外部输入。
# 为简化,我们假设澄清问题就是输出。
clarify_q = state.get(“clarification_question”, “请提供更多细节。”)
return {
“final_answer”: clarify_q,
“step_history”: step_history
}
3.3 组装与执行:让图运转起来
现在,我们用更复杂的条件边来组装整个图,实现“澄清-回答”的循环。
from langgraph.graph import StateGraph, END
workflow = StateGraph(AgentState)
# 添加所有节点
workflow.add_node(“classify”, classify_and_route_node)
workflow.add_node(“handle_fault”, handle_fault_node)
workflow.add_node(“handle_query”, handle_query_node)
workflow.add_node(“ask_clarify”, ask_clarification_node)
workflow.add_node(“finalize”, lambda state: state) # 一个空节点,作为汇聚点
# 设置入口
workflow.set_entry_point(“classify”)
# 关键:分类后的条件路由
def route_based_on_type_and_clarity(state: AgentState) -> str:
q_type = state.get(“query_type”, “unclear”)
needs_clarify = state.get(“needs_clarification”, False)
if needs_clarify:
# 如果需要澄清,则进入澄清节点
return “ask_clarify”
else:
# 否则,根据分类结果路由
return {
“fault”: “handle_fault”,
“query”: “handle_query”,
“consult”: “handle_fault” # 咨询也先走知识库流程
}.get(q_type, “ask_clarify”) # 默认情况也去澄清
workflow.add_conditional_edges(
“classify”,
route_based_on_type_and_clarity
)
# 澄清节点之后,我们需要重新分类。这里模拟用户已回答,将新输入合并后重新开始。
# 一种方法是让`ask_clarify`节点不直接连接END,而是连接一个“等待输入”的节点,输入后重新进入`classify`。
# 为简化演示,我们假设澄清后自动生成一个模拟回答,并重新路由。
def after_clarification(state: AgentState) -> str:
# 在实际中,这里state[‘messages’]里应该包含了用户的回复。
# 我们模拟用户回复后,应重新进行分类。
return “classify”
workflow.add_edge(“ask_clarify”, “classify”) # 形成一个澄清循环
# 处理节点完成后,都汇聚到finalize节点,然后结束
workflow.add_edge(“handle_fault”, “finalize”)
workflow.add_edge(“handle_query”, “finalize”)
workflow.add_edge(“finalize”, END)
# 编译图
app = workflow.compile()
现在,我们可以运行这个智能体了:
# 初始化状态
initial_state = {
“user_query”: “我的服务无法访问了,怎么办?”,
“messages”: [],
“step_history”: []
}
# 执行图
final_state = app.invoke(initial_state)
print(“最终答案:”, final_state[“final_answer”])
print(“执行步骤:”, final_state[“step_history”])
对于“我的服务无法访问了”这个问题,图会沿着
classify -> handle_fault -> finalize -> END
的路径执行,最终可能输出一个从知识库找到的解决方案,或者自动创建的工单信息。如果用户一开始输入的是“我想问一下”,由于意图不明确,图会走
classify -> ask_clarify -> classify -> ...
的循环,直到
classify
节点识别出明确意图为止。
4. 高级模式与生产级考量
上面的例子展示了LangGraph的核心用法,但对于生产环境,还有几个至关重要的高级模式和工程问题需要解决。
4.1 人工接管与中断机制
一个健壮的Agent不能一直“自动驾驶”。当它不确定或遇到无法处理的边界情况时,应该能“举手”请求人工介入。这在LangGraph中可以通过“暂停”和“外部驱动”来实现。
LangGraph的
Checkpointer
和
Interrupt
机制就是干这个的。简单来说,你可以在图中定义一个特殊的“人工审核”节点。当流程到达这个节点时,图的执行会被暂停,并将当前状态持久化到数据库(Checkpoint)。然后,你的系统可以发送一个通知(如到管理后台),等待人工输入。人工审核完成后,系统再向这个被暂停的图实例注入新的状态(如人工批示),并恢复执行。
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
from langgraph.graph import MessagesState
# 使用支持暂停的检查点存储器
memory = AsyncSqliteSaver.from_conn_string(“:memory:”)
workflow = StateGraph(MessagesState, config_schema=MyConfig)
# ... 添加节点和边 ...
# 定义一个发送通知并等待的节点
def human_review_node(state: MessagesState):
# 1. 将当前问题和Agent建议保存到工单系统
ticket_id = create_review_ticket(state[“messages”])
# 2. 抛出中断,等待外部输入
raise PendingInterruption(“wait_for_human”, {“ticket_id”: ticket_id})
# 当从中断恢复时,可以从state中读取人工输入的结果
human_feedback = state.get(“human_feedback”)
return {“messages”: human_feedback}
workflow.add_node(“human_review”, human_review_node)
# 在需要的地方(如LLM置信度低时)路由到这个节点
在生产中,你必须设计好这个“人机回环”的接口和状态恢复逻辑,这是确保Agent可靠性的安全网。
4.2 子图与模块化设计
当智能体流程非常复杂时,把所有的节点和边都放在一个主图里会变得难以维护。LangGraph支持 子图 ,允许你将一个功能模块(例如,一个完整的“文档检索与摘要”流程)封装成一个独立的图,然后在主图中将其作为一个节点来调用。
这样做的好处是:
- 高内聚低耦合 :每个子图可以独立开发、测试和调试。
- 复用性 :通用的处理流程(如“信息核实”)可以被多个主图复用。
- 可视化清晰 :主图的结构会更简洁,你可以通过折叠子图来查看不同层级的抽象。
# 假设我们有一个封装好的“检索增强生成(RAG)”子图
rag_subgraph = create_rag_graph().compile()
# 在主图中,可以把这个编译好的子图当作一个节点添加
def rag_node(state: AgentState):
# 准备子图需要的输入状态
subgraph_state = {“question”: state[“user_query”]}
# 调用子图
result = rag_subgraph.invoke(subgraph_state)
# 处理子图输出,更新主状态
return {“retrieved_data”: result[“answer”]}
workflow.add_node(“rag_processor”, rag_node)
4.3 可观测性与调试
当Agent行为不符合预期时,如何调试?LangGraph内置了强大的追踪功能。你可以通过配置将每一步的执行详情(输入State、输出State、节点名称、耗时等)输出到控制台,或集成到像LangSmith这样的APM平台。
from langsmith import Client
from langgraph.graph import StateGraph
client = Client()
# 在编译时配置
app = workflow.compile(checkpointer=memory, debug=True) # debug=True会在控制台打印详细日志
# 或者,使用LangSmith进行更细致的追踪
app = workflow.compile(
checkpointer=memory,
config={“configurable”: {“thread_id”: “user_123”}},
# LangChain/LangGraph通常与LangSmith自动集成,只需设置环境变量
)
我的经验是,在开发阶段一定要把
debug=True
打开,并习惯查看每一步的状态流转。
此外,像我们在State中定义的
step_history
字段,也是一个简单有效的自定义追踪手段,可以帮助你快速定位问题发生在哪个业务节点。
4.4 持久化与状态管理
对于需要长时间运行、多次交互的Agent(如一个客服对话机器人),状态的持久化至关重要。你不能每次用户说话都从头开始。
Checkpointer
就是LangGraph官方推荐的状态持久化方案。它不仅能保存State,还能保存图的结构和中断点,确保会话的连续性。
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
# 使用SQLite存储检查点(生产环境可用Postgres)
checkpointer = AsyncSqliteSaver.from_conn_string(“sqlite:///checkpoints.db”)
app = workflow.compile(checkpointer=checkpointer)
# 第一次调用,传入thread_id标识同一个会话
config = {“configurable”: {“thread_id”: “conversation_001”}}
result1 = app.invoke({“user_query”: “你好”}, config=config)
# 十分钟后,用户再次发言,使用相同的thread_id,图会从上次中断的地方继续
result2 = app.invoke({“user_query”: “我上次问的那个问题…”}, config=config)
选择Checkpointer时,要考虑并发访问和性能。
对于高并发场景,基于内存的检查点可能不够用,需要选择像
PostgresSaver
这样的分布式存储后端。
5. 避坑指南与最佳实践
在从LangChain迁移到LangGraph,以及实际开发Agent的过程中,我踩过不少坑,也总结出一些让项目更稳健的经验。
5.1 State设计的“纯度”与副作用
坑:
在节点函数中直接修改传入的State字典,或者执行一些不可预测的副作用(如直接向数据库写入)。
解:
保持节点函数的“相对纯净”。它应该只读取State,通过返回值来声明如何更新State。所有对外部系统的写操作(数据库、API调用),都应该封装在
@tool
装饰的函数内,并通过调用工具的方式来执行。这样做的目的是让图的计算过程更可预测、可回溯。如果需要在节点内写日志,也最好通过State传递日志信息,由一个专门的“日志节点”统一处理。
5.2 条件边的路由函数要简单稳定
坑:
在条件路由函数
route_function
中编写复杂的业务逻辑或调用LLM。
解:
条件边函数应该尽可能简单、快速、确定。它最好只做基于State现有字段的简单判断。复杂的路由决策(比如用LLM判断下一步该干嘛),应该设计成一个独立的“路由决策”节点。这个节点负责更新State中的一个字段(如
next_node
),然后由一条固定的条件边读取这个字段来决定走向。这样逻辑更清晰,也更容易调试。
# 不推荐:在条件边函数里做复杂操作
def complex_route(state):
analysis = llm.invoke(f“分析{state[‘query’]}”) # 避免在这里调用LLM!
if “复杂” in analysis:
return “node_a”
else:
return “node_b”
# 推荐:使用一个专门的决策节点
def decision_node(state):
analysis = llm.invoke(f“分析{state[‘query’]}”)
next_node = “node_a” if “复杂” in analysis else “node_b”
return {“next_node”: next_node}
def simple_route(state):
return state.get(“next_node”, “default_node”) # 条件边函数只做简单读取
5.3 处理好循环与避免死循环
坑:
图中存在循环(如澄清循环),但没有设置终止条件,导致Agent和用户陷入无限问答。
解:
一定要在循环路径上设置“安全阀”。可以在State中设置一个计数器(如
clarification_attempts
),每次循环递增。在条件路由函数中判断,如果超过阈值(比如3次),则强制跳转到“转人工”或“默认回复”节点,并结束循环。
class AgentState(TypedDict):
...
clarification_attempts: int = 0 # 使用pydantic或默认值初始化
def route_after_clarify(state: AgentState) -> str:
if state[“clarification_attempts”] >= 3:
return “escalate_to_human” # 跳转到人工节点
elif state[“needs_clarification”]:
return “ask_clarify”
else:
return “process_query”
5.4 工具调用与错误处理
坑: 工具调用失败(网络超时、API异常)导致整个图执行崩溃。 解: 在工具函数内部做好完善的异常捕获和容错处理,返回结构化的错误信息,而不是抛出异常。同时,在调用工具的节点里,也要检查工具返回的结果是否有效,并能够根据错误类型更新State,引导图走向错误处理或重试节点。
@tool
def call_external_api(params):
try:
response = requests.post(url, json=params, timeout=10)
response.raise_for_status()
return {“success”: True, “data”: response.json()}
except requests.exceptions.Timeout:
return {“success”: False, “error”: “timeout”, “message”: “API请求超时”}
except Exception as e:
return {“success”: False, “error”: “unknown”, “message”: str(e)}
def api_node(state):
result = call_external_api.invoke(state[“params”])
if not result[“success”]:
# 更新State,让下一个节点知道出错了
return {“api_status”: “failed”, “error_detail”: result}
# 正常处理结果
return {“api_status”: “success”, “api_data”: result[“data”]}
5.5 测试策略:分层测试与模拟
测试一个基于图的Agent比测试普通函数更复杂。我的策略是分层测试:
- 单元测试 :单独测试每个节点函数,模拟输入State,断言输出State。
-
集成测试
:测试一个子图或简单的条件分支。可以使用
app.invoke并注入特定的初始状态,验证最终的输出状态和步骤历史是否符合预期。 -
端到端测试
:模拟真实用户对话流,测试整个编译好的图。这里需要大量使用Mock对象来模拟LLM和工具调用,确保测试的稳定性和速度。可以使用
unittest.mock库来patch掉ChatOpenAI.invoke和@tool函数,让它们返回预设的答案。
从LangChain到LangGraph,不仅仅是换了一个库,更是思维模式从“链”到“图”的升级。它迫使你更清晰地思考智能体的状态空间和决策流程。刚开始可能会觉得繁琐,但一旦你习惯了这种声明式的、可视化的编程方式,再回去看那些面条式的回调代码就会觉得难以忍受。LangGraph带来的最大收益是 可控性 和 可维护性 ,这对于构建真正能在生产环境落地的AI智能体来说,是至关重要的基石。
更多推荐
所有评论(0)