当前位置:首页 > AI > 正文内容

RAG 知识库搭建实战完全指南:从零构建智能问答系统

廖万里18小时前AI1

RAG(Retrieval-Augmented Generation,检索增强生成)是当前AI应用开发的核心技术,它通过将检索系统与生成模型结合,让大语言模型能够利用外部知识库进行精准回答,有效解决了模型知识截止、幻觉问题等痛点。

一、RAG 核心概念

RAG 是一种将信息检索与文本生成相结合的技术架构。传统的LLM(大语言模型)存在以下局限:

1. 知识截止问题:模型训练数据有时间限制,无法回答最新问题

2. 幻觉问题:面对不确定的问题,模型可能编造错误信息

3. 领域知识缺失:通用模型缺乏特定领域的专业知识

RAG 通过引入外部知识库解决了这些问题。其核心流程是:

用户提问 → 向量检索 → 获取相关文档 → 结合文档生成答案

这种方式让模型能够基于真实、可溯源的知识进行回答,大幅提升了准确性和可信度。

二、RAG 技术架构

一个完整的 RAG 系统包含以下核心组件:

1. 文档处理模块

- 文档加载:支持 PDF、Word、Markdown 等多种格式

- 文本切分:将长文档切分为适当大小的片段

- 元数据管理:保留文档来源、页码等信息

2. 向量化模块

- Embedding 模型:将文本转换为向量表示

- 维度选择:通常 768-1536 维

- 多语言支持:处理中英文混合场景

3. 向量数据库

- 存储向量:高效存储和索引文档向量

- 相似度检索:快速找到最相关的文档片段

- 持久化:数据持久保存,支持增量更新

4. 生成模块

- Prompt 构建:将检索结果与用户问题组合

- LLM 调用:使用大模型生成最终答案

- 答案优化:添加引用来源、格式化输出

三、向量数据库选型

向量数据库是 RAG 系统的核心基础设施。以下是主流选择:

1. Chroma

- 特点:轻量级、易于上手、支持本地运行

- 适用:小型项目、原型验证、个人使用

- 优点:零配置启动、Python 原生支持

2. FAISS (Facebook AI Similarity Search)

- 特点:Meta 开源、高性能、纯内存运行

- 适用:大规模向量检索、对性能要求高的场景

- 优点:支持 GPU 加速、索引类型丰富

3. Milvus

- 特点:企业级、分布式、高可用

- 适用:生产环境、大规模部署

- 优点:支持水平扩展、完善的监控体系

4. Pinecone

- 特点:全托管服务、免运维

- 适用:快速上线、无运维需求

- 优点:开箱即用、自动扩展

本文以 Chroma 为例进行实战演示,因为它上手最简单,适合学习和中小型项目。

四、文档切分策略

文档切分直接影响检索效果。常见的切分策略:

1. 固定长度切分

按字符数或 token 数切分,简单但可能切断语义。

from langchain.text_splitter import CharacterTextSplitter

# 创建文本切分器
text_splitter = CharacterTextSplitter(
    chunk_size=500,      # 每个片段500字符
    chunk_overlap=50,    # 片段间重叠50字符
    separator="\n\n"     # 按段落切分
)

# 执行切分
chunks = text_splitter.split_text(long_text)
print(f"切分后得到 {len(chunks)} 个片段")

2. 递归字符切分

按层级结构(段落 → 句子 → 词)递归切分,保持语义完整性。

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 递归切分器,优先按段落、句子、词切分
recursive_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)

chunks = recursive_splitter.split_text(document)
for i, chunk in enumerate(chunks[:3]):
    print(f"片段 {i+1}: {chunk[:100]}...")

3. 语义切分

使用嵌入模型计算句子相似度,在语义变化处切分。

from langchain_experimental.text_splitters import SemanticChunker
from langchain.embeddings import HuggingFaceEmbeddings

# 使用语义切分器
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)

semantic_splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile"
)

chunks = semantic_splitter.split_text(document)

最佳实践建议:

- chunk_size:500-1000 字符(中文),200-500 tokens(英文)

- chunk_overlap:chunk_size 的 10%-20%

- 保留文档结构信息作为元数据

五、嵌入模型选择

嵌入模型负责将文本转换为向量,选择要点:

1. OpenAI Embeddings

from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    openai_api_key="your-api-key"
)

text = "这是一段测试文本"
vector = embeddings.embed_query(text)
print(f"向量维度: {len(vector)}")

2. 本地嵌入模型(推荐)

使用 HuggingFace 的开源模型,无需 API 费用:

from langchain.embeddings import HuggingFaceEmbeddings

# 使用中文友好的嵌入模型
model_name = "BAAI/bge-large-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}

embeddings = HuggingFaceEmbeddings(
    model_name=model_name,
    model_kwargs=model_kwargs,
    encode_kwargs=encode_kwargs
)

texts = ["第一段文本", "第二段文本", "第三段文本"]
vectors = embeddings.embed_documents(texts)
print(f"生成了 {len(vectors)} 个向量")

中文推荐模型:

- BAAI/bge-large-zh-v1.5:性能最强,1024 维

- BAAI/bge-small-zh-v1.5:轻量级,512 维

- sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2:多语言支持

六、实战:构建完整 RAG 系统

下面我们使用 Python 实现一个完整的本地 RAG 智能问答系统。

步骤1:安装依赖

# pip install langchain langchain-community chromadb sentence-transformers
# pip install unstructured markdown PyPDF2

步骤2:初始化向量数据库

import os
from langchain_community.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader, PyPDFLoader

class RAGKnowledgeBase:
    """RAG 知识库管理类"""
    
    def __init__(self, persist_directory="./chroma_db"):
        self.persist_directory = persist_directory
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-large-zh-v1.5",
            model_kwargs={"device": "cpu"},
            encode_kwargs={"normalize_embeddings": True}
        )
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,
            chunk_overlap=50,
            separators=["\n\n", "\n", "。", "!", "?", " ", ""]
        )
        self.vectorstore = None
    
    def add_documents(self, file_paths):
        documents = []
        for file_path in file_paths:
            if file_path.endswith(".pdf"):
                loader = PyPDFLoader(file_path)
            else:
                loader = TextLoader(file_path, encoding="utf-8")
            docs = loader.load()
            documents.extend(docs)
        
        chunks = self.text_splitter.split_documents(documents)
        print(f"文档切分完成,共 {len(chunks)} 个片段")
        
        if self.vectorstore is None:
            self.vectorstore = Chroma.from_documents(
                documents=chunks,
                embedding=self.embeddings,
                persist_directory=self.persist_directory
            )
        else:
            self.vectorstore.add_documents(chunks)
        
        print(f"已添加 {len(file_paths)} 个文档到知识库")
    
    def search(self, query, k=5):
        if self.vectorstore is None:
            raise ValueError("知识库未初始化")
        return self.vectorstore.similarity_search(query, k=k)

步骤3:实现问答功能

from langchain.chains import RetrievalQA
from langchain_community.llms import Ollama
from langchain.prompts import PromptTemplate

class RAGQueryEngine:
    """RAG 问答引擎"""
    
    def __init__(self, knowledge_base, llm_model="qwen2.5:7b"):
        self.knowledge_base = knowledge_base
        self.prompt_template = PromptTemplate(
            template="""你是一个专业的知识库问答助手。请根据参考信息回答问题。
参考信息:
{context}
用户问题:{question}
要求:只基于参考信息回答,不要编造内容。回答:""",
            input_variables=["context", "question"]
        )
        
        try:
            self.llm = Ollama(model=llm_model)
        except Exception as e:
            print(f"Ollama 初始化失败: {e}")
            self.llm = None
        
        if self.llm:
            self.qa_chain = RetrievalQA.from_chain_type(
                llm=self.llm,
                chain_type="stuff",
                retriever=knowledge_base.vectorstore.as_retriever(
                    search_kwargs={"k": 4}
                ),
                return_source_documents=True
            )
    
    def query(self, question):
        if not self.llm:
            return {"answer": "LLM 未初始化", "sources": []}
        result = self.qa_chain.invoke({"query": question})
        return {
            "answer": result["result"],
            "sources": [{"content": doc.page_content[:200]} 
                       for doc in result["source_documents"]]
        }

七、性能优化技巧

1. 混合检索优化

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# 向量检索器
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# BM25 关键词检索器
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5

# 混合检索器
ensemble_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, bm25_retriever],
    weights=[0.5, 0.5]
)

2. 重排序优化

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-large")

def rerank_results(query, documents, top_k=5):
    pairs = [(query, doc.page_content) for doc in documents]
    scores = reranker.predict(pairs)
    scored_docs = list(zip(scores, documents))
    scored_docs.sort(key=lambda x: x[0], reverse=True)
    return [doc for _, doc in scored_docs[:top_k]]

八、部署建议

生产环境架构:

- 前端:FastAPI + Swagger 文档

- 向量库:Milvus 或 Qdrant(集群部署)

- LLM:vLLM 部署开源模型

- 缓存:Redis 缓存热门查询

监控指标:

- 检索延迟:P50/P95/P99

- 检索准确率:人工评估或 A/B 测试

- 用户满意度:点赞率、反馈统计

总结

RAG 技术是当前 AI 应用落地的关键基础设施。通过本文学习,你应该掌握:

1. 核心概念:理解 RAG 如何解决 LLM 的局限性

2. 技术架构:了解文档处理、向量化、检索、生成各模块

3. 实战能力:能够使用 Python 构建本地 RAG 系统

4. 优化技巧:掌握混合检索、重排序、缓存等优化方法

建议从简单的 Chroma + 本地 LLM 开始,逐步迭代到生产级架构。RAG 的价值在于让 AI 能够基于真实知识回答问题,是企业 AI 落地的重要方向。

本文链接:https://www.kkkliao.cn/?id=918 转载需授权!

分享到:

版权声明:本文由廖万里的博客发布,如需转载请注明出处。


发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。