从Vibe Coding到LangGraph:AI工作流编排实战指南
1. 从补全代码到描述意图编程范式正在发生什么变化过去两年我身边做开发的朋友分成了很明显的两拨。一拨人还在纠结“Copilot 补全的代码能不能直接用”另一拨人已经开始对着编辑器说“帮我把这个页面的加载状态改成骨架屏顺便把错误重试加上”然后看着 AI 一口气改完五六个文件。后面这种工作方式圈子里给了它一个挺形象的名字——Vibe Coding。这个词最早由 Andrej Karpathy 提出来核心意思就是你不再逐行敲代码而是用自然语言描述你想要的感觉和结果让大模型去生成、修改、串联整个实现你负责判断“对不对味”。但 Vibe Coding 只解决了“写”的问题。当你真的想做一个能自己查资料、自己调工具、自己决定下一步干什么的 AI 应用时光靠对话式生成就不够了。你会遇到状态怎么保存、多个步骤怎么编排、出错怎么回滚、循环怎么控制这些工程问题。这时候 LangGraph 就进入了视野。它是 LangChain 团队推出的一个专门用来构建有状态、多步骤 AI 工作流的框架把整个流程建模成一张图节点是动作边是流转条件状态在整张图里共享和传递。这篇文章我想聊的就是这条演进线从 Vibe Coding 这种“意图驱动”的编码方式到 LangGraph 这种“图驱动”的 AI 应用编排框架中间到底发生了什么为什么会有这个转变以及一个普通开发者该怎么上手。不管你是刚接触 LLM 应用开发的新手还是已经用 LangChain 写过一些链式调用的老手都能从里面找到可以直接抄作业的东西。我会尽量把原理讲透把踩过的坑摊开把能复现的步骤写清楚。2. Vibe Coding 到底是什么意图驱动编码的底层逻辑2.1 从“写代码”到“描述意图”的转变传统编程里开发者的大脑里要先有一棵完整的逻辑树输入是什么、分支怎么走、异常怎么处理、数据结构怎么设计然后把这些翻译成具体的语法。Vibe Coding 把这个过程倒过来了。你只需要描述“我想要什么”大模型负责把它翻译成可运行的代码。这背后依赖的是 LLM 对大量代码语料的学习它见过足够多的模式所以能根据你的意图补全出合理的实现。我自己的体验是用这种方式写前端页面和写脚本特别快。比如你说“做一个带搜索过滤的表格数据从本地 JSON 读支持按名称和日期排序”它几秒钟就能给你一个能跑的版本。你不需要记住某个 API 的具体参数也不需要查文档描述清楚就行。这种效率提升是实打实的尤其是做原型和验证想法的时候。但这里有个关键前提你得能判断它写出来的东西对不对。Vibe Coding 不是让你放弃理解代码而是把精力从“怎么写”转移到“写什么”和“对不对”上。如果你完全看不懂生成的代码那出了问题你也没法修这就很危险。2.2 Vibe Coding 的适用边界与典型场景Vibe Coding 最适合的场景有几个共同特征逻辑相对独立、边界清晰、不需要复杂的状态管理。比如写一个数据清洗脚本、做一个静态页面、生成一些测试用例、把一段旧代码翻译成新语言这些都很适合。它的优势在于快速试错你可以在几分钟内看到好几个不同版本的实现然后挑一个最顺眼的继续改。但一旦涉及到多步骤的 AI 工作流Vibe Coding 就开始吃力了。举个例子你想做一个“自动调研助手”先让模型理解用户问题然后决定要不要搜索搜索完再总结总结完再判断信息够不够不够就换个关键词再搜。这个流程里有条件分支、有循环、有状态累积你很难用一句“帮我写一个调研助手”就让模型生成一个稳定可靠的实现。就算它生成了你也很验证它每一步到底在干什么。提示Vibe Coding 生成的代码一定要跑一遍再信。我见过太多看起来没问题、一跑就报错的例子尤其是涉及异步和状态的部分。2.3 为什么 Vibe Coding 会自然过渡到 LangGraph当你用 Vibe Coding 的方式去构建 AI 应用时你会发现一个规律简单的单轮对话很快就能搞定但稍微复杂一点的需求就会逼着你去设计流程。而一旦你开始设计流程你就会想要一个东西来帮你管理流程。这个东西就是 LangGraph。LangGraph 的出现不是为了替代 Vibe Coding而是承接了 Vibe Coding 搞不定的那部分。你可以继续用 Vibe Coding 的方式快速生成节点内部的逻辑但节点之间怎么连、状态怎么传、循环怎么停这些交给 LangGraph 来管。两者其实是互补的Vibe Coding 负责“写”LangGraph 负责“编排”。3. LangGraph 核心概念拆解图、状态与节点3.1 为什么是“图”而不是“链”LangChain 最早的核心抽象是 Chain也就是把多个步骤串成一条线。这个模型很简单A 的输出给 BB 的输出给 C一路走到底。但现实中的 AI 工作流很少是一条直线。你可能需要根据模型的输出决定下一步走哪个分支可能需要回到之前的步骤重新来一遍可能需要在多个步骤之间共享一份不断更新的状态。这些用 Chain 来表达就很别扭。LangGraph 换了一个思路把整个工作流建模成一张有向图。图里有节点和边节点代表一个具体的操作边代表节点之间的流转关系。最关键的是这张图可以带环也就是说你可以从节点 A 走到节点 B再从节点 B 走回节点 A形成循环。这个特性让 LangGraph 天然适合表达“反复思考直到满意”这类 AI 工作流。我刚开始用的时候也有点不习惯觉得图比链复杂。但用多了就发现图的表达能力确实强很多。你可以在图上清晰地看到整个流程长什么样哪个节点负责什么什么条件下会走哪条边。这种可视化带来的可维护性提升在项目变复杂之后特别明显。3.2 State整个工作流的共享记忆LangGraph 里最重要的概念是 State。你可以把它理解成整个工作流共享的一块内存所有节点都能读它、写它。State 通常是一个字典或者一个带类型定义的对象里面放着工作流运行过程中需要传递的所有信息。举个例子如果你做一个客服机器人State 里可能包含用户原始问题、对话历史、当前意图分类、检索到的知识库片段、最终回复。每个节点负责更新 State 里的某一部分下一个节点读取更新后的 State 继续处理。这种设计让节点之间解耦了每个节点只需要关心自己那部分逻辑不需要知道上游是谁、下游是谁。State 的定义方式直接决定了工作流的清晰度。我踩过的一个坑是一开始把所有东西都塞进一个巨大的字典里结果节点之间互相覆盖调试起来非常痛苦。后来改成用 TypedDict 明确定义每个字段的类型和用途并且约定好哪个节点负责写哪个字段问题就少了很多。3.3 Node 与 Edge动作与流转规则Node 就是图里的一个执行单元。它可以是调用一次 LLM、执行一次检索、跑一段 Python 函数、调用一个外部 API基本上任何你想做的事情都可以封装成一个节点。每个节点接收当前的 State执行自己的逻辑然后返回一个更新后的 State通常是部分更新。Edge 定义了节点之间怎么走。最简单的 Edge 是固定边A 执行完直接去 B。更常用的是条件边也就是根据 State 里的某个值决定下一步去哪个节点。比如你有一个“意图判断”节点它输出“咨询”就去检索节点输出“投诉”就去转人工节点。这种条件分支用 LangGraph 表达非常自然。还有一个很实用的概念叫入口点和终点。入口点是工作流开始的地方终点是结束的地方。你可以设置多个终点比如“成功结束”和“失败结束”走不同的节点。整个图跑起来就是从入口点出发沿着边一路走到某个终点。4. 从零搭建一个 LangGraph 工作流完整实操4.1 环境准备与依赖安装先把环境搭起来。我习惯用 conda 建一个独立环境避免和系统里的其他包冲突。Python 版本建议 3.10 以上因为 LangGraph 用到了一些较新的类型语法。conda create -n langgraph-demo python3.11 conda activate langgraph-demo pip install langgraph langchain langchain-openai如果你用的是其他模型提供商把langchain-openai换成对应的包就行。LangGraph 本身不绑定模型它只负责编排模型调用是通过 LangChain 的接口来的。注意API 密钥不要硬编码在代码里。用环境变量或者.env文件管理提交代码前检查一下有没有把密钥带上去。我见过不止一次因为密钥泄露导致账单暴涨的案例。4.2 定义 State 结构我们做一个简单的例子一个能判断问题类型并给出不同回复的助手。先定义 Statefrom typing import TypedDict, Literal class AssistantState(TypedDict): user_input: str category: Literal[question, complaint, other] response: str这里user_input是用户输入category是分类结果response是最终回复。用Literal限定取值范围这样类型检查能帮你提前发现一些错误。4.3 编写节点函数接下来写三个节点分类节点、问答节点、投诉节点。def classify_node(state: AssistantState) - dict: user_input state[user_input] # 实际项目中这里调用 LLM 做分类 if 怎么 in user_input or 如何 in user_input: category question elif 投诉 in user_input or 不满 in user_input: category complaint else: category other return {category: category} def answer_node(state: AssistantState) - dict: return {response: f关于「{state[user_input]}」我的回答是...} def complaint_node(state: AssistantState) - dict: return {response: 非常抱歉给您带来不便我们会尽快处理。}每个节点接收 State返回一个字典表示要更新的字段。LangGraph 会自动把这些更新合并到全局 State 里。4.4 构建图并编译运行现在把节点和边组装起来from langgraph.graph import StateGraph, START, END builder StateGraph(AssistantState) builder.add_node(classify, classify_node) builder.add_node(answer, answer_node) builder.add_node(complaint, complaint_node) builder.add_edge(START, classify) def route_by_category(state: AssistantState) - str: if state[category] question: return answer elif state[category] complaint: return complaint else: return answer builder.add_conditional_edges(classify, route_by_category) builder.add_edge(answer, END) builder.add_edge(complaint, END) graph builder.compile() result graph.invoke({user_input: 这个功能怎么用}) print(result[response])跑一下就能看到输出。这个例子虽然简单但把 LangGraph 的核心要素都用到了State 定义、节点函数、条件边、入口和终点。4.5 加入循环让工作流自己决定要不要继续上面那个例子是一条路走到黑。LangGraph 真正强大的地方是支持循环。我们改一下加一个“质量检查”节点如果回复质量不达标就回到生成节点重新生成。class LoopState(TypedDict): question: str draft: str quality: str attempts: int def generate_node(state: LoopState) - dict: attempts state.get(attempts, 0) 1 return {draft: f第{attempts}版草稿, attempts: attempts} def check_node(state: LoopState) - dict: if state[attempts] 3: return {quality: pass} return {quality: retry} builder StateGraph(LoopState) builder.add_node(generate, generate_node) builder.add_node(check, check_node) builder.add_edge(START, generate) builder.add_edge(generate, check) def should_continue(state: LoopState) - str: if state[quality] pass: return END return generate builder.add_conditional_edges(check, should_continue) graph builder.compile() result graph.invoke({question: 测试, attempts: 0})这个循环最多跑三次每次生成后检查不通过就回去重新生成。这种模式在 AI 工作流里非常常见比如让模型反复修改直到满足某个条件。5. LangChain 与 LangGraph 的关系不是替代是分工5.1 两者定位的差异很多人搞不清楚 LangChain 和 LangGraph 到底什么关系网上搜“langchain和langgraph的区别”也是高频问题。我的理解是LangChain 是一套工具箱提供了和 LLM 交互的各种组件比如模型封装、提示词模板、输出解析器、检索器、工具调用接口。LangGraph 是在这些组件之上的一层编排框架负责把这些组件按某种流程组织起来。打个比方LangChain 像是厨房里的各种食材和厨具LangGraph 像是菜谱和烹饪流程。你可以只用 LangChain 做一道简单的菜但如果要做一桌宴席就需要 LangGraph 来安排先后顺序和协调。5.2 什么时候用 LangChain什么时候用 LangGraph如果你的需求是单轮问答、简单的 RAG 检索、一次性的文本处理用 LangChain 的 Chain 就够了没必要上 LangGraph。LangGraph 的价值在于流程复杂到一定程度之后有多个步骤、有条件分支、有循环、需要维护状态、需要人工介入。我自己的判断标准是如果你画流程图的时候发现需要画箭头回指那就该用 LangGraph 了。如果是一条直线走到底LangChain 的 Chain 更简单直接。5.3 实际项目中的混合使用方式在实际项目里两者通常是混着用的。节点内部的逻辑用 LangChain 的组件来实现比如在某个节点里调用 LLM、解析输出、检索向量库。节点之间的流转用 LangGraph 来管理。这样既享受了 LangChain 丰富的组件生态又获得了 LangGraph 强大的编排能力。提示不要为了用 LangGraph 而用 LangGraph。如果你的流程真的很简单硬套图结构只会增加复杂度。6. 常见问题与排查技巧实录6.1 State 更新不生效或互相覆盖这是新手最容易遇到的问题。LangGraph 默认对 State 的更新是合并式的但如果你在多个节点里同时更新同一个字段后面的会覆盖前面的。解决办法是明确每个字段的写入责任或者使用 reducer 来定义合并逻辑。比如你可以给某个字段指定一个operator.add作为 reducer这样多个节点的更新会累加而不是覆盖。6.2 条件边返回值不匹配导致流程卡住条件边函数必须返回一个目标节点的名称或者 END。如果你返回了一个不存在的节点名图会报错。我建议把节点名定义成常量条件边函数里引用常量而不是硬编码字符串这样改名字的时候不容易漏。6.3 循环没有终止条件导致死循环带环的图一定要有明确的退出条件。我见过有人写了一个“反思-改进”循环但退出条件写错了结果跑了几百轮还没停。建议在循环里加一个最大迭代次数达到上限就强制退出避免意外消耗。6.4 调试时看不到中间状态LangGraph 支持流式输出你可以用graph.stream()来逐步查看每个节点的输出。调试的时候这个功能特别好用能看到 State 在每个节点之后变成了什么样。另外LangGraph 还支持持久化可以把每一步的状态存下来方便回放和排查。问题现象可能原因排查方向流程提前结束条件边返回了 END检查条件判断逻辑节点没被执行边没连对打印图的边结构确认State 字段丢失节点返回了空字典确认每个节点都返回了更新循环停不下来退出条件永远不满足加最大迭代次数兜底6.5 关于密钥和鉴权信息泄露的防范使用 LLM 时密钥管理是个必须重视的问题。我的做法是本地开发用.env文件并且把.env加到.gitignore里生产环境用环境变量或者密钥管理服务。另外在日志里打印请求信息时要注意脱敏不要把完整的请求头打出来。还有一点容易被忽略如果你把代码分享给别人或者发到网上先检查一下有没有不小心把密钥带进去。7. 这套东西还能怎么扩展把 LangGraph 跑通之后你会发现很多之前觉得麻烦的事情变得可行了。比如你可以做一个多智能体协作的系统每个智能体是一个子图它们之间通过消息传递来协调。也可以做一个带人工审核的流程在关键节点暂停等人确认后再继续。还可以把持久化打开让工作流支持断点续跑这在处理长任务时特别有用。我个人的体会是LangGraph 最大的价值不是让你写出更复杂的流程而是让你能把复杂流程写清楚。当一张图摆在面前每个节点的职责、每条边的条件都一目了然的时候维护和迭代就变成了一件可控的事情。这比在一堆嵌套的 if-else 里找 bug 要舒服太多了。最后分享一个小技巧刚开始用 LangGraph 的时候先用纸把流程图草图画出来标清楚每个节点干什么、什么条件下走哪条边、State 里需要哪些字段。图画清楚了代码就是翻译工作会快很多。如果图画不清楚那说明你对流程的理解还不够这时候写代码大概率会返工。