前言

💡 痛点:个人积累了几 GB 的文档、笔记、PDF,但找不到想要的信息?

🎯 解决方案:搭建一个本地 RAG 系统,让 AI 帮你从所有文档中精准检索答案。

RAG(Retrieval-Augmented Generation) 是目前最实用的 AI 应用架构之一。本文将带你从零搭建一个生产级个人知识库系统,支持:

  • 📚 多格式文档(PDF、Markdown、Word、网页)
  • 🔍 语义搜索(不是关键词匹配,而是理解意图)
  • 💬 对话式问答(带上下文记忆)
  • 🚀 本地部署(数据不出本地,隐私安全)
  • 📊 生产级优化(分块策略、重排序、评估体系)

一、RAG 系统架构详解

1.1 什么是 RAG?

无上下文

用户提问

传统 AI 问答

AI: 抱歉,我没有信息...

用户提问

RAG 系统

🔍 检索阶段
从知识库找相关文档

📄 增强阶段
将文档作为上下文

🤖 生成阶段
基于上下文回答

✅ 精准回答

传统 AI 问答 vs RAG 对比:

维度 传统 AI RAG 系统
📚 知识来源 训练时的固定数据 实时检索的最新文档
🎯 准确性 ⚠️ 容易幻觉 ✅ 基于真实文档
🔄 更新成本 需重新训练 只需更新知识库
💰 成本 高(大模型) 低(小模型 + 检索)

1.2 RAG 完整工作流程

🤖 LLM (大语言模型) 📊 Vector DB (向量数据库) 🔍 Retriever (检索器) 🖥️ UI 界面 👤 用户 🤖 LLM (大语言模型) 📊 Vector DB (向量数据库) 🔍 Retriever (检索器) 🖥️ UI 界面 👤 用户 检索阶段 (Retrieval) 生成阶段 (Generation) 提问:"年假政策是什么?" 传递查询 相似度搜索 返回 Top-K 相关文档 🔄 重排序 (Reranking) 携带上下文 + 问题 🧠 生成答案 返回答案 + 来源 显示结果

1.3 系统架构图

渲染错误: Mermaid 渲染失败: Parse error on line 3: ... A1[🌐 Web UI
(Streamlit)] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

1.4 技术选型对比

30% 30% 25% 15% 技术栈选择理由 LangChain (生态完善) Chroma (易部署) Ollama (本地免费) Streamlit (快速原型)

详细对比表:

组件 可选方案 推荐选择 理由
编排框架 LangChain / LlamaIndex / Haystack 🥇 LangChain 0.3+ 生态最完善,社区活跃
向量数据库 Chroma / Pinecone / PGVector / Qdrant 📊 Chroma(本地)
🐘 PGVector(生产)
本地免费,生产可扩展
Embedding 模型 OpenAI / Ollama / Sentence Transformers 🔢 Ollama Embeddings 本地运行,隐私安全
LLM GPT-4 / Claude / Ollama 🤖 Ollama (Llama 3.1) 本地运行,无 API 成本
文档加载 LangChain / Unstructured 📄 LangChain Loaders 支持 40+ 格式
Web UI Streamlit / Gradio / React 🌐 Streamlit 快速原型,Python 原生

二、环境准备与安装

2.1 系统要求检查清单

Yes

No

Yes

No

Yes

No

💻 硬件检查

CPU ≥ 4核?

RAM ≥ 8GB?

❌ 不满足

磁盘 ≥ 10GB?

✅ 满足要求

推荐配置 vs 最低配置:

配置项 最低配置 推荐配置 生产配置
💻 CPU 4 核 8 核 16 核+
🧠 RAM 8GB 16GB 32GB+
🎮 GPU ❌ 不需要 ⚡ 可选 (加速推理) 🚀 NVIDIA A100
💾 磁盘 10GB 50GB 200GB+
🕒 响应速度 5-10秒 1-3秒 < 1秒

2.2 安装 Ollama(本地 LLM 运行时)

Ollama 可以让你在本地运行开源大模型,无需 API Key。

macOS

Linux

Windows

下载 Ollama

系统类型?

brew install ollama

curl install.sh

WSL2 + install.sh

✅ 安装完成

ollama serve

✅ 服务启动
端口: 11434

安装步骤:

macOS/Linux:

# 一键安装
curl -fsSL https://ollama.com/install.sh | sh

# 验证安装
ollama --version
# 应显示:ollama version 0.5.7 或更高

# 启动 Ollama 服务(默认端口 11434)
ollama serve &

Windows (WSL2):

# 在 WSL2 中执行(同上)
curl -fsSL https://ollama.com/install.sh | sh

手动下载(如果脚本失败):

  • 访问 https://ollama.com/download
  • 下载对应系统版本
  • 安装后运行 ollama serve

2.3 下载 LLM 和 Embedding 模型

对话

对话-高质量

对话-快速

向量化

ollama pull

选择模型

llama3.1:8b
4.7GB

llama3.1:70b
40GB

phi3:mini
1.7GB

nomic-embed-text
274MB

✅ 模型下载完成

执行命令:

# 1. 下载对话模型(Llama 3.1 8B,约 4.7GB)
ollama pull llama3.1:8b

# 2. 下载 Embedding 模型(用于向量化文本)
ollama pull nomic-embed-text

# 验证模型
ollama list
# 应显示:
# NAME              SIZE   MODIFIED
# llama3.1:8b      4.7 GB  ...
# nomic-embed-text  274 MB  ...

模型选择建议:

用途 推荐模型 大小 速度 推荐场景
🤖 对话(高质量) llama3.1:70b 40GB 🐢 慢 生产环境 + GPU
🤖 对话(平衡) llama3.1:8b 4.7GB 🚀 快 ✅ 本地开发
🤖 对话(快速) phi3:mini 1.7GB ⚡ 极快 快速原型
🔢 Embedding nomic-embed-text 274MB 🚀 快 ✅ 推荐

2.4 安装 Python 依赖

创建虚拟环境

python3 -m venv rag-env

激活环境

pip install 依赖

✅ 环境准备完成

操作步骤:

# 创建虚拟环境(推荐)
python3 -m venv rag-env
source rag-env/bin/activate  # macOS/Linux
# rag-env\Scripts\activate  # Windows

# 安装依赖
pip install -U pip
pip install \
  langchain>=0.3.0 \
  langchain-ollama>=0.2.0 \
  langchain-community>=0.3.0 \
  chromadb>=0.5.0 \
  sentence-transformers>=3.0.0 \
  pypdf>=1.13.0 \
  python-docx>=1.1.0 \
  streamlit>=1.39.0 \
  tiktoken>=0.8.0 \
  rank-bm25>=0.2.2 \
  --index-url https://pypi.tuna.tsinghua.edu.cn/simple

依赖说明表:

包名 用途 是否必需
langchain RAG 编排框架核心 ✅ 必需
langchain-ollama Ollama 集成(LLM + Embeddings) ✅ 必需
chromadb 本地向量数据库 ✅ 必需
pypdf PDF 文档解析 ⚠️ 按需
python-docx Word 文档解析 ⚠️ 按需
streamlit Web UI 框架 ⚠️ 可选
tiktoken Token 计数(分块优化) ⚠️ 推荐

三、核心功能实现

3.1 项目结构

personal-rag/

📁 data/
原始文档

📁 vectorstore/
向量数据库文件

📁 src/
源代码

📄 app.py
Streamlit Web UI

📄 cli.py
命令行界面

📄 config.yaml
配置文件

📄 pdfs/

📄 markdown/

📄 web/

📄 document_loader.py

📄 text_splitter.py

📄 embeddings.py

📄 vector_store.py

📄 retriever.py

📄 qa_chain.py

完整目录结构:

personal-rag/
├── 📁 data/                      # 原始文档存放目录
│   ├── 📁 pdfs/                 # PDF 文件
│   ├── 📁 markdown/             # Markdown 文件
│   └── 📁 web/                 # 网页存档
├── 📁 vectorstore/              # Chroma 向量数据库文件
├── 📁 src/                     # 源代码
│   ├── 📄 __init__.py
│   ├── 📄 document_loader.py    # 文档加载模块
│   ├── 📄 text_splitter.py      # 文本分块模块
│   ├── 📄 embeddings.py         # Embedding 封装
│   ├── 📄 vector_store.py       # 向量数据库操作
│   ├── 📄 retriever.py         # 检索器(含重排序)
│   └── 📄 qa_chain.py          # 问答链
├── 📄 app.py                    # Streamlit Web UI
├── 📄 cli.py                    # 命令行界面
├── 📄 config.yaml               # 配置文件
├── 📄 requirements.txt          # 依赖清单
└── 📄 README.md                # 项目文档

3.2 配置文件详解

创建 config.yaml

# config.yaml - RAG 系统配置

# ═════════════════════════════════════════
# 🤖 LLM 配置
# ═════════════════════════════════════════
llm:
  provider: "ollama"          # ollama / openai / anthropic
  model: "llama3.1:8b"
  base_url: "http://localhost:11434/v1"
  temperature: 0.1              # 创造性(0=精确,1=创造性)
  max_tokens: 2048

# ═════════════════════════════════════════
# 🔢 Embedding 配置
# ═════════════════════════════════════════
embedding:
  provider: "ollama"
  model: "nomic-embed-text"
  base_url: "http://localhost:11434/api"

# ═════════════════════════════════════════
# ✂️ 文本分块配置
# ═════════════════════════════════════════
text_splitter:
  chunk_size: 512           # 每个块的最大字符数
  chunk_overlap: 50         # 块之间的重叠字符数
  separator: "\n\n"        # 分割符(优先按段落分割)

# ═════════════════════════════════════════
# 🔍 检索配置
# ═════════════════════════════════════════
retriever:
  search_type: "mmr"       # similarity / mmr(最大边际相关性)
  k: 5                      # 检索返回的文档数
  fetch_k: 20               # MMR 候选集大小
  lambda_mult: 0.5          # MMR 多样性参数

# ═════════════════════════════════════════
# 🔄 重排序配置
# ═════════════════════════════════════════
reranking:
  enabled: true
  model: "BAAI/bge-reranker-base"  # 重排序模型
  top_n: 3                            # 重排序后保留的文档数

# ═════════════════════════════════════════
# 📊 向量数据库配置
# ═════════════════════════════════════════
vectorstore:
  provider: "chroma"        # chroma / pinecone / pgvector
  persist_directory: "./vectorstore"
  collection_name: "personal_knowledge_base"

# ═════════════════════════════════════════
# 📁 文件路径配置
# ═════════════════════════════════════════
paths:
  data_dir: "./data"
  pdf_dir: "./data/pdfs"
  md_dir: "./data/markdown"
  web_dir: "./data/web"

配置参数详解:

参数 默认值 说明 调整建议
chunk_size 512 分块大小 📝 中文:256-512
📝 英文:512-1024
chunk_overlap 50 块重叠 设为 chunk_size 的 10%-20%
retriever.k 5 检索文档数 🎯 精准:3-5
🎯 全面:10-15
reranking.top_n 3 重排序后保留 通常 ≤ retriever.k

3.3 文档加载模块

创建 src/document_loader.py

"""
文档加载模块 - 支持多种格式的文档加载
"""
import os
from pathlib import Path
from typing import List, Optional
from langchain.document import Document
from langchain_community.document_loaders import (
    PyPDFLoader,
    Docx2txtLoader,
    TextLoader,
    UnstructuredMarkdownLoader,
    WebBaseLoader,
    CSVLoader,
    JSONLoader,
)


class DocumentLoader:
    """统一的文档加载器"""

    def __init__(self, data_dir: str = "./data"):
        self.data_dir = Path(data_dir)
        self.supported_extensions = {
            ".pdf": self._load_pdf,
            ".docx": self._load_docx,
            ".txt": self._load_txt,
            ".md": self._load_markdown,
            ".csv": self._load_csv,
            ".json": self._load_json,
        }

    def load_file(self, file_path: str) -> List[Document]:
        """
        加载单个文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            加载的文档列表
        """
        path = Path(file_path)
        extension = path.suffix.lower()

        if extension not in self.supported_extensions:
            raise ValueError(f"不支持的文件格式: {extension}")

        loader_func = self.supported_extensions[extension]
        return loader_func(str(path))

    def load_directory(self, directory: Optional[str] = None) -> List[Document]:
        """
        加载整个目录下的所有支持的文件
        
        Args:
            directory: 目录路径,默认为 self.data_dir
            
        Returns:
            加载的文档列表
        """
        target_dir = Path(directory) if directory else self.data_dir
        documents = []

        for file_path in target_dir.rglob("*"):
            if file_path.is_file() and file_path.suffix.lower() in self.supported_extensions:
                try:
                    docs = self.load_file(str(file_path))
                    # 添加元数据
                    for doc in docs:
                        doc.metadata["source"] = str(file_path)
                        doc.metadata["filename"] = file_path.name
                    documents.extend(docs)
                    print(f"✅ 已加载: {file_path.name}")
                except Exception as e:
                    print(f"❌ 加载失败 {file_path.name}: {e}")

        return documents

    def load_web_page(self, url: str) -> List[Document]:
        """
        加载网页内容
        
        Args:
            url: 网页 URL
            
        Returns:
            加载的文档列表
        """
        loader = WebBaseLoader(url)
        documents = loader.load()
        for doc in documents:
            doc.metadata["source"] = url
            doc.metadata["type"] = "web"
        return documents

    def _load_pdf(self, file_path: str) -> List[Document]:
        """加载 PDF 文件"""
        loader = PyPDFLoader(file_path)
        return loader.load()

    def _load_docx(self, file_path: str) -> List[Document]:
        """加载 Word 文档"""
        loader = Docx2txtLoader(file_path)
        return loader.load()

    def _load_txt(self, file_path: str) -> List[Document]:
        """加载纯文本文件"""
        loader = TextLoader(file_path, encoding="utf-8")
        return loader.load()

    def _load_markdown(self, file_path: str) -> List[Document]:
        """加载 Markdown 文件"""
        loader = UnstructuredMarkdownLoader(file_path)
        return loader.load()

    def _load_csv(self, file_path: str) -> List[Document]:
        """加载 CSV 文件"""
        loader = CSVLoader(file_path)
        return loader.load()

    def _load_json(self, file_path: str) -> List[Document]:
        """加载 JSON 文件"""
        loader = JSONLoader(
            file_path=file_path,
            jq_schema=".",  # 加载整个 JSON
            text_content=False
        )
        return loader.load()


# 使用示例
if __name__ == "__main__":
    loader = DocumentLoader("./data")
    
    # 加载单个文件
    docs = loader.load_file("./data/pdfs/example.pdf")
    print(f"加载了 {len(docs)} 个文档片段")
    
    # 加载整个目录
    all_docs = loader.load_directory()
    print(f"总共加载了 {len(all_docs)} 个文档片段")

支持的文档格式:

30% 20% 20% 15% 10% 5% 支持的文件格式 PDF (.pdf) Word (.docx) Markdown (.md) 纯文本 (.txt) CSV (.csv) JSON (.json)

3.4 文本分块模块

创建 src/text_splitter.py

"""
文本分块模块 - 将长文档分割成合适大小的块
"""
from typing import List
from langchain.document import Document
from langchain.text_splitter import (
    RecursiveCharacterTextSplitter,
    MarkdownTextSplitter,
    PythonCodeTextSplitter,
)


class TextSplitter:
    """智能文本分块器"""

    def __init__(
        self,
        chunk_size: int = 512,
        chunk_overlap: int = 50,
        separator: str = "\n\n"
    ):
        """
        初始化文本分块器
        
        Args:
            chunk_size: 每个块的最大字符数
            chunk_overlap: 块之间的重叠字符数(保持上下文连贯)
            separator: 分割符优先级
        """
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap
        self.separator = separator

    def split_documents(
        self,
        documents: List[Document],
        doc_type: str = "general"
    ) -> List[Document]:
        """
        将文档列表分块
        
        Args:
            documents: 原始文档列表
            doc_type: 文档类型(general/markdown/python)
            
        Returns:
            分块后的文档列表
        """
        if doc_type == "markdown":
            splitter = MarkdownTextSplitter(
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap
            )
        elif doc_type == "python":
            splitter = PythonCodeTextSplitter(
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap
            )
        else:
            splitter = RecursiveCharacterTextSplitter(
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap,
                separators=["\n\n", "\n", "。", ";", " ", ""]
            )

        split_docs = splitter.split_documents(documents)
        
        # 添加分块元数据
        for i, doc in enumerate(split_docs):
            doc.metadata["chunk_id"] = i
            doc.metadata["chunk_size"] = len(doc.page_content)
            
        print(f"✅ 分块完成: {len(documents)} 个文档 → {len(split_docs)} 个块")
        return split_docs

    def estimate_tokens(self, text: str) -> int:
        """
        估算 token 数量(用于分块大小规划)
        
        Args:
            text: 文本内容
            
        Returns:
            估算的 token 数
        """
        # 粗略估算:1 个中文字符 ≈ 2 个 token,1 个英文单词 ≈ 1.3 个 token
        chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
        english_words = len(text.split())
        return int(chinese_chars * 2 + english_words * 1.3)

分块策略可视化:

📄 长文档
10000字

✂️ 分块器

📦 块1
512字

📦 块2
512字

📦 块3
512字

...

🔗 重叠50字

分块大小选择指南:

chunk_size 适用场景 优点 缺点
🎯 128-256 精确检索 精准,噪声少 可能丢失上下文
⚖️ 512-1024 通用场景 平衡 ✅ 推荐
📚 2048+ 摘要生成 上下文完整 噪声多,成本高

chunk_overlap 的作用:

┌─────────────────────────────────────┐
│ 块 1: "...人工智能是...['未来'] 发展的方向..."     │
│                          ↑ chunk_overlap=50      │
│ 块 2: "['未来'] 发展的方向...将会深刻影响..."     │
└─────────────────────────────────────┘
→ 避免分割关键句子,保持语义连贯

3.5 Embedding 模块

创建 src/embeddings.py

"""
Embedding 模块 - 文本向量化
"""
from langchain.ollama import OllamaEmbeddings
from langchain.embeddings.base import Embeddings


class EmbeddingManager:
    """Embedding 管理器"""

    def __init__(
        self,
        model: str = "nomic-embed-text",
        base_url: str = "http://localhost:11434/api"
    ):
        """
        初始化 Embedding 模型
        
        Args:
            model: Embedding 模型名称(Ollama 拉取的模型)
            base_url: Ollama API 地址
        """
        self.model = model
        self.base_url = base_url
        self.embeddings = OllamaEmbeddings(
            model=model,
            base_url=base_url
        )

    def get_embeddings(self) -> Embeddings:
        """
        获取 LangChain Embeddings 对象
        
        Returns:
            LangChain Embeddings 实例
        """
        return self.embeddings

    def embed_query(self, text: str) -> List[float]:
        """
        将查询文本向量化(用于检索)
        
        Args:
            text: 查询文本
            
        Returns:
            向量表示
        """
        return self.embeddings.embed_query(text)

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        """
        将文档列表向量化(用于存储)
        
        Args:
            texts: 文档文本列表
            
        Returns:
            向量列表
        """
        return self.embeddings.embed_documents(texts)

Embedding 模型对比:

中文

英文

多语言

选择 Embedding 模型

使用场景?

bge-large-zh-v1.5
维度: 1024
质量: ⭐⭐⭐⭐⭐

nomic-embed-text
维度: 768
速度: ⭐⭐⭐⭐⭐

all-MiniLM-L6-v2
维度: 384
速度: ⭐⭐⭐⭐⭐

✅ 推荐

详细对比表:

模型 维度 速度 质量 本地运行 推荐场景
nomic-embed-text 768 ⚡ 快 ⭐⭐⭐⭐ ✅ 英文场景
all-MiniLM-L6-v2 384 ⚡⚡ 极快 ⭐⭐⭐ 快速原型
bge-large-zh-v1.5 1024 🚀 中 ⭐⭐⭐⭐⭐ ✅ 中文场景
text-embedding-3-small 1536 ⚡ 快 ⭐⭐⭐⭐ ❌ 需 API 云端部署

3.6 向量数据库模块

创建 src/vector_store.py

"""
向量数据库模块 - Chroma 封装
"""
from pathlib import Path
from typing import List, Optional
from langchain.document import Document
from langchain.vectorstores import Chroma
from langchain.embeddings.base import Embeddings


class VectorStoreManager:
    """向量数据库管理器"""

    def __init__(
        self,
        embeddings: Embeddings,
        persist_directory: str = "./vectorstore",
        collection_name: str = "personal_knowledge_base"
    ):
        """
        初始化向量数据库
        
        Args:
            embeddings: Embedding 模型
            persist_directory: 数据库持久化目录
            collection_name: 集合名称
        """
        self.embeddings = embeddings
        self.persist_directory = Path(persist_directory)
        self.collection_name = collection_name
        
        # 确保目录存在
        self.persist_directory.mkdir(parents=True, exist_ok=True)
        
        self.vectorstore = None

    def create_vectorstore(self, documents: List[Document]) -> Chroma:
        """
        创建向量数据库(首次索引)
        
        Args:
            documents: 分块后的文档列表
            
        Returns:
            Chroma 向量数据库实例
        """
        self.vectorstore = Chroma.from_documents(
            documents=documents,
            embedding=self.embeddings,
            persist_directory=str(self.persist_directory),
            collection_name=self.collection_name
        )
        print(f"✅ 向量数据库创建成功!共索引 {len(documents)} 个文档块")
        return self.vectorstore

    def load_vectorstore(self) -> Optional[Chroma]:
        """
        加载已有的向量数据库
        
        Returns:
            Chroma 向量数据库实例,如果不存在返回 None
        """
        if not self.persist_directory.exists():
            print("⚠️ 向量数据库不存在,请先创建")
            return None
            
        self.vectorstore = Chroma(
            persist_directory=str(self.persist_directory),
            embedding_function=self.embeddings,
            collection_name=self.collection_name
        )
        count = self.vectorstore._collection.count()
        print(f"✅ 向量数据库加载成功!当前有 {count} 个文档块")
        return self.vectorstore

    def add_documents(self, documents: List[Document]):
        """
        向已有数据库添加新文档
        
        Args:
            documents: 新文档列表
        """
        if self.vectorstore is None:
            raise ValueError("向量数据库未初始化,请先调用 create_vectorstore 或 load_vectorstore")
        
        self.vectorstore.add_documents(documents)
        print(f"✅ 已添加 {len(documents)} 个新文档块")

    def similarity_search(
        self,
        query: str,
        k: int = 5,
        filter: Optional[dict] = None
    ) -> List[Document]:
        """
        相似度搜索
        
        Args:
            query: 查询文本
            k: 返回的最相似文档数
            filter: 元数据过滤条件
            
        Returns:
            最相似的文档列表
        """
        if self.vectorstore is None:
            raise ValueError("向量数据库未初始化")
        
        return self.vectorstore.similarity_search(
            query=query,
            k=k,
            filter=filter
        )

    def similarity_search_with_score(
        self,
        query: str,
        k: int = 5
    ) -> List[tuple]:
        """
        带相似度分数的搜索
        
        Args:
            query: 查询文本
            k: 返回的最相似文档数
            
        Returns:
            (文档, 相似度分数) 元组列表
        """
        if self.vectorstore is None:
            raise ValueError("向量数据库未初始化")
        
        return self.vectorstore.similarity_search_with_score(
            query=query,
            k=k
        )

    def delete_collection(self):
        """删除整个集合(慎用)"""
        if self.vectorstore:
            self.vectorstore.delete_collection()
            print("✅ 集合已删除")

Chroma vs 其他向量数据库对比:

本地/小项目

中大型项目

大规模

超大规模

选择向量数据库

部署环境?

Chroma
✅ 极易部署
✅ 免费

PGVector
✅ 可扩展
⚠️ 需 PostgreSQL

Pinecone
✅ 全托管
❌ 付费

Milvus
✅ 高性能
⚠️ 部署复杂

✅ 推荐本地

✅ 推荐生产

详细对比表:

数据库 类型 部署难度 扩展性 推荐场景
Chroma 本地 ⭐ 极易 ⭐⭐ ✅ 本地/小项目
PGVector PostgreSQL 扩展 ⭐⭐ 易 ⭐⭐⭐⭐ ✅ 中大型项目
Pinecone 云服务 ⭐ 易 ⭐⭐⭐⭐⭐ 大规模云端
Qdrant 本地/云 ⭐⭐ 中 ⭐⭐⭐⭐ 高性能场景
Milvus 本地/云 ⭐⭐⭐ 难 ⭐⭐⭐⭐⭐ 超大规模

3.7 检索器模块(含重排序)

创建 src/retriever.py

"""
检索器模块 - 包含重排序等高级检索技术
"""
from typing import List
from langchain.document import Document
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import DocumentCompressorPipeline
from langchain_community.document_compressors import BgeRerank
from langchain.retrievers import MergerRetriever
from langchain.retrievers.multi_query import MultiQueryRetriever


class AdvancedRetriever:
    """高级检索器(含重排序、多查询等)"""

    def __init__(
        self,
        vectorstore,
        search_type: str = "mmr",
        k: int = 5,
        fetch_k: int = 20,
        lambda_mult: float = 0.5
    ):
        """
        初始化检索器
        
        Args:
            vectorstore: 向量数据库实例
            search_type: 搜索类型(similarity/mmr)
            k: 返回的文档数
            fetch_k: MMR 候选集大小
            lambda_mult: MMR 多样性参数(0=最多样,1=最相关)
        """
        self.vectorstore = vectorstore
        self.search_type = search_type
        self.k = k
        self.fetch_k = fetch_k
        self.lambda_mult = lambda_mult

    def get_basic_retriever(self):
        """获取基础检索器"""
        if self.search_type == "mmr":
            return self.vectorstore.as_retriever(
                search_type="mmr",
                search_kwargs={
                    "k": self.k,
                    "fetch_k": self.fetch_k,
                    "lambda_mult": self.lambda_mult
                }
            )
        else:
            return self.vectorstore.as_retriever(
                search_kwargs={"k": self.k}
            )

    def get_reranking_retriever(self, top_n: int = 3):
        """
        获取带重排序的检索器
        
        重排序(Reranking)的作用:
        - 基础检索可能返回不太相关的文档
        - 重排序模型会重新打分,提高最相关文档的排名
        
        Args:
            top_n: 重排序后保留的文档数
            
        Returns:
            带重排序的检索器
        """
        # 基础检索器
        base_retriever = self.get_basic_retriever()
        
        # 重排序模型(需提前下载:pip install sentence-transformers)
        reranker = BgeRerank(
            model="BAAI/bge-reranker-base",
            top_n=top_n
        )
        
        # 压缩管道
        pipeline = DocumentCompressorPipeline(
            transformers=[reranker]
        )
        
        # 上下文压缩检索器
        compression_retriever = ContextualCompressionRetriever(
            base_compressor=pipeline,
            base_retriever=base_retriever
        )
        
        return compression_retriever

    def get_multi_query_retriever(self, llm):
        """
        获取多查询检索器
        
        多查询检索的作用:
        - 自动生成多个不同角度的查询
        - 提高召回率
        
        Args:
            llm: 语言模型实例
            
        Returns:
            多查询检索器
        """
        return MultiQueryRetriever.from_llm(
            retriever=self.get_basic_retriever(),
            llm=llm
        )

检索优化技术栈:

用户查询

🔍 基础检索
Vector Similarity

🔄 重排序
Reranker

🎯 MMR 多样性采样

📊 Hybrid 检索
Vector + BM25

✨ 查询重写
LLM 优化查询

📚 多查询检索
多角度查询

✅ 最终文档

RAG 检索优化技术详解:

技术 原理 效果 难度
MMR 检索 多样性采样 避免返回重复内容 ⭐ 易
重排序 用交叉编码器重新打分 ⭐⭐⭐⭐⭐ 效果最好 ⭐⭐ 中
多查询检索 生成多个角度的查询 提高召回率 ⭐⭐ 中
Hybrid 检索 向量检索 + BM25 关键词 结合两者优势 ⭐⭐⭐ 难
元数据过滤 按时间/来源/作者过滤 提高精准度 ⭐ 易
查询重写 用 LLM 优化用户查询 理解用户真实意图 ⭐⭐ 中

3.8 问答链模块

创建 src/qa_chain.py

"""
问答链模块 - 构建 RAG 问答系统
"""
from typing import List, Dict, Any
from langchain.document import Document
from langchain.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.memory import ConversationBufferMemory
from langchain.chains import create_history_aware_retriever
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema import StrOutputParser


class RAGQuestionAnswering:
    """RAG 问答系统"""

    def __init__(
        self,
        llm,
        retriever,
        system_prompt: Optional[str] = None
    ):
        """
        初始化问答系统
        
        Args:
            llm: 语言模型实例
            retriever: 检索器实例
            system_prompt: 系统提示词(可选)
        """
        self.llm = llm
        self.retriever = retriever
        
        # 默认系统提示词
        self.system_prompt = system_prompt or """你是回答问题的助手。
使用以下检索到的上下文来回答问题。
如果不知道答案,就说不知道,不要编造答案。
保持答案简洁,最多 3-5 句话。

上下文:
{context}

问题:
{question}

回答:"""

    def create_basic_qa_chain(self):
        """
        创建基础 QA 链(无对话历史)
        
        Returns:
            QA 链
        """
        prompt = PromptTemplate(
            template=self.system_prompt,
            input_variables=["context", "question"]
        )
        
        # 创建文档合并链
        document_chain = create_stuff_documents_chain(
            llm=self.llm,
            prompt=prompt
        )
        
        # 创建检索 QA 链
        qa_chain = create_retrieval_chain(
            retriever=self.retriever,
            combine_docs_chain=document_chain
        )
        
        return qa_chain

    def create_conversational_qa_chain(self):
        """
        创建对话式 QA 链(带历史记忆)
        
        Returns:
            对话式 QA 链
        """
        # 对话历史感知检索器
        history_aware_retriever = create_history_aware_retriever(
            self.llm,
            self.retriever,
            PromptTemplate(
                template="根据对话历史和最新问题,生成一个独立的搜索查询。",
                input_variables=["chat_history", "input"]
            )
        )
        
        # QA 提示词
        qa_prompt = PromptTemplate(
            template="""根据以下上下文和对话历史回答问题:

上下文:{context}

对话历史:{chat_history}

问题:{input}

回答:""",
            input_variables=["context", "chat_history", "input"]
        )
        
        # 文档合并链
        document_chain = create_stuff_documents_chain(self.llm, qa_prompt)
        
        # 检索 QA 链
        qa_chain = create_retrieval_chain(
            history_aware_retriever,
            document_chain
        )
        
        return qa_chain

    def answer(self, question: str, chat_history: List[tuple] = None) -> Dict[str, Any]:
        """
        回答问题
        
        Args:
            question: 用户问题
            chat_history: 对话历史 [(human, ai), ...]
            
        Returns:
            包含答案和检索文档的字典
        """
        if chat_history:
            qa_chain = self.create_conversational_qa_chain()
            result = qa_chain.invoke({
                "input": question,
                "chat_history": chat_history
            })
        else:
            qa_chain = self.create_basic_qa_chain()
            result = qa_chain.invoke({"input": question})
        
        return {
            "answer": result["answer"],
            "source_documents": result["context"]
        }

提示词工程详解:

📝 提示词设计

1️⃣ 明确角色定位

2️⃣ 指定输出格式

3️⃣ 防止幻觉

4️⃣ 引用来源

✅ 优化效果

RAG 提示词最佳实践:

技巧 ❌ 错误示例 ✅ 正确示例
明确角色 “回答以下问题” “你是一位资深工程师,回答技术问题时附带代码示例”
指定格式 “用 Markdown 格式回答,代码用代码块包裹”
防止幻觉 “如果检索到的上下文无法回答问题,明确说’根据现有文档无法回答’”
引用来源 “回答时注明信息来源的文档名称和页码”

优化后的提示词模板:

你是我的个人知识库助手。

任务:
1. 根据检索到的上下文回答问题
2. 如果上下文不包含答案,说"文档中没有相关信息"
3. 回答时引用来源(格式:[来源: filename.pdf, 第 3 页])
4. 用中文回答,技术术语保留英文
5. 代码用 Markdown 代码块包裹

上下文:
{context}

问题:{question}

回答:

四、整合:构建完整应用

4.1 主程序入口(CLI)

创建 cli.py(命令行界面):

"""
命令行界面 - 与知识库交互
"""
import yaml
from pathlib import Path
from src.document_loader import DocumentLoader
from src.text_splitter import TextSplitter
from src.embeddings import EmbeddingManager
from src.vector_store import VectorStoreManager
from src.retriever import AdvancedRetriever
from src.qa_chain import RAGQuestionAnswering
from langchain_ollama import ChatOllama


def load_config(config_path: str = "config.yaml") -> dict:
    """加载配置文件"""
    with open(config_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def main():
    """主程序"""
    # 1. 加载配置
    config = load_config()
    print("✅ 配置加载成功")
    
    # 2. 初始化组件
    print("\n🚀 正在初始化组件...")
    
    # Embedding 模型
    embedding_manager = EmbeddingManager(
        model=config["embedding"]["model"],
        base_url=config["embedding"]["base_url"]
    )
    embeddings = embedding_manager.get_embeddings()
    print("  ✅ Embedding 模型已加载")
    
    # LLM
    llm = ChatOllama(
        model=config["llm"]["model"],
        base_url=config["llm"]["base_url"],
        temperature=config["llm"]["temperature"]
    )
    print("  ✅ LLM 已加载")
    
    # 向量数据库
    vectorstore_manager = VectorStoreManager(
        embeddings=embeddings,
        persist_directory=config["vectorstore"]["persist_directory"],
        collection_name=config["vectorstore"]["collection_name"]
    )
    
    # 3. 索引文档(如果数据库不存在)
    vectorstore = vectorstore_manager.load_vectorstore()
    
    if vectorstore is None:
        print("\n📚 开始索引文档...")
        loader = DocumentLoader(config["paths"]["data_dir"])
        documents = loader.load_directory()
        
        print("\n✂️ 开始分块...")
        text_splitter = TextSplitter(
            chunk_size=config["text_splitter"]["chunk_size"],
            chunk_overlap=config["text_splitter"]["chunk_overlap"]
        )
        split_documents = text_splitter.split_documents(documents)
        
        print("\n💾 创建向量数据库...")
        vectorstore = vectorstore_manager.create_vectorstore(split_documents)
    
    # 4. 创建检索器和 QA 系统
    print("\n🔍 初始化检索器...")
    retriever = AdvancedRetriever(
        vectorstore=vectorstore,
        search_type=config["retriever"]["search_type"],
        k=config["retriever"]["k"]
    ).get_reranking_retriever(
        top_n=config["reranking"]["top_n"]
    ) if config["reranking"]["enabled"] else AdvancedRetriever(
        vectorstore=vectorstore
    ).get_basic_retriever()
    
    print("\n💬 初始化 QA 系统...")
    qa_system = RAGQuestionAnswering(
        llm=llm,
        retriever=retriever
    )
    
    # 5. 交互式问答
    print("\n" + "="*50)
    print("🎯 个人知识库 RAG 系统已就绪!")
    print("="*50)
    print("输入你的问题,输入 'exit' 退出\n")
    
    chat_history = []
    
    while True:
        question = input("🧑 你: ").strip()
        
        if question.lower() in ["exit", "quit", "q"]:
            print("👋 再见!")
            break
        
        if not question:
            continue
        
        # 回答问题
        result = qa_system.answer(question, chat_history)
        
        # 显示答案
        print(f"\n🤖 AI: {result['answer']}\n")
        
        # 显示来源
        if result["source_documents"]:
            print("📚 参考来源:")
            for i, doc in enumerate(result["source_documents"], 1):
                source = doc.metadata.get("source", "未知来源")
                print(f"  [{i}] {source}")
        
        print()
        
        # 更新对话历史
        chat_history.append((question, result["answer"]))


if __name__ == "__main__":
    main()

CLI 工作流程:

🤖 QA System 📊 VectorStore 📄 Loader 💻 CLI 👤 用户 🤖 QA System 📊 VectorStore 📄 Loader 💻 CLI 👤 用户 loop [对话循环] 启动 cli.py 加载配置 检查数据库是否存在 不存在 加载文档 返回文档 创建向量数据库 ✅ 创建成功 输入问题 调用 QA 系统 检索 + 回答 返回答案 + 来源 显示结果 exit 👋 再见!

4.2 Web UI(Streamlit)

创建 app.py

"""
Web UI - 使用 Streamlit 构建可视化界面
"""
import yaml
import streamlit as st
from pathlib import Path
from src.document_loader import DocumentLoader
from src.text_splitter import TextSplitter
from src.embeddings import EmbeddingManager
from src.vector_store import VectorStoreManager
from src.retriever import AdvancedRetriever
from src.qa_chain import RAGQuestionAnswering
from langchain_ollama import ChatOllama


# 页面配置
st.set_page_config(
    page_title="个人知识库 RAG 系统",
    page_icon="📚",
    layout="wide"
)

# 加载配置
@st.cache_resource
def load_config():
    with open("config.yaml", "r", encoding="utf-8") as f:
        return yaml.safe_load(f)

@st.cache_resource
def init_components(config):
    """初始化组件(带缓存)"""
    # Embedding
    embedding_manager = EmbeddingManager(
        model=config["embedding"]["model"],
        base_url=config["embedding"]["base_url"]
    )
    embeddings = embedding_manager.get_embeddings()
    
    # LLM
    llm = ChatOllama(
        model=config["llm"]["model"],
        base_url=config["llm"]["base_url"],
        temperature=config["llm"]["temperature"]
    )
    
    # 向量数据库
    vectorstore_manager = VectorStoreManager(
        embeddings=embeddings,
        persist_directory=config["vectorstore"]["persist_directory"],
        collection_name=config["vectorstore"]["collection_name"]
    )
    
    vectorstore = vectorstore_manager.load_vectorstore()
    
    return llm, vectorstore_manager, vectorstore

# 主界面
def main():
    st.title("📚 个人知识库 RAG 系统")
    
    config = load_config()
    
    # 侧边栏
    with st.sidebar:
        st.header("⚙️ 配置")
        
        # 索引管理
        st.subheader("📦 文档索引")
        if st.button("🔄 重新索引所有文档"):
            with st.spinner("正在索引文档..."):
                loader = DocumentLoader(config["paths"]["data_dir"])
                documents = loader.load_directory()
                
                text_splitter = TextSplitter(
                    chunk_size=config["text_splitter"]["chunk_size"],
                    chunk_overlap=config["text_splitter"]["chunk_overlap"]
                )
                split_docs = text_splitter.split_documents(documents)
                
                _, vectorstore_manager, _ = init_components(config)
                vectorstore_manager.create_vectorstore(split_docs)
                
                st.success(f"✅ 索引完成!共 {len(split_docs)} 个文档块")
                st.experimental_rerun()
        
        # 检索配置
        st.subheader("🔍 检索配置")
        k = st.slider("检索文档数", min_value=1, max_value=10, value=config["retriever"]["k"])
        reranking_enabled = st.checkbox("启用重排序", value=config["reranking"]["enabled"])
        
        st.divider()
        st.caption("Powered by LangChain + Ollama + Chroma")
    
    # 主界面
    tab1, tab2, tab3 = st.tabs(["💬 问答", "📤 上传文档", "ℹ️ 关于"])
    
    with tab1:
        st.header("对话式问答")
        
        # 初始化对话历史
        if "messages" not in st.session_state:
            st.session_state.messages = []
        
        # 显示对话历史
        for message in st.session_state.messages:
            with st.chat_message(message["role"]):
                st.markdown(message["content"])
                if "sources" in message:
                    with st.expander("📚 参考来源"):
                        for src in message["sources"]:
                            st.caption(src)
        
        # 用户输入
        if prompt := st.chat_input("输入你的问题..."):
            # 显示用户消息
            st.session_state.messages.append({"role": "user", "content": prompt})
            with st.chat_message("user"):
                st.markdown(prompt)
            
            # 生成回答
            with st.chat_message("assistant"):
                message_placeholder = st.empty()
                with st.spinner("🤔 思考中..."):
                    # 初始化组件
                    llm, vectorstore_manager, vectorstore = init_components(config)
                    
                    if vectorstore is None:
                        message_placeholder.error("⚠️ 请先索引文档!")
                        return
                    
                    # 创建检索器和 QA 系统
                    retriever = AdvancedRetriever(
                        vectorstore=vectorstore,
                        k=k
                    ).get_reranking_retriever(top_n=3) if reranking_enabled else AdvancedRetriever(
                        vectorstore=vectorstore,
                        k=k
                    ).get_basic_retriever()
                    
                    qa_system = RAGQuestionAnswering(llm=llm, retriever=retriever)
                    
                    # 获取对话历史
                    chat_history = [
                        (m["content"], st.session_state.messages[i+1]["content"])
                        for i, m in enumerate(st.session_state.messages[:-1])
                        if m["role"] == "user" and i+1 < len(st.session_state.messages)
                    ]
                    
                    # 回答问题
                    result = qa_system.answer(prompt, chat_history)
                    
                    # 显示答案
                    message_placeholder.markdown(result["answer"])
                    
                    # 显示来源
                    sources = []
                    if result["source_documents"]:
                        with st.expander("📚 参考来源"):
                            for i, doc in enumerate(result["source_documents"], 1):
                                source = doc.metadata.get("source", "未知来源")
                                st.caption(f"[{i}] {source}")
                                sources.append(source)
                    
                    # 保存到历史
                    st.session_state.messages.append({
                        "role": "assistant",
                        "content": result["answer"],
                        "sources": sources
                    })
    
    with tab2:
        st.header("📤 上传文档")
        st.info("将文档放入 `data/` 目录下对应的子文件夹,然后点击侧边栏的「重新索引」按钮")
        
        # 显示当前文档列表
        data_dir = Path(config["paths"]["data_dir"])
        if data_dir.exists():
            st.subheader("当前已索引的文档:")
            for file_path in data_dir.rglob("*"):
                if file_path.is_file():
                    st.text(f"📄 {file_path.relative_to(data_dir)}")
    
    with tab3:
        st.header("ℹ️ 关于")
        st.markdown("""
        **个人知识库 RAG 系统**
        
        技术栈:
        - 🦜 LangChain - RAG 编排框架
        - 🦙 Ollama - 本地 LLM 运行时
        - 🎨 Chroma - 向量数据库
        - 🎈 Streamlit - Web UI
        
        功能特性:
        - ✅ 多格式文档支持(PDF、Word、Markdown、TXT)
        - ✅ 语义搜索(理解意图,不是关键词匹配)
        - ✅ 对话式问答(带上下文记忆)
        - ✅ 重排序优化(提高检索精准度)
        - ✅ 本地部署(数据不出本地)
        """)

if __name__ == "__main__":
    main()

Streamlit Web UI 界面预览:

🌐 Streamlit Web UI

📚 主界面

⚙️ 侧边栏

💬 问答 Tab

📤 上传文档 Tab

ℹ️ 关于 Tab

📦 重新索引按钮

🔍 检索配置滑块

🔄 重排序开关

✨ 对话式交互

📝 用户输入

🤖 AI 回答

📚 来源展示


五、运行与测试

5.1 首次运行(索引文档)

No

Yes

准备文档

放入 data/ 目录

启动 Ollama
ollama serve

运行 CLI
python cli.py

数据库存在?

索引文档

加载数据库

✅ 系统就绪

操作步骤:

# 1. 准备文档
mkdir -p data/{pdfs,markdown,web}
# 将你的 PDF、Markdown 等文档放入对应目录

# 2. 启动 Ollama 服务
ollama serve &

# 3. 运行 CLI 版本
python cli.py
# 首次运行会自动索引文档

# 4. 运行 Web UI 版本
streamlit run app.py
# 访问 http://localhost:8501

5.2 测试问答

👤 用户: 公司年假政策是什么?

🔍 检索器

📊 向量数据库

返回 Top-5 相关文档

🔄 重排序

保留 Top-3

🤖 LLM 生成答案

✅ 显示答案 + 来源

示例对话:

🧑 你: 公司年假政策是什么?

🤖 AI: 根据公司员工手册第 3 章第 2 节的规定:
- 入职满 1 年:5 天年假
- 入职满 3 年:10 天年假  
- 入职满 5 年:15 天年假

[来源: employee_handbook.pdf, 第 12 页]

六、生产级优化

6.1 分块策略优化

# 针对不同文档类型使用不同分块策略

# PDF 文档(按段落分块)
pdf_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=100,
    separators=["\n\n", "\n", ".", "。", " ", ""]
)

# Markdown 文档(按标题分块)
md_splitter = MarkdownTextSplitter(
    chunk_size=1024,
    chunk_overlap=100
)

# 代码文件(按函数/类分块)
code_splitter = PythonCodeTextSplitter(
    chunk_size=1024,
    chunk_overlap=200
)

分块策略选择树:

PDF

Markdown

代码

通用

选择分块策略

文档类型?

Recursive
按段落分割

MarkdownTextSplitter
按标题分割

PythonCodeTextSplitter
按函数分割

Recursive
通用分割

chunk_size: 512

chunk_size: 1024

chunk_size: 1024

chunk_size: 512

6.2 Hybrid 检索(向量 + BM25)

from langchain.retrievers import BM25Retriever, EnsembleRetriever

# 向量检索器
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.7, 0.3]  # 向量检索权重 0.7,BM25 权重 0.3
)

Hybrid 检索原理:

用户查询

🔍 向量检索
语义相似度

🔍 BM25 检索
关键词匹配

📊 分数融合

🎯 重排序

✅ 最终结果

6.3 查询重写

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

# 查询重写提示词
rewrite_prompt = PromptTemplate(
    template="""用户原始查询:{query}

请将其重写为更适合向量检索的查询(保持原意,补充同义词):""",
    input_variables=["query"]
)

rewrite_chain = LLMChain(llm=llm, prompt=rewrite_prompt)

# 使用
rewritten_query = rewrite_chain.run(query)
results = retriever.get_relevant_documents(rewritten_query)

查询重写示例:

原始查询 重写后查询
“怎么登录?” “登录方法 登录步骤 登录流程 用户认证”
“报错咋办?” “错误解决方法 故障排查 错误处理 异常处理”
“性能差” “性能优化 性能调优 响应慢 速度提升”

6.4 评估体系

from langchain.evaluation import QAEvaluator, EmbeddingDistanceEvalChain

# 1. 答案相关性评估
qa_evaluator = QAEvaluator()

# 2. 检索准确率评估(需要标注数据集)
ground_truth = {
    "question": "公司年假政策",
    "expected_answer": "入职满 1 年 5 天,满 3 年 10 天...",
    "relevant_docs": ["employee_handbook.pdf"]
}

# 3. 向量相似度评估
embedding_eval = EmbeddingDistanceEvalChain(embeddings=embeddings)

评估指标详解:

渲染错误: Mermaid 渲染失败: Parse error on line 2: ... A[📊 RAG 评估体系] --> B[检索准确率
Recall@K -----------------------^ Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'LINK_ID'

七、部署到生产环境

7.1 使用 PostgreSQL + PGVector

# 安装 PGVector
pip install pgvector langchain-postgres

# 创建向量表
CREATE EXTENSION IF NOT EXISTS vector;

# 使用 PGVector 替代 Chroma
from langchain.vectorstores.pgvector import PGVector

vectorstore = PGVector(
    embeddings=embeddings,
    collection_name="knowledge_base",
    connection_string="postgresql://user:password@localhost:5432/rag_db"
)

PGVector 优势:

为何选择 PGVector?

✅ 成熟稳定
PostgreSQL 生态

✅ 可扩展
支持水平扩展

✅ 事务支持
ACID 保证

✅ 备份恢复
pg_dump/pg_restore

7.2 API 服务(FastAPI)

创建 api.py

"""
REST API - 部署为微服务
"""
from fastapi import FastAPI
from pydantic import BaseModel
from src.qa_chain import RAGQuestionAnswering
from src.vector_store import VectorStoreManager
from src.embeddings import EmbeddingManager
from langchain_ollama import ChatOllama

app = FastAPI(title="RAG API")

# 请求模型
class QueryRequest(BaseModel):
    question: str
    chat_history: list = []

class QueryResponse(BaseModel):
    answer: str
    sources: list

@app.post("/query", response_model=QueryResponse)
async def query(request: QueryRequest):
    """问答接口"""
    # 初始化组件(实际部署中应缓存)
    config = load_config()
    embeddings = EmbeddingManager(...).get_embeddings()
    vectorstore = VectorStoreManager(...).load_vectorstore()
    retriever = AdvancedRetriever(vectorstore=vectorstore).get_basic_retriever()
    qa_system = RAGQuestionAnswering(llm=ChatOllama(...), retriever=retriever)
    
    # 回答问题
    result = qa_system.answer(request.question, request.chat_history)
    
    return {
        "answer": result["answer"],
        "sources": [doc.metadata.get("source") for doc in result["source_documents"]]
    }

# 启动:uvicorn api:app --host 0.0.0.0 --port 8000

FastAPI 部署架构:

🌐 客户端

🚀 Nginx
反向代理

⚡ FastAPI
RAG API

📊 Vector DB
PGVector

🤖 LLM
Ollama

🔍 Retriever
检索器


八、常见问题 FAQ

❓ 常见问题

检索结果不相关?

回答不准确?

索引速度太慢?

如何支持大规模?

如何保护隐私?

调整分块大小

启用重排序

使用 Hybrid 检索

优化提示词

添加防止幻觉指令

使用批量 Embedding

换用更快的模型

迁移到 PGVector

使用 Pinecone/Milvus

本地部署 Ollama

敏感文档加密

Q1:检索结果不相关怎么办?

尝试以下优化:

  1. 调整分块大小:太大 → 噪声多,太小 → 丢失上下文
  2. 启用重排序reranking.enabled: true
  3. 使用 Hybrid 检索:向量 + BM25 结合
  4. 优化 Embedding 模型:换用 bge-large-zh-v1.5(中文场景)

Q2:回答不准确或有幻觉?

优化提示词:

如果检索到的上下文无法回答问题,明确说"根据现有文档无法回答"
不要编造答案!

Q3:索引速度太慢?

优化建议:

  1. 使用批量 Embedding(embed_documents 而非循环调用 embed_query
  2. 换用更快的 Embedding 模型(all-MiniLM-L6-v2
  3. 使用 GPU 加速(需安装 sentence-transformers[gpu]

Q4:如何支持更大规模的知识库?

迁移到生产级向量数据库:

  • 中型(10万+ 文档):PGVector
  • 大型(百万+ 文档):Pinecone / Milvus

Q5:如何保护隐私?

本文方案已经是完全本地部署

  • Ollama 本地运行,无需上传文档到云端
  • Chroma 本地存储,数据不出本地

额外建议:

  • 对敏感文档加密存储
  • 添加用户认证(FastAPI 中间件)
  • 定期备份向量数据库

总结

🎓 你学到了什么

📊 RAG 系统架构

🦜 LangChain 使用

📊 向量数据库操作

🔍 检索优化技术

🦙 本地 LLM 部署

🌐 Web UI 开发

✅ 完整理解

✅ 生产级

✅ Chroma/PGVector

✅ 重排序/Hybrid/查询重写

✅ Ollama

✅ Streamlit

下一步行动

🚀 下一步

今天:
运行本文代码

本周:
添加 10+ 篇文档

本月:
部署为 API 服务

✅ 验证可行性

✅ 测试问答效果

✅ 集成到工作流

资源链接

  • 📚 LangChain 官方文档:https://python.langchain.com/docs/
  • 🦙 Ollama 官网:https://ollama.com/
  • 🎨 Chroma 文档:https://docs.trychroma.com/
  • 📖 RAG 优化技术论文:https://arxiv.org/abs/2005.11401

本文基于 LangChain 0.3+、Ollama 0.5+、Chroma 0.5+ 编写。如有问题欢迎评论区讨论!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐