news 2026/5/3 23:52:39

我是怎么把 RAG、Memory、MCP 拼进同一个 LangGraph 的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我是怎么把 RAG、Memory、MCP 拼进同一个 LangGraph 的

很多同学学完每一块知识点都挺懂的,但一到"做个完整项目"就卡住了。

不是因为技术不会,是因为脑子里有一堆"乐高零件",却不知道该怎么把它们拼成一辆车。

结果往往是:RAG 单独跑得好,一接 Memory 就乱;Tools 调用成功了,和 LangGraph 的 State 一合并就报类型错;加了 MCP 之后,整个 Graph 的路由逻辑又要重写一遍。

上线之后发现:第一轮对话记得上下文,第二轮就失忆;RAG 检索到了文档,但 Agent 完全没用上;工具调用成功了,但返回结果没有被正确传入下一个节点。

这些坑的根源,不是单个技术出了问题,而是整合时的架构设计出了问题

这篇我们一次搞定:完整架构 + 完整代码 + 完整踩坑录。


01 架构总览:六层技术栈的协作方式

先把全局地图摆出来,知道自己在哪儿,再看细节。

这个项目叫PersonalAssistant,核心能力:

  • 多轮对话,有长期记忆(Memory)
  • 知识库问答(RAG,基于 Milvus)
  • 工具调用(搜索、日历、代码执行)
  • 外部 MCP 服务接入(高德地图、浏览器)
  • 流式输出,支持 Human-in-the-Loop 审批

整体是一个LangGraph StateGraph,四层结构从上到下:

用户接口层→ HTTP API (Express) / WebSocket / CLI

LangGraph 编排层→ StateGraph: router → retriever → agent → tools / Conditional Edge / Human-in-Loop / Checkpoint

能力模块层→ RAG(Milvus) | Tools | MCP | Memory(Redis)

LLM 层→ ChatOpenAI / Claude(带 Tool Calling)

为什么是这个结构?

RAG、Tools、MCP、Memory 不是四个并列的"功能",而是 Agent 在不同情况下调用的不同"武器"。LangGraph 的 StateGraph 扮演"调度员"角色,根据用户意图的类型,决定走哪条路。


02 项目初始化与 State 设计:地基打好了后面才不塌

先把骨架搭起来,然后定义整个 Graph 的"数据总线"——State。

安装依赖:

mkdircd# .env 配置# OPENAI_API_KEY=sk-...# MILVUS_URI=http://localhost:19530# REDIS_URL=redis://localhost:6379

State 设计是整个 Graph 的地基。State 设计烂了,后面每个节点都要付出代价。最多的错误:把所有东西都塞进一个大 state,每个节点都要处理一堆自己不需要的字段。

正确做法:State 只存「跨节点需要共享的数据」,不存临时计算结果。

// src/graph/state.tsimportAnnotationfrom"@langchain/langgraph"importBaseMessagefrom"@langchain/core/messages"exportinterfaceMemoryItemidstringcontentstringscorenumbercreatedAtnumberexportinterfaceRetrievedDoccontentstringsourcestringscorenumberexportconstAssistantStateAnnotationRoot// messages 用 Reducer,保证追加而非覆盖messagesAnnotationBaseMessagereducerdefault() =>userInputAnnotationstringreducer(_, next) =>default() =>""// 路由决策写入这里,conditional edge 来读routeToAnnotation"rag""tools""mcp""direct"nullreducer(_, next) =>default() =>nullrelevantMemoriesAnnotationMemoryItemreducer(_, next) =>default() =>retrievedDocsAnnotationRetrievedDocreducer(_, next) =>default() =>// true 时触发 Human-in-LoopneedsApprovalAnnotationbooleanreducer(_, next) =>default() =>falsesessionIdAnnotationstringreducer(_, next) =>default() =>"default"exporttypeAssistantStateTypetypeofAssistantStateState

三个设计要点:

  1. messages必须用messagesStateReducer,否则工具调用结果会把历史消息覆盖掉
  2. routeTo在 router 节点写入,conditional edge 读取,路由逻辑集中一处,不分散到各节点
  3. needsApproval控制 Human-in-Loop 触发,跟工具调用逻辑解耦

03 记忆模块:会话历史和长期记忆是两回事

Memory 踩坑最多的地方:把"会话历史"和"长期记忆"混为一谈

  • 会话历史messages):本轮对话上下文,存 State 里,Checkpoint 自动管理
  • 长期记忆:跨会话的用户信息(“叫 James,偏好 TypeScript,不喜欢冗长解释”),要单独存储和检索
// src/memory/store.ts —— 长期记忆的存储与检索importRedisfrom"ioredis"importOpenAIEmbeddingsfrom"@langchain/openai"constnewRedisenvREDIS_URLconstnewOpenAIEmbeddings// 存储:把内容向量化后存 RedisexportasyncfunctionsaveMemorysessionId: string, content: stringPromisevoidconst`mem:${sessionId}:${Date.now()}`constawaitembedQueryawaithsetvectorJSONstringifycreatedAtDatenowawaitsadd`memories:${sessionId}`// 检索:余弦相似度,阈值 0.75exportasyncfunctionretrieveMemories sessionId: string, query: string, topK = 3PromiseMemoryItemconstawaitembedQueryconstawaitsmembers`memories:${sessionId}`iflength0returnconstawaitPromiseallmapasyncconstawaithgetallifvectorreturnnullconstJSONparsevectorasnumberconstreduce(s, v, i) =>0constMathsqrtreduce(s, v) =>0Mathsqrtreduce(s, v) =>0returncontentcontentcreatedAtNumbercreatedAtreturnfilterBooleanasMemoryItemfilter(x) =>score0.75sort(a, b) =>scorescoreslice0// Graph 节点:对话开始时检索记忆exportasyncfunctionmemoryRetrieveNodestate: AssistantStateTypeconstawaitretrieveMemoriessessionIduserInputreturnrelevantMemories// Graph 节点:对话结束后更新记忆exportasyncfunctionmemorySaveNodestate: AssistantStateTypeifuserInputlength20awaitsaveMemorysessionId`用户问过:${state.userInput.slice(0, 100)}`return

运行效果:用户问「帮我继续上次的 TypeScript 项目」时,memoryRetrieveNode会在 Redis 里检索到相似度 > 0.75 的历史记录(比如「用户偏好 TypeScript,不喜欢冗长解释」),然后注入 System Message。LLM 不再重复问用户偏好,直接用对口的风格回答。


04 RAG 模块:检索到文档不等于 LLM 会用

RAG 整合时最容易踩的坑:retriever 返回了文档,但 LLM 完全没用上

原因是把检索结果直接塞进messages,但没有告诉 LLM “这些是你可以参考的文档”。

正确做法:把文档格式化成带明确标签的 System Message,明确指示 LLM “请优先根据以下文档回答”。

// src/rag/retriever.ts —— Milvus 检索 + RAG 节点importMilvusfrom"@langchain/milvus"importOpenAIEmbeddingsfrom"@langchain/openai"constnewOpenAIEmbeddingsletvectorStoreMilvusasyncfunctiongetVectorStorePromiseMilvusifawaitMilvusfromExistingCollectioncollectionName"personal_assistant_kb"urlenvMILVUS_URIreturn// 检索并过滤低分文档exportasyncfunctionretrieveDocsquery: string, topK = 4PromiseRetrievedDocconstawaitgetVectorStoreconstawaitsimilaritySearchWithScorereturnfilter([, score]) =>0.7map([doc, score]) =>contentpageContentsourcemetadatasource"unknown"// Graph 节点exportasyncfunctionragRetrieveNodestate: AssistantStateTypeconstawaitretrieveDocsuserInputreturnretrievedDocs// 关键:在 agent 节点里把 docs 注入 system messageexportfunctionbuildSystemPromptstate: AssistantStateTypestringlet"你是一个专业的 AI 助手,擅长回答各类问题。"ifrelevantMemorieslength0"\n\n## 关于用户的偏好\n"relevantMemoriesmap(m) =>`- ${m.content}`join"\n"ifretrievedDocslength0"\n\n## 参考知识库(请优先根据以下内容回答)\n"retrievedDocsmap(d, i) =>`[文档${i + 1}](来源: ${d.source})\n${d.content}`join"\n\n"return

运行效果:用户问「我们的 API 限流策略是什么?」时,ragRetrieveNode从 Milvus 检索到相关文档,buildSystemPrompt把文档格式化成「## 参考知识库(请优先根据以下内容回答)」注入 System Message。LLM 回答直接引用知识库内容,不再靠自身知识瞎猜。没有明确指示前,LLM 检索到文档也会视而不见。


05 Tools + MCP:本地工具与外部服务统一接入

Tools 和 MCP 的区别很多人没搞清楚:

  • Tools:你自己用DynamicStructuredTool写的本地工具函数
  • MCP:外部服务暴露的工具,通过langchain-mcp-adapters统一成 LangChain Tool 格式

对 Agent 来说,调用接口完全一样——都是tool.invoke(params),不用关心底层是本地函数还是远程 MCP Server。

// src/tools/index.ts —— 本地工具定义// src/mcp/client.ts —— MCP 工具接入(单例模式)importDynamicStructuredToolfrom"@langchain/core/tools"importMultiServerMCPClientfrom"langchain-mcp-adapters"importToolNodefrom"@langchain/langgraph/prebuilt"importfrom"zod"// 本地工具:搜索exportconstnewDynamicStructuredToolname"web_search"description"搜索互联网上的实时信息,适合查询最新新闻、价格、天气等"schemaobjectquerystringdescribe"搜索关键词"maxResultsnumberoptionaldefault3funcasyncconstawaitfetch`https://api.tavily.com/search?q=${encodeURIComponent(query)}&limit=${maxResults}`headers"x-api-key"envTAVILY_API_KEYconstawaitjsonreturnresultsmap(r: any) =>`${r.title}\n${r.content}`join"\n\n"// MCP 客户端:单例模式,服务启动时预热(避免第一次请求超时)let_mcpToolsanyexportasyncfunctioninitMCPToolsiflength0returnconstnewMultiServerMCPClientamaptransport"stdio"command"npx"args"-y""@amap/mcp-server""--key"envAMAP_KEYawaitgetToolsreturn// 构建 ToolNode(LangGraph 内置,自动处理 tool_calls → ToolMessage 回流)exportasyncfunctionbuildToolNodeconstawaitinitMCPToolsreturnnewToolNode

运行效果:用户问「北京明天天气怎么样」时,Agent 调用web_search工具,返回结构化搜索结果后再生成回答。用户问「从望京到三里屯怎么走」时,Agent 调用高德 MCP 的路线规划工具,获取实时路况后返回步行/驾车方案。两种工具对 Agent 的调用接口完全一样,切换成本为零。

MCP 单例的重要性:MCP Client 每次init都要与外部进程握手,耗时 1-3 秒。如果每次请求都重新 init,第一次用高德导航会直接超时。预热的方案是服务启动时调用一次initMCPTools()


06 Graph 主体:调度员把所有模块串起来

这是整个项目的核心——把前面所有模块接进 StateGraph,用 Conditional Edge 做智能路由。

// src/graph/index.ts —— 完整 Graph 定义importStateGraphSTARTENDMemorySaverfrom"@langchain/langgraph"importChatOpenAIfrom"@langchain/openai"importAIMessageSystemMessageHumanMessagefrom"@langchain/core/messages"importAssistantStateAssistantStateTypefrom"./state"importfrom"../memory/store"importfrom"../rag/retriever"importfrom"../tools"// 路由节点:判断走哪条分支(生产可换成 LLM 语义路由)asyncfunctionrouterNodestate: AssistantStateTypeconstuserInputtoLowerCaseifincludes"文档"includes"知识库"returnrouteTo"rag"asconstifincludes"搜索"includes"天气"includes"新闻"returnrouteTo"tools"asconstifincludes"地图"includes"路线"includes"附近"returnrouteTo"mcp"asconstreturnrouteTo"direct"asconst// Agent 核心节点:把记忆+文档注入 system,绑定工具asyncfunctionagentNodestate: AssistantStateTypeconstawaitinitMCPToolsconstnewChatOpenAImodel"gpt-4o"temperature0.3bindToolsconstawaitinvokenewSystemMessagebuildSystemPromptmessages// 敏感工具触发 Human-in-Loopconsttool_callssome(tc) =>"send_email""delete_file""execute_code"includesnamereturnmessagesasAIMessageneedsApproval// 构建完整 GraphexportasyncfunctionbuildGraphconstnewMemorySaver// 生产换 PostgresSaverconstawaitbuildToolNodereturnnewStateGraphAssistantStateaddNode"memory_retrieve"addNode"router"addNode"rag_retrieve"addNode"agent"addNode"tools"addNode"memory_save"addEdgeSTART"memory_retrieve"addEdge"memory_retrieve""router"// 路由分支:rag 先检索再 agent,其他直接 agentaddConditionalEdges"router"(s) =>routeTo"direct"rag"rag_retrieve"tools"agent"mcp"agent"direct"agent"addEdge"rag_retrieve""agent"// Agent → 工具调用 or 结束addConditionalEdges"agent"(s) =>constmessagesmessageslength1asAIMessageifneedsApprovalreturn"need_approval"// Human-in-Loopiftool_callslengthreturn"call_tools"return"done"need_approval"tools"// 实际生产需要 interrupt 机制call_tools"tools"done"memory_save"addEdge"tools""agent"// 工具结果回流给 agentaddEdge"memory_save"ENDcompile

运行效果:用户发送「帮我查一下公司 API 文档里关于鉴权的部分」,Graph 执行路径为:memory_retrieve(检索到用户偏好 TypeScript)→router(识别为 rag)→rag_retrieve(Milvus 检索到3篇相关文档)→agent(注入记忆+文档,LLM 生成回答)→memory_save(记录本次提问)→END。全程约 2.3s,用户只感受到一次流式输出。


07 HTTP 接口:普通对话 + 流式输出都支持

// src/server.ts —— Express + SSE 流式输出importfrom"express"importfrom"./graph"importHumanMessagefrom"@langchain/core/messages"constexpressusejsonletgraphAwaitedReturnTypetypeofasyncfunctioninitawaitbuildGraphlisten3000() =>consolelog"PersonalAssistant :3000"// 普通对话post"/chat"asyncconst"default"bodyconstawaitinvokeuserInputmessagesnewHumanMessageconfigurablethread_idconstmessagesmessageslength1jsonreplycontent// 流式输出(SSE)post"/chat/stream"asyncconst"default"bodysetHeader"Content-Type""text/event-stream"setHeader"Cache-Control""no-cache"constawaitstreamuserInputmessagesnewHumanMessageconfigurablethread_idstreamMode"messages"forawaitconstofifcontentwrite`data: ${JSON.stringify({ content: msg.content, node: meta.langgraph_node })}\n\n`write"data: [DONE]\n\n"endinit

运行效果:/chat接口适合单次问答,等待完整回答再返回,延迟约 2-5s;/chat/stream用 SSE 推送,首 token 延迟约 300ms,用户看到打字机效果,长回答体验明显更好。前端监听data: [DONE]事件关闭连接。


08 常见坑:整合时最容易栽的 6 个地方

现象根因解法
Checkpoint 混串A 用户看到 B 用户的对话thread_id没有按用户隔离sessionId来自鉴权,不信任请求 body
Memory 失效存了记忆,下次不认识用户相似度阈值太高先调低到 0.6,确认能检索到再调高
工具返回丢失工具调用成功,LLM 没用结果ToolMessage 没有回流到 agent确认tools → agent边存在,messages 是 Reducer 模式
RAG 没被用上检索到文档,LLM 回答仍靠自身知识System Message 没明确指示加「请优先根据以下文档回答」的明确指令
MCP 初始化慢第一次请求超时每次请求都重新握手单例模式,服务启动时预热
Human-in-Loop 卡死需审批的请求一直 pendingresume 逻辑没实现graph.updateState+ 二次invoke的 resume 流程

坑1(Checkpoint 混串)是生产事故高发区。MemorySaverthread_id就是隔离单元,如果来自请求 body,任何用户都可以传别人的 sessionId,看到别人的对话历史。

// 正确做法:sessionId 来自鉴权,不信任用户输入post"/chat"asyncconst`${req.user.id}:${req.body.conversationId ?? "main"}`// ...

运行效果:上面这 6 个坑全部是生产事故高发区。其中最危险的是「Checkpoint 混串」,上线后如果 sessionId 来自用户请求 body,任何用户只需猜到别人的 sessionId 就能看到对话历史,属于严重数据泄露。


09 生产部署:从能跑到能用的最后一公里

// 生产 Checkpoint:把 MemorySaver 换成 PostgresSaver(重启不丢数据)// npm install @langchain/langgraph-checkpoint-postgresimportPostgresSaverfrom"@langchain/langgraph-checkpoint-postgres"constPostgresSaverfromConnStringenvDATABASE_URLawaitsetup// 自动建表// PM2 守护进程// pm2 start dist/server.js --name personal-assistant --max-memory-restart 512M

生产部署清单:

  • Checkpoint 换成PostgresSaverRedisSaverMemorySaver重启即丢)
  • Redis 开持久化(appendonly yes
  • Milvus 数据卷挂载到宿主机
  • 所有 API Key 用环境变量,不写死代码
  • 加请求限速(防止 LLM 调用超额)
  • 接入 LangSmith 做链路追踪,方便排查 Graph 执行问题

运行效果:接入 LangSmith 后,一个完整请求在追踪面板里会展开成树形结构:PersonalAssistant > memory_retrieve > router > rag_retrieve > agent(llm) > tools(web_search) > agent(llm) > memory_save,每个节点的输入输出、耗时、token 消耗一目了然。出了问题不用加日志猜,直接在面板里看哪一步出了差错。

一个容易忽视的细节:LangSmith 在整合型项目里尤其有用。一个请求经过 router → rag_retrieve → agent → tools → agent 这一串节点,不接追踪的话出了问题根本不知道卡在哪一步。


总结

把所有技术栈拼成一个能跑的完整项目,核心是六件事:

  • State 是地基:设计好 State 结构,后面每个节点都清晰,改起来不牵一发而动全身
  • Memory 要分层:会话历史(messages)和长期记忆(Redis)是两回事,不要混用
  • RAG 要告知 LLM:检索到文档不等于 LLM 会用,System Message 里必须明确指示优先参考
  • 工具调用要单例:MCP Client 预热初始化,避免每次请求都重新握手超时
  • Checkpoint 要隔离thread_id来自鉴权,不信任用户输入,生产环境必须用持久化存储
  • 整合是架构问题:单个技术能跑不代表整合后能用,每条「节点 → 节点」的数据流向都要想清楚

这套架构是一个可以继续生长的骨架,后续还有很多值得深入的方向:Multi-Agent 协作、更精细的路由策略、知识库的动态更新……后面会一篇一篇拆开来讲。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/3 23:41:47

Arm Fast Models与PVBus技术:嵌入式系统仿真与验证的核心

1. Arm Fast Models与PVBus技术概述在嵌入式系统仿真和验证领域,Arm Fast Models作为业界领先的虚拟平台解决方案,为芯片设计提供了高效的预硅验证环境。PVBus(Programmers View Bus)作为其核心总线协议,承担着连接处理…

作者头像 李华
网站建设 2026/5/3 23:40:15

白帽变黑帽:2026年安全响应人员监守自盗案深度剖析与行业重构

引言:当守护者变成毁灭者 2026年4月30日,美国佛罗里达州南区联邦法院的法槌落下,一个足以震撼全球网络安全行业的判决正式生效:前Sygnia公司事件响应经理Ryan Goldberg(40岁)与前DigitalMint公司勒索软件谈…

作者头像 李华
网站建设 2026/5/3 23:27:25

Ophiuchi架构解析:Next.js + Tauri + Docker的完美融合

Ophiuchi架构解析:Next.js Tauri Docker的完美融合 【免费下载链接】ophiuchi-desktop Localhost SSL Proxy Server Manager using Docker 项目地址: https://gitcode.com/gh_mirrors/op/ophiuchi-desktop Ophiuchi是一款基于Next.js、Tauri和Docker技术栈…

作者头像 李华