这两年AI圈最热闹的方向,一定是智能体。但“AI Agent”这个词现在快被玩坏了——各种套壳应用都敢叫自己智能体,实际上只是把模型接了个提示词,回答完问题就结束,跟真正的“干活”差了十万八千里。我前阵子在一个Agent项目里被折磨得够呛,后来换成LangGraph才真正找到把AI从“一问一答”变成“自主执行”的感觉。这个开源框架解决的核心问题特别朴素:如何让大模型自己决定“下一步做什么”,并且真的去执行、去查资料、去调接口、去修正错误,而不是每次都要人来推一把。
这篇文章不会讲那些玄乎的“Agent哲学”,我会直接从工程角度拆解LangGraph的核心机制、状态图设计、工具调用方式,用一个完整的实战项目把流程跑通,再把我踩过的高频坑和排查方法都分享出来。适合刚接触智能体开发的后端工程师、AI应用开发者,以及准备Agent方向面试的朋友。读完你就能明白,LangGraph不是又一个花哨的框架,而是把Agent落地这件事真正工程化了。
1. 为什么大模型本身不会“干活”:从问答机器到智能体的三步进化
1.1 大模型本质上只是一个“单步文本预测器”
先说一个可能让不少人破防的事实:大模型本身并不会“干活”,它只会做一件事——根据你给的上下文,预测下一段最合理的文本。你问它“帮我查一下上海的天气”,它不会真的打开天气网站,它只是在你的问题后面补了一段“好的,我来帮你查询”这类文本。你看着像模像样,但它什么都没做。
这就是“问答机器”的天然缺陷:模型只能根据训练数据里的知识和你提供的上下文进行推测,无法接触外部世界的实时信息,无法操作业务系统,更无法在连续多步的任务中记住自己已经做到哪一步、下一步该干什么。早期很多Agent Demo之所以翻车,就是这个原因——把模型当成万能的执行者,结果它只能在文本层打转。
那智能体到底比问答机器强在哪?关键在于多了一个“感知-决策-执行”的循环能力。真正的智能体能自己判断“我还缺什么信息”,自己调用工具去获取,然后根据结果继续推进下一个决策。这就是所谓“循环”的意义——模型不再是回答完就完事,而是被放入一个可以反复迭代的流程里,直到任务完成。
1.2 智能体三要素:循环、工具、状态
在实际项目里,我总结智能体本质上需要三个东西才能成立。
第一个是循环控制。模型必须被允许在一个循环里反复决策。比如任务“写一份竞品分析报告”,它要先搜索资料、再总结要点、再检查格式,这中间可能需要好几次工具调用,任何一次都可能导致新的问题,所以要能循环,而不是一次问答就结束。
第二个是工具调用能力。模型需要通过函数调用的方式,把“查询”、“计算”、“写文件”这些动作委托给外部系统。这不是让它生成长篇大论描述“我想做什么”,而是产生结构化的调用参数,然后由真实代码去执行。
第三个是状态管理。这是最容易被忽略但也是最关键的一环。整个任务过程中,模型已经读过哪些资料、生成过哪些中间结果、当前的输入是什么、下一步该基于什么继续,这些都要有一个统一的状态载体去维护。
你单用LangChain能做到工具调用,但循环和状态管理非常别扭;你单写代码也能做循环,但每轮都要处理记忆、序列化、并发,工作量一下子变大。LangGraph正是把这三件事统一在一个“状态图”模型里,用图的方式描述整个Agent的运行流程,开发者只需要定义节点和边,框架负责调度、状态传递和循环控制。
1.3 LangGraph最直接解决的痛点
如果你之前试着写过Agent,大概率会撞上这几堵墙。
第一堵墙是“怎么让模型一轮轮决策”。很多同学的写法是外面套一个for循环,手动截断,模型说“我要调用工具”,你就解析参数,调用完再拼回消息,再发给模型……第一次写觉得还行,第二次就发现状态传递极其容易出错,几个上下文变量绕得头晕。
第二堵墙是“分支逻辑写成一坨if-else”。真实的Agent往往有多个分支:模型判断需要更多信息时走工具调用分支,判断信息足够时走生成最终答案分支,异常时走错误处理分支。这些分支手动管理非常痛苦。
第三堵墙是“记忆和断点续跑”。任务执行到一半进程崩了,或者需要人工审核干预,之前的对话状态能不能保留?
LangGraph的解决思路非常工程化:把Agent的运行流程定义成一个图,节点是具体的执行逻辑(比如“调用LLM”、“调用工具”),边是节点之间的流转关系,状态是全局共享的数据结构。所有流程分支都在图上走,用条件边决定下一步走哪条路。循环本质就是回到某个节点继续执行,状态管理也变成了对共享数据结构的更新。一旦适应了这个思路,你会觉得Agent开发突然变得清晰了。
2. 新手必看:5分钟跑通你的第一个LangGraph智能体
2.1 环境准备:别急着装,先确认版本
动手之前,先确认Python版本和依赖环境。LangGraph要求Python 3.9以上,我用的是3.11。安装的时候直接走pip就行。
pip install langgraph langchain-core langchain-openai如果你要调用OpenAI兼容接口(现在很多国产模型也都支持),还需要langchain-openai这个包。这里有个小建议:别一上来就装最新的langchain全家桶,LangGraph对版本有一定要求,装完最好检查一下。
python -c "import langgraph; print(langgraph.__version__)"提示:如果你公司内网有代理镜像,记得先配置pip源,否则安装速度会让人崩溃。
2.2 定义一个真实工具:让模型“有手可动”
智能体要干活,必须有工具。这里我先用一个最简单的“城市天气查询”工具来演示——重点不是工具本身,而是整个Agent循环的结构。
from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """ 查询指定城市的当前天气情况。 参数: city: 城市名称,比如北京、上海。 """ # 实际项目中这里会调用真实天气API weather_map = { "北京": "晴,25度,微风", "上海": "小雨,22度,东南风3级", "广州": "多云,30度,湿度较大", } return weather_map.get(city, f"抱歉,暂无{city}的天气数据")注意两个小细节:函数一定要有类型注解,而且docstring要写清楚参数含义。因为模型是靠这些信息来理解“什么时候该调用这个工具”以及“参数应该怎么填”的,写得太含糊,模型就会在参数上胡编。
2.3 构建状态图并注册节点:让流程“跑起来”
接下来是LangGraph的核心动作——定义状态、创建图、注册节点、添加边。我先用一个最小可跑的示例,不做花哨设计。
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI from langgraph.prebuilt import ToolNode, tools_condition # 1. 定义状态:所有节点共享的数据结构 class AgentState(TypedDict): messages: list # 对话历史 # 2. 初始化模型,并绑定工具 llm = ChatOpenAI( model="gpt-4o-mini", # 换成你的模型 api_key="sk-xxx", # 换成你的key temperature=0 ) llm_with_tools = llm.bind_tools([get_weather]) # 3. 定义节点:调用模型 def call_model(state: AgentState): result = llm_with_tools.invoke(state["messages"]) return {"messages": [result]} # 4. 构建图 graph_builder = StateGraph(AgentState) # 注册节点 graph_builder.add_node("call_model", call_model) graph_builder.add_node("tools", ToolNode([get_weather])) # 添加边:起点->模型节点 graph_builder.set_entry_point("call_model") # 条件边:模型决定是否调用工具 graph_builder.add_conditional_edges( "call_model", tools_condition, # 如果模型返回tool_calls,就跳转tools节点,否则结束 { "tools": "tools", END: END, } ) # 工具节点执行完后,回到模型节点继续决策 graph_builder.add_edge("tools", "call_model") # 5. 编译图 graph = graph_builder.compile()这段代码里最关键的是add_conditional_edges——它就是智能体循环决策的开关。tools_condition是LangGraph内置的条件函数,它会检查模型的返回结果里是否包含工具调用请求:如果包含,就跳转到tools节点执行工具;如果不包含,说明模型认为可以直接回答了,流程就走END结束。
2.4 编译、调用与结果解读
图编译完成后,调用方式和你熟悉的LangChain链式调用差不多:
result = graph.invoke({"messages": [{"role": "user", "content": "上海今天天气怎么样?"}]}) for m in result["messages"]: if hasattr(m, "content") and m.content: print(f"{m.type}: {m.content}") if hasattr(m, "tool_calls") and m.tool_calls: print(f"tool_calls: {m.tool_calls}")运行之后,你会看到一条清晰的执行链条:用户的提问→模型返回一条带工具调用的消息(tool_calls里的参数是{"city": "上海"})→tools节点执行get_weather拿到天气数据→工具结果回传给模型→模型基于工具结果生成最终回答。
这就是一个完整的Agent循环,从“提问”到“决策”到“执行”再到“最终回答”,模型全程不需要人工干预。你能明显感到,这和单纯调一次大模型接口完全是两种体验。
3. 状态管理才是LangGraph的灵魂:State、节点、边与记忆机制
3.1 State就是智能体的“账本”
刚接触LangGraph的人很容易把State当成普通的全局变量,但它的设计远比全局变量讲究。State本质上是一个“多层智能体共享账本”,所有节点都只能往账本上记账,然后从账本里读信息,节点之间不直接传参。
我实际开发中会在State里放这些内容:messages(完整对话历史)、scratchpad(中间推理笔记)、tool_results(上次工具调用的结果)、final_output(最终输出)、retry_count(重试次数)。不同类型的字段承担不同职责,比如messages是给模型看的,tool_results是给后续节点做判断用的。
为什么要统一放在State里?因为Agent本质上是多轮迭代过程,模型每一轮决策都要基于完整上下文。如果中间结果散落在各个局部变量里,一旦流程分支,信息就丢了。用State统一管理,无论流程走到哪个节点,都能拿到完整的“记忆”,这就是工程化落地的关键。
3.2 条件边如何让模型拥有“决策权”
LangGraph里最漂亮的设计就是条件边。模型的输出不是一个固定结果,而是对“下一步走哪条路”的决策信号。
graph_builder.add_conditional_edges( "call_model", lambda state: "tools" if state["messages"][-1].tool_calls else END, {"tools": "tools", END: END} )上面这个条件函数做的事很简单:看模型最后一条消息有没有tool_calls。有就去执行工具,没有就结束。你可以在这个条件函数里做更复杂的分流,比如判断是否达到最大重试次数,或者检查某类关键词来决定是不是要走人工审核分支。
从工程角度看,条件边让人工介入变得非常自然——你不需要在业务代码里到处埋点判断,只需要在图上定义一个分支节点,把“什么情况走哪条路”的规则写清楚,流程就自动分流了。这也是LangGraph能实现循环的本质原因:普通链式调用是“一条路走到黑”,而图结构的节点可以回头反复执行。
3.3 ToolNode:工具调用的标准接线端子
从上面的示例你可能注意到了,我并没有自己写解析工具调用的代码——这是ToolNode帮我干的。这个内置节点做的事情包括:
- 从模型返回的消息中提取tool_calls
- 根据tool_calls里的函数名和参数,找到注册过的对应工具
- 逐个执行工具,并把结果封装成ToolMessage追加到状态里
用ToolNode最大的好处是省去了大量解析胶水代码。你可以在一个ToolNode里挂多个工具,模型会自动在多个工具里做选择。比如一个Agent同时挂了get_weather、calculate、web_search三个工具,模型就会根据问题的性质决定调用哪个。
这里有个注意事项:如果你有某些带敏感操作的工具,比如“删除文件”、“转账”,千万别直接丢进ToolNode让模型自由调用。正确做法是单独用条件边拦一道人工审批逻辑,或者写一个包装器做权限校验。安全边界永远要在代码层面控死,不能依赖模型自觉。
3.4 用Checkpointer把“记忆”落盘
模型一多轮就忘事,这是所有Agent开发者的痛点。LangGraph解决这个问题的方式是提供MemorySaver和PostgresSaver这类Checkpointer,让图在运行过程中能保存状态快照,并在下次运行时恢复。
from langgraph.checkpoint.memory import MemorySaver memory = MemorySaver() graph = graph_builder.compile(checkpointer=memory) # 用thread_id区分不同的会话 result = graph.invoke( {"messages": [{"role": "user", "content": "我刚刚问了什么?"}]}, config={"configurable": {"thread_id": "session_001"}} )Checkpointer解决的是“记忆”问题,而thread_id解决的是“会话隔离”问题。同一张图可以被多个用户并发使用,每个thread_id对应一个独立的对话状态,互不干扰。用起来很像数据库行锁,不同线程写不同行,逻辑上干干净净。
我实际项目里会配合Postgres做持久化,这样Agent进程重启、换甲,会话状态都不会丢。这个能力在线上环境属于刚需,否则用户聊到一半刷新一下页面,Agent就把前因后果忘干净了。
4. 实战:从查资料到写报告,手把手构建一个多工具智能体
4.1 场景设计与工具选型
为了让你真正理解LangGraph的生产价值,我设计一个相对完整的场景:一个“行业信息调研Agent”,任务是根据用户给出的主题,自动查资料、做计算、生成Markdown报告。
这个场景很典型,因为它需要模型连续做多件事:
- 拆解用户主题,判断需要哪些信息
- 调用搜索工具获取原始素材
- 读取素材后做数字计算(比如市场规模增长率)
- 最后整合所有信息,撰写结构化的报告
工具选型上,我做三个足够演示但不过于复杂的:
search_web(query): 模拟在线搜索,返回一段文字素材extract_keywords(text): 模拟信息抽取,返回关键实体calculate_growth(current, previous): 计算增长率
三个工具分别覆盖检索、分析、计算三类能力。这样Agent在不同环节会调用不同的工具,比单一工具能更好展示状态图的多分支流转。
4.2 完整代码实现
from typing import TypedDict from langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode, tools_condition from langchain_core.tools import tool from langchain_openai import ChatOpenAI # ---------- 工具定义 ---------- @tool def search_web(query: str) -> str: """模拟网络搜索,返回与query相关的素材段落。""" data = { "AI": "2025年全球AI市场规模预计达1900亿美元,同比增长约28%。主要增长来自生成式AI。", "新能源": "2025年新能源行业投资额达到4500亿美元,电池成本同比下降12%。", "教育": "在线教育市场2025年规模为600亿美元,增速放缓至8%。", } return data.get(query, f"未找到关于{query}的搜索结果") @tool def calculate_growth(current: float, previous: float) -> float: """计算增长率百分比,参数current为当前值,previous为基期值。""" return round((current - previous) / previous * 100, 2) @tool def extract_keywords(text: str) -> str: """抽取文本中的核心关键词,用逗号分隔返回。""" # 简化实现,实际可以用NER模型或LLM抽取 import re words = re.findall(r"[\u4e00-\u9fa5A-Za-z0-9]{2,}", text) seen = [] for w in words: if w not in seen: seen.append(w) return ", ".join(seen[:5]) # ---------- 状态定义 ---------- class ReportState(TypedDict): messages: list topic: str # 用户给的主题 research_data: list # 搜索到的素材 statistics: str # 计算统计结果 keywords: str # 抽取的关键词 final_report: str # 最终报告 # ---------- 模型初始化 ---------- llm = ChatOpenAI(model="gpt-4o-mini", api_key="sk-xxx") llm_with_tools = llm.bind_tools([search_web, calculate_growth, extract_keywords]) # ---------- 节点函数 ---------- def call_model(state: ReportState): result = llm_with_tools.invoke(state["messages"]) return {"messages": [result]} def run_tools(state: ReportState): # 这里从模型消息里提取工具调用结果,由ToolNode自动执行 # 在真实写法里,我们不应该重复执行工具 # 这里只是为了演示状态同步,实际使用请直接依赖ToolNode last_msg = state["messages"][-1] if last_msg.tool_calls: return {"messages": [last_msg]} return {} def create_report(state: ReportState): """最后一个节点:汇总所有中间数据生成报告。""" context = "\n".join(state.get("research_data", [])) report_prompt = f""" 你是一名行业分析师。请基于以下素材撰写一份简短报告。 素材: {context} 关键词:{state.get('keywords', '')} 统计:{state.get('statistics', '')} """ result = llm.invoke([ {"role": "system", "content": "你是一个严谨的分析师。"}, {"role": "user", "content": report_prompt} ]) return {"final_report": result.content} # ---------- 构图 ---------- builder = StateGraph(ReportState) builder.add_node("call_model", call_model) builder.add_node("tools", ToolNode([search_web, calculate_growth, extract_keywords])) builder.add_node("create_report", create_report) builder.set_entry_point("call_model") builder.add_conditional_edges( "call_model", tools_condition, {"tools": "tools", END: END} ) builder.add_edge("tools", "call_model") # 这里为了演示完整状态流转,简化实现,直接定义入口 # 实际项目中应通过条件边在合适时机跳转到create_report builder.add_edge("create_report", END) graph = builder.compile()注意:上面这个实现里,
run_tools节点是我为了讲清楚状态字段而简化加入的。真正项目中,工具的执行交给ToolNode即可,你不需要自己写run_tools节点,重点看create_report如何汇总状态字段生成最终产出。
4.3 运行日志解读与成效分析
执行这个Agent时,$graph.invoke会把整个链条跑完。你打印state["final_report"]就能拿到最终报告。我实际跑过之后,发现几个非常关键的工程细节。
第一,消息列表本身就是Agent的“工作日志”。每走过一个节点,messages里会增加一条新消息,你完全可以把整个列表打开发给调试工具看,从message type的变化就能还原出Agent每个决策。这对排查问题极其有用。
第二,工具的顺序依赖需要靠prompt引导。比如要先搜索再计算,你需要在初始系统提示词里写明“先搜索素材,再做计算”。模型不是天生就知道该按什么顺序调用工具的,系统提示词就是给它写的操作手册。开始跑的时候我偷懒没写,结果模型上来就给calculate_growth传了一堆虚构数字。
第三,中间数据字段要显式传。上面代码里,research_data、statistics这些字段在ToolNode执行后并没有自动填充到state顶层,它们只是存在于消息内容里。所以create_report节点里需要重新解析。这暴露了一个非常重要的点:LangGraph的状态字段不是自动魔法,节点重新计算后需要显式return新值,框架才会更新。
4.4 工程化落地建议
跑通demo之后,真正上生产还差几步。
第一步,把搜索工具换成真实API。无论是用SerpAPI、博查还是内部搜索系统,核心要点都是:工具返回的结构化数据,尽量只保留模型决策需要的最小信息,不要一股脑把几十页网页正文交给模型,否则token消耗会让你肉疼。
第二步,设置超时和重试。我建议在工具调用外面包一层带超时的装饰器,给每个工具一个明确的超时阈值,比如搜索10秒、计算3秒。超时了就返回一个明确错误描述给模型,让它决定是重试还是换方案。这比无限卡死强一万倍。
第三步,做成本护栏。Agent的循环特性意味着一次任务可能产生大量token。我线上环境会用“最大模型调用次数”来兜底,比如达到15次模型调用后强制结束并返回“任务太复杂,请缩小范围”。这个逻辑可以写进条件边里,非常体现LangGraph的灵活性。
5. LangGraph、Dify、AutoGen怎么选:我的选型思考
5.1 三者的定位差异
现在市面上的智能体开发方式五花八门,最常被拿来比较的是LangGraph、Dify和AutoGen。后台不少同学问这三者到底有什么区别,我直接用一张表说清楚。
| 对比项 | LangGraph | Dify | AutoGen |
|---|---|---|---|
| 定位 | 低层Agent编排框架 | 低代码AI应用开发平台 | 多智能体对话框架 |
| 开发门槛 | 中高,需写代码 | 低,可视化拖拽 | 中,需写代码 |
| 灵活性 | 极高,图结构自由设计 | 中,受平台模板限制 | 较高,专注多智能体对话 |
| 调试体验 | 状态快照+图可视化 | 可视化流程+日志 | 日志为主 |
| 适合场景 | 复杂生产级Agent,需要精细控制 | 快速搭建MVP,非技术团队试用 | 学术研究、复杂多Agent辩论 |
| 受害者痛点 | 入门曲线陡 | 灵活度受限 | 生产稳定性较弱 |
我的直观感受是:Dify更像“Agent界的建站工具”,省钱省力,但在节点级控制、状态字段自定义、复杂分支逻辑上会让人抓狂;AutoGen的核心思想是多智能体互相讨论,听起来很酷,但在真实业务场景里,让两个Agent对话很容易扯皮扯半天不出结果;LangGraph则更像“Agent界的编程语言”,每个细节你都能控制,代价是需要写更多代码。
5.2 什么场景我建议直接选LangGraph
一个判断标准:如果你的Agent需要精确的流程控制、复杂的业务分支、生产级状态持久化,那就别犹豫,选LangGraph。
比如智能客服工单系统,你需要先判断工单类型,再分配给不同处理节点,中间可能要调用用户画像系统、库存系统、知识库系统,最后还要把处理结果写回工单。这个场景里的分支逻辑是多叉的,涉及的工具是异构的,对状态一致性要求还特别高。用可视化平台很难把这种复杂逻辑表达清楚,用纯粹手写代码又容易乱,LangGraph的图结构正好是这个复杂度的“甜点位”。
如果你的应用相对简单——就是接一个大模型,加一两个检索工具,然后就是普通的问答——那Dify可能30分钟就搞定了,硬上LangGraph属于杀鸡用牛刀。工具选型的第一原则永远是匹配复杂度,而不是追逐技术热点。
6. 踩坑记:LangGraph开发中的高频问题与排查
6.1 状态字段被覆盖,而不是累加
这是我遇到的第一个坑,也是新手最容易踩的。我在节点里写下这种方式:
def agent_node(state): return {"collected_data": new_data}运行之后发现,第二轮循环时collected_data字段被覆盖成了最新的值,之前的数据全丢了。LangGraph默认的行为就是把每个节点的return结果以“整体替换”的方式更新到State。
如果要累加,必须用Annotated配合operator.add:
from typing import Annotated from operator import add class State(TypedDict): collected_data: Annotated[list, add]这个Annotated语法初看有点劝退,但实际是LangGraph最优雅的设计之一:你可以自定义reducer来精确控制“新数据和旧数据合并”的方式。用add就是追加,用自定义函数还可以做去重、排序、只保留最新N条,非常灵活。
6.2 模型陷入循环调用同一个工具
我跑Agent时经常看到模型反复调用同一个搜索工具,传的参数几乎一样,然后返回结果也是一样,整个循环像卡在了原地。原因是模型判断“还需要更多信息”,但又没有新思路,只能反复用老办法。
这种问题我一般从三个方向排查。第一,检查系统提示词里是否给了足够的“下一步决策指导”,比如明确告诉模型“如果搜索结果已出现至少3个不同维度的信息,就进入总结阶段”。第二,在条件边里加次数计数器,超过最大调用次数强制跳转结束。第三,检查工具返回的内容是否够丰富,如果工具返回过于简短,模型得不到有价值的信息,自然容易陷入低效循环。
6.3 多轮对话的消息历史爆炸
用LangGraph的agents时,messages会随着循环不断增长。一开始我图省事,把所有历史消息都塞给模型,结果token消耗飞速暴涨,到了第三四轮对话,请求体已经大得离谱。
解决办法是给消息做裁剪或摘要。LangGraph里可以用一个单独的节点来做消息压缩,比如只保留系统提示词、最近两轮对话、以及清晰标注的中间工具执行结果。这个节点本质上也只是一个普通节点,放在循环里每两轮执行一次。既不破坏图结构,又控制了成本。
6.4 调试不如预期时,先看尖峰日志
这个建议可能比任何代码技巧都重要。LangGraph官方会建议你用LangSmith之类的平台做云端调试,但本地开发时我更喜欢直接在关键节点打印状态变化,尤其是每次call_model后的消息数量和最后一条消息的类型。
def call_model(state): print(f"[DEBUG] messages count: {len(state['messages'])}") print(f"[DEBUG] last message type: {state['messages'][-1].type}") result = llm_with_tools.invoke(state["messages"]) if result.tool_calls: print(f"[DEBUG] model wants to call: {result.tool_calls}") return {"messages": [result]}从打印日志里基本能一眼看出问题出在哪个环节:是模型压根没生成工具调用(说明prompt或绑定有问题),还是生成了工具调用但工具执行报错(说明工具注册或参数解析有问题),还是工具执行成功但模型没把工具结果塞进上下文(说明消息结构有问题)。带着这种思路排查,效率比盲猜高很多。
6.5 面试常问的几个LangGraph问题
因为热词里出现了“智能体面试”,我也顺带提几个我遇到过的典型面试题,算是给大家刷个印象。
面试官特别喜欢问LangGraph和LangChain是什么关系。其实LangChain是个包含模型调用、Prompt管理、链式组合等多种能力的综合框架,而LangGraph是独立出来的专门做Agent流程编排的库,它在底层用图结构管理状态和循环,可以脱离LangChain单独使用,但通常和LangChain的组件(比如ChatOpenAI、Message类)配合最舒服。
第二个高频问题是“LangGraph的State是可变的吗”。标准理解是:State是节点之间共享的数据结构,节点返回的更新会被reducer合并到全局状态里,所以你看到的是“可变”效果,但更准确的描述是“基于不可变快照的增量更新机制”。
第三个问题是“如何实现人工审核”。核心思路就是用interrupt_before参数或Command中断功能,让图在指定节点前暂停,等待外部输入再继续。比如在工具执行前插入人工审核节点,用graph.invoke(..., config={"interrupt_before": ["tools"]})控制中断点,后续用graph.invoke(Command(resume=...))继续执行。
结尾
我现在几乎所有的Agent项目都在用LangGraph。从最初写demo时对状态图懵懵懂懂,到后来真正用它在生产环境跑通工单自动分类、资料检索、报告生成一整条流水线,最大的体会是:LangGraph的价值不在于它有多少炫酷的函数,而在于它逼着你把Agent的整个思考过程显性化、流程化、可回溯。这个思维方式一旦建立,你对AI应用的认知就不再是“我调了个模型”,而是“我设计了一个能自主执行任务的系统”。
最后分享一个小技巧:刚开始接触LangGraph,不要一上来就想搞大而全的多智能体架构,先用最简单的StateGraph把一个工具调用循环跑通,再逐步加状态字段、加工具、加分支。我见过太多人卡在第一步,不是因为LangGraph难,而是因为想一步到位设计得太复杂。先慢下来,把最小闭环跑顺,后面的扩展自然会顺理成章。