LangGraph 调试指南:Graph 执行轨迹怎么看,问题怎么快速定位
很多同学第一次把 LangGraph Agent 推上生产,跑了一周突然接到反馈:「你那个 AI 有时候会卡死,有时候答非所问」。打开控制台日志一看,只有一行请求进来、一行回复出去,中间那几十次 LLM 调用、工具调用、状态流转,全是黑盒。
你根本不知道:Agent 走了哪条分支?LLM 在某个节点拿到的 Prompt 是什么?工具调用成功了没有?哪一步状态数据搞坏了?
靠console.log打日志?有意义,但 Graph 一复杂,你不知道往哪打。靠猜?靠猜是要翻车的。
这篇把调试 LangGraph 的完整工具链讲清楚:从本地快速定位到 LangSmith 生产级追踪,从看懂执行轨迹到还原「事故现场」——一套打法搞定。
01 执行轨迹的本质:Graph 到底把什么信息藏着了
一个 LangGraph Graph 运行一次,内部发生的事情可以抽象成这样一棵树:
- 节点名称:走了哪个分支(planner → executor,还是走了 tools?)
- 输入 State:进入节点时的完整状态
- 输出 Patch:这个节点改了哪些字段(注意是增量,不是全量)
- 耗时:哪个节点是性能瓶颈
但默认的invoke()调用只给你最终结果,中间那些全被吃掉了。
要「看见」执行轨迹,有两种路径:本地调试(stream + streamEvents)和云端追踪(LangSmith)。
调试工具选型一览:
| 工具 | 适用场景 | 信息粒度 | 能否事后查 |
|---|---|---|---|
stream() | 本地开发,看节点粒度 | 节点输入/输出 | ❌ |
streamEvents() | 本地开发,看 LLM 调用细节 | LLM Prompt/Token/工具调用 | ❌ |
| LangSmith | 生产环境,事后回溯 | 全链路,含 Prompt 原文 | ✅ |
| Checkpoint History | 有持久化时,时间旅行 | 每步完整 State 快照 | ✅ |
02 本地调试:stream + streamEvents 双剑合璧
LangGraph 的stream()不只是给流式输出用的,它是「执行轨迹的可观测接口」。
stream()有两种模式:streamMode: "values"(每个 chunk 是当前完整 State,调试推荐)和streamMode: "updates"(默认,每个 chunk 只有当前节点输出的增量 Patch,生产省流量)。
下面这个完整示例,把 Graph 搭建、stream 调试、路由决策日志全部整合在一起:
importStateGraphAnnotationSTARTENDfrom"@langchain/langgraph"importChatOpenAIfrom"@langchain/openai"importHumanMessageBaseMessagefrom"@langchain/core/messages"importfrom"@langchain/core/tools"importToolNodefrom"@langchain/langgraph/prebuilt"importfrom"zod"constChatStateAnnotationRootmessagesAnnotationBaseMessagereducerconsttoolasyncquerystring`搜索结果:${query} 的相关数据`name"search"description"搜索最新信息"schemaobjectquerystringconstconstnewChatOpenAImodel"gpt-4o-mini"bindTools// ⚠️ 路由函数:最容易出问题的地方,每次路由都打印决策依据// 条件边走错了不报错,会静默跑偏,必须主动把依据打出来functionshouldContinuestate: typeof ChatState.State"tools"typeofENDconstmessagesmessageslength1const"tool_calls"inArrayisArraytool_callstool_callslength0consolelog`🔀 路由 → type: ${lastMessage._getType()}, hasToolCalls: ${hasToolCalls}`ifconsolelog" tool_calls:"JSONstringifytool_callsnull2return"tools"returnENDconstnewStateGraphChatStateaddNode"agent"asyncmessagesawaitinvokemessagesaddNode"tools"newToolNodeaddEdgeSTART"agent"addConditionalEdges"agent""tools"ENDaddEdge"tools""agent"compile// 用 stream 替代 invoke,每个节点执行完都会 yield 一次// streamMode: "values" → 每个 chunk 是当前完整 State(调试推荐,一眼看全量)// streamMode: "updates" → 默认,每个 chunk 只有这步的 Patch(生产省流量)forawaitconstofawaitstreammessagesnewHumanMessage"帮我搜一下Q1销售数据"streamMode"values"constmessageslength0constmessages1consolelog`\n✅ State 更新 | messages: ${msgCount}条 | 最新: ${lastMsg?.content?.toString().slice(0, 80)}`运行后你会看到:每个节点执行完后当前的完整 State 是什么,路由函数每次决策时走了哪条边,工具调用了没有。这比单纯看最终结果直观十倍。
03 streamEvents:看穿 LLM 调用的黑盒
光看节点粒度不够。80% 的问题出在节点内部——Prompt 传错了?Tool Call 没触发?
streamEvents把节点内部每一次 LLM 调用的入参和出参全都暴露出来:
// streamEvents 的 version 必须写 "v2"forawaitconstofstreamEventsmessagesnewHumanMessage"帮我分析销售数据"version"v2"// LLM 调用开始:这里能看到完整 Prompt,是调试的第一个切入点// LLM 回答不对时,先看这里——80% 是 Prompt 构建有 bug,不是模型的问题ifevent"on_chat_model_start"consolelog`\n🔵 [${event.metadata?.langgraph_node}] LLM 调用开始`consolelog" 完整 Prompt:"JSONstringifydatainputmessagesslice2null2// LLM 调用结束:看回复内容和 Token 消耗ifevent"on_chat_model_end"constdataoutputusage_metadataconsolelog`🟢 LLM 调用结束 | 回复: ${event.data.output.content?.toString().slice(0, 100)}`consolelog` Token: prompt=${usage?.input_tokens} completion=${usage?.output_tokens}`// 工具调用入参和结果ifevent"on_tool_start"consolelog`\n🔧 工具 [${event.name}] 调用,入参:`JSONstringifydatainputifevent"on_tool_end"consolelog`✅ 工具 [${event.name}] 结果:`dataoutput关键事件类型速查:
| 事件名 | 触发时机 | 最有用的字段 |
|---|---|---|
on_chat_model_start | LLM 调用前 | data.input.messages(完整 Prompt) |
on_chat_model_end | LLM 调用后 | data.output.content、usage_metadata |
on_tool_start | 工具调用前 | name、data.input |
on_tool_end | 工具调用后 | data.output |
on_chain_start | 节点开始 | metadata.langgraph_node(节点名) |
排查「答非所问」的标准动作:先开streamEvents,找到对应节点的on_chat_model_start,把完整 Prompt 打印出来看,绝大多数情况问题就在那里。
04 LangSmith:生产环境的「飞行记录仪」
本地调试靠 console.log 够用。但生产环境有个致命问题:你不可能一直盯着日志,用户遇到问题时你已经不在场了。
LangSmith 是 LangChain 官方的追踪服务,原理是:Graph 运行时每一步都被自动上报保存,出问题了打开后台回放。接入方式:
// ─── 第一步:.env 文件加 4 个变量,代码零改动 ───// LANGCHAIN_TRACING_V2=true// LANGCHAIN_API_KEY=your-key # smith.langchain.com 获取// LANGCHAIN_PROJECT=my-agent-prod # 建议按 dev/prod 分组// OPENAI_API_KEY=your-openai-key// ⚠️ 关键坑:dotenv.config() 必须在所有 LangChain import 之前// LangChain 初始化时会读取 env,如果 dotenv 在后面加载就晚了importasfrom"dotenv"config// 第一行importChatOpenAIfrom"@langchain/openai"// 之后再 import// ─── 第二步:调用时加 metadata 和 tags,方便事后检索 ───constawaitinvokemessagesnewHumanMessageconfigurablethread_idmetadatauser_id// 方便按用户筛选:找「用户xxx投诉的那次」session_id// 方便按会话筛选environment"production"// 区分 dev/prodtags"production""agent-v2"// ─── LangSmith Trace 树结构(后台看到的样子)───// Graph Run (root)// ├── agent(第一次)// │ └── ChatOpenAI// │ ├── Input: { messages: [用户输入] } ← 完整 Prompt 在这里// │ └── Output: { tool_calls: [search] }// ├── tools → search → 结果返回// └── agent(第二次)// └── ChatOpenAI// └── Output: { content: "根据数据分析..." }//// 定位问题三步法:// 1. 用 user_id/session_id 找到那次出问题的 run// 2. 展开 Trace 树,找第一个红色(失败)或异常耗时节点// 3. 点进去看 LLM 的输入 PromptLangSmith 没数据,八成是 dotenv 加载顺序错了——dotenv.config()放在 LangChain import 之后,LangChain 初始化时还没读到 env,LangSmith 没被激活。
另一种常见情况:LANGCHAIN_PROJECT留空,数据全堆进 “default”,找起来很混乱。建议用my-agent-dev/my-agent-prod明确命名。
05 Checkpoint 调试:时间旅行还原「事故现场」
如果 Graph 用了 Checkpoint,你还有超强调试能力:从任意历史节点重新执行。
生产场景:用户聊了 20 轮,第 15 轮出错了,总不能让他重来。找到出错前的 checkpoint,修复 State,从那里继续跑。
importMemorySaverfrom"@langchain/langgraph"importPostgresSaverfrom"@langchain/langgraph-checkpoint-postgres"// ⚠️ MemorySaver 只适合本地调试,生产必须用 PostgresSaver// MemorySaver 把所有 checkpoint 存内存,长时间跑必然撑爆constnewMemorySaver// 生产环境替换为:// const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL!);// await checkpointer.setup(); // 首次运行建表constcompileconstconfigurablethread_id"debug-session-001"// 跑一次,产生 checkpoint 历史awaitinvokemessagesnewHumanMessage"帮我分析一下销售数据"// 查看这个 thread 的全部 checkpoint(按时间倒序)forawaitconstofgetStateHistoryconsolelog`step: ${snapshot.metadata?.step}``| next: ${snapshot.next}``| messages: ${snapshot.values.messages?.length}条``| checkpoint_id: ${snapshot.config?.configurable?.checkpoint_id}`// 从某个 checkpoint 重新执行// 拿上面打印的 checkpoint_id,填进去,就能精确「回滚到那一刻」重新调试constawaitinvokenullconfigurablethread_id"debug-session-001"checkpoint_id"填入目标 checkpoint_id"consolelog"从断点恢复结果:"getStateHistory给你每个节点之间的完整 State 快照。找到出问题的那步,拿 checkpoint_id,精确「回滚到那一刻」重新调试。
06 常见坑:调试本身也有陷阱
坑1:stream 模式没看清楚。updates(默认)只有 Patch,values才是全量 State。调试时务必传{ streamMode: "values" },否则看到的 chunk 只有变化的字段,不是完整状态。
坑2:LangSmith 没数据。九成九是 dotenv 加载顺序问题,见第 04 章。dotenv.config()必须在所有 LangChain import 之前,这是最常见的接入坑。
坑3:MemorySaver 线上跑内存炸了。MemorySaver只适合本地,生产必须切PostgresSaver(见第 05 章代码示例)。
坑4:streamEvents 里metadata.langgraph_node为空。原因是 LLM 在 Graph 外部初始化,没被 Graph 上下文捕获。解决方法:把 LLM 调用移到节点函数内部执行,或确保所有 LLM 调用都在 Graph 的invoke/stream上下文链路上。
坑5:全量 LangSmith 追踪费用暴涨。用采样率控制,只追踪 10% 的请求:设置if (Math.random() < 0.1) process.env.LANGCHAIN_TRACING_V2 = "true"; else delete process.env.LANGCHAIN_TRACING_V2;,或者只在 catch 块里开启追踪,发生错误才上报。
总结
这篇把 LangGraph 调试从本地到生产的完整链路走了一遍:
stream()用streamMode: "values"是本地调试的起点:每个节点执行后能看到全量 State,比updates模式直观得多,不用自己拼增量 Patch。streamEvents()是看清 LLM 黑盒的唯一方式:on_chat_model_start里的完整 Prompt 是定位「答非所问」问题的第一个切入点,80% 的问题就藏在这里。- 路由函数里加决策日志,是调试条件路由的最快路径:条件边走错了不报错,只会静默跑偏,必须主动把路由依据打出来。
- LangSmith 是生产环境的飞行记录仪:4 个 env var 零侵入接入;dotenv 加载顺序是最常见接入坑;
user_id + session_id是事后定位问题的关键标签。 - Checkpoint 时间旅行是高阶调试能力:找到出错前的 checkpoint_id,修复 State,从那里重跑;生产环境必须用 PostgresSaver 替换 MemorySaver,否则必内存溢出。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
