从零搭建本地AI知识库:基于RAG的智能问答系统实战指南

发布时间:2026/7/30 18:32:37
从零搭建本地AI知识库:基于RAG的智能问答系统实战指南 从零搭建本地AI知识库基于RAG的智能问答系统实战指南文章目录从零搭建本地AI知识库基于RAG的智能问答系统实战指南前言一、RAG 架构原理1.1 什么是 RAG1.2 为什么选择 RAG1.3 RAG vs 微调不是非此即彼二、技术选型2.1 整体架构图2.2 核心组件选型三、环境准备3.1 安装依赖3.2 项目结构四、完整代码实现4.1 配置文件4.2 文档处理与入库4.3 检索器4.4 生成器4.5 主程序五、运行效果5.1 第一步文档入库5.2 第二步启动问答六、进阶优化技巧6.1 检索质量优化6.2 分块策略优化6.3 更多优化方向七、总结下一步建议前言大语言模型LLM的能力有目共睹但直接使用通用模型时会面临两个核心痛点知识时效性差训练数据有截止日期和缺乏私有领域知识无法访问企业内部文档。RAGRetrieval-Augmented Generation检索增强生成通过先检索、再生成的方式优雅地解决了这两个问题。本文将带你从零开始用 Python 搭建一个完整的本地 RAG 智能问答系统覆盖从文档处理、向量存储到检索生成的全链路实现。一、RAG 架构原理1.1 什么是 RAGRAG 的核心思想很简单在让 LLM 回答问题之前先从知识库中检索出相关文档片段然后将这些片段作为上下文一并喂给模型。一个完整的 RAG 流程包含三个阶段用户提问 → [检索] → 召回相关文档片段 → [增强] → 拼接上下文 → [生成] → LLM 生成回答1.2 为什么选择 RAG对比维度直接使用 LLM微调Fine-tuningRAG知识更新成本高需重新训练高需重新微调低更新文档即可私有数据支持不支持支持支持可解释性差差好可追溯来源计算资源需求低高中幻觉控制差一般较好1.3 RAG vs 微调不是非此即彼一个常见的误区是认为 RAG 和微调是对立的。实际上微调改变模型怎么说风格、格式、领域理解RAG决定模型说什么事实、知识、数据在生产环境中两者经常配合使用。二、技术选型2.1 整体架构图┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ 文档输入 │ ──→ │ 文档处理 │ ──→ │ Embedding │ │ (PDF/TXT/ │ │ (分块/清洗) │ │ 向量化 │ │ MD/DOCX) │ │ │ │ │ └─────────────┘ └──────────────┘ └──────┬──────┘ │ ▼ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ LLM 生成 │ ←── │ Prompt 拼接 │ ←── │ 向量检索 │ │ 回答 │ │ (上下文问题) │ │ (Top-K 召回) │ └─────────────┘ └──────────────┘ └─────────────┘2.2 核心组件选型组件推荐方案备选方案Embedding 模型BAAI/bge-large-zh-v1.5text2vec-base-chinese向量数据库ChromaDB本地轻量FAISS / Milvus / QdrantLLM通义千问 / ChatGLM / GPT任意支持 OpenAI 接口的模型文档解析LangChain TextSplitterUnstructured / LlamaParse框架LangChainLlamaIndex选型建议如果是个人学习或小型项目ChromaDB bge-large-zh 是最佳起步组合零成本、易部署。三、环境准备3.1 安装依赖pipinstalllangchain langchain-community pipinstallchromadb pipinstallsentence-transformers pipinstallopenai pipinstallpypdf3.2 项目结构rag-demo/ ├── data/ # 知识库文档存放目录 │ ├── doc1.pdf │ ├── doc2.txt │ └── doc3.md ├── vectorstore/ # 向量数据库持久化目录 ├── config.py # 配置文件 ├── ingest.py # 文档处理与入库脚本 ├── retriever.py # 检索器 ├── generator.py # 生成器 └── main.py # 主程序入口四、完整代码实现4.1 配置文件# config.pyimportos# Embedding 配置 EMBEDDING_MODELBAAI/bge-large-zh-v1.5# 中文 embedding 模型EMBEDDING_DEVICEcpu# 有 GPU 可改为 cuda# 向量数据库配置 VECTORSTORE_PATH./vectorstoreCOLLECTION_NAMEmy_knowledge_base# LLM 配置 # 使用 OpenAI 兼容接口通义千问/DeepSeek/OpenAI 均可LLM_API_KEYos.getenv(LLM_API_KEY,your-api-key-here)LLM_API_BASEos.getenv(LLM_API_BASE,https://dashscope.aliyuncs.com/compatible-mode/v1)LLM_MODELqwen-plus# 检索配置 CHUNK_SIZE500# 文档分块大小字符数CHUNK_OVERLAP50# 分块重叠保持上下文连贯TOP_K3# 检索返回的文档片段数量4.2 文档处理与入库# ingest.py文档加载、分块、向量化并存入向量数据库importosimportglobfromlangchain_community.document_loadersimport(TextLoader,PyPDFLoader,UnstructuredMarkdownLoader,)fromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_community.embeddingsimportHuggingFaceEmbeddingsfromlangchain_community.vectorstoresimportChromafromconfigimport(EMBEDDING_MODEL,EMBEDDING_DEVICE,VECTORSTORE_PATH,COLLECTION_NAME,CHUNK_SIZE,CHUNK_OVERLAP,)defload_documents(data_dir:str./data):加载 data 目录下所有支持的文档loaders_map{.txt:TextLoader,.pdf:PyPDFLoader,.md:UnstructuredMarkdownLoader,}documents[]forfile_pathinglob.glob(os.path.join(data_dir,*)):extos.path.splitext(file_path)[1].lower()loader_clsloaders_map.get(ext)ifloader_clsisNone:print(f[跳过] 不支持的文件类型:{file_path})continueprint(f[加载]{file_path})docsloader_cls(file_path).load()# 为每个文档添加来源元数据fordocindocs:doc.metadata[source_file]os.path.basename(file_path)documents.extend(docs)print(f共加载{len(documents)}个文档页/段)returndocumentsdefsplit_documents(documents):将长文档切分为合适大小的块text_splitterRecursiveCharacterTextSplitter(chunk_sizeCHUNK_SIZE,chunk_overlapCHUNK_OVERLAP,separators[\n\n,\n,。,,,,, ,],)chunkstext_splitter.split_documents(documents)print(f切分为{len(chunks)}个文本块)returnchunksdefget_embeddings():初始化 Embedding 模型returnHuggingFaceEmbeddings(model_nameEMBEDDING_MODEL,model_kwargs{device:EMBEDDING_DEVICE},encode_kwargs{normalize_embeddings:True},)defbuild_vectorstore(chunks,embeddings):构建向量数据库并持久化vectorstoreChroma.from_documents(documentschunks,embeddingembeddings,collection_nameCOLLECTION_NAME,persist_directoryVECTORSTORE_PATH,)print(f向量数据库已保存到{VECTORSTORE_PATH})returnvectorstoredefingest():完整入库流程print(*50)print(开始文档入库流程)print(*50)# Step 1: 加载文档documentsload_documents(./data)ifnotdocuments:print(未找到任何文档请在 data/ 目录下放置文件)return# Step 2: 分块chunkssplit_documents(documents)# Step 3: 向量化并存储embeddingsget_embeddings()build_vectorstore(chunks,embeddings)print(*50)print(文档入库完成)print(*50)if__name____main__:ingest()4.3 检索器# retriever.py向量检索模块fromlangchain_community.vectorstoresimportChromafromconfigimport(VECTORSTORE_PATH,COLLECTION_NAME,EMBEDDING_MODEL,EMBEDDING_DEVICE,TOP_K,)fromingestimportget_embeddingsdefget_vectorstore():加载已有的向量数据库embeddingsget_embeddings()returnChroma(collection_nameCOLLECTION_NAME,embedding_functionembeddings,persist_directoryVECTORSTORE_PATH,)defretrieve(query:str,top_k:intTOP_K): 检索与 query 最相关的 top_k 个文档片段 返回: [(content, source, score), ...] vectorstoreget_vectorstore()resultsvectorstore.similarity_search_with_relevance_scores(query,ktop_k)retrieved[]fordoc,scoreinresults:retrieved.append({content:doc.page_content,source:doc.metadata.get(source_file,unknown),score:round(score,4),})returnretrieved4.4 生成器# generator.pyLLM 生成模块fromlangchain_openaiimportChatOpenAIfromconfigimportLLM_API_KEY,LLM_API_BASE,LLM_MODEL# 系统提示词引导模型基于检索内容回答SYSTEM_PROMPT你是一个专业的知识库问答助手。请严格根据以下检索到的上下文信息来回答用户的问题。 要求 1. 回答必须基于上下文内容不要编造信息 2. 如果上下文中没有相关信息请明确告知根据知识库内容未找到相关信息 3. 回答时请引用信息来源文件名 4. 保持回答简洁、准确、有条理 检索到的上下文信息 {context} defget_llm():初始化 LLMreturnChatOpenAI(modelLLM_MODEL,api_keyLLM_API_KEY,base_urlLLM_API_BASE,temperature0.1,# 低温度保证回答稳定max_tokens2000,)defgenerate_answer(query:str,retrieved_docs:list)-str: 基于检索结果生成回答 # 拼接上下文context_parts[]fori,docinenumerate(retrieved_docs,1):context_parts.append(f[片段{i}] 来源:{doc[source]}\n内容:{doc[content]})context\n\n.join(context_parts)# 构建提示system_promptSYSTEM_PROMPT.format(contextcontext)user_promptf用户问题:{query}# 调用 LLMllmget_llm()responsellm.invoke([{role:system,content:system_prompt},{role:user,content:user_prompt},])returnresponse.content4.5 主程序# main.pyRAG 智能问答系统主入口fromretrieverimportretrievefromgeneratorimportgenerate_answerdefrag_query(question:str)-str:完整的 RAG 问答流程print(f\n{*50})print(f问题:{question})print(f{*50})# Step 1: 检索print(\n[1/2] 正在检索知识库...)retrievedretrieve(question)print(f检索到{len(retrieved)}个相关片段:)fori,docinenumerate(retrieved,1):print(f 片段{i}(相似度:{doc[score]}):{doc[content][:80]}...)# Step 2: 生成print(\n[2/2] 正在生成回答...)answergenerate_answer(question,retrieved)print(f\n回答:{answer})print(f{*50}\n)returnanswerdefinteractive_mode():交互式问答循环print(*50)print(RAG 智能问答系统已启动)print(输入问题开始问答输入 quit 退出)print(*50)whileTrue:questioninput(\n你的问题: ).strip()ifquestion.lower()in(quit,exit,q):print(再见)breakifnotquestion:continuerag_query(question)if__name____main__:interactive_mode()五、运行效果5.1 第一步文档入库将你的文档PDF、TXT、Markdown放入data/目录然后执行python ingest.py输出示例 开始文档入库流程 [加载] ./data/产品手册.pdf [加载] ./data/FAQ.txt [加载] ./data/技术文档.md 共加载 15 个文档页/段 切分为 42 个文本块 向量数据库已保存到 ./vectorstore 文档入库完成 5.2 第二步启动问答python main.py交互示例你的问题: 产品的保修期是多久 [1/2] 正在检索知识库... 检索到 3 个相关片段: 片段1 (相似度: 0.8923): 本产品自购买之日起享受12个月免费保修服务... 片段2 (相似度: 0.7841): 保修范围不包括人为损坏和自然灾害造成的故障... 片段3 (相似度: 0.6512): 如需保修服务请联系就近售后服务中心... [2/2] 正在生成回答... 回答: 根据产品手册本产品自购买之日起享受12个月免费保修服务。 保修范围不包括人为损坏和自然灾害造成的故障。如需保修服务 请联系就近售后服务中心。 来源: 产品手册.pdf六、进阶优化技巧6.1 检索质量优化问题简单的向量相似度检索可能会召回语义相近但答非所问的内容。方案一混合检索Hybrid Search结合关键词检索BM25和语义检索向量取长补短fromlangchain_community.retrieversimportBM25Retrieverfromlangchain.retrieversimportEnsembleRetrieverdefhybrid_retrieve(query,top_k3):混合检索BM25 向量检索# BM25 关键词检索bm25_retrieverBM25Retriever.from_documents(all_chunks)bm25_retriever.ktop_k# 向量语义检索vector_retrieverget_vectorstore().as_retriever(search_kwargs{k:top_k})# 集成检索器各占 50% 权重ensembleEnsembleRetriever(retrievers[bm25_retriever,vector_retriever],weights[0.5,0.5],)returnensemble.invoke(query)方案二查询重写Query Rewriting用 LLM 将用户的口语化问题改写为更适合检索的形式defrewrite_query(original_query:str)-str:用 LLM 重写查询提升检索效果promptf请将以下用户问题改写为更适合检索的简洁关键词形式保留核心语义 原始问题:{original_query}改写后:llmget_llm()returnllm.invoke(prompt).content.strip()方案三重排序Reranking先粗检索召回更多候选如 Top-20再用更精准的模型重排序取 Top-3fromsentence_transformersimportCrossEncoderdefrerank(query:str,candidates:list,top_k:int3):使用 CrossEncoder 对候选结果重排序modelCrossEncoder(BAAI/bge-reranker-large)pairs[(query,doc[content])fordocincandidates]scoresmodel.predict(pairs)rankedsorted(zip(candidates,scores),keylambdax:x[1],reverseTrue)return[docfordoc,_inranked[:top_k]]6.2 分块策略优化不同的分块策略对检索效果影响巨大策略适用场景优点缺点固定长度分块通用场景实现简单可能切断语义按段落/标题分块结构化文档语义完整块大小不均匀递归字符分块混合文档灵活适配需调参语义分块长文/论文语义最优计算开销大6.3 更多优化方向多路召回同时检索多个粒度的分块如 256/512/1024 字符上下文压缩对检索结果做摘要后再喂给 LLM节省 token对话记忆支持多轮对话理解上下文中的指代关系流式输出使用 SSE 实现打字机效果提升用户体验引用溯源在回答中标注每个论断的来源文档和位置七、总结本文从 RAG 的核心原理出发完整实现了一个本地知识库问答系统。回顾关键要点RAG 检索 生成用检索解决知识更新问题用 LLM 解决理解表达问题文档分块是影响检索质量的第一道关卡需要根据文档类型选择策略Embedding 模型直接决定语义检索的精度中文场景推荐 bge 系列混合检索 重排序是生产级 RAG 的标配能显著提升召回质量Prompt 工程不可忽视好的系统提示词能有效抑制幻觉下一步建议尝试将系统部署为 Web 服务FastAPI Streamlit/Gradio接入更多文档类型Word、Excel、网页爬取添加文档管理界面上传、删除、重建索引引入评估体系RAGAS 等框架量化检索和生成质量本文完整代码已开源欢迎 Star 和交流。如果你在实践过程中遇到问题欢迎在评论区讨论参考资料LangChain 官方文档ChromaDB 文档BGE Embedding 模型RAG 论文Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks