资讯动态

从零搭建大模型知识库:RAG完整链路与Agent实战

发布时间:2026/10/17 7:22:27 来源:尧图企业网站定制
先给结论大模型知识库这件事本质上不是在调模型而是在做一套靠谱的信息流水线。很多刚上手的小白程序员以为“知识库把文档扔给大模型”实际跑一遍就会发现文档切得不合理、检索结果对不上问题、Agent调不到正确工具——每一步都能把效果打骨折。这篇文章我会把从文档加载、文本切分、向量化、检索到Agent工具调用这套完整链路掰开揉碎地讲清楚全部基于可复现的代码实践适合刚入门、想自己从零搭一个知识库Demo的人。1. 先搞清楚知识库这题考的是什么1.1 为什么直接问大模型不够用大模型本身是个“填空题选手”它只会根据训练时见过的数据做概率预测你问它“我们公司报销流程是什么”它根本没见过你公司的制度文档只能编。这就是很多人说的幻觉问题本质是事实性缺失。知识库要解决的就是给大模型装一个“外挂记忆体”把私有文档提前切块、转成向量存起来每次用户提问时先检索出相关片段再把这些片段塞进Prompt里让模型基于证据回答。这个思路业界叫RAGRetrieval-Augmented Generation检索增强生成是目前低成本落地私有知识的首选方案不需要微调模型不需要租几十张显卡只需要一台普通服务器加一个向量数据库就能跑起来。1.2 RAG的核心链路一句话说清四个环节整条链路可以用一句话概括“先找材料再读材料最后总结材料。”具体拆开是四个环节文档解析与切分、文本向量化、向量检索、Prompt生成。文档进来先解析成纯文本按一定策略切成小块每块用嵌入模型转成向量存进向量数据库用户提问时把问题也转成向量在库里做相似度搜索找到最相关的若干块文本最后连同问题一起交给大模型组织答案。这个流程里最容易踩坑的是前两步——文本切分和向量化。切得好不好直接决定检索能不能命中向量模型选得好不好决定相似度计算靠不靠谱。很多Demo跑出来效果差两张排查路径要么是切出来的块语义不完整要么是检索回来的片段根本不含答案。1.3 和微调Fine-tuning对比什么时候该用哪个微调是改变模型本身的参数让模型“记住”特定格式或风格。RAG是改变模型的输入上下文让模型“查阅”外部资料。两者不是二选一的关系而是解决不同的问题。我个人的判断标准很简单场景推荐方案原因知识频繁更新比如制度文档每周改RAG改文档即可不用重训模型需要引用具体出处比如客服回答RAG可以把来源文档一并输出需要让模型学会某种格式比如固定JSON输出结构微调RAG不改变模型的输出习惯私有数据量大检索命中率成为瓶颈RAG微调检索负责找材料微调负责格式化回答对大多数中小团队来说第一优先级永远是RAG。成本低、迭代快、出问题能定位到具体文档而微调是个一旦效果不好就难以定位的“黑盒操作”。2. 从头搭一套核心模块与技术选型2.1 文档切分决定知识召回上限的第一个坑文本切分是整个知识库最容易被低估的环节。如果你把一份500页的产品手册按固定宽度直接截断那结果一定是灾难一句话被拦腰斩断、表格被拆得七零八落、代码块里的上下文丢了一半。检索时模型拿到的是一堆残缺片段回答自然也是残缺的。推荐的实操方案是分层切分先按文档结构标题、段落、列表粗分再对过长的块做二次细分。常用的策略是控制chunk_size在400~800个字符之间overlap设置50~100个字符。Overlap很重要——它让相邻两个块之间保留一定的重复内容避免某个关键信息恰好落在分界线上被丢失。注意切分不是越小越好。块太小语义信息不足检索到的片段往往缺少上下文块太大向量之间的区分度下降且塞进Prompt时容易超出上下文窗口。400~800是一个经过实践检验的折中区间具体数值要根据文档类型微调。2.2 嵌入模型选型向量好不好直接决定检索准不准嵌入模型的作用是把文本映射成高维向量语义相近的文本在向量空间里的距离也更近。选模型的三个关键指标中文支持度、向量维度、推理延迟。中文场景首推社区开源的BGE系列中文嵌入模型它对中文长文本的支持和检索效果在同量级模型里是比较成熟的。向量维度影响着存储和检索开销常见有384维、768维、1024维维度越高通常表达越精细但检索耗时也会变长。小规模Demo用384维完全够用生产环境再考虑升级。我在实际项目里测下来有一类问题特别容易被忽视嵌入模型对“问句”和“文档句子”之间的语义匹配能力明显弱于“同类型短文本”之间的匹配。因为文档里的句子通常是陈述语气用户提问通常是疑问语气这两类文本在向量空间里可能离得比想象中远。这个问题稍后在重排序环节还有救。2.3 向量数据库选择本地起步与生产部署怎么选向量数据库负责存储向量并执行相似度搜索。市面上选择很多我把比较主流的几个拉出来对比一下向量库适合场景特点Chroma本地原型/Demo安装简单API友好无需额外部署服务FAISS中等规模检索纯内存索引速度极快但需要自己管理持久化Milvus生产级大规模分布式部署支持混合查询适合海量数据Qdrant生产级中等规模Rust编写性能好支持过滤向量联合检索如果你是第一次跑通全流程我建议直接用Chroma零配置起步pip install之后几分钟就能用。等Demo验证了效果再考虑把存储层迁移到生产级的方案这个过程中数据结构和API改动需要提前设计好避免后期大改。3. 端到端实操一个最小可用的知识库Demo3.1 环境准备与依赖安装我用的是一台普通的开发机Python 3.10依赖就三个库LangChain编排用、Chroma向量存储、以及调用大模型的OpenAI兼容客户端库。安装命令如下pip install langchain langchain-community chromadb openai大模型部分我建议用云端API服务商提供的兼容接口通过配置环境变量的方式切换这样本地不用部署显卡也能先跑通逻辑。配置方式如下export OPENAI_API_KEYyour-api-key export OPENAI_API_BASEhttps://api.你的服务商域名.com/v1 export OPENAI_MODEL你的模型名称这里有个小提示不要把所有逻辑都堆在LangChain里写先手动实现一遍切分和检索的裸流程你会对这条链路理解得更深。框架只是工具理解才是核心。3.2 编写文档加载与切分代码以下代码实现了对本地TXT和Markdown文档的加载、切分和打印统计。重点观察chunk_size和overlap对切分结果的影响这是后续调优的基础。from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # 加载文档 loader TextLoader(knowledge_base/help_center.txt, encodingutf-8) documents loader.load() # 按递归字符切分优先尝试换行符、空格、标点逐级降级 splitter RecursiveCharacterTextSplitter( chunk_size600, chunk_overlap100, separators[\n\n, \n, 。, , , , , ] ) chunks splitter.split_documents(documents) print(f原始文档数: {len(documents)}) print(f切分后块数: {len(chunks)}) for i, chunk in enumerate(chunks[:3]): print(f--- 第{i1}块前200字 ---) print(chunk.page_content[:200])RecursiveCharacterTextSplitter的原理是先按最长分隔符切如果切出来的块还是超过chunk_size就降级到下一个分隔符继续切。这意味着即使遇到没有换行的超长连续文本也能最终在标点处切断而不是硬截断。我自己常用“。”和“”作为中文分句的分隔符实践下来比纯按字符截断的语义完整性好很多。3.3 构建向量索引与检索链路向量化这一步要做的就是把上一步切好的块逐个传给嵌入模型得到向量后写入Chroma。LangChain里已经封装好了这条流程但我要提醒你务必手动检查一次向量库里的内容是否和源文档一致这一步能帮你排查大量“检索结果莫名其妙”的问题。from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 使用社区开源中文嵌入模型 embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, model_kwargs{device: cpu} ) # 创建向量库首次运行会自动建立索引 vectorstore Chroma.from_documents( documentschunks, embeddingembeddings, persist_directory./chroma_db ) print(向量库已创建共, vectorstore._collection.count(), 条向量)检索时用相似度搜索取回TopK个片段。这里我习惯先取10个候选后面再接重排序精筛比直接取Top3的命中率高很多。query 如何重置密码 retrieved vectorstore.similarity_search_with_score(query, k10) for doc, score in retrieved: print(f相似度: {score:.4f} - {doc.page_content[:80]}...)相似度得分直观反应了检索质量。如果Top1的得分远低于0.5说明问题很可能出在嵌入模型和切分策略上而不是生成模型。3.4 把检索结果交给大模型生成回答检索完成后Prompt的构造是最后一个关键环节。最低要求是告诉模型只依据检索到的材料回答、不要编造、材料不足时明确说明不知道。from openai import OpenAI client OpenAI() context \n\n.join([doc.page_content for doc, _ in retrieved[:5]]) prompt f你是一个智能客服助手。请只根据以下知识库片段回答用户问题。 如果片段中没有正确答案请直接回复“根据现有资料无法回答”。 知识库片段 {context} 用户问题{query} response client.chat.completions.create( modelyour-model-name, messages[{role: user, content: prompt}], temperature0.2, ) print(response.choices[0].message.content)temperature设为0.2是为了尽量贴近事实减少模型自由发挥的空间。如果你发现回答里仍然有编造内容一个立竿见影的办法是在Prompt里强制要求“只能引用片段原文中的信息不能添加任何未出现的细节”效果比反复调temperature更明显。4. 把“检索”升级成“智能Agent”——让Agent学会用工具4.1 Agent与单纯RAG的区别从“被动问答”到“主动调配”单纯的RAG服务只能做一件事输入问题检索资料给出回答。一旦遇到需要多步推理的任务比如用户问“帮我对比上一季度和这个季度的退货率并解释差异原因”单次检索根本接不住——你需要先查两个时间段的数据再查业务背景再汇总分析。Agent的核心能力是“决策”它不再是一个被动的问答管道而是能够自主判断这个问题需要调用哪个工具、检索哪类知识、分几步才能完成。知识库在这种设计下退化为Agent可调用的一类工具和“查数据库”“查天气”“发邮件”处于同等地位。4.2 工具调用与知识库检索的编排Agent最常见的实现方式是ReActReasoning Acting本质是一个循环模型观察用户输入模型决定调用哪个工具并生成工具参数程序执行工具把结果返回给模型模型根据结果决定继续调用其他工具还是给出最终答案在这个框架下你的向量知识库就是一个普通的工具函数函数名如search_knowledge_base(query)描述为“搜索内部知识库适用于产品使用、制度流程、技术FAQ等问题”。给工具写清楚描述很重要因为模型是靠描述来判断什么情况调用它的。4.3 一个简单的Agent检索流程实现下面用LangChain的Agent机制实现一个最小可用的知识库Agent包含两个工具一个查知识库一个做基础计算。from langchain.agents import initialize_agent, Tool from langchain.tools import tool from langchain_community.vectorstores import Chroma tool def search_knowledge_base(query: str) - str: 搜索内部知识库适用于产品使用、流程制度、技术FAQ等问题。 docs vectorstore.similarity_search(query, k3) return \n\n.join([d.page_content for d in docs]) tool def simple_math(expression: str) - str: 计算简单的数学表达式。 return str(eval(expression)) tools [ Tool(name知识库检索, funcsearch_knowledge_base, description搜索内部知识库), Tool(name数学计算, funcsimple_math, description计算数学表达式), ] agent initialize_agent( toolstools, llmclient, agentchat-conversational-react-description, verboseTrue, max_iterations3, ) result agent.run(我们部门的报销流程是什么如果上个月报销了800元这个月500元一共多少) print(result)跑一遍可以看到Agent的执行过程先检索报销流程再调用数学工具计算金额最后汇总输出。这种多步编排能力就是普通RAG和Agent之间最本质的功能差异。我个人的实测体会是Agent最大的问题在于不可控。模型在每一步的“思考”是有概率性的偶尔会调用错误的工具或死循环。所以生产环境里我给Agent加了两个保险丝一是把max_iterations限制在3~5次二是对关键链路手动编排不交给模型自由发挥。能写死流程的就不该用AgentAgent只用来处理那些真正需要动态决策的场景。5. 常见问题与调试经验实录5.1 检索结果不准问题多半不在检索遇到“检索回来的内容明显不相关”的情况我建议倒查三层第一层是切分问题。打开chunks列表肉眼检查文本块是否完整。如果一句话被切到两个块里这个块本身就失去了查询价值。第二层是嵌入模型问题。把问题和相关文档都转成向量手动算一下余弦相似度如果相似度极低就是模型对这类文本的语义理解不够考虑换更大的嵌入模型。第三层是索引同步问题。新增的文档有没有真正写进向量库Chroma的persist_directory在重启后是否正确加载这个坑我踩过好几次看似是检索变差其实是库没更新。5.2 切分把语义切断了怎么判断和规避判断方法很简单找一个长段落人工标注这段的中心思想是什么再去看切分后的块。如果每个块都保留了中心思想切分合格如果出现“块A描述了上半段原因块B直接讲结论”说明分界线砍在了逻辑转折处。规避办法优先使用基于结构的切分策略。Markdown和HTML文档先按标题结构切表格先按行切代码文件先按函数切。我习惯在切分前做一步结构化解析把文档转成“标题正文”的片段列表再对每个片段做二次切分。虽然麻烦一点但检索质量的提升非常明显。5.3 响应太慢如何从检索侧优化性能慢不一定是生成模型的锅检索侧同样能成为瓶颈。常见原因有三个向量库中向量数量过大导致全量扫描、Embedding模型在CPU上推理过慢、以及每次请求都重新加载向量库。针对这三个原因优化手段也对应三条第一给向量库增加聚类索引或HNSW索引参数搜索范围缩小一个量级第二把Embedding模型换成量化版或加速版推理时间能压缩到原来的三分之一第三把向量库加载和嵌入模型初始化放到服务启动时完成不要在每个请求里重复加载。如果你用的是Chroma可以设置chroma_server来常驻运行避免重复加载的开销。5.4 用重排序Rerank力挽狂澜重排序是检索质量提升性价比最高的一步操作。召回阶段先用低成本的向量检索取出10个候选块再用一个更强的排序模型对候选块重新打分把真正相关的排在前面这一步能将Top1命中率提升不少。from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder reranker HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-base) compression_retriever ContextualCompressionRetriever( base_compressorCrossEncoderReranker(modelreranker, top_n3), base_retrievervectorstore.as_retriever(search_kwargs{k: 10}) ) docs compression_retriever.invoke(如何重置密码)原理解释一下向量检索看的是“语义相似性”重排序模型看的是“问答对的相关性”。打个比方向量检索是海选简历看学校专业是否相关重排序是面试直接判断这个人能不能解决具体问题。海选要快面试要准两个环节配合才能选出最合适的候选人。6. 效果评估与后续迭代方向6.1 评测指标不要凭感觉说“效果还行”没有量化指标就无法迭代。我用过的评测方法中最实用的是构建一个“问题-答案-来源段落”的黄金测试集每条数据标注一个正确答案所在的文档路径。然后跑批量评测计算命中率Top1/5召回命中率、MRR倒数排名均值和忠实度生成的回答是否全部来自检索片段。我一般准备50条测试问题就够起步了100条以上可以做比较稳定的对比实验。每次改动切分策略、嵌入模型或检索参数后跑一遍测试集用数据说话比直觉靠谱一百倍。忠实度可以用一个简单的脚本来检查生成的回答中是否包含检索片段以外的关键实体如果包含说明模型在“自由发挥”需要加大Prompt约束力度。6.2 下一步迭代混合检索、结构化路由与缓存基础RAG跑通之后有三个明确的升级方向。混合检索是其中收益最明显的把向量检索和关键词BM25检索结合起来取两者结果的并集再做重排序。向量检索擅长处理“意思相近但字面不同”的查询BM25擅长精确匹配产品名、工单编号这类结构化关键词两者互补性很强。结构化路由是针对多知识库场景。比如一个知识库里既有产品文档又有销售制度每次检索都搜全部内容会互相干扰。可以用一个轻量的意图分类模型先判断问题属于哪个领域再路由到对应的知识库精确度和响应速度都会更好。缓存层则可以显著降低成本。把高频问题的答案缓存起来命中缓存时完全不调用大模型响应时间从秒级降到毫秒级。注意缓存key不能直接用用户原问题因为同一个问题的表述方式千变万化应该用检索阶段命中的文档ID列表加上问题的Embedding向量相近度来做缓存判断这个小改动实测能提升30%以上的缓存命中率。最后再分享一点我个人的体会知识库构建到最后拼的其实是工程细节而不是模型大小。文档质量、切分策略、检索链路、评测方法每一环都需要用数据驱动的方式去调。小白入门的路径不应该是一上来就追求复杂的Agent编排而是先花一周时间把一个最小RAG链路跑通亲手体会一次“切分-向量化-检索-生成”的完整闭环然后再去叠加Agent能力。很多架构看起来很高级的失败项目追溯到底层其实是最基础的检索质量没做扎实。先笨再巧这条路径是目前我看到的最稳妥的成长方式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑