尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零搭建大模型应用底座:RAG、记忆、API与MCP全链路实战

从零搭建大模型应用底座:RAG、记忆、API与MCP全链路实战 1. 从零搭建大模型应用底座为什么RAG、记忆、API和MCP缺一不可过去一年我经手了六七个大模型落地项目从最开始的“调个API就完事”到后来被各种问题按在地上摩擦踩过的坑比写过的代码还多。最典型的一个场景是业务方要求做一个内部知识问答助手我第一反应是“这不就是RAG吗向量库一搭、文档一灌、检索一拼收工”。结果上线第一周就被打脸——用户问“我上周提的那个采购申请现在到哪一步了”系统一脸茫然因为它根本不知道“上周”和“我”指的是什么。这就是典型的只有RAG没有记忆的后果。后来我逐步把整个架构补齐形成了现在这套相对稳定的组合RAG负责知识检索记忆模块负责会话状态API层负责统一调度和对外服务MCP负责工具调用和外部系统对接鉴权审计负责安全兜底。这五个部分不是拍脑袋凑出来的而是被真实需求一步步逼出来的。这篇文章我就把这套架构的搭建过程完整拆一遍包括每个模块为什么这么设计、参数怎么定、代码怎么写、坑在哪里。适合已经了解大模型基本调用、准备做生产级应用的同学参考纯小白也能看懂思路因为我会尽量用生活化的类比来解释。先说清楚这套东西解决什么问题。你可以把大模型想象成一个很聪明但记性极差的顾问他读过很多书预训练知识但你问他你们公司上个月的销售数据他不知道你跟他聊了半小时他转头就忘了你叫什么你让他帮你查一下库存系统他没有手也没有账号。RAG就是给他配了一个资料室记忆就是给他配了一个笔记本API就是给他配了一个前台接待MCP就是给他配了一双手去操作各种系统鉴权审计就是给他配了一个合规监督员。缺了任何一个这个顾问都没法真正干活。整套架构的核心思路是分层解耦。RAG、记忆、工具调用各自独立通过API层统一编排。这样做的好处是每个模块可以单独替换和升级——比如今天用某个向量库明天想换另一个只要接口不变上层逻辑不用动。MCP的引入则是为了解决工具调用的标准化问题以前每接一个外部系统就要写一套适配代码现在通过MCP协议统一描述工具能力大模型自己就能理解怎么调用。注意不要一上来就追求大而全。我见过太多项目在POC阶段就引入五六个组件结果调试成本爆炸。建议先用RAGAPI跑通最小闭环再逐步加记忆和MCP。2. RAG知识库搭建从文档灌入到检索调优的完整实操2.1 文档处理与切分策略的取舍RAG的第一步永远是把文档变成向量库能存的东西。这一步看起来简单实际上决定了整个系统检索质量的上限。我试过直接把整篇PDF塞进去也试过按固定字数硬切最后发现效果最好的是按语义结构切分重叠窗口的组合策略。具体做法是这样的先用文档解析工具把PDF、Word、Markdown等格式统一转成纯文本保留标题层级信息。然后按标题层级做一级切分比如一个二级标题下的内容作为一个大块。如果某个块超过800个token再按段落做二级切分每个段落块控制在300到500个token之间相邻块之间保留50到80个token的重叠。为什么要重叠因为很多问题的答案跨在段落边界上不重叠的话检索出来只有半截大模型补全的时候容易编。# 文档切分核心逻辑示意 def split_document(text, max_tokens500, overlap60): paragraphs text.split(\n\n) chunks [] current_chunk for para in paragraphs: if count_tokens(current_chunk para) max_tokens: chunks.append(current_chunk.strip()) # 保留尾部作为下一个块的开头实现重叠 current_chunk current_chunk[-overlap:] para else: current_chunk \n\n para if current_chunk.strip(): chunks.append(current_chunk.strip()) return chunks这里有个经验值max_tokens不要超过500。我实测过块太大检索精度会明显下降因为一个块里信息太杂向量表示会被稀释。但也不能太小小于200token的块往往语义不完整检索出来大模型看不懂。500左右是个比较稳的平衡点。另外关于“RAG知识库能不能存图片”这个问题答案是能但要看怎么存。常见做法是用多模态嵌入模型把图片转成向量检索到图片后把图片URL或base64传给多模态大模型。但这条路成本高、链路长我一般建议先把图片里的文字用OCR提取出来走文本RAG图片本身作为附件链接附在文本块后面。这样性价比最高。2.2 向量库选型与检索参数调优向量库的选择上我踩过的坑主要集中在“一开始用重型方案后来发现根本用不上”。如果你数据量在百万级以下我强烈建议先用轻量方案跑通比如基于本地文件的向量存储或者单机版向量数据库。数据量上来了再考虑分布式方案。检索参数里最关键的是top_k和相似度阈值。top_k决定召回多少条候选阈值决定低于多少分的直接丢弃。我的经验是top_k设在5到8之间阈值设在0.7左右余弦相似度。top_k太小容易漏太大容易引入噪声干扰大模型判断。阈值太低会召回一堆不相关的太高又可能把勉强相关的也过滤掉。还有一个容易被忽略的点是混合检索。纯向量检索对语义相似但字面不匹配的情况处理得好但对精确匹配比如产品编号、人名反而容易翻车。我的做法是向量检索和关键词检索各跑一遍然后用加权融合排序。权重一般设向量0.7、关键词0.3具体根据业务调。参数建议值说明chunk_size300-500 token太大检索精度下降太小语义不完整chunk_overlap50-80 token防止答案跨边界被截断top_k5-8候选太多引入噪声相似度阈值0.7左右低于此值的结果直接丢弃向量权重0.7混合检索中向量检索的权重关键词权重0.3混合检索中关键词检索的权重2.3 RAG瓶颈的真实表现与应对RAG的瓶颈我总结为三类检索不到、检索到但排得靠后、检索到了但大模型不用。第一类通常是切分或嵌入模型的问题解决办法是换嵌入模型或者调整切分粒度。第二类是排序问题可以加一个重排序模型把最相关的顶到前面。第三类最隐蔽往往是因为提示词里没有明确要求“基于以下资料回答”大模型自己发挥去了。实操心得每次调整RAG参数后一定要用同一批测试问题跑一遍对比。我一般准备30到50个覆盖不同场景的问题记录每次的命中率和回答质量用数据说话而不是凭感觉。3. 记忆模块设计让大模型记住“你是谁”和“你刚才说了什么”3.1 短期记忆与长期记忆的分层记忆模块的核心问题是记什么、记多久、怎么取。我的方案是分两层短期记忆存当前会话的完整对话历史长期记忆存跨会话的关键信息摘要。短期记忆比较简单就是维护一个消息列表每次请求把最近N轮对话带上。N一般设5到10轮太多会撑爆上下文窗口太少又记不住。这里有个技巧不是简单截断而是对早期对话做摘要压缩。比如超过10轮后把最早5轮压缩成一段摘要保留关键信息这样既省token又不丢上下文。长期记忆就复杂一些。我的做法是在每轮对话结束后用大模型提取本轮的关键信息用户身份、偏好、重要事实存到一个结构化的记忆库里。下次用户来的时候先根据用户ID检索相关记忆拼到系统提示词里。这样即使用户隔了一周再来系统也能记得他上次说过什么。# 记忆提取与存储示意 def extract_memory(user_input, assistant_reply): prompt f从以下对话中提取需要长期记住的关键信息 以JSON格式返回字段包括user_fact, preference, important_event。 如果没有值得记住的信息返回空JSON。 用户{user_input} 助手{assistant_reply} memory call_llm(prompt) return parse_json(memory) def build_context(user_id, current_query): short_term get_recent_messages(user_id, limit10) long_term retrieve_memories(user_id, current_query, top_k3) return short_term, long_term3.2 记忆检索的触发时机与注入方式记忆检索不是每轮都做那样太浪费。我的策略是按需触发当用户输入里包含“上次”“之前”“我记得”这类词或者用户ID对应的长期记忆库非空且当前话题与历史记忆有语义关联时才去检索长期记忆。注入方式也有讲究。长期记忆不能直接拼在对话历史里那样大模型会分不清哪些是当前对话哪些是历史记忆。我的做法是放在系统提示词的一个独立区块里明确标注“以下是关于该用户的历史信息供参考”。这样大模型知道这是背景知识不会跟当前对话混淆。注意记忆模块最容易出的问题是“记了不该记的”。比如用户随口说了一句“今天天气不错”系统当成偏好存下来了。所以提取记忆的提示词里一定要加约束只提取对后续服务有实际价值的信息。3.3 记忆与RAG的协同记忆和RAG看起来都是“给大模型补充信息”但定位完全不同。RAG补的是通用知识记忆补的是个性化信息。两者在上下文里的位置应该分开RAG检索结果放在“参考资料”区块记忆放在“用户背景”区块当前对话放在消息列表里。这样大模型能清楚区分信息来源回答时也会更准确。我遇到过两者冲突的情况RAG检索到的文档说某个政策是A但用户上次说自己适用的是B。这时候我的处理原则是记忆优先于RAG因为个性化信息的时效性和针对性更强。但这个规则要在提示词里写清楚否则大模型自己会纠结。4. API层与MCP工具链统一调度与标准化工具调用4.1 API层的职责与设计原则API层是整个系统的门面对外提供统一的问答接口对内负责编排RAG、记忆和工具调用。我设计API层时遵循三个原则入参极简、出参结构化、错误可追溯。入参一般就三个用户ID、会话ID、用户输入。其他所有东西——用哪个知识库、要不要调工具、记忆检索几条——都由API层内部根据配置和上下文决定。这样前端接入成本极低换业务场景也不用改接口。出参我坚持用结构化格式包含回答内容、引用来源、工具调用记录、耗时统计。引用来源很重要用户看到答案的同时能看到依据信任度完全不一样。工具调用记录则是审计的基础。# API层核心编排逻辑示意 async def chat(user_id, session_id, query): # 1. 获取短期记忆 history get_short_term_memory(session_id) # 2. 按需检索长期记忆 memories retrieve_long_term_memory(user_id, query) # 3. RAG检索 rag_results retrieve_knowledge(query, top_k5) # 4. 判断是否需要工具调用 tool_calls plan_tools(query, available_tools) # 5. 组装上下文调用大模型 response await call_llm( system_promptbuild_system_prompt(memories, rag_results), messageshistory [{role: user, content: query}], toolstool_calls ) # 6. 处理工具调用结果 if response.has_tool_call: tool_result execute_tool(response.tool_call) response await call_llm_with_tool_result(response, tool_result) # 7. 更新记忆 update_memory(session_id, user_id, query, response.content) # 8. 记录审计日志 audit_log(user_id, session_id, query, response, tool_calls) return response4.2 MCP协议的核心价值与接入方式MCP解决的是一个很实际的问题大模型怎么知道有哪些工具可用、每个工具怎么调。在没有MCP之前每接一个外部系统都要写一套适配代码工具描述格式五花八门大模型经常调错参数。MCP把这些标准化了工具提供方按MCP协议暴露能力大模型按统一格式调用。我接入MCP的流程一般是这样的先确认目标系统有没有现成的MCP Server有就直接连没有就自己写一个MCP Server包装原有API。MCP Server的核心是描述工具的名称、功能、参数 schema大模型根据这些描述决定调不调、怎么调。{ name: query_inventory, description: 查询指定商品的库存数量, parameters: { type: object, properties: { product_id: { type: string, description: 商品编号 }, warehouse: { type: string, description: 仓库名称可选 } }, required: [product_id] } }这里有个关键点工具描述的质量直接决定大模型调用的准确率。描述要写清楚工具做什么、什么场景下用、参数什么含义。我见过因为描述写得太模糊导致大模型乱调工具的情况改完描述后准确率从60%提到90%以上。4.3 工具调用的编排与降级策略工具调用不是越多越好。我的原则是能不用工具就不用必须用的时候确保一次调对。每次请求最多允许调2到3个工具多了容易乱。而且工具调用要有超时和降级超时了返回“暂时无法获取实时数据”而不是让整个请求挂掉。还有一个容易忽略的点是工具调用的幂等性。查询类工具无所谓但写入类工具比如提交工单一定要做幂等防止大模型重复调用导致重复提交。我的做法是在工具层加一个请求ID相同请求ID的重复调用直接返回上次结果。实操心得MCP工具刚接入时建议先用测试环境跑一批典型问题观察大模型的调用决策是否符合预期。我一般会准备20个“该调工具”和20个“不该调工具”的问题做对比测试。5. 鉴权审计安全兜底与全链路可追溯5.1 鉴权体系的分层设计鉴权这块我吃过亏。早期项目为了图快API直接裸奔结果被外部扫到接口一天跑了上万次调用。后来痛定思痛把鉴权做成了三层接入层鉴权、用户层鉴权、工具层鉴权。接入层鉴权解决“谁能调这个API”的问题用API Key或者Token机制。用户层鉴权解决“这个用户能访问哪些数据”的问题通过用户ID关联权限配置。工具层鉴权解决“这个用户能调哪些工具”的问题不同角色的用户可用工具集不同。# 三层鉴权检查示意 def check_auth(api_key, user_id, tool_name): # 第一层API Key有效性 if not validate_api_key(api_key): raise AuthError(无效的API Key) # 第二层用户数据权限 user_perms get_user_permissions(user_id) if not user_perms: raise AuthError(用户无权限) # 第三层工具调用权限 if tool_name and tool_name not in user_perms.allowed_tools: raise AuthError(f用户无权调用工具 {tool_name}) return True关于“unexpected status 401 unauthorized: incorrect api key provided”这类报错我遇到太多次了。绝大多数情况是三个原因Key复制时带了空格、Key对应的账户余额不足被停用、Key的环境变量没加载上。排查顺序就是先检查Key字符串本身再检查账户状态最后检查环境变量注入。5.2 审计日志的设计与落库审计日志不是简单记个流水而是要能回答四个问题谁、什么时候、问了什么、系统做了什么。我的日志结构包含用户ID、会话ID、请求时间、用户输入、RAG检索结果摘要、记忆检索结果、工具调用记录、大模型原始输出、最终返回内容、耗时、Token消耗。这些信息落库时要注意脱敏。用户输入里可能包含手机号、身份证号等敏感信息落库前要做脱敏处理。工具调用记录里可能包含内部系统地址也要过滤。我的做法是写一个脱敏函数在日志落库前统一过一遍。审计字段是否脱敏用途用户ID否追溯用户行为用户输入是分析问题类型脱敏后存储RAG结果摘要否分析检索质量工具调用记录部分内部地址脱敏调用参数保留大模型输出是质量分析脱敏后存储Token消耗否成本核算5.3 异常检测与告警审计日志不只是事后查还要能实时告警。我设了几个告警规则单用户短时间内请求频率异常、工具调用失败率突增、大模型输出包含敏感词、Token消耗突增。触发告警后自动降级或限流防止问题扩大。注意审计日志的存储周期要根据合规要求定。一般建议至少保留6个月重要业务建议保留1年以上。存储时做好冷热分离近期日志放热存储方便查询历史日志归档到冷存储降低成本。6. 常见问题排查与实战避坑指南6.1 上下文长度超限的应对“api error: 400 this models maximum context length is 1048576 tokens”这个报错我见过好几次。虽然现在很多模型上下文窗口很大但RAG检索结果记忆对话历史工具描述加起来很容易超。我的应对策略是动态裁剪优先保留最近的对话历史和RAG检索结果长期记忆和早期对话按相关性排序后截断。具体做法是给每个部分设一个token预算对话历史不超过40%RAG结果不超过30%记忆不超过15%工具描述不超过10%留5%给系统提示词和输出。超了就按预算裁剪裁剪时优先丢低相关性的内容。6.2 工具调用失败的排查路径工具调用失败一般分三类大模型没调、调了但参数错、调了但执行失败。排查路径是先看审计日志里大模型有没有发起工具调用没有的话检查工具描述是否清晰有调用但参数错的话检查参数schema定义执行失败的话看工具服务本身的日志。我整理了一个速查表现象可能原因排查方法大模型不调工具工具描述不清晰检查description是否说明使用场景参数格式错误schema定义不严谨检查required和type定义工具执行超时下游服务慢检查工具服务响应时间重复调用缺少幂等控制加请求ID去重权限报错工具层鉴权未通过检查用户工具权限配置6.3 RAG检索质量差的调优顺序RAG效果不好时不要盲目调参。我的调优顺序是先看切分、再看嵌入模型、然后看检索参数、最后看提示词。切分问题占我遇到问题的六成以上很多情况下把切分粒度调对效果立刻上来。嵌入模型的问题占两成换一个更适合中文或多语言的模型就能解决。检索参数和提示词各占一成。6.4 记忆模块的常见坑记忆模块最容易出的问题是“记忆污染”错误的信息被存进长期记忆后续每次对话都被带偏。我的做法是长期记忆写入前加一道校验用大模型判断这条信息是否值得记住、是否准确。另外记忆要有过期机制超过一定时间没被检索到的记忆自动降权或清除。还有一个坑是记忆检索的延迟。如果每次对话都去检索长期记忆响应时间会明显增加。我的优化是加一层缓存用户最近检索过的记忆缓存在内存里下次直接命中。7. 整套架构的部署与扩展建议7.1 部署架构与资源规划这套架构我一般拆成三个服务部署API服务、RAG服务、工具服务。API服务无状态可以水平扩展。RAG服务包含向量库和嵌入模型资源消耗大单独部署。工具服务按需部署每个MCP Server可以独立进程。资源规划上嵌入模型如果本地部署建议至少16G显存。向量库内存占用跟数据量成正比百万级向量大概需要4到8G内存。API服务本身很轻2核4G就够。大模型调用如果走外部API不需要本地GPU如果本地部署那又是另一套资源规划了。7.2 性能优化的几个关键点性能优化我主要做三件事缓存、异步、批处理。缓存包括嵌入结果缓存、RAG检索结果缓存、记忆缓存。异步主要是工具调用和RAG检索并行执行不要串行等。批处理主要是嵌入计算多个文档块一起算比一个个算快很多。还有一个容易忽略的点是连接池。向量库、数据库、外部API都要配连接池否则高并发下连接建立的开销会拖垮响应时间。7.3 后续扩展方向这套架构后续可以往几个方向扩展多模态RAG支持图片和音频检索Agentic RAG让大模型自己决定检索策略GraphRAG引入知识图谱增强推理能力。但我的建议是不要为了追新而追新先把当前架构跑稳有明确需求再扩展。我在实际项目中的体会是这套架构的价值不在于每个模块多先进而在于它们之间的配合是否顺畅。RAG检索准、记忆记得住、工具调得对、鉴权审计算得清这四件事做好大模型应用就能真正落地干活而不是停留在演示阶段。最后分享一个小技巧每次上线新功能前用一批历史问题做回归测试对比新旧版本的命中率和响应质量用数据决定是否发布比拍脑袋靠谱得多。
返回列表