1. 从零开始为什么需要 LangGraph 和 LangSmith如果你最近在捣鼓大语言模型LLM应用尤其是想构建一个能处理多步骤、有状态、甚至需要“思考”流程的智能体Agent那你大概率已经听过 LangChain 的大名。LangChain 提供了一个强大的框架把提示词、模型、工具等组件像乐高一样拼起来。但当你真的开始构建一个稍微复杂点的应用比如一个能自动分析数据、生成报告、再根据反馈修改报告的智能工作流时你可能会发现单纯用链Chain来组织逻辑代码会变得有点“面条化”——各种回调、条件判断堆在一起维护和调试都成了噩梦。这时候LangGraph就该登场了。你可以把它理解为 LangChain 的“工作流引擎”或“状态机”。它用“图”Graph的概念来建模你的应用逻辑。图中的节点Node是一个个执行单元比如调用一次 LLM、执行一个工具边Edge则定义了节点之间的流转条件。这让你能清晰地设计出包含循环、分支、并行等复杂逻辑的流程代码结构一目了然。比如一个客服机器人可以先理解用户意图节点A然后根据意图决定是查询知识库节点B还是转人工节点C查询后还可以判断答案是否满意不满意则重新组织语言循环回节点A的某种形式。用 LangGraph 来画这个流程图比用一堆if-else写要清晰和健壮得多。然而一旦流程复杂了新的问题又来了这图跑起来到底发生了什么为什么在这个节点卡住了每次调用 LLM 的输入输出是什么开销是多少这就轮到LangSmith亮相了。LangSmith 是 LangChain 官方出品的应用监控与调试平台。你可以把它看作 LLM 应用的“黑匣子”或“调试器”。它能自动追踪你应用中每一个 LangChain/LangGraph 组件的执行过程记录下详细的输入、输出、耗时、token 消耗甚至模型内部的思考过程如果模型支持。当你的智能体给出了一个匪夷所思的回答时你可以直接在 LangSmith 的界面上回放整个调用链精准定位是哪个节点的提示词出了问题还是哪个工具返回了错误数据。所以LangGraph 负责“建造”复杂、可靠的应用逻辑骨架而 LangSmith 负责给这个骨架装上“X光”和“体检仪”让你能看清内部运作快速诊断问题。对于任何严肃的 LLM 应用开发尤其是基于智能体的应用这两者几乎是不可或缺的黄金组合。本教程将聚焦于 JavaScript/TypeScript 环境手把手带你完成从环境搭建、核心概念理解到构建第一个工作流并利用 LangSmith 进行深度调试的全过程。2. 环境搭建与项目初始化在开始写代码之前我们需要先把战场布置好。这里假设你已经有 Node.js建议版本 18 或以上和 npm 的基本使用经验。2.1 创建项目并安装核心依赖首先创建一个新的项目目录并初始化mkdir my-langgraph-app cd my-langgraph-app npm init -y接下来安装我们需要的核心包。这里会用到 LangChain 的 JS/TS 主包、LangGraph 扩展、OpenAI 的模型集成包这里以 OpenAI 为例你也可以选择 Anthropic、Google Gemini 等以及 LangSmith 的 SDK。npm install langchain/langgraph langchain/core langchain/openai npm install langsmith同时为了有更好的类型提示和开发体验我们安装 TypeScript 及相关类型定义如果你用纯 JS可以跳过类型定义部分npm install typescript types/node ts-node --save-dev npx tsc --init2.2 配置环境变量与 API 密钥LLM 应用离不开各种 API 密钥。我们将使用.env文件来管理它们避免将敏感信息硬编码在代码中。在项目根目录创建.env文件。你需要准备以下密钥OPENAI_API_KEY: 从 OpenAI 平台获取。LANGSMITH_API_KEY: 从 LangSmith 官网注册并获取。LANGSMITH_TRACING: 设置为true以启用追踪。LANGSMITH_PROJECT: 你的项目名称例如my-first-langgraph。你的.env文件内容大致如下OPENAI_API_KEYsk-your-openai-key-here LANGSMITH_API_KEYlsv2-your-langsmith-key-here LANGSMITH_TRACINGtrue LANGSMITH_PROJECTmy-first-langgraph注意.env文件必须被添加到.gitignore中切勿提交到版本控制系统。为了让 Node.js 能读取这些变量我们通常使用dotenv包。安装它npm install dotenv然后在你的应用入口文件例如index.ts的最顶部添加import * as dotenv from dotenv; dotenv.config(); // 确保环境变量已加载 if (!process.env.OPENAI_API_KEY) { throw new Error(OPENAI_API_KEY is not set in .env file); } if (!process.env.LANGSMITH_API_KEY) { console.warn(LANGSMITH_API_KEY is not set. Tracing will be disabled.); }2.3 验证基础环境让我们写一个最简单的脚本来测试环境是否正常。创建一个test-env.ts文件import { ChatOpenAI } from langchain/openai; import * as dotenv from dotenv; dotenv.config(); async function test() { // 1. 测试 OpenAI 连接 const model new ChatOpenAI({ modelName: gpt-4o-mini, // 或 gpt-3.5-turbo temperature: 0, }); try { const response await model.invoke(Hello, world!); console.log(OpenAI 连接成功回复, response.content); } catch (error) { console.error(OpenAI 连接失败, error); } // 2. 检查 LangSmith 配置 if (process.env.LANGSMITH_TRACING true) { console.log(LangSmith 追踪已启用项目, process.env.LANGSMITH_PROJECT); } else { console.log(LangSmith 追踪未启用。); } } test();用npx ts-node test-env.ts运行它。如果看到成功的问候回复和 LangSmith 配置信息那么恭喜你基础环境已经就绪。此时由于LANGSMITH_TRACINGtrue这次简单的调用应该已经被记录到 LangSmith 平台上了。你可以登录 LangSmith 网站在指定的项目下看到这次调用记录。这是你第一次感受到 LangSmith 的威力——无需额外代码自动追踪。3. LangGraph 核心概念与第一个工作流理解了“为什么”也搭好了环境现在我们来解剖一下 LangGraph 的核心部件并构建一个实实在在的图。3.1 理解 State 和 NodeLangGraph 是一个基于状态State流转的系统。整个图有一个共享的状态对象每个节点的执行可以读取和修改这个状态。State状态 通常是一个 TypeScript 接口或类型定义了工作流中需要流转的所有数据。例如一个写作助手的状态可能包含topic主题、draft草稿、feedback反馈、revision_count修改次数等字段。Node节点 图中的一个执行单元。它本质上是一个函数接收当前的State作为输入返回一个包含对State修改的对象。这个函数可以是同步的也可以是异步的async。Edge边 定义节点执行完毕后接下来应该执行哪个节点。边可以是固定的always指向某个节点也可以是条件式的conditional根据状态值决定下一步。让我们定义一个简单的状态用于一个“对话模拟器”// 定义状态接口 interface AgentState { // 用户输入的消息 messages: Array{ role: user | assistant | system; content: string }; // 助手的“情绪”会影响回复风格 mood: happy | neutral | grumpy; // 对话轮次 turn: number; }3.2 构建一个简单的对话循环图我们将构建一个包含两个节点的图Node 1: 调用模型- 根据当前对话历史和情绪生成助手回复。Node 2: 更新状态- 判断对话是否应该结束比如超过5轮并可能随机改变助手的情绪。首先创建模型节点import { ChatOpenAI } from langchain/openai; import { StateGraph, Annotation } from langchain/langgraph; import { BaseMessage, HumanMessage, AIMessage } from langchain/core/messages; // 1. 使用 Annotation 创建状态图的“蓝图”这提供了更好的类型安全。 const StateBlueprint Annotation.Root({ messages: AnnotationBaseMessage[]({ reducer: (prev, curr) [...prev, ...curr], default: () [], }), mood: Annotationstring({ reducer: (prev, curr) curr ?? prev, default: () neutral, }), turn: Annotationnumber({ reducer: (prev, curr) curr ?? prev, default: () 0, }), }); // 2. 初始化模型 const model new ChatOpenAI({ modelName: gpt-4o-mini, temperature: 0.7, }); // 3. 定义“调用模型”节点 async function callModel(state: typeof StateBlueprint.State) { console.log([callModel] 情绪: ${state.mood}, 轮次: ${state.turn}); // 构建系统提示词注入情绪 const systemPrompt 你是一个聊天助手。你现在的情绪是${state.mood}。请根据这个情绪来回应用户。如果情绪是 happy就热情洋溢如果是 grumpy就简短且略带不耐烦如果是 neutral就专业且友好。; // 准备消息历史系统消息 之前的对话消息 const messagesForModel [ new SystemMessage(systemPrompt), ...state.messages, ]; // 调用模型 const response await model.invoke(messagesForModel); // 返回对状态的更新将AI回复添加到消息列表轮次1 return { messages: [response], turn: state.turn 1, }; }接着定义“更新状态”节点它负责判断是否结束并可能改变情绪// 4. 定义“更新状态”节点 function updateState(state: typeof StateBlueprint.State) { console.log([updateState] 进入当前轮次: ${state.turn}); const updates: any {}; // 条件1: 如果对话超过5轮触发结束 if (state.turn 5) { console.log([updateState] 对话已达 ${state.turn} 轮准备结束。); // 这里我们不直接结束而是通过边的逻辑来处理。先标记一个意图。 updates.shouldEnd true; } else { updates.shouldEnd false; } // 条件2: 随机改变情绪每轮有20%几率改变 if (Math.random() 0.2) { const moods: AgentState[mood][] [happy, neutral, grumpy]; const newMood moods[Math.floor(Math.random() * moods.length)]; console.log([updateState] 情绪由 ${state.mood} 变为 ${newMood}); updates.mood newMood; } return updates; }现在我们用StateGraph把这些节点和边连接起来// 5. 创建状态图 const workflow new StateGraph(StateBlueprint) .addNode(call_model, callModel) // 添加第一个节点 .addNode(update_state, updateState) // 添加第二个节点 .addEdge(__start__, call_model) // 设置入口点 .addEdge(call_model, update_state) // call_model 后总是执行 update_state .addConditionalEdges( update_state, // 从 update_state 节点出发 // 条件函数根据状态决定下一个节点 (state: typeof StateBlueprint.State) { if (state.shouldEnd) { return end; // 指向结束 } return call_model; // 指回 call_model形成循环 } ); // 6. 编译图得到一个可执行的对象 const app workflow.compile();3.3 运行与调试你的第一个图现在让我们运行这个工作流并观察其状态变化// 7. 准备初始状态 const initialState { messages: [new HumanMessage(你好今天天气真不错。)], mood: neutral as const, turn: 0, }; // 8. 执行图 async function runGraph() { console.log( 开始执行对话循环图 ); // 使用 streamEvents 可以实时看到每个节点的输入输出对调试非常有用 const stream await app.streamEvents(initialState, { version: v1 }); for await (const event of stream) { // 事件类型有很多我们过滤出节点执行结束的事件 if (event.event on_chain_end event.name) { console.log(\n[事件] 节点 ${event.name} 执行完毕。); console.log( 输入:, JSON.stringify(event.data.input, null, 2)); console.log( 输出:, JSON.stringify(event.data.output, null, 2)); } } // 获取最终状态 const finalState await app.invoke(initialState); console.log(\n 对话结束 ); console.log(最终情绪:, finalState.mood); console.log(总对话轮次:, finalState.turn); console.log(最后几条消息:); finalState.messages.slice(-3).forEach(msg { console.log( ${msg._getType()}: ${msg.content}); }); } runGraph().catch(console.error);运行这段代码你会在控制台看到一个持续的对话循环直到达到5轮后结束。助手的回复风格会随着mood的变化而改变。更重要的是由于我们一开始就配置了LANGSMITH_TRACINGtrue这个复杂工作流的每一步包括每个节点的输入、输出、耗时都已经被完整地记录到了 LangSmith 平台。实操心得在开发初期多用app.streamEvents来运行你的图。它能让你像看日志一样清晰地看到状态是如何在节点间流动和变化的这对于理解 LangGraph 的执行模型和调试逻辑错误至关重要。app.invoke则更适用于获取最终结果的场景。4. 深入 LangSmith可视化、追踪与调试我们的图已经在默默地向 LangSmith 发送数据了。现在让我们登录 LangSmith 平台看看这些数据如何转化为强大的调试能力。4.1 在 LangSmith 中探索你的追踪记录登录与查看项目 访问 LangSmith 官网并登录。在左侧边栏你应该能看到以LANGSMITH_PROJECT环境变量命名的项目例如my-first-langgraph。点击进入。理解追踪列表 主页面会列出所有追踪记录Traces。每次你调用app.invoke或app.stream都会生成一条主追踪Trace。点击最近的一条对应你刚运行的对话循环。剖析追踪详情 打开后你会看到一个时间线视图。这直观地展示了你 LangGraph 工作流的执行过程根节点Root 代表整个app.invoke调用。子节点 展开根节点你会看到call_model和update_state作为子节点依次出现。call_model节点下可能还会进一步展开显示对 OpenAI API 的调用详情。查看输入输出 点击任何一个节点例如call_model右侧面板会显示其详细的输入Input和输出Output。对于call_model输入是完整的消息列表包含系统提示输出就是 AI 的回复消息对象。这对于验证提示词是否按预期组装至关重要。检查元数据 在节点详情中你还能看到执行耗时Latency、使用的模型、消耗的 Token 数如果模型支持等信息。这是进行性能分析和成本估算的一手资料。4.2 利用 LangSmith 进行提示词工程与迭代假设我们运行几次后发现当mood为grumpy时AI 的回复还不够“暴躁”不符合我们的设计预期。传统调试可能需要反复修改代码、运行、看日志。而用 LangSmith你可以筛选与对比 在项目追踪列表使用过滤器筛选出mood为grumpy的追踪可能需要你在状态或元数据中记录这个信息。同时选中几条这样的追踪。对比分析 LangSmith 支持对比视图。你可以并排查看不同运行下call_model节点的输入即你的系统提示词和用户消息以及 AI 的输出。这能帮你快速判断是提示词描述的问题还是模型本身的问题。快速迭代 直接在 LangSmith 上修改提示词它提供了一个编辑器然后点击“运行”进行单次测试。无需改动你的本地代码就能看到新提示词下的输出效果。确定最优提示词后再将修改同步回你的代码库。4.3 在代码中更精细地控制追踪除了全局环境变量你还可以在代码层面更灵活地控制 LangSmith 追踪。import { traceable } from langsmith/traceable; import { RunnableConfig } from langchain/core/runnables; // 1. 使用 traceable 包装任意函数 const myBusinessLogic traceable( async (input: string, config?: RunnableConfig) { // 这个函数的执行会被 LangSmith 单独记录为一个节点 console.log(处理输入: ${input}); // ... 一些复杂逻辑 ... return 处理结果: ${input.toUpperCase()}; }, { name: my_business_logic } // 指定在 LangSmith 中显示的名称 ); // 2. 在 LangGraph 节点中调用它 async function complexNode(state: typeof StateBlueprint.State) { const result await myBusinessLogic(state.messages[state.messages.length - 1].content); return { processed: result }; } // 3. 你也可以为单次调用附加自定义元数据方便后续筛选 const config: RunnableConfig { metadata: { userId: user_123, experiment: v2_prompt, }, tags: [production, test_feature], }; // 调用时传入 config const finalState await app.invoke(initialState, { config });这样在 LangSmith 中你不仅能看见 LangGraph 的节点还能看见你自定义业务逻辑函数my_business_logic的独立追踪记录并且可以通过metadata和tags进行高效筛选和管理。踩坑实录 初期很容易忘记给关键的自定义函数加上traceable包装或者没有传递config导致在 LangSmith 中看到调用链不完整某个环节突然“黑盒化”。建议将为 LangGraph 节点内部调用的重要工具函数、数据预处理函数等都包装起来形成完整的可观测链路。5. 构建实用智能体一个具有工具调用能力的工作流前面的例子展示了循环和状态但真正的智能体Agent核心能力之一是使用工具Tools。让我们构建一个更实用的智能体一个能帮我们查询天气并决定穿衣建议的助手。5.1 定义工具Tools首先我们模拟两个工具一个查询天气一个提供穿衣建议。import { tool } from langchain/core/tools; import { z } from zod; // 工具1查询天气模拟 const fetchWeatherTool tool( async ({ city }: { city: string }) { // 模拟一个网络请求或数据库查询 console.log([工具调用] 查询城市: ${city}); await new Promise(resolve setTimeout(resolve, 100)); // 模拟延迟 const weatherData { city, temperature: Math.floor(Math.random() * 15) 10, // 10-24度 condition: [晴朗, 多云, 小雨, 大风][Math.floor(Math.random() * 4)], humidity: Math.floor(Math.random() * 40) 50, // 50-90% }; return JSON.stringify(weatherData); }, { name: fetch_weather, description: 根据城市名称获取当前的天气信息。, schema: z.object({ city: z.string().describe(要查询天气的城市名称例如北京、上海), }), } ); // 工具2生成穿衣建议模拟 const dressingAdviceTool tool( async ({ weatherInfo, activity }: { weatherInfo: string; activity: string }) { console.log([工具调用] 生成穿衣建议活动: ${activity}); const weather JSON.parse(weatherInfo); let advice 在${weather.city}今天天气${weather.condition}气温${weather.temperature}°C湿度${weather.humidity}%。; if (weather.temperature 15) { advice 建议穿外套或薄羽绒服。; } else if (weather.temperature 25) { advice 建议穿长袖T恤或衬衫备一件薄外套。; } else { advice 建议穿短袖、短裤等夏装。; } if (activity.includes(户外)) { advice 由于是户外活动请注意防晒和补充水分。; } return advice; }, { name: dressing_advice, description: 根据天气信息和计划的活动生成穿衣建议。, schema: z.object({ weatherInfo: z.string().describe(由 fetch_weather 工具返回的天气信息字符串。), activity: z.string().describe(用户计划进行的活动例如户外徒步、上班通勤、晚上聚餐。), }), } ); const tools [fetchWeatherTool, dressingAdviceTool];5.2 创建具有工具调用能力的图我们将创建一个新的图其工作流是用户提问 - 模型决定是否调用工具及调用哪个 - 执行工具 - 将工具结果返回给模型 - 模型生成最终回答。import { Annotation } from langchain/langgraph; import { ChatOpenAI } from langchain/openai; import { StateGraph } from langchain/langgraph; import { ToolNode } from langchain/langgraph/prebuilt; import { HumanMessage, BaseMessage } from langchain/core/messages; // 1. 定义状态需要记录消息和模型决定调用的工具如果有 const AgentState Annotation.Root({ messages: AnnotationBaseMessage[]({ reducer: (prev, curr) [...prev, ...curr], default: () [], }), // 用于存储模型决定要调用的工具调用请求 toolCalls: Annotationany[]({ reducer: (prev, curr) curr ?? prev, default: () [], }), // 用于存储工具执行的结果 toolResults: Annotationany[]({ reducer: (prev, curr) curr ?? prev, default: () [], }), }); // 2. 创建支持工具调用的模型 const modelWithTools new ChatOpenAI({ modelName: gpt-4o-mini, temperature: 0, }).bindTools(tools); // 关键将工具绑定到模型 // 3. 定义“模型”节点它决定是直接回复还是调用工具 async function agentNode(state: typeof AgentState.State) { const message state.messages[state.messages.length - 1]; // 调用模型传入当前对话历史 const response await modelWithTools.invoke([message]); // 检查响应中是否包含工具调用请求 const toolCalls response.tool_calls || []; if (toolCalls.length 0) { // 模型决定调用工具 console.log([agentNode] 模型决定调用工具: ${toolCalls.map(tc tc.name).join(, )}); return { messages: [response], // 将包含工具调用的消息存入历史 toolCalls, // 传递工具调用请求给下一个节点 }; } else { // 模型直接给出最终回答 console.log([agentNode] 模型直接回复。); return { messages: [response], // 将最终回复存入历史 }; } } // 4. 使用预构建的 ToolNode 来处理工具调用 // ToolNode 会自动执行 toolCalls 中的所有工具并将结果收集到 toolResults 中 const toolNode new ToolNode(tools); // 5. 定义“处理工具结果”节点将工具执行结果格式化后重新放入消息流 function processToolResults(state: typeof AgentState.State) { const lastMessage state.messages[state.messages.length - 1]; const toolResults state.toolResults; // 将每个工具执行结果转换为 AI 模型能识别的格式 const toolResultMessages toolResults.map(result ({ tool_call_id: result.tool_call_id, role: tool as const, name: result.name, content: result.result, })); console.log([processToolResults] 处理了 ${toolResultMessages.length} 个工具结果); // 返回更新将工具结果也添加到消息历史中这样下一轮模型就能看到 return { messages: toolResultMessages, toolResults: [], // 清空为下一次循环准备 }; } // 6. 构建图 const agentWorkflow new StateGraph(AgentState) .addNode(agent, agentNode) .addNode(tools, toolNode) .addNode(process_results, processToolResults) .addEdge(__start__, agent) // 从 agent 开始 .addConditionalEdges( agent, // 根据 agent 节点的输出来决定下一步 (state: typeof AgentState.State) { // 如果模型产生了工具调用则下一步去执行工具 if (state.toolCalls state.toolCalls.length 0) { return tools; } // 否则直接结束 return __end__; } ) .addEdge(tools, process_results) // 工具执行完后处理结果 .addEdge(process_results, agent); // 处理完结果后将信息反馈给 agent继续循环 const agentApp agentWorkflow.compile();5.3 运行并观察工具调用智能体现在让我们运行这个更高级的智能体async function runAgent() { const initialMessage new HumanMessage(我明天在北京有户外徒步活动该怎么穿衣服); const initialState { messages: [initialMessage], toolCalls: [], toolResults: [], }; console.log( 启动智能体工作流 ); console.log(用户问题: ${initialMessage.content}); const stream await agentApp.streamEvents(initialState, { version: v1 }); for await (const event of stream) { if (event.event on_chain_end event.name) { console.log(\n[事件] 节点 ${event.name} 结束。); } // 特别监听工具调用和结果 if (event.event on_tool_start) { console.log([工具开始] ${event.name} 被调用。); } if (event.event on_tool_end) { console.log([工具结束] ${event.name} 执行完毕。); } } const finalState await agentApp.invoke(initialState); console.log(\n 智能体工作流结束 ); const finalAIMessage finalState.messages.find(m m._getType() ai !m.tool_calls); if (finalAIMessage) { console.log(助手最终回复:\n, finalAIMessage.content); } } runAgent().catch(console.error);运行这段代码你会在控制台看到类似以下的逻辑流agent节点收到用户问题。模型意识到需要先查询天气于是决定调用fetch_weather工具输出中包含工具调用请求。图流转到tools节点执行fetch_weather工具获取模拟天气数据。流转到process_results节点将天气数据格式化。图流回agent节点。此时消息历史包含了用户问题、上一次模型的工具调用请求、以及工具返回的天气结果。模型看到天气结果后意识到还需要生成穿衣建议于是决定调用dressing_advice工具。再次经过tools-process_results-agent的循环。模型在收到穿衣建议的工具结果后综合所有信息生成最终的自然语言回复并且不再调用新工具于是工作流结束。整个过程中LangSmith 完整记录了每一次模型调用、每一个工具执行的输入输出和耗时。你可以清晰地看到模型“思考”的过程它为什么决定先调用 A 工具而不是 B 工具工具返回的数据是否被正确理解这对于优化工具描述description和提示词至关重要。核心技巧 工具的描述description和参数模式schema是引导模型正确使用工具的关键。描述要清晰、具体说明工具的用途和适用场景。参数模式使用zod定义清晰的describe能帮助模型更好地理解每个参数需要什么信息。如果模型频繁错误调用或参数不对首先应该检查并优化这两部分。6. 错误处理、持久化与高级模式一个健壮的生产级应用必须考虑错误和状态的持久化。LangGraph 提供了相应的机制。6.1 为节点和边添加错误处理在复杂的流程中任何一个节点如调用外部 API、查询数据库都可能失败。LangGraph 允许你定义错误处理逻辑。import { StateGraph } from langchain/langgraph; const workflow new StateGraph(AgentState) .addNode(agent, agentNode) .addNode(tools, toolNode) // 使用 .addEdge 的重载版本可以指定错误处理 .addEdge(agent, tools) .addEdge(tools, process_results) // 配置一个“错误处理”节点 .addNode(handle_error, async (state) { console.error([handle_error] 节点捕获到错误状态:, state); // 可以在这里记录错误、发送警报、尝试恢复等 return { messages: [new AIMessage(抱歉处理您的请求时遇到了问题请稍后再试或联系管理员。)], }; }) // 将 tools 节点的错误指向 handle_error 节点 .addEdge(tools, handle_error, { conditional: true, condition: (state) state.errorOccurred }) // 注意上面的 condition 是示意实际错误信息需要通过状态传递。 // 更常见的做法是使用 try-catch 包装节点函数在出错时修改状态如设置 errorOccurred: true .addEdge(handle_error, __end__);更实用的方法是在节点函数内部进行try-catch将错误信息写入状态然后由条件边来决定下一步流向错误处理节点。6.2 状态的检查点与持久化对于长时间运行或需要中断恢复的智能体如一个需要多轮交互才能完成复杂任务的支持机器人LangGraph 支持**检查点Checkpoint**机制。你可以将某个时刻的完整状态保存下来例如存到数据库后续可以从这个检查点恢复执行。import { MemorySaver } from langchain/langgraph; // 1. 创建一个内存存储生产环境应换成 Redis、PostgreSQL 等持久化存储 const memory new MemorySaver(); // 2. 在编译图时传入存储 const persistentApp workflow.compile({ checkpointer: memory, }); // 3. 运行图并指定一个线程ID用于标识这次会话 const config { configurable: { thread_id: user_session_12345 } }; const initialState { messages: [new HumanMessage(你好)] }; // 第一次调用会创建检查点 let result1 await persistentApp.invoke(initialState, config); console.log(第一次回复:, result1.messages[-1].content); // 模拟一段时间后用户发送后续消息 const laterState { messages: [new HumanMessage(我们刚才说到哪了继续。)], }; // 第二次调用传入相同的 thread_idLangGraph 会自动加载上次的检查点状态并在此基础上继续 let result2 await persistentApp.invoke(laterState, config); console.log(第二次回复:, result2.messages[-1].content); // 此时result2.messages 会包含整个会话的历史模型拥有完整的上下文。MemorySaver是一个简单的内存实现。LangChain 社区提供了更多后端存储的实现如RedisSaver、PostgresSaver你可以根据项目需求选择。6.3 探索预构建模式AgentExecutor对于最常见的“模型-工具”循环模式LangGraph 提供了一个高度优化和封装的预构建图createReactAgent。它内部实现了我们上面构建的类似逻辑但更健壮、功能更全。import { createReactAgent } from langchain/langgraph/prebuilt; import { ChatOpenAI } from langchain/openai; const model new ChatOpenAI({ modelName: gpt-4o-mini, temperature: 0 }); const tools [fetchWeatherTool, dressingAdviceTool]; // 使用之前定义的工具 // 一行代码创建功能完整的智能体 const agent createReactAgent({ llm: model, tools, // 可选自定义提示词、状态修改器等 }); // 使用方式和之前类似 const stream await agent.streamEvents({ messages: [new HumanMessage(上海今天天气如何)], }); for await (const event of stream) { // 处理事件流... } const finalResult await agent.invoke({ messages: [new HumanMessage(上海今天天气如何)], });createReactAgent是快速构建智能体的最佳实践起点。它处理了工具调用、结果整合、错误处理等大量样板代码。当你需要更复杂的定制如自定义状态结构、特殊的循环逻辑时再回过头来使用基础的StateGraph进行构建。7. 性能优化与生产部署考量当你的 LangGraph 应用从 demo 走向生产性能和稳定性就成为关键。7.1 利用 LangSmith 进行性能剖析LangSmith 不仅是调试器也是性能分析工具。关注以下几点延迟Latency 在追踪列表中可以按总耗时排序。找出最慢的几次调用。点击进入分析是哪个节点通常是call_model或工具耗时最长。对于模型调用考虑是否可以使用更快的模型如gpt-3.5-turbo代替gpt-4或优化提示词减少输出长度。对于自定义工具优化其内部逻辑或增加缓存。Token 消耗 如果使用 OpenAI 等按 Token 计费的模型LangSmith 会记录每次模型调用的输入/输出 Token 数。分析哪些交互消耗 Token 最多。可能是历史消息过长可以考虑使用ConversationSummaryBufferMemory等记忆组件来压缩历史或者设计更精简的提示词。错误率 在 LangSmith 仪表板中可以查看不同节点或工具的失败率。高频失败的工具可能是外部服务不稳定或参数校验不严需要加固。7.2 图的优化策略并行执行 如果图中有多个彼此独立的节点可以考虑让它们并行执行。LangGraph 支持通过addNode和addEdge定义并行分支最后再通过addEdge汇聚。这能显著减少工作流的总耗时。条件边优化 条件边addConditionalEdges的判断逻辑应尽可能简单高效。避免在条件函数中执行复杂的计算或 I/O 操作。状态精简 只把必要的数据放在 State 中。过大的状态对象会在节点间传递时产生额外的序列化/反序列化开销。对于中间计算结果可以考虑存储在节点局部变量中而不是全部塞进 State。7.3 生产部署注意事项配置管理 将 API 密钥、模型参数、图结构配置等抽取到配置文件如config.yaml或环境变量中便于不同环境开发、测试、生产的切换。异步与并发 LangGraph 节点函数天然支持async/await。确保你的工具函数和业务逻辑也是非阻塞的以充分利用 Node.js 的异步 I/O 能力处理高并发请求。限流与降级 对于调用的外部 API如 OpenAI、天气 API务必实现限流rate limiting和重试机制。考虑使用类似p-limit的库控制并发数。为关键工具设计降级方案例如当天气 API 失败时返回一个基于城市平均气候的默认建议。日志与监控 除了 LangSmith集成你现有的日志系统如 Winston、Pino。在关键节点和工具调用处记录业务日志。设置监控告警关注错误率、延迟、Token 消耗等关键指标。版本化 当你修改了图的结构、提示词或工具最好通过LANGSMITH_PROJECT环境变量或代码中的metadata来区分版本。这样在 LangSmith 中可以根据版本筛选追踪记录方便对比不同版本的效果。从入门到构建复杂工作流再到利用 LangSmith 进行深度观察和优化这条路径为你开发可靠、可维护、可观测的 LLM 应用提供了坚实的工程基础。记住LangGraph 帮你管理复杂性而 LangSmith 赋予你洞察复杂性的能力。两者的结合能让你在智能体开发的路上走得更稳、更远。