RAG 创建实战:从零搭一个能回答问题的知识库

发布时间:2026/9/30 13:08:16

RAG 创建实战:从零搭一个能回答问题的知识库 1. 引言今天不聊概念直接上手。我带你从零搭一个 RAGRetrieval-Augmented Generation检索增强生成系统让大模型能基于你自己的文档回答问题。整个过程我会按真实操作的口吻来写每一步都告诉你我做了什么、踩了什么坑、怎么排查。先交代一下我的环境Python 3.10Windows 11用 OpenAI 兼容接口本地也可以用 Ollama 跑开源模型。代码我尽量写得能直接复制运行。本文的最终成果一个能直接复制运行、可复用的 RAG 脚本——你只要把docs/里的资料换成自己的就能让大模型基于你的知识库回答问题。适用读者有 Python 基础、想快速搭建 RAG 的开发者。阅读完你将获得什么从原理到踩坑的完整实战路径以及一套开箱即用的代码照着敲就能跑通。2. 原理与大纲先花两分钟把原理讲清楚后面动手才不会懵。RAG 说白了就一句话先检索再生成。大模型本身记不住你的私有资料那就先把资料切成块、转成向量存起来用户提问时先从向量库里捞出最相关的几块拼进 prompt 一起喂给模型让它「看着资料回答」。整个流程拆开就是四步加载把文档读进来切块把长文档切成小块向量化存储把块转成向量存进向量库检索生成提问时检索相关块拼给模型生成答案下面这张图把整个 RAG 流程串起来后面每一步都对应图里的一个环节用户提问加载文档切分文档向量化存储向量库 ChromaDB检索相关块拼进 Prompt大模型生成答案返回回答下面按这个顺序一步步来。先看大纲引言原理与大纲准备工作装依赖准备文档数据切分文档生成向量并存入向量库构建检索链完整跑通一个可复用的脚本实现方式对比踩坑记录总结2. 准备工作装依赖打开终端先建个虚拟环境免得把系统 Python 搞乱python-mvenv rag_env rag_env\Scripts\activate# Windows# source rag_env/bin/activate # Linux/Mac然后装依赖。我用的核心库是 LangChain 0.3 系列、ChromaDB 做向量库、以及 OpenAI 的 SDKpipinstalllangchain langchain-community langchain-openai chromadb openai python-dotenv装完验证一下版本防止版本不兼容python-cimport langchain; print(langchain.__version__)我这边输出的是0.3.x。如果你装到 0.2 或更早建议升级到 0.3因为后面有些 API 写法不一样。3. 准备文档数据RAG 的第一步是「喂数据」。我准备了几篇 Markdown 格式的技术笔记放在docs/目录下。你也可以用 PDF、TXT、网页后面我会说怎么换加载器。先写一个加载文档的脚本。这里我用 LangChain 的目录加载器一次性把整个文件夹读进来fromlangchain_community.document_loadersimportDirectoryLoader,TextLoader loaderDirectoryLoader(docs,glob**/*.md,loader_clsTextLoader,loader_kwargs{encoding:utf-8},)docsloader.load()print(f加载了{len(docs)}个文档)这里有个坑Windows 下如果文档是 GBK 编码TextLoader默认用 UTF-8 读会报UnicodeDecodeError。我一开始就踩了后来在loader_kwargs里显式指定encodingutf-8才解决。如果你的文档是别的编码改成对应的就行。4. 切分文档加载进来的文档可能很长直接塞给向量库效果很差所以要先切块。切块的大小和重叠度直接影响检索质量我一般这样配fromlangchain_text_splittersimportRecursiveCharacterTextSplitter splitterRecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50,separators[\n\n,\n,。,,, ,],)chunkssplitter.split_documents(docs)print(f切分成{len(chunks)}个块)chunk_size500表示每块约 500 字符chunk_overlap50让相邻块有 50 字符重叠避免把一句话从中间切断导致语义丢失。separators里我加了中文标点这样中文文档切得更自然。5. 生成向量并存入向量库接下来把每个块转成向量存进 ChromaDB。这一步是 RAG 的核心——把文本变成机器能算相似度的数字。我用 OpenAI 的 embedding 模型fromlangchain_openaiimportOpenAIEmbeddingsfromlangchain_community.vectorstoresimportChroma embeddingsOpenAIEmbeddings(modeltext-embedding-3-small)vectorstoreChroma.from_documents(documentschunks,embeddingembeddings,persist_directory./chroma_db,)print(向量库创建完成已持久化到 ./chroma_db)跑完你会看到./chroma_db目录下生成了索引文件。persist_directory指定持久化路径这样下次启动不用重新算向量。如果你没有 OpenAI 的 key也可以用本地模型。我试过用 Ollama 跑nomic-embed-text效果也还行fromlangchain_community.embeddingsimportOllamaEmbeddings embeddingsOllamaEmbeddings(modelnomic-embed-text)6. 构建检索链向量库建好了现在把它接进大模型。这里我用 LangChain 的RetrievalQA链它会把「检索 生成」串起来fromlangchain_openaiimportChatOpenAIfromlangchain.chainsimportRetrievalQA llmChatOpenAI(modelgpt-4o-mini,temperature0)qa_chainRetrievalQA.from_chain_type(llmllm,chain_typestuff,retrievervectorstore.as_retriever(search_kwargs{k:4}),)answerqa_chain.invoke(什么是 RAG)print(answer[result])k4表示每次检索取最相似的 4 个块喂给模型。chain_typestuff表示把所有检索结果直接拼进 prompt适合块数不多的情况。7. 完整跑通一个可复用的脚本上面几步拆开讲实际用的时候我习惯合成一个脚本方便反复跑。下面是我最终用的版本importosfromdotenvimportload_dotenvfromlangchain_community.document_loadersimportDirectoryLoader,TextLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_openaiimportOpenAIEmbeddings,ChatOpenAIfromlangchain_community.vectorstoresimportChromafromlangchain.chainsimportRetrievalQA load_dotenv()# 1. 加载loaderDirectoryLoader(docs,glob**/*.md,loader_clsTextLoader,loader_kwargs{encoding:utf-8})docsloader.load()# 2. 切分splitterRecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50,separators[\n\n,\n,。,,, ,])chunkssplitter.split_documents(docs)# 3. 向量化 存储embeddingsOpenAIEmbeddings(modeltext-embedding-3-small)vectorstoreChroma.from_documents(documentschunks,embeddingembeddings,persist_directory./chroma_db)# 4. 检索 生成llmChatOpenAI(modelgpt-4o-mini,temperature0)qa_chainRetrievalQA.from_chain_type(llmllm,chain_typestuff,retrievervectorstore.as_retriever(search_kwargs{k:4}),return_source_documentsTrue,)whileTrue:queryinput(请输入问题输入 exit 退出)ifquery.lower()exit:breakresultqa_chain.invoke(query)print(\n回答,result[result],\n)# 打印来源文档方便追溯答案出处print(--- 来源文档 ---)fori,docinenumerate(result[source_documents],1):sourcedoc.metadata.get(source,未知来源)snippetdoc.page_content[:100]print(f[{i}] 来源{source})print(f 片段{snippet}...)print().env文件里放你的 API keyOPENAI_API_KEYsk-xxxx9. 实现方式对比上面用的是 LangChain ChromaDB 这一套也是目前最主流的组合。但 RAG 的实现路径不止一条我把我试过的几种列出来优缺点都摆一摆你按自己的场景挑。方式一LangChain ChromaDB本文用的优点生态成熟、文档多、上手快切块、向量化、检索一条龙都封装好了缺点依赖较重LangChain 版本升级 API 容易变ChromaDB 单机够用数据量大或要并发时吃力方式二LlamaIndex FAISS优点对「索引」这件事做得更细支持多种索引结构检索精度高缺点概念比 LangChain 多学习曲线陡一点FAISS 是内存索引重启要重新加载方式三纯手写embedding 向量检索 prompt 拼接优点最轻量没有框架包袱每一步都看得懂、可控缺点要自己处理切块、持久化、检索逻辑代码量上去了维护成本高方式四RAGFlow / Dify 这类平台优点可视化配置拖拽就能搭适合快速验证和给非技术同事用缺点定制性差想改底层逻辑就受限数据量大时性能不一定好怎么选我的建议是想快速跑通、验证想法用方式一对检索精度要求高、愿意折腾试方式二想彻底搞懂原理强烈建议手写一遍方式三团队里非技术人多再考虑方式四。怎么选我的建议是想快速跑通、验证想法用方式一对检索精度要求高、愿意折腾试方式二想彻底搞懂原理强烈建议手写一遍方式三团队里非技术人多再考虑方式四。四种方式放在一起对比优缺点和适用场景一目了然方式优点缺点适用场景方式一LangChain ChromaDB生态成熟、文档多、上手快切块、向量化、检索一条龙都封装好了依赖较重LangChain 版本升级 API 容易变ChromaDB 单机够用数据量大或要并发时吃力快速跑通、验证想法个人或小团队起步方式二LlamaIndex FAISS对「索引」这件事做得更细支持多种索引结构检索精度高概念比 LangChain 多学习曲线陡一点FAISS 是内存索引重启要重新加载对检索精度要求高、愿意折腾的进阶场景方式三纯手写最轻量没有框架包袱每一步都看得懂、可控要自己处理切块、持久化、检索逻辑代码量上去了维护成本高想彻底搞懂原理、学习 RAG 内部机制方式四RAGFlow / Dify 平台可视化配置拖拽就能搭适合快速验证和给非技术同事用定制性差想改底层逻辑就受限数据量大时性能不一定好团队里非技术人多、需要快速交付原型四种方式的定位差异用一张图看得更清楚方式四RAGFlow / Dify可视化、拖拽即用定制性差方式三纯手写最轻量、可控代码量大、维护成本高方式二LlamaIndex FAISS索引精细、精度高概念多、内存索引方式一LangChain ChromaDB生态成熟、上手快依赖较重、单机够用按场景选择8. 踩坑记录这一节把我实际遇到的问题列出来你遇到类似情况可以直接对照坑 1编码问题现象加载文档报UnicodeDecodeError解决loader_kwargs{encoding: utf-8}或改成文档实际编码坑 2检索结果答非所问现象模型回答跟问题对不上解决调大k值或减小chunk_size让块更聚焦也可以检查文档切分是否把关键信息切断了坑 3向量库重复写入现象每次跑脚本都往同一个./chroma_db追加导致检索结果重复解决重建前先删掉旧目录或者用Chroma(persist_directory..., embedding...)加载已有库而不是重新from_documents坑 4LangChain 版本 API 变化现象RetrievalQA或Chroma导入报错解决确认langchain是 0.3.xlangchain-community和langchain-openai都装了11. 总结到这里一个能用的 RAG 系统就跑通了。核心流程就四步加载文档 → 切块 → 向量化存储 → 检索生成。你只要把docs/里的内容换成自己的资料就能让大模型基于你的知识库回答问题。最后用一张图回顾整个核心流程加载文档切块向量化存储检索生成回答说到底RAG 的本质就一件事给大模型配一个「外挂记忆」。模型记不住你的私有资料那就把资料变成可检索的向量提问时先捞出来再让它回答。不管用 LangChain、LlamaIndex 还是手写绕来绕去都是「检索 生成」这两个动作。理解了这一点换什么框架都只是换工具核心思路不变。下一步你可以试试换 PDF 加载器、接入本地模型、或者把检索结果和引用来源一起返回。有问题欢迎在评论区交流。
延伸阅读

更多相关文章

2026/9/30 13:03:13

Windows Server 2022部署全流程:初始化、远程管理与安全加固

Windows Server 2022 装完的那一刻,很多人第一反应是直接挂业务、装环境、跑服务,但我自己的习惯恰恰相反——先花点时间把这台机器"打理干净"。一套新系统从裸装到能安心托付业务,中间的配置环节远比安装本身更考验人。选什么版本…

2026/9/30 13:03:13

2026年AI配音做PPT讲解怎么选?

做PPT讲解、汇报材料、课程课件时,很多人卡在最后一步:PPT做完了,配音却不知道怎么弄。如果是几十页的PPT,逐页真人录音不仅耗时间,后面修改一页内容,还可能需要重新录整段。AI配音更适合这种需要反复修改、…

2026/9/30 13:03:13

JDK切换实战指南:多版本共存的环境变量配置与排坑

经常有同事问我:你电脑上装了几个 JDK?我一般回答三个,8、17、21,按需切。JDK切换看着是个基础操作,但真动起手来,环境变量配置失败、java -version和javac -version各说各话、IDEA 里编译还是旧版本&#…

2026/9/30 14:03:26

嵌入式驱动开发:从能跑到会崩的量产工程化鸿沟

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 14:03:26

MFC TCP网络通信实战:心跳保活、粘包处理与断线续传

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 14:03:26

使用Filler4提取微信小程序视频:手把手实操与原理剖析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/30 14:03:26

昇思 MindSpore 大模型单卡微调推理:自助搭建流程

一、摘要基于昇思 MindSpore 在单张昇腾 NPU(310P/910B)完成大模型微调 推理是轻量化落地常用方案。单卡流程包含:环境准备、权重加载、数据集构建、LoRA 微调、模型保存、离线推理全链路。相比于全参数微调,LoRA 低秩适配极大降…

2026/9/30 13:58:25

方差、标准差、MSE与RMSE:数据工程师的指标选择实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/29 11:07:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/29 21:48:03

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 7:00:49

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 0:01:22

MATLAB+Yalmip+CPLEX实战:综合能源系统优化调度全流程解析

做综合能源系统优化调度这活儿,最痛苦的不是建模本身,而是模型写完之后不知道该怎么求解。看论文里轻飘飘一句“采用Yalmip调用CPLEX求解”,自己上手时却往往卡在环境配置、变量声明、约束写法和求解状态判读上,一耗就是两三天。这…

2026/9/30 0:01:22

I3C比I2C快10倍?RK3576实战:速率、DTS配置与混合总线避坑指南

I3C 比 I2C 快 10 倍?这句话在嵌入式群里传了很久,每次都能吵出一堆截图。前段时间我正好在 RK3576 上调板级 I3C 接口,从控制器寄存器一路摸到 Linux DTS 配置,踩了不少坑,也把这笔速度账彻底算明白了。本文就用 RK35…

2026/9/30 0:01:22

字符串转对象:JSON.parse、new Function与URLSearchParams

“字符串转对象”这几个字,我在技术群里见过的问法至少有十几种:有人拿着一串{a:1,b:2}说 JSON.parse 直接报错,有人要从 URL 里抠出参数,还有人只是想把abc变成能挂属性的东西。js 这门语言里,字符串和对象之间的转换…

2026/9/29 3:53:39

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/29 9:46:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/30 10:28:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑