我是怎么把 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 / CLILangGraph 编排层→ 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_KEYsk-...# MILVUS_URIhttp://localhost:19530# REDIS_URLredis://localhost:6379State 设计是整个 Graph 的地基。State 设计烂了后面每个节点都要付出代价。最多的错误把所有东西都塞进一个大 state每个节点都要处理一堆自己不需要的字段。正确做法State 只存「跨节点需要共享的数据」不存临时计算结果。// src/graph/state.tsimportAnnotationfromlangchain/langgraphimportBaseMessagefromlangchain/core/messagesexportinterfaceMemoryItemidstringcontentstringscorenumbercreatedAtnumberexportinterfaceRetrievedDoccontentstringsourcestringscorenumberexportconstAssistantStateAnnotationRoot// messages 用 Reducer保证追加而非覆盖messagesAnnotationBaseMessagereducerdefault() userInputAnnotationstringreducer(_, next) default() // 路由决策写入这里conditional edge 来读routeToAnnotationragtoolsmcpdirectnullreducer(_, next) default() nullrelevantMemoriesAnnotationMemoryItemreducer(_, next) default() retrievedDocsAnnotationRetrievedDocreducer(_, next) default() // true 时触发 Human-in-LoopneedsApprovalAnnotationbooleanreducer(_, next) default() falsesessionIdAnnotationstringreducer(_, next) default() defaultexporttypeAssistantStateTypetypeofAssistantStateState三个设计要点messages必须用messagesStateReducer否则工具调用结果会把历史消息覆盖掉routeTo在 router 节点写入conditional edge 读取路由逻辑集中一处不分散到各节点needsApproval控制 Human-in-Loop 触发跟工具调用逻辑解耦03 记忆模块会话历史和长期记忆是两回事Memory 踩坑最多的地方把会话历史和长期记忆混为一谈。会话历史messages本轮对话上下文存 State 里Checkpoint 自动管理长期记忆跨会话的用户信息“叫 James偏好 TypeScript不喜欢冗长解释”要单独存储和检索// src/memory/store.ts —— 长期记忆的存储与检索importRedisfromioredisimportOpenAIEmbeddingsfromlangchain/openaiconstnewRedisenvREDIS_URLconstnewOpenAIEmbeddings// 存储把内容向量化后存 RedisexportasyncfunctionsaveMemorysessionId: string, content: stringPromisevoidconstmem:${sessionId}:${Date.now()}constawaitembedQueryawaithsetvectorJSONstringifycreatedAtDatenowawaitsaddmemories:${sessionId}// 检索余弦相似度阈值 0.75exportasyncfunctionretrieveMemories sessionId: string, query: string, topK 3PromiseMemoryItemconstawaitembedQueryconstawaitsmembersmemories:${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 节点importMilvusfromlangchain/milvusimportOpenAIEmbeddingsfromlangchain/openaiconstnewOpenAIEmbeddingsletvectorStoreMilvusasyncfunctiongetVectorStorePromiseMilvusifawaitMilvusfromExistingCollectioncollectionNamepersonal_assistant_kburlenvMILVUS_URIreturn// 检索并过滤低分文档exportasyncfunctionretrieveDocsquery: string, topK 4PromiseRetrievedDocconstawaitgetVectorStoreconstawaitsimilaritySearchWithScorereturnfilter([, score]) 0.7map([doc, score]) contentpageContentsourcemetadatasourceunknown// Graph 节点exportasyncfunctionragRetrieveNodestate: AssistantStateTypeconstawaitretrieveDocsuserInputreturnretrievedDocs// 关键在 agent 节点里把 docs 注入 system messageexportfunctionbuildSystemPromptstate: AssistantStateTypestringlet你是一个专业的 AI 助手擅长回答各类问题。ifrelevantMemorieslength0\n\n## 关于用户的偏好\nrelevantMemoriesmap(m) - ${m.content}join\nifretrievedDocslength0\n\n## 参考知识库请优先根据以下内容回答\nretrievedDocsmap(d, i) [文档${i 1}](来源: ${d.source})\n${d.content}join\n\nreturn运行效果用户问「我们的 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 工具接入单例模式importDynamicStructuredToolfromlangchain/core/toolsimportMultiServerMCPClientfromlangchain-mcp-adaptersimportToolNodefromlangchain/langgraph/prebuiltimportfromzod// 本地工具搜索exportconstnewDynamicStructuredToolnameweb_searchdescription搜索互联网上的实时信息适合查询最新新闻、价格、天气等schemaobjectquerystringdescribe搜索关键词maxResultsnumberoptionaldefault3funcasyncconstawaitfetchhttps://api.tavily.com/search?q${encodeURIComponent(query)}limit${maxResults}headersx-api-keyenvTAVILY_API_KEYconstawaitjsonreturnresultsmap(r: any) ${r.title}\n${r.content}join\n\n// MCP 客户端单例模式服务启动时预热避免第一次请求超时let_mcpToolsanyexportasyncfunctioninitMCPToolsiflength0returnconstnewMultiServerMCPClientamaptransportstdiocommandnpxargs-yamap/mcp-server--keyenvAMAP_KEYawaitgetToolsreturn// 构建 ToolNodeLangGraph 内置自动处理 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 定义importStateGraphSTARTENDMemorySaverfromlangchain/langgraphimportChatOpenAIfromlangchain/openaiimportAIMessageSystemMessageHumanMessagefromlangchain/core/messagesimportAssistantStateAssistantStateTypefrom./stateimportfrom../memory/storeimportfrom../rag/retrieverimportfrom../tools// 路由节点判断走哪条分支生产可换成 LLM 语义路由asyncfunctionrouterNodestate: AssistantStateTypeconstuserInputtoLowerCaseifincludes文档includes知识库returnrouteToragasconstifincludes搜索includes天气includes新闻returnrouteTotoolsasconstifincludes地图includes路线includes附近returnrouteTomcpasconstreturnrouteTodirectasconst// Agent 核心节点把记忆文档注入 system绑定工具asyncfunctionagentNodestate: AssistantStateTypeconstawaitinitMCPToolsconstnewChatOpenAImodelgpt-4otemperature0.3bindToolsconstawaitinvokenewSystemMessagebuildSystemPromptmessages// 敏感工具触发 Human-in-Loopconsttool_callssome(tc) send_emaildelete_fileexecute_codeincludesnamereturnmessagesasAIMessageneedsApproval// 构建完整 GraphexportasyncfunctionbuildGraphconstnewMemorySaver// 生产换 PostgresSaverconstawaitbuildToolNodereturnnewStateGraphAssistantStateaddNodememory_retrieveaddNoderouteraddNoderag_retrieveaddNodeagentaddNodetoolsaddNodememory_saveaddEdgeSTARTmemory_retrieveaddEdgememory_retrieverouter// 路由分支rag 先检索再 agent其他直接 agentaddConditionalEdgesrouter(s) routeTodirectragrag_retrievetoolsagentmcpagentdirectagentaddEdgerag_retrieveagent// Agent → 工具调用 or 结束addConditionalEdgesagent(s) constmessagesmessageslength1asAIMessageifneedsApprovalreturnneed_approval// Human-in-Loopiftool_callslengthreturncall_toolsreturndoneneed_approvaltools// 实际生产需要 interrupt 机制call_toolstoolsdonememory_saveaddEdgetoolsagent// 工具结果回流给 agentaddEdgememory_saveENDcompile运行效果用户发送「帮我查一下公司 API 文档里关于鉴权的部分」Graph 执行路径为memory_retrieve检索到用户偏好 TypeScript→router识别为 rag→rag_retrieveMilvus 检索到3篇相关文档→agent注入记忆文档LLM 生成回答→memory_save记录本次提问→END。全程约 2.3s用户只感受到一次流式输出。07 HTTP 接口普通对话 流式输出都支持// src/server.ts —— Express SSE 流式输出importfromexpressimportfrom./graphimportHumanMessagefromlangchain/core/messagesconstexpressusejsonletgraphAwaitedReturnTypetypeofasyncfunctioninitawaitbuildGraphlisten3000() consolelogPersonalAssistant :3000// 普通对话post/chatasyncconstdefaultbodyconstawaitinvokeuserInputmessagesnewHumanMessageconfigurablethread_idconstmessagesmessageslength1jsonreplycontent// 流式输出SSEpost/chat/streamasyncconstdefaultbodysetHeaderContent-Typetext/event-streamsetHeaderCache-Controlno-cacheconstawaitstreamuserInputmessagesnewHumanMessageconfigurablethread_idstreamModemessagesforawaitconstofifcontentwritedata: ${JSON.stringify({ content: msg.content, node: meta.langgraph_node })}\n\nwritedata: [DONE]\n\nendinit运行效果/chat接口适合单次问答等待完整回答再返回延迟约 2-5s/chat/stream用 SSE 推送首 token 延迟约 300ms用户看到打字机效果长回答体验明显更好。前端监听data: [DONE]事件关闭连接。08 常见坑整合时最容易栽的 6 个地方坑现象根因解法Checkpoint 混串A 用户看到 B 用户的对话thread_id没有按用户隔离sessionId来自鉴权不信任请求 bodyMemory 失效存了记忆下次不认识用户相似度阈值太高先调低到 0.6确认能检索到再调高工具返回丢失工具调用成功LLM 没用结果ToolMessage 没有回流到 agent确认tools → agent边存在messages 是 Reducer 模式RAG 没被用上检索到文档LLM 回答仍靠自身知识System Message 没明确指示加「请优先根据以下文档回答」的明确指令MCP 初始化慢第一次请求超时每次请求都重新握手单例模式服务启动时预热Human-in-Loop 卡死需审批的请求一直 pendingresume 逻辑没实现用graph.updateState 二次invoke的 resume 流程坑1Checkpoint 混串是生产事故高发区。MemorySaver的thread_id就是隔离单元如果来自请求 body任何用户都可以传别人的 sessionId看到别人的对话历史。// 正确做法sessionId 来自鉴权不信任用户输入post/chatasyncconst${req.user.id}:${req.body.conversationId ?? main}// ...运行效果上面这 6 个坑全部是生产事故高发区。其中最危险的是「Checkpoint 混串」上线后如果 sessionId 来自用户请求 body任何用户只需猜到别人的 sessionId 就能看到对话历史属于严重数据泄露。09 生产部署从能跑到能用的最后一公里// 生产 Checkpoint把 MemorySaver 换成 PostgresSaver重启不丢数据// npm install langchain/langgraph-checkpoint-postgresimportPostgresSaverfromlangchain/langgraph-checkpoint-postgresconstPostgresSaverfromConnStringenvDATABASE_URLawaitsetup// 自动建表// PM2 守护进程// pm2 start dist/server.js --name personal-assistant --max-memory-restart 512M生产部署清单Checkpoint 换成PostgresSaver或RedisSaverMemorySaver重启即丢Redis 开持久化appendonly yesMilvus 数据卷挂载到宿主机所有 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%免费】
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2579856.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!