LangGraph实战:基于图编排构建复杂AI工作流与状态管理
1. 项目概述为什么我们需要 LangGraph如果你已经用了一段时间的 LangChain构建过一些简单的聊天机器人或者文档问答应用可能会遇到一个瓶颈当业务流程稍微复杂一点涉及到多轮对话、条件分支或者需要维护一个长期的状态时用 LangChain 原生的 Chain 和 Agent 来组织代码会感觉有点“力不从心”。代码会变得冗长状态管理混乱调试起来像在走迷宫。这正是 LangGraph 要解决的问题。简单来说LangGraph 是一个基于图Graph来编排和运行复杂 LLM 工作流的框架。它把整个应用流程看作一张由节点Node和边Edge组成的有向图。节点负责执行具体的任务比如调用 LLM、执行工具、处理数据边则定义了任务之间的流转逻辑比如根据 LLM 的输出决定下一步去哪。这种范式特别适合那些有明确状态、需要循环或条件判断的复杂应用比如多智能体协作系统、带审核流程的客服机器人、或者游戏 NPC 的对话引擎。我第一次用 LangGraph 重构一个老旧的客服系统时感觉就像从杂乱无章的平房搬进了结构清晰的楼房。之前用 if-else 和全局变量硬凑的逻辑现在被清晰地画在了一张图上哪个环节出问题一目了然。更重要的是它内置的状态管理机制让追踪一次完整会话的“记忆”变得异常简单。接下来我会带你从零开始拆解 LangGraph 的核心概念并用一个完整的实战项目让你彻底掌握如何用它来构建真正强大、可维护的 AI 应用。2. LangGraph 核心三要素State, Node, Edge要理解 LangGraph必须吃透它的三个核心概念状态State、节点Node和边Edge。这是构建任何图的基础。2.1 状态State工作流的“记忆中枢”在 LangGraph 中State 是一个贯穿整个图执行过程的、可变的共享数据容器。你可以把它想象成一个全局的字典dict但这个字典的结构是预先定义好的。所有节点都读取和修改这个 State从而完成信息的传递和任务的推进。定义一个 State 通常使用 Pydantic 的BaseModel或者 TypedDict。我强烈推荐使用BaseModel因为它能提供类型提示和验证减少运行时错误。from typing import Annotated, List from typing_extensions import TypedDict from pydantic import BaseModel, Field import operator # 方法一使用 TypedDict较基础 class BasicState(TypedDict): messages: List[str] # 消息列表 query: str # 用户查询 answer: str # 最终答案 # 方法二使用 Pydantic BaseModel推荐功能更强大 class GraphState(BaseModel): # 使用 Annotated 和 operator.add 来声明这是一个“追加”操作 messages: Annotated[List[str], operator.add] Field(default_factorylist) user_query: str llm_response: str needs_human_review: bool False review_feedback: str 上面代码中messages字段的Annotated[List[str], operator.add]是关键。它告诉 LangGraph当多个节点修改这个字段时应该使用operator.add即列表的操作来合并更新而不是后一个覆盖前一个。这是实现对话历史累积的常用技巧。其他字段如user_query、llm_response则是普通的覆盖更新。实操心得在定义 State 时一定要想清楚每个字段的更新方式。对于需要累积的历史信息如对话记录、中间结果使用Annotated和operator.add对于临时变量或最终结果使用普通字段。规划好 State 的结构就等于规划好了整个工作流的数据流事半功倍。2.2 节点Node执行具体任务的“工作单元”Node 是图的核心执行单元本质上就是一个接收 State、修改 State 并返回新 State 的函数。每个节点应该只负责一件明确的事情遵循单一职责原则。from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4o-mini) def retrieve_information(state: GraphState): 模拟信息检索节点 # 从 State 中获取用户查询 query state.user_query # 模拟一个检索过程实际中可能调用向量数据库 retrieved_info f根据查询‘{query}’检索到的相关文档摘要... # 更新 State将检索结果附加到消息历史中 state.messages.append(f检索信息{retrieved_info}) return state def call_llm(state: GraphState): 调用大语言模型生成回复的节点 # 组装上下文历史消息 用户最新查询 context \n.join(state.messages[-5:]) # 取最近5条历史作为上下文 prompt f 基于以下上下文信息回答用户问题。 上下文 {context} 用户问题{state.user_query} 请给出专业、准确的回答。 # 调用 LLM response llm.invoke(prompt) # 更新 State state.llm_response response.content state.messages.append(fAI回复{response.content}) return state def human_review_check(state: GraphState): 检查是否需要人工审核的节点路由节点 # 一个简单的规则如果回复中包含“财务”、“法律”、“机密”等关键词则需审核 sensitive_keywords [财务, 法律, 机密, 合同, 赔偿] response state.llm_response state.needs_human_review any(keyword in response for keyword in sensitive_keywords) return state节点设计的关键在于“原子化”。不要把太多逻辑塞进一个节点。比如把检索、LLM调用、格式化输出拆分成三个独立的节点这样每个节点都易于测试、复用和调试。2.3 边Edge控制流程的“交通规则”Edge 决定了执行完一个节点后下一步应该去哪个节点。边可以是固定的always_go_to也可以是有条件的conditional_edge后者是实现分支和循环的关键。from langgraph.graph import StateGraph, END # 1. 创建图构建器 workflow StateGraph(GraphState) # 2. 添加节点 workflow.add_node(retrieve, retrieve_information) workflow.add_node(generate, call_llm) workflow.add_node(review_check, human_review_check) workflow.add_node(human_review, human_review_node) # 假设已定义 # 3. 设置入口点 workflow.set_entry_point(retrieve) # 4. 添加固定边无条件流转 workflow.add_edge(retrieve, generate) workflow.add_edge(generate, review_check) # 5. 添加条件边实现分支 from langgraph.graph import END def route_after_review(state: GraphState): 根据审核检查结果决定路由 if state.needs_human_review: return human_review # 需要审核去人工审核节点 else: return END # 不需要审核直接结束 workflow.add_conditional_edges( review_check, # 源节点 route_after_review, # 路由判断函数 { human_review: human_review, # 如果函数返回human_review则跳转到同名节点 END: END # 如果函数返回END则结束图执行 } ) # 6. 从人工审核节点出来后固定结束 workflow.add_edge(human_review, END) # 7. 编译图 app workflow.compile()条件边add_conditional_edges是 LangGraph 最强大的特性之一。路由函数 (route_after_review) 返回一个字符串这个字符串必须映射到后续的节点名或END。通过组合固定边和条件边你可以构建出任意复杂的流程图包括循环例如让某个节点在条件满足时跳回之前的节点。3. 构建你的第一个 LangGraph 应用带审核的问答机器人理论讲得再多不如动手做一遍。我们来构建一个完整的“带敏感信息审核的智能问答机器人”。这个应用流程是用户提问 - 检索信息 - LLM生成初稿 - 检查是否涉及敏感内容 - 若涉及则模拟人工审核并修改 - 最终回复。3.1 环境准备与依赖安装首先确保你的 Python 环境在 3.9 以上。然后安装必要的包pip install langgraph langchain-openai pydantic这里我们使用langchain-openai来调用 OpenAI 的模型你也可以替换成 Anthropic、Groq 或其他 LangChain 支持的模型。3.2 定义应用状态与节点我们将定义比之前更完整的状态和节点。from typing import Annotated, List, Optional from pydantic import BaseModel, Field import operator from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage # 1. 定义状态 class QABotState(BaseModel): # 完整的对话历史使用追加更新 conversation_history: Annotated[List, operator.add] Field(default_factorylist) # 当前轮次的用户输入 current_query: str # 检索到的背景信息 retrieved_context: str # LLM生成的原始回复 draft_response: str # 是否需要人工审核 needs_review: bool False # 审核意见模拟 review_notes: str # 最终回复 final_response: str # 2. 初始化LLM llm ChatOpenAI(modelgpt-4o-mini, temperature0.7) # 3. 定义节点函数 def retrieve_node(state: QABotState): 模拟检索节点根据问题‘检索’相关信息 print(f[节点检索] 正在处理查询‘{state.current_query}’) # 这里简化处理实际应接入向量数据库 # 假设我们有一个简单的“知识库” knowledge_base { 公司政策: 员工每年享有15天带薪年假。, 报销流程: 所有报销需在发票开具后30天内提交至财务系统并附上直属领导审批。, 数据安全: 客户机密数据严禁通过外部邮件发送必须使用内部加密通道。 } # 简单关键词匹配“检索” context for key, value in knowledge_base.items(): if key in state.current_query: context value break if not context: context 未在知识库中找到精确匹配信息将基于模型通用知识回答。 state.retrieved_context context state.conversation_history.append(HumanMessage(contentf用户查询{state.current_query})) state.conversation_history.append(AIMessage(contentf[系统检索结果]{context})) return state def generate_draft_node(state: QABotState): 生成回复草稿节点 print(f[节点生成草稿] 基于上下文生成回复。) # 构建提示词 prompt f 你是一个专业的公司内部助手。请根据以下背景信息回答用户问题。 如果背景信息不足请基于你的常识给出谨慎、保守的建议并说明信息可能不完整。 【背景信息】 {state.retrieved_context} 【用户问题】 {state.current_query} 请直接给出回答无需提及“根据背景信息”等前缀。 response llm.invoke(prompt) state.draft_response response.content state.conversation_history.append(AIMessage(contentf[AI生成草稿]{response.content})) return state def review_check_node(state: QABotState): 审核检查节点判断草稿是否需要人工审核 print(f[节点审核检查] 检查回复草稿的敏感性。) draft state.draft_response # 定义敏感词和需要审核的复杂主题 sensitive_terms [开除, 赔偿, 法律诉讼, 核心机密, 董事会决议, 超过100万] complex_topics [财务预算, 人事调整, 战略合作] needs_review False notes [] # 规则1包含敏感词 for term in sensitive_terms: if term in draft: needs_review True notes.append(f提及敏感词‘{term}’) break # 规则2涉及复杂主题且回复较长可能过于具体 if any(topic in state.current_query for topic in complex_topics) and len(draft) 200: needs_review True notes.append(问题涉及复杂主题且回复较详细建议审核。) # 规则3草稿中包含不确定表述 if 可能 in draft or 不确定 in draft or 建议咨询 in draft: notes.append(回复中包含不确定性表述。) # 仅记录不强制审核 state.needs_review needs_review state.review_notes ; .join(notes) if notes else 无特殊备注 return state def human_review_simulation_node(state: QABotState): 模拟人工审核节点实际中会是一个人工介入的界面 print(f[节点人工审核模拟] 正在审核。审核意见{state.review_notes}) # 模拟审核逻辑如果涉及法律财务则回复变得保守 draft state.draft_response if 法律 in state.current_query or 财务 in state.current_query: revised f[经审核修订]关于此问题涉及公司具体政策建议您直接联系法务部或财务部获取官方文件。原始AI生成内容仅供参考{draft[:100]}... elif 机密 in draft: revised [经审核修订]您查询的内容可能涉及保密信息。请确认您有相应权限并通过内部安全渠道进一步咨询。 else: revised f[经审核确认]{draft} state.final_response revised state.conversation_history.append(HumanMessage(content[系统人工审核环节])) state.conversation_history.append(AIMessage(contentf审核后最终回复{revised})) return state def finalize_node(state: QABotState): 最终处理节点如果无需审核则直接使用草稿作为最终回复 if not state.needs_review: print(f[节点最终处理] 无需审核直接发布草稿。) state.final_response state.draft_response state.conversation_history.append(AIMessage(contentf[最终回复]{state.draft_response})) return state3.3 组装图并执行现在我们把所有节点用边连接起来形成一个完整的工作流。from langgraph.graph import StateGraph, END # 1. 创建图 builder StateGraph(QABotState) # 2. 添加所有节点 builder.add_node(retrieve, retrieve_node) builder.add_node(generate_draft, generate_draft_node) builder.add_node(review_check, review_check_node) builder.add_node(human_review, human_review_simulation_node) builder.add_node(finalize, finalize_node) # 3. 设置入口点 builder.set_entry_point(retrieve) # 4. 添加固定边检索 - 生成草稿 - 审核检查 builder.add_edge(retrieve, generate_draft) builder.add_edge(generate_draft, review_check) # 5. 添加条件边根据审核检查结果分流 def decide_after_check(state: QABotState): if state.needs_review: return yes_review else: return no_review builder.add_conditional_edges( review_check, decide_after_check, { yes_review: human_review, # 需要审核去人工审核节点 no_review: finalize # 不需要审核去最终处理节点 } ) # 6. 添加固定边人工审核后 - 最终处理最终处理后 - 结束 builder.add_edge(human_review, finalize) builder.add_edge(finalize, END) # 7. 编译图得到可执行的应用 app builder.compile() # 8. 运行图 # 定义初始状态 initial_state QABotState(current_query请问公司的报销流程是怎样的) print( 执行工作流普通查询 ) result app.invoke(initial_state) print(f最终回复{result[final_response]}\n) # 测试一个需要审核的查询 initial_state_sensitive QABotState(current_query如果泄露了客户机密数据会有什么法律后果) print( 执行工作流敏感查询 ) result2 app.invoke(initial_state_sensitive) print(f最终回复{result2[final_response]}) print(f审核意见{result2[review_notes]}) print(f完整对话历史长度{len(result2[conversation_history])})运行这段代码你会看到控制台打印出每个节点的执行日志并最终输出针对不同问题的、经过不同路径处理后的回复。对于敏感问题它会走“人工审核”分支你会看到[节点人工审核模拟]被触发。4. LangGraph 高级特性与实战技巧掌握了基础构建后我们来看看 LangGraph 那些能让你的应用更强大、更优雅的高级特性。4.1 子图Subgraph模块化与复用当你的工作流变得非常庞大时可以把其中功能独立的模块封装成子图。子图本身也是一个完整的图可以被主图调用这极大地提升了代码的模块化和复用性。假设我们有一个“信息核实”子流程它本身包含检索、多源比对、可信度评分三个步骤。from langgraph.graph import StateGraph # 1. 定义子图专用的状态可以是主状态的一部分 class VerificationState(BaseModel): query: str retrieved_sources: List[str] Field(default_factorylist) consistency_score: float 0.0 verified_answer: str # 2. 构建子图 verification_workflow StateGraph(VerificationState) def retrieve_multiple_sources(state: VerificationState): # 模拟从多个来源检索 state.retrieved_sources [ f来源A关于‘{state.query}’的信息。, f来源B关于‘{state.query}’的不同观点。 ] return state def check_consistency(state: VerificationState): # 简单模拟一致性检查 if state.retrieved_sources and len(set(state.retrieved_sources)) 1: state.consistency_score 1.0 else: state.consistency_score 0.5 return state def synthesize_answer(state: VerificationState): if state.consistency_score 0.8: state.verified_answer f信息一致可采纳{state.retrieved_sources[0]} else: state.verified_answer f信息存在不一致请谨慎参考。来源如下{; .join(state.retrieved_sources)} return state # 添加节点和边 verification_workflow.add_node(retrieve, retrieve_multiple_sources) verification_workflow.add_node(check, check_consistency) verification_workflow.add_node(synthesize, synthesize_answer) verification_workflow.add_edge(retrieve, check) verification_workflow.add_edge(check, synthesize) verification_workflow.set_entry_point(retrieve) verification_workflow.set_finish_point(synthesize) # 设置子图终点 # 编译子图 verification_subgraph verification_workflow.compile() # 3. 在主图中我们可以把这个子图当作一个“超级节点”来添加 # 假设主图 builder 已经存在 # builder.add_node(verify_info, verification_subgraph)注意事项子图和主图的状态类型需要兼容。通常的做法是子图使用一个更具体的 State 类型然后在主图的节点函数中调用子图时进行状态的提取和合并。或者主图 State 包含一个子图 State 类型的字段。这需要一些额外的状态映射代码是使用子图时的主要复杂度来源。4.2 持久化与长期记忆CheckpointerLangGraph 的Checkpointer功能是实现“长期记忆”和“持久化工作流”的关键。它允许你将图执行的状态保存到数据库如 PostgreSQL、MySQL或内存中之后可以随时从某个节点恢复执行。这对于处理耗时很长的流程如需要人工审批几天后才继续或构建有状态的对话机器人至关重要。from langgraph.checkpoint.sqlite import SqliteSaver import sqlite3 from langgraph.graph import StateGraph, START # 1. 初始化 SQLite 检查点存储 conn sqlite3.connect(checkpoints.db) checkpointer SqliteSaver(conn) # 2. 在编译图时传入 checkpointer builder StateGraph(QABotState) # ... 添加节点和边 ... app builder.compile(checkpointercheckpointer) # 关键在这里 # 3. 使用线程IDthread_id来标识一个独立的会话或流程实例 config {configurable: {thread_id: user_session_12345}} # 第一次调用状态会被保存 initial_state QABotState(current_query第一个问题) result1 app.invoke(initial_state, configconfig) print(f第一次执行后的最终回复{result1[final_response]}) # 模拟流程中断...比如等待人工审核 # 第二次基于同一个 thread_id 和新的输入继续执行 # 我们需要一个“入口节点”来处理新输入并合并到已有状态中 def new_query_node(state: QABotState): # 在实际应用中这里会从外部获取新问题 state.current_query 基于之前的回答我的第二个问题是... return state # 我们可以设计一个新的图或者修改原图将新查询作为起点 # 关键是使用相同的 configcheckpointer 会加载上次保存的状态 # 假设我们更新了状态中的 current_query 后继续执行后续节点 # app.invoke(new_state, configconfig)实操心得thread_id的设计非常重要。它可以是用户ID、会话ID、工单号等。对于需要暂停/恢复的流程在需要等待的节点如human_review之后不要直接连接END而是连接一个“等待”节点。当外部事件如人工点击通过触发时再根据thread_id恢复图执行从“等待”节点继续往下走。这实现了异步、持久化的长流程。4.3 动态边与循环有时下一个节点不是静态决定的而是需要根据当前状态动态计算甚至可能需要循环执行某个节点直到条件满足。def quality_check_node(state: QABotState): 质量检查节点如果回答太短则要求重新生成 if len(state.final_response.strip()) 50: # 假设回复少于50字符则质量不足 state.conversation_history.append(AIMessage(content[质量检查]回复内容过短将尝试重新生成。)) # 通过修改一个标志位触发循环 state.retry_generation True else: state.retry_generation False return state def dynamic_router(state: QABotState): 动态路由函数 if hasattr(state, retry_generation) and state.retry_generation: # 如果需要重试则跳回生成草稿节点形成循环 return generate_draft else: # 否则继续下一步或结束 return proceed_to_end # 在图中使用 # builder.add_node(quality_check, quality_check_node) # builder.add_conditional_edges( # quality_check, # dynamic_router, # { # generate_draft: generate_draft, # 循环回去 # proceed_to_end: END # } # )警告循环必须设置终止条件否则会导致无限循环。在上面的例子中quality_check_node不能一直让retry_generation为 True必须在某次循环后将其设为 False或者设置最大循环次数。5. LangGraph vs LangChain如何选择与结合使用这是初学者最常问的问题。它们不是替代关系而是互补关系。LangChain是一个组件库和集成框架。它提供了与上百种LLM、向量数据库、工具Tools开箱即用的连接器以及Chain和Agent这种高级抽象用于快速组装简单的、线性的AI应用。它的核心价值在于“连接”和“标准化接口”。LangGraph是一个工作流编排框架。它专注于管理复杂的、有状态的、带分支循环的应用程序逻辑。它不关心你用的LLM是OpenAI还是Annotated检索用的是Pinecone还是Chroma它只关心你的业务流程怎么走。它的核心价值在于“控制流”和“状态管理”。如何选择如果你的应用是简单的“输入-LLM-输出”或者“检索-生成”这种线性管道用LangChain 的 Chain就够了更轻量。如果你的应用需要多轮交互、条件判断、循环、人工介入、多智能体协作或者你需要对流程有清晰的图示和强大的状态追踪能力那么LangGraph是你的不二之选。如何结合在 LangGraph 的节点Node内部你可以尽情使用 LangChain 的各种组件例如在一个节点里你可以用 LangChain 的RetrievalQAChain 来检索用OpenAIFunctionsAgent来调用工具。LangGraph 负责宏观的流程调度LangChain 负责微观的任务执行。这是最佳实践。from langchain.chains import RetrievalQA from langchain_community.vectorstores import Chroma # ... 其他 LangChain 导入 def complex_retrieval_node(state: GraphState): 在 LangGraph 节点内使用完整的 LangChain 链 # 1. 使用 LangChain 初始化向量库和检索器 vectorstore Chroma(...) retriever vectorstore.as_retriever() # 2. 创建 LangChain Chain qa_chain RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrieverretriever ) # 3. 执行 Chain result qa_chain.invoke({query: state.user_query}) # 4. 将结果更新到 LangGraph 的 State 中 state.retrieved_answer result[result] return state6. 常见问题与调试技巧实录在实际使用中你肯定会遇到各种问题。这里记录了我踩过的一些坑和解决方法。6.1 状态更新不符合预期问题明明在节点里修改了state.some_list.append(item)但执行后发现列表还是空的。原因LangGraph 默认使用“浅合并”策略。对于可变对象如列表、字典直接修改其内容可能不会被 LangGraph 的更新机制捕获。解决方案推荐在定义 State 时对列表字段使用Annotated[List, operator.add]。这样你只需要在节点中return {messages: [new_message]}LangGraph 会自动将其追加到原列表。如果必须原地修改确保最后返回的是一个新的状态对象或包含更新字段的字典。例如return {some_list: state.some_list}即使你只是修改了原列表。6.2 条件边Conditional Edge不生效问题定义的条件边路由函数被执行了但流程没有按预期分支。排查步骤检查路由函数返回值确保返回值是字符串并且精确匹配add_conditional_edges方法中映射字典的键。大小写、空格都要一致。检查映射字典字典的键是路由函数可能的返回值值必须是图中已添加的节点名称或END。打印调试在路由函数开头加print(f路由函数被调用state: {state})确认函数被触发时的状态是否符合你的条件判断逻辑。6.3 图编译或执行时报错Pydantic验证错误问题类似pydantic.error_wrappers.ValidationError的错误。原因传递给节点的 State 或节点返回的更新字典与定义的State(BaseModel) 结构不匹配。解决方案确保节点函数接收和返回的类型注解正确。节点返回的字典其键必须是 State 模型中定义的字段名。如果只更新部分字段可以返回一个字典如return {field1: new_value}LangGraph 会将其与旧状态合并。使用print(state.dict())在节点开始处打印状态检查数据结构。6.4 如何可视化我的工作流图LangGraph 提供了内置的可视化方法对于调试和理解复杂流程极其有用。# 方法一生成 Mermaid 图代码可用于 Markdown 或 Mermaid 在线编辑器 from langchain_core.runnables.graph import MermaidDrawer try: # 注意MermaidDrawer 可能在不同版本中位置有变化 graph_image MermaidDrawer().draw(app.get_graph()) print(graph_image) except: # 方法二使用 get_graph() 方法输出文本表示 graph app.get_graph() print(graph) # 或者直接打印编译后的图 print(app.get_graph().draw_mermaid())将输出的 Mermaid 代码复制到 Mermaid Live Editor 中就能看到一张清晰的流程图。6.5 性能优化技巧节点轻量化每个节点应只做一件事。避免在单个节点中进行耗时极长的操作如检索大量文档。如果操作很重考虑将其拆分为“准备”、“执行”、“后处理”多个节点或者使用异步。状态最小化State 中只存放必要的数据。不要在 State 里存储大对象如整个文档内容。可以存放引用或摘要。合理使用检查点Checkpointer虽然强大但每次保存/读取状态都有开销。对于不需要暂停/恢复的短流程可以不使用。对于长流程也要评估检查点的频率。并发执行如果多个节点间没有依赖关系理论上可以并发执行。LangGraph 本身是顺序执行但你可以通过设计将可以并发的任务放在不同的分支最后再通过一个节点合并结果。7. 从入门到精通下一步学习路径通过上面的指南你应该已经能够用 LangGraph 构建复杂的 AI 工作流了。要更上一层楼我建议你深入研究官方文档与源码LangGraph 的官方文档是宝藏尤其是StateGraph、Checkpointer和MessageGraph用于纯消息传递场景的 API 参考。读源码能帮你理解其内部执行机制。学习经典模式多智能体协作创建多个具有不同角色的 Agent 节点让他们通过共享的 State 或互相发送消息来协作完成任务。人工在环Human-in-the-loop利用Checkpointer实现流程暂停等待外部如人工审核、用户输入事件后恢复。自省与修复循环设计一个“批判”节点检查 LLM 输出的质量如果不达标则循环回“重生成”节点。集成到 Web 服务使用 FastAPI 或 Flask 将编译好的app包装成 API。结合thread_id为每个前端会话提供有状态的、可恢复的 AI 服务。探索社区项目GitHub 上有许多基于 LangGraph 的开源项目如复杂的游戏 NPC、自动化客服系统、多步骤内容创作平台等参考它们的架构设计。LangGraph 带来的最大改变是让我们能够以工程师熟悉的、结构化的方式去设计和实现复杂的 AI 逻辑。它把原本缠绕在 Prompt 和胶水代码里的业务逻辑清晰地抽取出来画成了一目了然的图。当你下次再面对一个“如果这样就那么做如果那样就这么做”的需求时不妨先拿出纸笔画一画你会发现用 LangGraph 来实现它是如此的自然。