环境准备与依赖安装

在开始构建 RAG 系统之前,需要先准备好 Python 运行环境和必要的依赖包。推荐使用 Python 3.10 或更高版本,以确保对最新大模型 SDK 的兼容性。建议通过虚拟环境隔离项目依赖,避免与系统级包产生冲突。对于生产环境,Docker 容器化部署能进一步提高环境一致性和可移植性。

核心依赖主要包括 LlamaIndex 框架、ChromaDB 向量数据库、HuggingFace 嵌入模型以及 LangChain 工具链。需要注意的是,LlamaIndex 0.10.x 版本之后 API 发生了较大变化,本文以 0.10.30 版本为例进行讲解。如果你使用的是旧版本,部分导入路径和函数签名需要相应调整。

# requirements.txt
llama-index==0.10.30
llama-index-vector-stores-chroma==0.1.9
chromadb==0.4.24
sentence-transformers==2.7.0
huggingface-hub==0.20.3
unstructured==0.13.7
python-dotenv==1.0.1

# 安装命令
pip install -r requirements.txt

安装完成后,建议快速验证环境是否正常。可以通过导入 llama_index 和 chromadb 检查是否有报错。如果遇到 CUDA 相关错误,说明你的 PyTorch 版本与当前环境不匹配,需要单独安装与 CUDA 版本对应的 PyTorch 包。对于仅使用 CPU 的场景,可以跳过 GPU 相关依赖以减小镜像体积。

ChromaDB向量数据库部署

ChromaDB 是目前最流行的开源向量数据库之一,它以轻量级、易部署和原生 Python 支持著称。与 Pinecone、Weaviate 等托管服务相比,ChromaDB 最大的优势是完全本地运行,数据完全自主可控,非常适合对数据隐私有要求的企业场景。不过,在超大规模数据量(千万级以上)时,ChromaDB 的性能会明显不如专业级向量数据库。

在生产环境中,建议启用 ChromaDB 的持久化存储模式,将向量数据保存到磁盘而非内存。这样可以避免服务重启后数据丢失,同时也支持多进程共享同一份索引。默认情况下,ChromaDB 使用 SQLite 作为元数据存储,HNSW 算法进行近似最近邻搜索,对于大多数企业应用场景已经足够。

import chromadb
from chromadb.config import Settings

# 持久化客户端初始化
client = chromadb.PersistentClient(
    path="./chroma_db",
    settings=Settings(
        anonymized_telemetry=False,
        allow_reset=True
    )
)

# 创建集合(对应数据库表)
collection = client.get_or_create_collection(
    name="enterprise_kb",
    metadata={"hnsw:space": "cosine"}
)

下表对比了当前主流的向量数据库在部署成本、查询性能和生态支持方面的差异。对于刚起步的团队,ChromaDB 是性价比最高的选择;当数据量超过千万级或需要多租户隔离时,建议迁移至 Milvus 或 Qdrant。

数据库 部署方式 最大规模 查询延迟(p99) 适合场景
ChromaDB 本地/嵌入式 百万级 50-100ms 原型验证、中小项目
Qdrant Docker/云服务 亿级 20-50ms 生产环境、高并发
Milvus Kubernetes 十亿级 10-30ms 超大规模、企业级
Pinecone 全托管云服务 亿级 15-40ms 不想运维、快速上线

LlamaIndex核心模块配置

LlamaIndex(原 GPT Index)是专门为 LLM 上下文增强设计的框架,其核心思想是将外部数据索引化,让大模型在回答问题时能够检索到相关的上下文片段。与 LangChain 相比,LlamaIndex 在数据索引和检索模块上更加专业,提供了更丰富的加载器、解析器和检索策略。对于企业知识库场景,LlamaIndex 的上手门槛更低,配置更加直观。

一个完整的 LlamaIndex RAG 流程包含四个核心组件:数据加载器(Loader)、文档处理器(Parser)、索引构建器(Index)和检索器(Retriever)。数据加载器负责从各种来源(PDF、Word、数据库、API)读取原始数据;文档处理器将非结构化文本切分为适合嵌入的块;索引构建器调用嵌入模型生成向量并存入向量数据库;检索器则在用户提问时,从向量库中召回最相关的 Top-K 个片段。

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core.storage.storage_context import StorageContext

# 1. 加载文档(支持 PDF、Word、Markdown 等)
documents = SimpleDirectoryReader("./data").load_data()

# 2. 配置 ChromaDB 向量存储
chroma_collection = chromadb.PersistentClient(path="./chroma_db").get_or_create_collection("kb")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

# 3. 构建索引(自动嵌入+存储)
index = VectorStoreIndex.from_documents(
    documents,
    storage_context=storage_context,
    show_progress=True
)

# 4. 创建查询引擎
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("公司的年假制度是什么?")
print(str(response))

在实际配置中,有几个关键参数需要根据业务场景调整。首先是 chunk_size,它决定了每段文本的长度,通常设置为 512 到 1024 个 token 之间。块太小会导致上下文信息不足,块太大则会导致检索精度下降。其次是 chunk_overlap,它控制相邻块之间的重叠token数,一般设置为 chunk_size 的 10% 到 20%,可以确保关键信息不会被截断在两个块的边界。

嵌入模型选择与优化

嵌入模型是 RAG 系统的"翻译官",它将文本转换为高维向量,使得语义相似的文本在向量空间中距离相近。嵌入模型的选择直接决定了检索的准确率,是 RAG 系统中最关键的组件之一。目前主流的选择分为 API 调用和本地部署两种路线,两者在成本、延迟和隐私性上有显著差异。

对于中文场景,OpenAI 的 text-embedding-3-large 模型表现优异,支持 3072 维向量,在 MTEB 中文榜单上排名靠前,但需要支付 API 调用费用且数据需要上传至海外服务器。国内厂商如百度、智谱、MiniMax 也提供了中文嵌入模型,延迟更低且符合数据合规要求。如果预算有限或数据敏感度高,可以使用 HuggingFace 上的开源模型如 BGE-M3 或 m3e,在本地 GPU 上运行,完全免费且离线可用。

from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 本地部署 BGE-M3 嵌入模型(中文友好)
embed_model = HuggingFaceEmbedding(
    model_name="BAAI/bge-m3",
    device="cuda",  # 或 "cpu"
    max_length=512
)

# 全局配置
from llama_index.core import Settings
Settings.embed_model = embed_model
Settings.chunk_size = 512
Settings.chunk_overlap = 64
💡 选型建议:如果是内部工具或原型验证,优先使用 OpenAI API 快速验证效果;如果是面向客户的生产系统且涉及内部文档,建议使用 BGE-M3 本地部署,在准确率和成本之间取得平衡。

实战:构建企业知识库问答

假设某公司需要搭建内部知识库问答系统,数据源包括产品手册(PDF)、员工手册(Word)、技术文档(Markdown)和常见问题(Excel)。我们需要将这些异构数据统一处理,构建一个支持自然语言查询的 RAG 系统。整个流程包括数据采集、清洗、切分、嵌入、索引和查询六个阶段。

在实际项目中,文档清洗是最容易被忽视但影响巨大的环节。原始 PDF 往往包含页眉页脚、水印、目录等噪声,如果不加处理,这些内容会被切分后进入向量库,导致检索结果中混入大量无关片段。建议使用 unstructured 或 PyMuPDF 进行预处理,过滤掉页眉页脚和重复页眉。对于表格类数据,需要特殊处理,因为简单的文本切分会破坏表格的结构信息。

from llama_index.core import Document
from llama_index.readers.file import PDFReader, DocxReader
import pandas as pd

def load_enterprise_documents(data_dir: str) -> list[Document]:
    docs = []
    
    # PDF 文档
    pdf_reader = PDFReader()
    docs.extend(pdf_reader.load_data(f"{data_dir}/manuals/"))
    
    # Word 文档
    docx_reader = DocxReader()
    docs.extend(docx_reader.load_data(f"{data_dir}/policies/"))
    
    # Excel 转 Markdown
    df = pd.read_excel(f"{data_dir}/faq/company_faq.xlsx")
    for _, row in df.iterrows():
        docs.append(Document(
            text=f"Q: {row['question']}\nA: {row['answer']}",
            metadata={"source": "faq", "category": row['category']}
        ))
    
    return docs

构建好索引后,查询阶段还需要做一层重排序(Rerank)来提升准确率。向量检索返回的 Top-10 片段中,可能只有 3-4 个真正相关,其余都是近似匹配。通过引入 Cross-Encoder 重排序模型,可以对这些片段进行精细打分,重新排序后取 Top-3 作为最终上下文。这一步通常能将回答准确率提升 15% 到 25%。

性能测试与调优

系统上线前,必须对检索延迟和回答质量进行全面测试。检索延迟包括嵌入延迟(将用户问题转为向量)和向量查询延迟(在数据库中搜索相似向量)。在本地 GPU 上,BGE-M3 的嵌入延迟约为 30-50ms,ChromaDB 的向量查询延迟约为 20-40ms,端到端延迟可以控制在 100ms 以内,完全满足实时交互需求。

回答质量的评估相对复杂,通常采用人工评估和自动评估相结合的方式。人工评估需要业务专家对 50-100 个典型问题的回答进行打分,评估维度包括事实准确性、回答完整性和引用来源可靠性。自动评估可以使用 RAGAS 或 faithfulness 指标,通过大模型自动判断回答是否基于检索到的上下文生成。下表展示了不同嵌入模型在内部测试集上的表现对比。

嵌入模型 向量维度 嵌入延迟 检索准确率 月度成本
text-embedding-3-large 3072 ~150ms (API) 92.5% ~$50
BGE-M3 (本地) 1024 ~35ms (GPU) 89.3% $0
m3e-base (本地) 768 ~20ms (GPU) 85.1% $0
text-embedding-3-small 1536 ~80ms (API) 88.7% ~$15

生产环境部署时,还需要考虑缓存策略和流量控制。对于高频重复问题,可以在查询层增加 Redis 缓存,将问题和答案的映射关系缓存 5-10 分钟,既能降低嵌入模型的调用成本,又能显著提升响应速度。对于并发请求,建议使用异步查询引擎(AsyncQueryEngine),配合 Uvicorn + FastAPI 部署为 Web 服务,单实例通常可以支撑 50-100 QPS 的并发查询。