从 LangChain 直接跳到 LangGraph 改造 RAG 知识库这个事我前后折腾了小半年踩了不少坑也把官方文档翻了不止一遍。如果你正在做知识库问答或者是企业内部文档检索那一套看完这篇文章应该能少走很多弯路。我会从最基础的为什么改怎么改讲起然后结合一个真实的知识库问答项目把整个改造过程拆开揉碎给你看。1. 先搞清楚LangChain 和 LangGraph 到底差在哪1.1 从一条链到一张图编程模型的变化LangChain 刚火起来的时候大家用的最多的就是Chain这个概念一层套一层像流水线一样。RetrievalQA这类组件把检索 提示词拼接 LLM 调用包在一起调用起来确实方便。但问题也出在这太顺手了导致你很难插进自定义逻辑。比如我想在检索之后加一个判断——如果检索结果相关度太低就不直接生成回答而是让用户换一种问法。放在 LangChain 的 Chain 里你得去改源码或者用一些很别扭的 hack。LangGraph 的做法是把这个流程拆成节点Node和边Edge用一个 State 对象在各个节点之间传递数据。每个节点就是一个函数节点之间可以由条件边控制走向相当于把 RAG 流程画成了一张有向图。打个比方LangChain 是工厂里的传送带从入口到出口一条路走到底LangGraph 是带分拣口的流水线每个工位做完自己的事根据检查结果决定送到下一个工位还是打回重做。这个差异直接决定了你改造 RAG 知识库时的自由度。如果你的知识库场景很固定——用户提问、召回、拼提示词、生成回答——用 LangChain 完全够。但如果你要做 Agent 化检索、多轮追问、多路召回合并或者判断该不该调知识库这种动态路由LangGraph 几乎是必须的。1.2 什么时候该升级到 LangGraph什么时候没必要我见过不少团队一听到 LangGraph 是下一代框架立刻把线上系统翻个底朝天结果上线后 Bug 比原来还多。选型这事不是越新越好得看你的 RAG 系统到底有没有状态流转的需求。建议升级到 LangGraph 的场景需要根据检索结果决定下一步动作比如检索为空时要改写问题重新查需要多轮对话中维护上下文状态比如用户说刚才那个文档里的数据呢你得记得上一轮聊的是哪份文档需要并行调用多个检索源然后做一个合并和重排。这些都属于控制流复杂用 Chain 表达起来很费劲。不建议升级的场景知识库就是一个固定集合问答模式单一用户进来提问系统检索 TopK 拼 Prompt输出答案就结束。这时候引入 LangGraph 只会增加代码量和排错成本收益很低。我自己的判断标准很简单如果你的 RAG 系统里开始出现 if else 判断下一步该干什么而且这个判断的维度超过两个那就该上 LangGraph 了。2. RAG 知识库改造的前置准备2.1 改造前先盘点你的 RAG 系统卡在哪很多人一上来就急着写代码把 LangChain 代码翻译成 LangGraph结果只是换了套写法底层问题一个没解决。我建议先做一次完整体检把 RAG 系统的瓶颈找出来再说。我当时梳理了几个维度。召回质量用同一批测试问题跑一遍人工看检索结果和问题的相关性。如果 Top5 里有用结果不到 3 个说明切片策略或者向量检索配置有问题这种情况不是换个框架能救的。生成质量如果检索回来内容看起来都对但大模型回答还是答非所问那是提示词和生成链路的问题。性能瓶颈统计一下整个链路的延迟看哪一步耗时最长。有时候是向量检索慢有时候是 LLM 生成太长有时候是文档预处理阶段的 embedding 环节卡住了。异常处理用户问的问题在知识库完全没覆盖时系统是硬答还是直接说不知道多轮对话中断了怎么恢复这些在 LangChain 里通常没有好的处理方式。把这些问题列成一张表标出哪些是换框架能解决的哪些是换框架解决不了的。我自己做完盘点发现真正需要 LangGraph 解决的其实是最后一项——异常处理和动态决策其他问题在 LangChain 里调参就能解决。2.2 核心组件梳理向量库、切片策略、检索逻辑改造之前还要把组件关系理清楚。一个知识库 RAG 系统从数据流上看就几块文档加载、文本切片、向量化、向量存储、检索、重排、生成。LangGraph 管的是后面几个环节的控制流但前面几个环节的质量直接决定后面能不能运转好。切片策略这里多说一句。很多人图省事固定按 500 字切还带 50 字重叠。这个参数在 LangChain 和 LangGraph 里都一样不是框架能替你解决的。我后来改成了按 Markdown 标题结构来切每节作为一个语义块小的章节就合并大章节再按段落切。原因很简单知识库里的文档都是有结构的按结构切比按字数切更能保持语义完整。向量化我用的 Embedding 模型也对比过几款最后选了在领域数据上表现相对均衡的那一个这块后续可以单独写一篇。检索逻辑是改造的重点。原来 LangChain 里直接用vectorstore.as_retriever(search_kwargs{k: 4})问题在于这个 TopK 是死的用户问题简单的时候可能只需要 2 个片段就够了复杂问题 4 个可能不够。LangGraph 的节点设计就能解决这个灵活性。3. LangGraph 改造实操从一个基础 RAG 链开始3.1 定义 State改造的第一步LangGraph 里最核心的概念是 State。简单说State 就是所有节点之间传递数据的容器。你定义的 State 结构会决定整个图怎么流转。我最初设计好基础版就长这样from typing import TypedDict, List from langgraph.graph import StateGraph class RagState(TypedDict): question: str documents: List[str] context: str answer: str need_rewrite: bool retry_count: int这个 State 里question是用户输入documents是检索出来的文档片段context是拼好的上下文answer是最终回答。后面两个是改造时加的need_rewrite表示当前检索结果是否需要改写问题再查一次retry_count记录重试次数防止死循环。这里值得注意的一点是State 定义得越窄节点之间耦合越小。我见过有人把整个文档库都塞进 State图形是画出来了跑起来内存直接拉满。State 只该存流程中流转的必要信息不需要存大块数据。3.2 把检索和生成拆成独立节点LangGraph 的节点就是一个普通函数输入是个字典State 的子集输出是个字典更新 State 的部分字段。下面是我的基础版节点def retrieve_node(state: RagState) - dict: 检索节点根据问题从向量库取回相关文档片段 question state[question] docs vectorstore.similarity_search(question, k6) return { documents: [d.page_content for d in docs], context: \n\n.join([d.page_content for d in docs]) } def generate_node(state: RagState) - dict: 生成节点根据检索上下文生成最终答案 prompt f你是知识库问答助手。请严格根据以下资料回答用户问题。 如果资料中没有相关信息请直接说知识库中没有找到相关信息。 资料 {state[context]} 用户问题{state[question]} response llm.invoke(prompt) return {answer: response.content}然后组装成图from langgraph.graph import StateGraph, START, END graph StateGraph(RagState) graph.add_node(retrieve, retrieve_node) graph.add_node(generate, generate_node) graph.add_edge(START, retrieve) graph.add_edge(retrieve, generate) graph.add_edge(generate, END) app graph.compile()这样一个最基本的 RAG 图就搭出来了。调用方式result app.invoke({question: 什么是 LangGraph}) print(result[answer])这段代码跑通之后你就已经完成从 LangChain 到 LangGraph 的最核心迁移了。从外层看调用方只需要传入 question返回 answer和之前 LangChain 的RetrievalQA用法差不多。但内部结构已经完全变了检索逻辑和生成逻辑解耦成独立函数随时可以在中间插节点。注意LangGraph 的节点函数有个约定——它接收的 State 是只读视图你要更新 State只能通过返回值返回增量。一开始我总想着在节点内部直接修改state[question] ...结果发现根本没生效。这是新手最容易踩的坑必须通过返回值来更新。3.3 加入条件路由让系统学会先查后答MCPModel Context Protocol是另一套东西跟 LangGraph 不在一个维度上后面有机会细讲。回到改造——基础版本能跑之后我做的第一个实质改进是加入条件路由。之前的流程是无脑检索、无脑生成。问题是用户提了一个知识库完全没有的问题系统也硬要从向量库里拉一堆看似相关但实际没用的片段然后让 LLM 编一个看似合理的答案。这在企业内部知识库场景是非常危险的因为员工会拿这个答案当真。我的方案是加一个检索质量评估节点。检索完先不急着生成而是让一个快速分类模型判断当前检索结果和用户问题是否相关。判断结果是pass就继续生成是fail就进入另一个处理节点。这在 LangGraph 里就是一条条件边的事def check_relevance_node(state: RagState) - dict: 用一个小模型判断检索结果是否与问题相关 prompt f以下是用户问题和检索到的知识片段请判断片段内容是否与问题相关。 如果片段内容能支撑回答用户问题输出 PASS否则输出 FAIL。 用户问题{state[question]} 知识片段 {state[context]} result judge_llm.invoke(prompt).content.strip().upper() return {need_rewrite: result ! PASS} def should_rewrite(state: RagState) - str: 条件判断是否走重写流程 if state[need_rewrite] and state[retry_count] 2: return rewrite return generate # 加入图中 graph.add_node(check_relevance, check_relevance_node) graph.add_node(rewrite_question, rewrite_question_node) graph.add_edge(retrieve, check_relevance) graph.add_conditional_edges( check_relevance, should_rewrite, { rewrite: rewrite_question, generate: generate } ) graph.add_edge(rewrite_question, retrieve)should_rewrite函数就是一个纯 Python 的判断逻辑如果检索质量不过关而且还没重试超过两次就去改写问题再查一遍。rewrite_question_node里利用 LLM 把用户问题改写得更具体、更适合检索。重试次数在 State 里 1这样即使改写两次还是查不到也不会陷入死循环。这个改造彻底改变了系统的行为模式原来是不管查没查到都要硬答现在是先判断能不能答不能答就换个方式查实在查不到就明确告诉用户不知道。效果立竿见影。上线后我用了一批知识库外的问题做测试之前那些胡说八道的回答基本消失了取而代之的是知识库中没有找到相关信息。对知识库问答来说不知道比硬编一个答案好一百倍。这个时候我才真正感觉到LangGraph 的价值不在代码量而在它让这种分叉逻辑变得清晰可控。4. 进阶改造把控制流真正用起来4.1 多路召回与重排光是给检索加一个判断还不够。企业知识库的痛点是文档多、来源杂经常一个问题在多个地方都有相关描述而且说法还不一致。之前单路检索 TopK 截断的方式很容易漏掉真正有用的片段。改造之后我做了两路召回一路走向量相似度召回按语义找另一路走全文关键词召回按字面找。两条路并行执行各取 Top10再做一次合并去重和相关性重排。LangGraph 的并行节点在这里帮了大忙——两个检索节点之间没有依赖关系可以并行执行实际延迟只相当于一路的时间。LangGraph 没有专门为平行提供单独语义——本质上是多个节点拥有同一来源same source作为前置边自然就会并行执行。def retrieve_vector_node(state: RagState) - dict: 按向量相似度召回 docs vectorstore.similarity_search(state[question], k10) return {vector_docs: [d.page_content for d in docs]} def retrieve_keyword_node(state: RagState) - dict: 按关键词召回 docs keyword_search(state[question], top_k10) return {keyword_docs: [d.page_content for d in docs]} def merge_rerank_node(state: RagState) - dict: 两个检索结果合并去重重新排序取 Top5 all_docs dedupe(state[vector_docs] state[keyword_docs]) reranked reranker.rank(state[question], all_docs, top_k5) return {documents: reranked, context: \n\n.join(reranked)}图里建边的时候要注意merge_rerank_node要等两个检索节点都结束后才能执行。LangGraph 会自己处理这个依赖关系只要把两条边都加上就行。并行节点的好处不只是快更重要的是检索质量。多路召回相当于让系统和用户对话时有更多候选证据可用重排则把关最后的证据质量。之前一个问题的答案经常只来自单个片段现在可以从多个片段里综合信息回答的深度和准确性都明显提升。当然这也意味着你要额外维护一个重排模型的服务或接口成本高一截但对企业知识库的准确率要求来说这笔投入值得。4.2 知识不足时的兜底与反思另外一个让我觉得这个改造真值的场景是联合检索。公司内部的知识库不只有文档还有网站、Wiki、数据库里的工单记录。文档里查到的东西可能过时了工单里反而有最新的处理方案。于是我在图里加了一个决策节点文档检索结果的置信度不高时自动去查工单库补充上下文。这里的判断逻辑不再是简单二分类而是一个综合评估。比如用户问的偏运维操作类问题文档检索如果只有零星命中我就让工单查询节点接管一次。LangGraph 的灵活性在这一步体现出来了——整个流程从单纯的检索后生成变成了多数据源动态选择这已经不是传统 RAG 的范畴了。按 LangGraph 的节点化设计真实场景的增强从多知识点级联逐渐走向 Agent 化LLM 不再是被动从上下文里拼答案而是主动参与找什么材料、从哪找、怎么判断够没够的全过程中。加入 Agent 节点后系统可以自行决策是否启用反思节点对模型生成的回答做一个反向校验拿答案中涉及的事实点回原文核对防止幻觉。5. 常见问题与排查技巧5.1 常见问题速查表改造过程中坑不少我把自认为最典型的问题整理成一张表方便你对照排查。现象可能原因解决办法RecursionError: maximum recursion depth exceeded图里出现了循环边且没有设置最大步数限制给app.invoke传recursion_limit25参数检查条件边的条件函数是否会死循环比如重试次数没递增状态在节点间传递丢失节点函数返回了空字典或者键名不匹配检查返回值里键名是否完全一致用app.get_state(config)查看当前 State 内容START 后没有节点执行图里的节点名拼写错误边指向了不存在的节点用graph.get_graph().draw_mermaid()把图可视化出来检查条件边总是走同一个分支should_xxx函数返回值与字典里的键值不匹配先单独 test 条件函数确认返回值在{branch_a: ..., branch_b: ...}里存在并行节点没按预期并行执行不一定注意多个检索节点的输入输出键如果相同可能会互相覆盖使用不同的键名如vector_docs和keyword_docs最后再合并切换 LangGraph 后回答质量下降之前 LangChain 的隐式 Prompt 模板丢了LangChain 的链组件内部拼接有默认 Prompt改造后这部分逻辑需要自己在节点里手写建议把原来的 Prompt 抄过来再优化内存持续增长State 存了太多大对象比如全文片段列表节点内只保留需要的字段生成完成后把documents置空5.2 调试和观察技巧LangGraph 的调试体和 LangChain 不一样。LangChain 里主要是看链的每一步日志。LangGraph 里推荐在节点函数内部打印或者接入日志但更优雅的方式是用 LangGraph 的get_state方法在任意断点查看当前状态。我实际开发中会用langgraph.debug模式把每一步的状态变化打出来或者在自己的图后面接一个回调函数把关键节点执行耗时记录到日志。性能上一个节点只干一件事出了问题定位就是秒级的事。代码写了一段时间后你会发现之前用 LangChain 写验证检索质量、改写 query、重新检索需要自己实现一套状态机流程长了非常容易出错LangGraph 用图结构天然把这些逻辑固化下来用一个dot可视化看全貌可读性提升很明显。6. 改造完成后的几点体会改造完成之后我在实际使用中最大的感受是LangGraph 带给你的不只是一个新框架而是一种从线性流程思维到状态图思维的转变。到目前为止这个知识库问答系统已经稳定跑了一段时间。我自己最大的体会就是框架迁移不是目的把流程控制做好才是目的。LangGraph 相比 LangChain 的真正优势在于它给了你一个足够好用的工具让你去表达如果 A 就做 B否则做 C但无论怎样都要 D的复杂决策逻辑。你只要愿意把流程拆细系统行为就会变得越来越可控。最后分享一个实用小技巧如果你的项目里需要从 LangChain 迁移到 LangGraph不要一次性全部重写。先挑一个非核心但独立的功能模块比如检索质量判断用 LangGraph 重写一遍验证稳定后再把主链路迁移过来。你一边迁移一边积累经验后面大改会顺畅很多。这个过渡思路也适合其他任何框架迁移场景。
企业数字化 ERP 产品动态
相关推荐
LangChain到LangGraph:RAG知识库流程编排改造实战 做 RAG 知识库这两年,我最大的感受是:LangChain 上手很快,但真正想把检索流程做得复杂、可控、能应对生产环境,它那套链式写法会越来越拧巴。这个项目就是我在已有的 LangChain RAG 知识库基础上,整体迁移到 LangGraph… · 2026/9/24 23:39:11
人工智能策略模拟系统实战:从算法到系统的工程化路径 1. 从标题拆解开始:这个项目到底在做什么“人工智能策略模拟的技术路径:从算法到系统”这个标题,乍一看像是学术论文的题目,但如果你在一线做过AI项目落地,就会知道它其实描述的是一个非常具体的工程问题:如… · 2026/9/24 23:39:11
企业级研发Agent设计:从Jira集成到意图识别的架构实践 1. 这不是在搭积木:为什么企业级研发 Agent 不能照搬开源 Demo“Agent”这个词最近两年像被吹胀的气球,从技术社区飘进会议室PPT,再落到老板们签批的预算单上。但凡带“智能”俩字的系统,不塞几个Agent模块,好像就不好… · 2026/9/24 23:39:11
深度学习新闻分类推荐系统:从TextCNN到个性化推荐 简介:这份基于深度学习的新闻分类推荐系统Python实现源码,是专为课程设计与期末大作业准备的高分项目,下载后无需修改即可运行,适用于需要快速交付完整课题的高校学生。系统涵盖新闻数据预处理、文本分类模型训练、推荐逻辑展示等… · 2026/9/24 23:59:53
汽车电子底层软件开发:AUTOSAR与CAN总线实战解析 1. 这门“汽车电子底层软件开发就业课”到底在教什么?——不是写个LED闪烁就能上岗的很多人看到“汽车电子底层软件开发就业课”这个标题,第一反应是:不就是嵌入式C语言单片机CAN通信?刷几道LeetCode、调通一个STM32 CAN收发例程&… · 2026/9/24 23:59:53
Vim基础操作全攻略:保存退出、模式切换与高频命令实战 1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保… · 2026/9/24 23:59:53
Python+CNN车牌识别实战:从数据预处理到模型训练与部署 简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据… · 2026/9/24 23:59:53
AI元人文:从工具使用到思维重构的深度探索 最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决… · 2026/9/24 23:59:53