环境准备与依赖安装
在开始构建 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
实战:构建企业知识库问答
假设某公司需要搭建内部知识库问答系统,数据源包括产品手册(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 的并发查询。