
先看一个现象很多技术团队拿到大模型项目时第一反应都是“把公司文档、知识库、数据库接进 LLM”。这个动作通常一周内就能跑通 Demo——上传 PDF做向量化问它“报销流程是什么”它能答出来于是老板觉得离上线不远了。然后从 Demo 到生产一拖就是两三个月问题一个接一个回答经常引用错误来源、旧数据一直更新不及时、不同员工的权限没有隔离、评测集没建好根本不敢灰度、线上成本又超出预算。真正做过 LLM 应用的人都会承认一个略显刺耳的判断把 LLM 连接到数据只是整个工程里最显眼、也相对容易完成的 21%。剩下的 79%不在“连接”这一步而在连接之后的数据质量、评测、安全、成本和产品化迭代。这篇文章会先讲清楚为什么“连接数据”只是 21% 的解法然后带你完整走一遍 RAG 的链路和最小代码示例最后把生产级 LLM 应用真正会卡住你的那 79% 逐项拆开。如果你正准备给公司业务接入大模型而不是只做一个能演示的聊天框这篇文章应该能帮你少走一大段弯路。1. 如何理解“21% 的解法”LLM 应用有一个非常迷惑人的特点入口太简单。调用一个 Chat API把用户问题丢过去它就回答得像模像样。也正是因为入口简单很多团队把“模型不认识我的私有数据”当成了最关键的问题认为只要通过向量数据库把文档接进去一切就解决了。但如果你把一次完整 LLM 应用交付拆开来看就会发现“连接数据”只覆盖了这样几个环节把文档拆成片段、把片段转成向量、把向量存入数据库、检索时把相关片段拼进提示词。这些环节加起来大约是全部工作的 21%。这 21% 解决的是“模型不知道你公司知识”的输入侧问题它不会自动解决“答案是否准确”“数据是否越权”“上线后是否稳定”“成本是否可控”“用户是否愿意用”等一系列输出侧和工程侧问题。为什么是“21%”而不是一个精确数字因为它想说明一个结构性问题连接数据的动作是外显的、可演示的而衡量答案好坏、把流程做成产品是内隐的、需要反复迭代的。很多团队把 80% 的精力投在了外显的 21% 上反复调 embeddings、换向量数据库、改分块大小却迟迟没有建立评测集没有设计权限边界没有考虑数据更新最后系统上线时依然不靠谱。所以这篇文章想传递的第一条判断是不要被“连接”的快感迷惑连接只是入口不是终局。2. 连接数据的主流方案对比RAG、长上下文、微调与 Agent 工具调用在动手写代码之前先把“连接数据”这件事放到更大的坐标里看。目前让 LLM 使用私有数据有四种主流路径它们的成本和适用场景完全不同。方案核心原理适合场景主要局限RAG检索增强生成先从知识库检索相关片段再把片段拼入提示词让模型回答知识库问答、企业内部文档、动态更新的数据检索质量直接影响回答质量链路较长长上下文窗口用足够长的上下文直接把整份文档塞给模型单篇长文档分析、少量资料总结成本随 token 线性增长超出窗口会截断不适合海量知识微调用标注数据更新模型权重让模型学会某种风格或固定知识风格对齐、特定任务格式化、私有术语理解不是知识的可靠载体容易过拟合或遗忘更新成本高Agent 工具调用模型通过函数调用或 MCP 协议按需访问外部 API、数据库、搜索需要实时数据、多步操作、动态决策需要可靠的调用链和严格的权限校验失败恢复复杂在实际项目里这几条路径并非互斥。最典型的组合是RAG 负责静态文档问答工具调用负责查询实时业务数据和触发动作微调只用来做输出格式和语气的调整。很多团队一上来就想微调却发现模型既不能记住所有新知识也无法保证回答引用真实来源最后绕了一圈还是回到 RAG。这背后的原因很简单微调是在改模型的“行为习惯”而不是给模型装一个“可检索的硬盘”。RAG 之所以成为主流不是因为它最先进而是因为它把“知道”和“回答”分离了。模型负责推理和组织语言知识库负责提供事实两者解耦之后数据更新不需要重新训练来源引用也能被审计。这个设计模式正好呼应前面说的 21%——连接数据是一种架构选择而架构选择解决的是“能不能持续维护”的问题不是“一次回答对不对”的问题。3. RAG 完整链路拆解从文档到答案要真正理解 RAG不能只把它当成“向量数据库 提示词”。一套完整的 RAG 链路包含三层每一层都会影响最终答案的质量。3.1 数据准备层这一层做的是把原始文档变成可供检索的片段。过程包括格式解析、清洗噪声、分块、元数据标注。分块看起来简单却是影响检索质量最直接的因素之一。如果块太大一个片段里混杂多个主题向量检索时匹配精度降低如果块太小语义信息不完整召回的内容可能支离破碎。常见做法是设置 500 到 1000 个字符的块大小并保留 10% 到 20% 的块重叠避免一个完整语义被生硬切断。好的分块还会同步保存来源页码、章节、标题等元数据方便后续引用溯源。3.2 检索层检索层负责从片段集合里找出与用户问题最相关的若干片段。最简单的实现是向量相似度检索把用户问题编码成向量在向量数据库中用余弦相似度或内积找到最接近的片段。但纯向量检索有一个典型弱点它擅长语义相近的匹配却对精确关键词、编号、产品型号、人名等实体不够敏感。所以生产级系统通常采用混合检索把向量检索与 BM25 等关键词检索结合起来用权重融合两类结果再用重排序模型精排。3.3 生成层生成层把检索到的片段按提示词模板组装成上下文交给 LLM 输出答案。这里有两个关键点。第一提示词必须明确约束模型“只依据资料回答”否则模型仍然可能脑补。第二答案需要带上引用来源比如“根据产品手册第 3 章”这样用户能自行核对也能降低幻觉的负面影响。生成层还要考虑回答风格、长度、多轮对话历史等产品因素但它只负责“写得好不好”无法补救检索层“根本没找到”的问题。把 RAG 三层放在一起看能得出一个更清晰的结论RAG 的最终质量 数据准备质量 × 检索质量 × 生成质量。三者是乘法关系任何一层为零结果都是零。这也是为什么“把数据接进去”只是 21% 的重要原因——连接只是把三层链路搭通了三层各自的工程质量才是那真正决定成败的 79%。4. 最小端到端示例本地产跑通一个 RAG下面用一个最小示例演示 RAG 完整链路。为了让流程更直观这里统一使用 LangChain 作为应用编排框架向量库使用 Chroma模型接口使用 OpenAI 兼容接口你可以在不改变核心逻辑的前提下替换为本地模型或其他向量库。4.1 环境准备建议使用 Python 3.10 或更高版本安装以下依赖pip install langchain langchain-openai langchain-community chromadb pypdf openai如果要用本地嵌入模型可以考虑安装sentence-transformers并把后面的 embeddings 初始化替换为HuggingFaceEmbeddings。本文示例以 OpenAI 嵌入接口为例关键在理解流程不限定具体供应商。4.2 加载文档与分块假设我们有一份产品手册产品手册.pdf放在项目的data目录下。第一步是把 PDF 里的文本解析出来并按语义切分。# 文件路径rag_demo/01_split.py from langchain_community.document_loaders import PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader PyPDFLoader(./data/产品手册.pdf) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap100, separators[\n\n, \n, 。, , , , , ], ) chunks splitter.split_documents(documents) print(f共切分 {len(chunks)} 个片段) for i, chunk in enumerate(chunks[:3]): print(f\n--- 片段 {i 1} ---) print(chunk.page_content[:200])这里RecursiveCharacterTextSplitter会优先按段落、句子等自然边界切分尽量避免把一句话切断。分块大小需要根据文档类型和问题粒度调整后面第 5 节会展开讨论。4.3 生成嵌入并写入向量库把切分好的片段通过嵌入模型转成向量写入向量数据库。写入成功后后续查询就可以直接调用向量检索接口。# 文件路径rag_demo/02_build_index.py from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma from rag_demo import chunks # 复用上一步的分块结果 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./data/chroma_db, ) print(f已写入 {vectorstore._collection.count()} 条向量记录)如果你的 Chroma 版本较新persist_directory的写法可能会被标记为弃用请参考当前版本文档改用PersistentClient初始化。这里的关键不是某个 API 细节而是“先有嵌入再有向量库”的固定流程。4.4 检索并生成回答下面把检索器和生成提示词组合成一条完整的问答链路。# 文件路径rag_demo/03_qa_chain.py from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.vectorstores import Chroma from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma( persist_directory./data/chroma_db, embedding_functionembeddings, ) retriever vectorstore.as_retriever(search_kwargs{k: 4}) llm ChatOpenAI(modelgpt-4o-mini, temperature0.2) prompt ChatPromptTemplate.from_template( 你是一个技术支持助手。请根据以下资料回答用户问题。 【资料】 {context} 【问题】 {question} 回答要求 1. 只使用上述资料回答不要编造事实。 2. 如果资料中没有答案请明确说“资料中没有找到相关信息”。 3. 每个关键结论后标注来源片段编号例如[片段1]。 ) def format_docs(chunks): return \n\n.join( f[片段{i 1}] {chunk.page_content} for i, chunk in enumerate(chunks) ) chain ( { context: lambda x: format_docs(retriever.invoke(x[question])), question: lambda x: x[question], } | prompt | llm | StrOutputParser() ) answer chain.invoke({question: 这个产品支持哪些部署方式}) print(answer)这段代码里最关键的是retriever.invoke那一步它决定了模型能看到哪些上下文。如果这一步没有检索到正确答案后面提示词写得再好也无法补救。这也是调试 RAG 时优先检查检索结果的原因。4.5 怎么判断跑通运行03_qa_chain.py后观察输出是否包含来自文档的真实信息并且标注了[片段x]来源。为了确认系统不是靠模型记忆回答你可以故意问一个资料里不存在的细节比如“支持哪些完全没有提到过的功能”理想的回答是给出“资料中没有找到相关信息”的拒答。如果模型仍然编造了答案说明提示词约束不够或者检索结果干扰项太多需要继续调整。5. 检索质量才是真正的分水岭代码跑通之后麻烦才真正开始。从“能回答”到“每次都回答得对”中间隔着检索质量这道分水岭。下面几个方向是生产项目中最常遇到的问题。5.1 分块策略需要实验而不是抄参数很多团队照搬社区里的chunk_size500却没有考虑自己文档的特点。代码文档、法律合同、客服工单、产品 FAQ它们的语义粒度差别极大。一个比较务实的做法是准备一个几十条的测试问题集分别用不同分块参数跑检索观察“是否召回正确答案”。分块参数调整之后还要重新验证嵌入效果因为块的大小变化会直接影响向量相似度计算的语义单位。5.2 混合检索解决“精确匹配”盲区向量检索擅长语义相似但遇到产品编号、合同编号、异常码这类精确信息时往往不如关键词检索稳定。LangChain 提供EnsembleRetriever可以很方便地融合 BM25 和向量检索。# 文件路径rag_demo/04_hybrid_retriever.py from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore Chroma( persist_directory./data/chroma_db, embedding_functionembeddings, ) vector_retriever vectorstore.as_retriever(search_kwargs{k: 6}) bm25_retriever BM25Retriever.from_documents(chunks) bm25_retriever.k 4 ensemble EnsembleRetriever( retrievers[bm25_retriever, vector_retriever], weights[0.3, 0.7], ) docs ensemble.invoke(产品故障码 E204 表示什么) for doc in docs: print(doc.page_content[:150])混合检索的权重需要调试。通常在新的知识域里关键词检索权重可以适当提高因为命名实体和术语本身是强信号。5.3 重排序、权限过滤与引证从向量库召回 10 到 20 个候选片段之后可以再用一个重排序模型精排把最相关的 3 到 5 个片段送入生成层。重排序会增加一点延迟但能明显提升答案质量。更关键的是权限控制。如果系统里同时存在不同级别的文档检索时必须按当前用户的权限过滤而不是把所有内容都送入模型。否则 LLM 可能会把某位员工不应看到的薪资信息、内部评审意见直接答出来这属于生产事故级别的漏洞。引证能力则依赖元数据写入向量库时保留来源页码、文档标题、更新时间生成提示词时强制模型带上引用才可能做成可信的解释型问答系统。6. 剩下 79% 是什么生产级 LLM 应用工程清单到这里可以正式回答标题里的问题了。连接数据只是 21%接下来这些工作才是大头。6.1 评测闭环没有评测就没有迭代所有 RAG 调优都必须建立在评测集之上。评测集不一定要很大但必须覆盖真实用户的高频问题、边界问题和拒答问题。每次修改分块参数、切换嵌入模型、调整提示词都用同一套评测集跑一遍记录检索命中率和答案正确率。否则你会陷入“调了这个东西感觉效果好一点”的主观幻觉。成熟的团队会把这个过程变成自动化回归提交代码后自动跑一批案例比较答案质量指标出现明显回退就阻止合并。6.2 数据治理与安全权限连接数据之前先想清楚数据从哪来、是否脱敏、谁能看。一个常见的架构是把知识库按数据源分库在检索入口处注入用户身份通过元数据过滤器限制可检索范围。不要等到出了问题才补权限。另一个容易忽略的是 Prompt 注入知识库里的文档可能被人写入“忽略以上指令”之类的文本从而操纵模型行为。解决思路包括对上传文档做内容检测、在提示词中增加隔离边界、对执行敏感动作的 Agent 做二次确认。6.3 成本、延迟与推理精度LLM 应用的成本大头通常不在向量库而在生成模型的 token 消耗。常见的优化方向包括用路由把简单问题分给轻量模型对高频问题做语义缓存控制检索片段数量避免上下文过长在推理精度上根据模型和硬件选择合适的浮点格式。社区里讨论较多的fp16、fp32、bf16精度选择本质上是在回答质量、显存占用和推理速度之间做权衡fp32精度最高但占用和延迟大fp16能压缩一半显存bf16在数值稳定性上对抗溢出的表现更好。如果你只是调用云端 API这些精度问题由服务方处理如果你在本地部署模型就需要结合显卡规格做实测选型不要盲目追求更高精度。6.4 编排框架要不要用、怎么用LangChain、LlamaIndex 这类框架可以让你快速铺出完整链路但很多团队在深入使用后都感到它抽象层次太多出了问题不好排查。我的建议是早期用它快速验证中期把核心链路用原生代码重写让它变成你能完全掌控的管道。尤其是检索和提示词拼接这两处值得保留在业务代码里方便打印日志、做降级、加权限过滤。“LLM 应用为什么需要编排框架”这个问题的真实答案不是因为它能解决所有问题而是因为它能帮你把多个组件的连接方式标准化减少胶水代码。当你发现框架里的黑盒开始阻碍你排查问题时就是拆掉框架的时机。6.5 Agent 化与 MCP连接方式正在变化单纯的“读文档”连接只是单向的越来越多的生产场景需要模型主动调用工具比如查询订单系统、提交工单、访问数据库。微软、OpenAI、Anthropic 等厂商都在推进标准化工具连接协议MCPModel Context Protocol就是其中之一。MCP 的核心思想是把工具调用接口标准化模型通过一个统一的客户端协议连接不同的数据源和工具而不是为每个系统写一遍专用集成。这意味着“连接数据”的定义正在从“把文档向量化”扩展到“让模型能安全地访问实时系统和执行动作”。工具调用极大提升了 Agent 的能力边界但也把权限校验、调用审计、失败恢复的复杂度翻倍。一定要在 Agent 化之前先把 RAG 的评测和监控底座打好否则你只是在搭建一个更难排查的结论机器。6.6 可观测性与更新机制LLM 应用的可观测性比传统后端复杂得多因为你不仅要看系统是否报错还要看回答质量、检索命中率、用户反馈。建议至少记录以下内容用户问题、检索到的片段 ID、送入模型的完整提示词、模型输出、耗时和 token 数。有了这些日志你才能在用户投诉“答错了”时还原现场。数据更新机制同样关键知识库不能永远只靠一次性脚本写入。线上系统要考虑定时抽取增量文档、删除下线文档、检测文档版本变化并在检索结果中体现数据更新时间避免模型拿着过期信息回答当天的业务问题。这 79% 的工作不会像“连接数据”那样让人获得即时的成就感但每一项都在决定系统能否长期稳定运行。7. 常见问题与排查思路问题现象可能原因排查方式解决方案回答内容与文档不符检索未召回正确答案打印检索到的片段检查问题与片段相似度调整分块大小切换嵌入模型引入混合检索模型仍然编造答案提示词约束不够或检索结果干扰项太多检查送入模型的上下文是否包含正确答案强化“只依据资料回答”的指令增加拒答策略缩小检索范围问题包含产品编号查询不到向量检索对精确关键词不敏感查看检索结果中是否出现目标编号使用 BM25 关键词检索或做混合检索回答有正确答案但来源错误多个相似文档片段混在一起检查元数据是否完整重排序是否将正确片段排后完善元数据增加引证机制用重排序模型精排不同用户看到相同答案检索时未做权限过滤查看检索调用是否传入了用户身份在检索层增加元数据权限过滤按用户所属范围隔离数据启动时无法写入向量库向量库版本与 API 不兼容查看控制台报错和依赖版本根据当前版本文档调整初始化方式或锁定依赖版本上线后成本快速飙升每次请求送入过多 token或大量高配模型调用查看耗时和 token 用量日志增加缓存路由简单问题到轻量模型控制片段数量8. 最佳实践与工程建议结合上面的分析这里整理一份可以直接对照执行的工程建议清单。第一先建评测集再优化。没有评测集就别调 RAG 的任何一个参数。哪怕一开始只有 30 条问题也能帮你避免反复横跳。第二用“最小链路”打通再逐步加固。先跑通 PDF 加载、分块、向量化、检索、生成的完整链路确认数据能流通再考虑混合检索、重排序、缓存、监控。每一步只加一个变量问题才定位得准。第三把权限当成第一优先级而不是最后一件事。知识库接入业务数据的那一刻就要考虑“谁能看到什么”。越权访问在 LLM 应用中的危害比传统系统更大因为模型会把不该说的内容以非常自然的语言说出来。第四提示词模板要版本化。把提示词和代码分开管理每次修改记录 diff。你很快会发现LLM 应用的性能波动很多来自提示词误改模板没有版本管理根本无法回滚。第五对知识的更新频率做分级。产品 FAQ 可以每天重建索引公司制度文档可以每周刷新研发内部代码库可以实时同步。不是所有数据都需要同一条更新链路分级可以显著节省成本。第六考虑浮点精度时以实测为准。部署到本地 GPU 时用同一批评测案例分别测试fp32、fp16、bf16记录显存占用和回答质量差异。不要只看理论不同模型对低精度格式的敏感度差异很大。第七不要把 Agent 化当成炫技。在 RAG 的检索生成链路稳定之前不要急着上工具调用和 MCP。工具调用带来的错误、安全和故障恢复复杂度会把你从模型问题直接推入分布式系统问题的深渊。9. 写在最后把 LLM 连接到数据是每一位开发者进入大模型应用世界最先迈出的一步也是门槛最低、最快见效的一步。它确实解决了“模型不知道你的私有知识”这个基础问题但别把它当成全部。21% 这个比例的无情之处在于它提醒我们连接是必要的条件却不是充分的条件。一篇好的技术博客最后不会告诉你“未来已来”而是告诉你下一步怎么走。这里的下一步很具体先为你的业务建立一个 30 到 50 条问题的评测集用 4.2 到 4.4 的示例代码把最小 RAG 跑通打印每一步的日志看看检索结果是否真的正确然后把第 6 节里那些看似繁琐的评测、权限、监控、更新机制一条条补上。等你把这些事情做完再回头看“连接数据”这件事你会理解为什么它只是整个工程里最漂亮的那 21%。如果你的团队正卡在“Demo 能跑但不敢上线”的阶段请回到工程清单从评测集开始补课。已经在上线边缘挣扎的朋友建议先检查检索日志和权限过滤有没有做好。把这两块补上系统稳定性大概率会立刻上一个台阶。