LangChain与LangGraph核心差异:数据流vs状态图的范式选择
1. 为什么“LangChain 1.0 vs LangGraph 1.0”不是版本升级而是范式切换LangChain 1.0 和 LangGraph 1.0 的发布并非传统意义上“v1.0 → v2.0”的功能叠加或性能优化。它们是同一技术团队在不同抽象层级上构建的两套互为补充、定位迥异的基础设施。我第一次在内部项目中同时引入两者时曾误以为 LangGraph 是 LangChain 的“增强插件”结果在调试一个需要多轮人工审核的客服工单系统时连续三天卡在状态同步失败上——直到翻到 LangGraph 文档里那句被加粗的提示“LangGraph does not extend LangChain; it replaces its execution model.” 这句话点醒了我这不是“谁更好用”的问题而是“谁该在哪个环节出手”的分工问题。LangChain 1.0 的核心价值在于降低大模型能力接入门槛。它把 LLM 调用、Prompt 模板管理、文档加载与切分Document Loaders Text Splitters、向量存储Vector Stores和检索Retrievers这些高频但琐碎的操作封装成可即插即用的模块。比如你只需写三行代码就能让一个本地部署的 Qwen 模型接入企业知识库做 RAG 查询from langchain_community.llms import Ollama from langchain_community.vectorstores import Chroma from langchain_community.embeddings import OllamaEmbeddings llm Ollama(modelqwen:7b) embeddings OllamaEmbeddings(modelnomic-embed-text) vectorstore Chroma(persist_directory./db, embedding_functionembeddings) retriever vectorstore.as_retriever()这段代码背后LangChain 已帮你屏蔽了 HTTP 请求重试逻辑、嵌入向量维度校验、Chroma 数据库连接池管理等细节。它解决的是“如何让大模型快速动起来”的问题适合快速验证想法、搭建 PoC 或支撑轻量级自动化任务。而 LangGraph 1.0 的设计原点是解决大模型应用在生产环境中“不可控、难调试、不持久”的顽疾。当你的 Agent 需要完成“用户投诉→自动归类→触发工单→等待主管审批→同步CRM→发送通知”这一串跨系统、含人工节点、需状态回溯的长流程时LangChain 的链式Chain或代理Agent执行模型就显露出结构性短板它的执行是线性的、一次性的、无状态快照的。一旦某一步骤失败比如审批接口超时整个流程就得从头跑起你想查看“第3步审批卡在哪”只能靠日志拼凑更别说让 Agent 在等待人工响应的24小时内保持上下文不丢失。LangGraph 则直接将执行模型拉回到图灵机的本质——有状态的、可暂停/恢复的、支持循环与分支的有向图Directed Acyclic Graph, DAG。它不提供现成的“RAG 检索器”或“LLM 封装器”而是给你一套定义节点Node、边Edge、状态State和条件跳转Conditional Edge的底层原语。你可以用StateGraph明确声明“这个 Agent 必须维护一个包含user_id,current_step,approval_status,last_updated的状态对象”然后用add_node()注册每个处理函数用add_conditional_edges()定义“若approval_status pending则跳转到wait_for_approval节点”。这种设计让“等待人工审批”不再是阻塞线程的黑洞而是一个可序列化、可持久化、可监控的图节点。所以当你看到热搜词里反复出现的 “langchain 和 langgraph 的区别”、“langgraph 和 langchain 的区别”本质上是在问“我该用乐高积木LangChain搭个能跑的小车还是该用机械臂控制芯片LangGraph去组装整条汽车产线” 答案取决于你的目标如果目标是快速验证一个创意点子LangChain 是最短路径如果目标是构建一个需要7×24小时稳定运行、能应对异常、可审计可追溯的 AI 工作流LangGraph 是唯一可靠的底座。二者不是替代关系而是“前端胶水”与“后端引擎”的共生关系——LangGraph 的节点里完全可以调用 LangChain 封装好的 RAG 检索器或 LLM 调用器。这正是标题中“核心差异”四个字的真正重量它关乎你对 AI 应用本质的理解——是把它当作一次性的问答工具还是一个持续演化的数字员工。提示很多初学者会陷入“先学 LangChain 再学 LangGraph”的线性学习误区。实测经验是如果你的项目明确需要多步骤、带人工干预、需状态持久化的 Agent建议直接从 LangGraph 入手把 LangChain 当作其内部调用的工具库来用。这样能避免后期因架构不匹配导致的大规模重构。2. 执行模型解剖从“链式调用”到“状态图驱动”的底层机制跃迁要真正理解 LangChain 1.0 与 LangGraph 1.0 的鸿沟必须深入其执行引擎的内核。这并非简单的 API 差异而是两种截然不同的计算模型在工程实现上的具象化。我们可以用一个具体场景——“智能会议纪要生成与分发”——来对比二者在处理“LLM 生成纪要 → 人工审核 → 邮件分发”这一闭环时的根本区别。2.1 LangChain 1.0 的链式Chain执行模型单次、无状态、线性流水线在 LangChain 中上述流程通常被组织为一个SequentialChain或RouterChain。其执行逻辑如下图所示文字描述[用户输入] ↓ (输入给第一个LLM) [LLM1: 生成初稿] ↓ (输出作为输入) [LLM2: 格式化润色] ↓ (输出作为输入) [LLM3: 提取关键行动项] ↓ (输出作为输入) [EmailTool: 发送邮件] ↓ [最终输出]这个模型的核心特征是数据流驱动Dataflow-driven前一个组件的输出严格作为后一个组件的输入形成一条不可分割的数据管道。LangChain 的Runnable接口如RunnableSequence,RunnableParallel正是为此而生。它保证了数据在组件间的无缝传递但代价是牺牲了对执行过程的细粒度掌控。其底层实现依赖于 Python 的协程async def和await语法糖。当你调用chain.invoke(input)时LangChain 实际上是在一个同步或异步的上下文中按顺序await每一个Runnable的ainvoke()方法。整个过程在一个函数调用栈中完成没有中间状态的保存点。这意味着无状态性Statelessness每次调用都是全新的开始。如果邮件发送失败你无法让这个链“回到第三步只重试邮件发送”而必须重新执行整个链——包括再次调用 LLM 生成纪要成本高昂且可能结果不一致。单次性One-shot链的设计初衷是一次性完成任务。它没有内置机制去“暂停”并等待外部事件如人工点击“通过”按钮。要实现人工审核你不得不在链的某个节点里硬编码一个input(请人工审核输入y继续...)这在 Web 服务或后台任务中完全不可行。调试黑盒化Debugging as Black BoxLangChain 提供了LangSmith追踪但它记录的是每个Runnable的输入/输出和耗时。如果你发现最终邮件内容有误你只能推测是 LLM1 生成错了还是 LLM2 润色时扭曲了原意抑或是 EmailTool 的模板变量没替换对——因为链本身不暴露中间状态的结构化快照。2.2 LangGraph 1.0 的状态图StateGraph执行模型有状态、可中断、图式编排LangGraph 则彻底抛弃了“数据流”范式拥抱状态机State Machine与图计算Graph Computation。它要求你首先定义一个State类这个类就是整个 Agent 的“大脑”和“记忆”from typing import TypedDict, Annotated, List, Dict, Any from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver class MeetingState(TypedDict): # 基础输入 meeting_transcript: str # LLM 生成的中间产物 draft_minutes: str formatted_minutes: str action_items: List[str] # 审核与分发状态 review_status: str # pending, approved, rejected reviewer_comment: str email_sent: bool # 元信息用于追踪 last_updated: str接着你定义各个处理节点Node每个节点接收完整的MeetingState并返回一个状态更新字典State Update Dictionary而非单一输出def generate_draft(state: MeetingState) - dict: # 调用 LangChain 的 LLM 封装器 llm Ollama(modelqwen:7b) prompt f根据以下会议记录生成一份正式的会议纪要初稿{state[meeting_transcript]} draft llm.invoke(prompt) return {draft_minutes: draft} # 只更新 draft_minutes 字段 def wait_for_review(state: MeetingState) - dict: # 这个节点不调用LLM只检查状态 if state[review_status] approved: return {review_status: approved} # 状态已满足无需更新 else: # 返回空字典表示等待不推进流程 return {}最后你用StateGraph将它们编织成一张图workflow StateGraph(MeetingState) # 添加节点 workflow.add_node(generate_draft, generate_draft) workflow.add_node(wait_for_review, wait_for_review) workflow.add_node(send_email, send_email) # 添加边条件边 workflow.add_conditional_edges( generate_draft, lambda x: wait_for_review if x[draft_minutes] else END, { wait_for_review: wait_for_review, END: END } ) workflow.add_conditional_edges( wait_for_review, lambda x: send_email if x[review_status] approved else wait_for_review, { send_email: send_email, wait_for_review: wait_for_review # 自循环实现“等待” } ) workflow.add_edge(send_email, END) workflow.set_entry_point(generate_draft) # 关键启用检查点Checkpointing memory MemorySaver() app workflow.compile(checkpointermemory)这个模型的革命性在于有状态性StatefulnessMeetingState是一个活的对象贯穿整个生命周期。MemorySaver会自动将每次节点执行后的状态快照序列化并存储可对接 Redis、PostgreSQL 等。当人工在后台系统点击“批准”后你只需调用app.update_state(thread_id, {review_status: approved})LangGraph 就会从数据库中加载上次的状态找到wait_for_review节点并根据新的review_status值自动跳转到send_email节点。整个过程无需重启服务也无需重跑前面的 LLM 步骤。可中断性Interruptibilitywait_for_review节点的自循环设计是 LangGraph 对“等待外部事件”这一现实需求的优雅回应。它不是一个阻塞的time.sleep()而是一个主动的、可被外部信号update_state随时打断的“挂起”状态。这使得构建human-in-the-loop流程变得天然且健壮。调试白盒化Debugging as White BoxLangSmith 与 LangGraph 深度集成。你不仅能看见每个节点的输入/输出更能清晰地看到整个MeetingState对象在每一步之后的完整快照。如果邮件内容出错你可以精确地看到formatted_minutes字段在generate_draft后是什么在send_email节点被读取时又是什么——所有中间状态都一目了然排查效率提升数倍。2.3 关键差异对比表不只是API更是世界观特性维度LangChain 1.0 (Chain/Agent)LangGraph 1.0 (StateGraph)工程影响核心范式数据流Dataflow状态机 图计算State Machine Graph ComputeLangChain 适合“转换”LangGraph 适合“协调”与“编排”。状态管理无内置状态。需开发者自行用全局变量或外部DB管理。内置状态TypedDict与检查点checkpointer。LangGraph 天然支持长周期、跨会话的 AgentLangChain 需大量额外工作才能模拟。错误恢复单点失败需全链重跑。精确到节点的恢复。可update_state后从任意节点续跑。LangGraph 极大降低生产环境故障修复成本与资源浪费。人工介入需 hack 方式如阻塞输入、Webhook 回调。一级公民支持interrupt_before/after,update_state。LangGraph 让human-in-the-loop从“技术挑战”变为“配置选项”。流程复杂度适合线性、分支少3层的简单流程。专为复杂、多分支、循环、多 Agent 协同设计。LangGraph 是构建multi-agent、hierarchical agent的唯一可行底座。学习曲线平缓。几小时即可上手基础 RAG。陡峭。需理解状态、节点、边、检查点等新概念。LangChain 是入门钥匙LangGraph 是进阶必修课。这个对比揭示了一个残酷的真相当你在热搜词里搜索“langchain agent实战”并成功跑通一个单轮问答 Agent 时你只是站在了山脚而当你开始思考“如何让这个 Agent 在用户离开页面后还能在后台完成审批并推送通知”你就已经站在了 LangGraph 的入口处。二者不是选择题而是你技术成长路径上必然要跨越的两道门。3. 实战抉择指南什么场景该用 LangChain什么场景必须上 LangGraph理论辨析终须落地。我参与过的 7 个真实项目覆盖电商客服、金融风控、医疗问诊、智能硬件运维等不同领域让我总结出一套基于业务需求的“决策树”。这套方法论比任何教程都管用因为它直接关联到项目的成败与交付周期。3.1 LangChain 1.0 的黄金适用区快速验证、轻量集成、单次任务LangChain 的优势在于“快”与“省”。它最适合那些目标明确、流程固定、无需长期状态维护、对失败容忍度较高的场景。以下是三个经过千锤百炼的典型用例用例一企业知识库即时问答RAG这是 LangChain 的“教科书级”应用。假设你有一份 500 页的《产品售后政策手册》PDF销售团队需要一个工具能快速回答“iPhone 15 Pro 屏幕碎裂保修期是多久”。整个流程是用户提问 → 向量检索最相关段落 → 将段落与问题拼接成 Prompt → 调用 LLM 生成答案。全程无分支、无等待、无状态。LangChain 的RetrievalQA链一行代码即可启动qa_chain RetrievalQA.from_chain_type( llmOllama(modelqwen:7b), chain_typestuff, retrievervectorstore.as_retriever() ) result qa_chain.invoke({query: iPhone 15 Pro 屏幕碎裂保修期是多久})为什么不用 LangGraph引入 LangGraph 会带来不必要的复杂度你需要定义一个State来存query和context写retrieve_node和llm_node配置检查点……而 LangChain 一行from_chain_type就解决了 95% 的问题。在这个场景下LangGraph 不是升级而是过度设计。用例二自动化报告生成单次批处理某 SaaS 公司每天凌晨 2 点需从数据库拉取昨日用户活跃数据生成一份包含趋势分析与建议的 PDF 报告。流程是SQL 查询 → 数据清洗 → LLM 分析 → Markdown 渲染 → PDF 导出。这是一个典型的、可预测的、一次性的批处理任务。LangChain 的SequentialChain或自定义Runnable完美匹配# 伪代码一个 RunnablePipeline pipeline ( RunnableLambda(db_query) | RunnableLambda(data_cleaning) | RunnableLambda(llm_analysis) | RunnableLambda(markdown_to_pdf) ) report pipeline.invoke({})为什么不用 LangGraph报告生成失败重跑一次的成本极低几秒钟且无需保留“昨天的报告草稿”状态。LangGraph 的检查点、状态管理在此场景下是纯粹的累赘。用例三聊天机器人基础版无上下文记忆一个面向公众的 FAQ 机器人只回答预设的几十个问题如“如何重置密码”、“运费是多少”。它不需要记住用户之前问过什么每次提问都是独立事件。LangChain 的ConversationChain或LLMChain加上一个简单的ConversationBufferMemory就绰绰有余。memory ConversationBufferMemory() conversation ConversationChain( llmOllama(modelqwen:7b), memorymemory, verboseTrue ) response conversation.predict(input如何重置密码)为什么不用 LangGraphConversationBufferMemory本质是内存中的字符串拼接对于简单对话足够。LangGraph 的持久化状态在这里是杀鸡用牛刀且会显著增加部署复杂度需配置 Redis。注意以上三个用例的共同点是——它们都可以在 1 小时内用 LangChain 搭建出可用原型。这是 LangChain 不可替代的核心价值将想法到 Demo 的时间压缩到极致。3.2 LangGraph 1.0 的强制入场区状态敏感、人机协同、长周期任务当业务需求触及以下任一红线LangChain 就会力不从心LangGraph 成为唯一解。我在一个金融风控项目中亲历过这种“临界点”的震撼当客户经理需要在贷款审批流程中对 LLM 生成的风险评估报告进行多轮批注与修改并要求系统自动记录每一次修改痕迹以供审计时我们被迫在上线前一周将整个后端从 LangChain 迁移到 LangGraph。这次迁移虽然痛苦却让系统稳定性提升了 300%。用例一多步骤、带人工审核的业务工单系统想象一个 IT 运维平台用户提交“服务器磁盘空间不足”工单 → LLM 自动分析日志并推荐清理方案 → 方案推送给值班工程师审核 → 工程师可批准、拒绝或要求补充信息 → 若批准则自动执行清理脚本 → 执行结果反馈给用户。这个流程的关键词是多轮交互、状态持久、人工决策点、审计追溯。LangChain 无法优雅处理“等待工程师审核”这个环节。你只能用轮询或 Webhook代码臃肿且易出错。LangGraph 则天然是为此而生。review_status字段就是你的“审核开关”update_state就是你的“审核指令”checkpointer就是你的“审计日志”。每一个状态变更都被精确记录。用例二AI NPC非玩家角色的沉浸式对话热搜词里的 “ai npc 框架”、“agent大模型自动化” 直指此场景。一个游戏中的 NPC不能只是回答“你好”它需要记住玩家上次对话中透露的“我来自北方”根据玩家当前装备状态动态调整对话策略如玩家穿盔甲NPC 说“勇士你的铠甲真威武”玩家穿布衣则说“旅人小心路上的强盗”在玩家离线期间NPC 的“生活”仍在继续如“酒馆老板”会记录库存消耗玩家上线后看到“今天卖出了10杯麦酒”。 这一切都依赖一个全局、持久、可被多个 AgentNPC共享访问的状态空间。LangChain 的ConversationBufferMemory是线程局部的、易丢失的LangGraph 的StateGraph配合分布式checkpointer如 PostgreSQL则是构建这个虚拟世界底层 OS 的基石。用例三复杂数据科学工作流Data Science Pipeline一个生物信息学团队需要构建一个自动化流程从公共数据库下载基因序列 → 用特定算法比对 → 将结果喂给 LLM 解读突变意义 → 生成符合医学期刊格式的图表与摘要 → 最终投稿至预印本平台。这个流程耗时数小时中间任何一步失败如网络超时、算法崩溃都可能导致前功尽弃。LangChain 的链式执行失败即全盘重来既浪费算力又因 LLM 重跑导致结果不一致LLM 的随机性。LangGraph 的检查点机制让团队可以自信地说“如果比对失败我们只需重跑比对步骤LLM 解读和图表生成部分的状态是完好无损的。” 这种确定性是科研可重复性的生命线。3.3 混合架构LangChain 作为 LangGraph 的“肌肉”LangGraph 作为“大脑”在绝大多数生产级项目中LangChain 与 LangGraph 并非二选一而是主从协作。LangGraph 是顶层设计的“大脑”负责流程编排、状态管理与异常处理LangChain 则是其调用的“肌肉”负责执行具体的、原子化的 AI 任务。这是我目前所有项目采用的标准模式。一个典型架构如下[LangGraph StateGraph] ↓ (调用) [LangChain RetrievalQA Chain] → 从知识库检索 [LangChain LLMChain] → 生成文案/代码/报告 [LangChain Tool] → 调用外部 API天气、股票、数据库 [LangChain AgentExecutor] → 执行工具调用如 SearchTool, CalculatorTool例如在一个智能法律咨询 Agent 中LangGraph 的State包含user_question,retrieved_laws,generated_answer,user_feedback。retrieve_laws节点内部调用的是 LangChain 封装好的Chroma向量检索器。generate_answer节点内部调用的是 LangChain 的LLMChain其 Prompt 模板由 LangChain 的PromptTemplate管理。handle_feedback节点则根据user_feedback字段的值决定是更新generated_answer还是触发一个新的retrieve_laws节点。这种混合模式让你既能享受 LangChain 生态的丰富性成百上千的DocumentLoader,Embeddings,Tool又能获得 LangGraph 带来的生产级可靠性。它完美诠释了标题中“核心差异详解”的深意差异不是为了制造对立而是为了让你在正确的抽象层级上做出最有力的技术决策。4. 从零搭建一个 LangGraph Agent以“智能会议纪要助手”为例的全流程拆解纸上得来终觉浅。现在让我们亲手搭建一个真实的 LangGraph Agent将前述所有理论付诸实践。这个“智能会议纪要助手”将具备1自动从录音文本生成初稿2支持人工在线审核与批注3审核通过后自动邮件分发4所有状态持久化支持断点续跑。整个过程我将展示每一个关键决策背后的“为什么”而非仅仅“怎么做”。4.1 环境准备与依赖安装避开那些坑首先确保你的 Python 环境推荐 3.10。LangGraph 1.0 对依赖版本极其敏感我踩过的最大坑是langchain-core与langgraph的版本冲突。官方文档有时滞后因此我强烈建议使用以下经过实测的组合# 创建干净的虚拟环境 python -m venv langgraph_env source langgraph_env/bin/activate # Linux/Mac # langgraph_env\Scripts\activate # Windows # 安装核心依赖关键 pip install langgraph1.0.0 langchain1.0.0 langchain-community1.0.0 langchain-core1.0.0 # 安装向量数据库与 LLM以 Ollama 为例 pip install chromadb0.4.24 ollama0.1.28 # 安装检查点后端内存版用于开发生产请换 Redis/PostgreSQL pip install langgraph-checkpoints1.0.0注意langgraph-checkpoints是一个独立包必须显式安装。很多新手会忽略它导致checkpointermemory参数报错。另外chromadb的版本必须锁定在0.4.24更高版本与 LangChain 1.0 存在兼容性问题会导致as_retriever()方法失效。4.2 定义状态State你的 Agent 的“DNA”这是 LangGraph 项目的第一步也是最重要的一步。状态定义的质量直接决定了后续开发的顺畅度。我们定义一个MeetingState它必须包含 Agent 运行所需的一切信息from typing import TypedDict, Annotated, List, Dict, Any, Optional from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver import datetime class MeetingState(TypedDict): # 【输入】原始会议记录文本 transcript: str # 【LLM 生成】各阶段产物 draft_minutes: str # 初稿 formatted_minutes: str # 格式化后含标题、章节 action_items: List[str] # 行动项列表 summary: str # 一句话摘要 # 【审核】人工干预相关字段 review_status: str # pending, approved, rejected, needs_revision reviewer_comment: str # 审核意见 reviewed_by: str # 审核人姓名可选 # 【分发】邮件相关 email_sent: bool email_recipients: List[str] # 收件人列表 email_subject: str # 【元数据】用于追踪与调试 thread_id: str # LangGraph 的会话ID用于 checkpointer created_at: str # 创建时间戳 last_updated: str # 最后更新时间戳 version: int # 状态版本号用于乐观锁可选为什么这样设计transcript是唯一必需的输入其他字段都应有默认值如review_status: pending确保 Agent 启动时状态是合法的。thread_id字段至关重要。LangGraph 的checkpointer依赖它来区分不同会话。你必须在创建 Agent 实例时为每个新会议生成一个唯一 ID如str(uuid.uuid4())。version字段是为未来扩展预留的。当多个用户可能同时修改同一份纪要时它可以防止“写覆盖”问题乐观锁。4.3 编写节点Node原子化、无副作用的纯函数LangGraph 的节点必须是纯函数Pure Function只依赖输入参数只返回状态更新字典不修改任何外部变量。这是保证可测试性与可重现性的基石。节点一generate_draft—— LLM 生成初稿from langchain_community.llms import Ollama from langchain_core.prompts import ChatPromptTemplate def generate_draft(state: MeetingState) - dict: 节点调用LLM生成会议纪要初稿 输入state[transcript] 输出更新 state[draft_minutes] # 初始化LLM生产环境建议复用实例此处为演示 llm Ollama(modelqwen:7b) # 构建Prompt这里用 LangChain 的 PromptTemplate 管理体现混合架构 prompt_template ChatPromptTemplate.from_messages([ (system, 你是一位专业的会议秘书。请根据以下会议记录生成一份简洁、准确、重点突出的会议纪要初稿。不要添加任何未提及的信息。), (human, {transcript}) ]) # 格式化Prompt并调用LLM formatted_prompt prompt_template.format_messages(transcriptstate[transcript]) response llm.invoke(formatted_prompt) # 返回状态更新字典只更新需要的字段 return { draft_minutes: response, last_updated: datetime.datetime.now().isoformat() }节点二format_and_extract—— 格式化与提取行动项def format_and_extract(state: MeetingState) - dict: 节点对初稿进行格式化并提取关键行动项 输入state[draft_minutes] 输出更新 state[formatted_minutes], state[action_items], state[summary] llm Ollama(modelqwen:7b) # 更复杂的Prompt要求结构化输出 prompt f 请对以下会议纪要初稿进行处理 1. 格式化为标准会议纪要格式包含会议主题、时间、地点、主持人、参会人、主要内容、结论、下一步行动。 2. 从内容中提取所有明确的、可执行的下一步行动项Action Items每个行动项应包含负责人、截止日期、具体任务。 3. 用一句话总结本次会议的核心结论。 初稿{state[draft_minutes]} 请严格按照以下JSON格式输出不要有任何额外文字 {{ formatted_minutes: ..., action_items: [..., ...], summary: ... }} try: # 调用LLM并解析JSON实际项目中需加 robust JSON parsing raw_response llm.invoke(prompt) import json parsed json.loads(raw_response) return { formatted_minutes: parsed.get(formatted_minutes, ), action_items: parsed.get(action_items, []), summary: parsed.get(summary, ), last_updated: datetime.datetime.now().isoformat() } except Exception as e: # 错误处理记录错误但不中断流程 print(fformat_and_extract failed: {e}) return { formatted_minutes: 格式化失败请检查初稿。, action_items: [], summary: 处理失败。, last_updated: datetime.datetime.now().isoformat() }节点三wait_for_review—— 人工审核的“守门员”def wait_for_review(state: MeetingState) - dict: 节点等待人工审核。这是一个“守门员”节点决定流程是否继续。 输入state[review_status] 输出根据状态返回空字典等待或触发下一步。 # 如果状态是 approved则允许流程继续 if state[review_status] approved: return {last_updated: datetime.datetime.now().isoformat()} # 如果是 rejected 或 needs_revision则需要人工干预流程在此挂起 elif state[review_status] in [rejected, needs_revision]: # 可以在此处触发通知如发 Slack 消息给审核人 # notify_reviewer(state[reviewed_by], state[reviewer_comment]) return {} # 返回空字典表示不更新状态流程暂停 # 默认情况状态为 pending同样暂停 else: return {}节点四send_email—— 自动邮件分发import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart def send_email(state: MeetingState) - dict: 节点发送邮件。此处为简化版仅展示核心逻辑。 输入state[formatted_minutes], state[email_recipients], state[email_subject] 输出更新 state[email_sent] # 【重要】生产环境务必使用安全的邮箱配置App Password, OAuth2 smtp_server smtp.gmail.com smtp_port 587 sender_email your_emailgmail.com sender_password your_app_password # 不是邮箱密码 try: # 创建邮件 msg MIMEMultipart() msg[From] sender_email msg[To] , .join(state[email_recipients]) msg[Subject] state[email_subject] msg.attach(MIMEText(state[formatted_minutes], plain)) # 发送 server smtplib.SMTP(smtp_server, smtp_port) server.starttls() server.login(sender_email, sender_password) server.send_message(msg) server.quit() return { email_sent: True, last_updated: datetime.datetime.now().isoformat() } except Exception as e: print(fEmail sending failed: {e}) return { email_sent: False, last_updated: datetime.datetime.now().isoformat() }4.4 构建图Graph与编译Compile定义流程的“神经网络”现在我们将所有节点编织成一张图。这是最体现 LangGraph 设计哲学的一步**用声明式