1. 从零构建基于RAG的AI知识库系统
最近在做一个内部知识管理系统的升级项目,需要让AI能够理解并回答关于公司技术文档的问题。经过一番技术选型,最终决定采用RAG(Retrieval-Augmented Generation)架构来实现这个需求。RAG的核心思想是通过检索相关文档片段来增强大语言模型的生成能力,既能利用LLM的强大理解能力,又能确保回答基于实际文档内容。
这个方案最大的优势是避免了直接微调大模型的高成本,同时又能保证回答的专业性和准确性。下面我就把整个实现过程详细记录下来,包括技术选型考量、具体实现步骤和踩过的一些坑。
2. 环境准备与模型部署
2.1 硬件与软件基础配置
我选择在一台配备NVIDIA RTX 3090显卡的工作站上部署这个系统。虽然nomic-embed-text模型对硬件要求不高,但考虑到后续可能扩展更大的模型,这样的配置比较保险。操作系统使用的是Ubuntu 22.04 LTS,Python环境通过conda管理,创建了专门的虚拟环境:
bash复制conda create -n rag python=3.10
conda activate rag
提示:建议使用Python 3.10而不是最新版本,因为部分依赖库对新版Python的支持还不够稳定。
2.2 模型选择与部署
在embedding模型的选择上,经过对比测试了几个开源模型后,最终选择了nomic-embed-text。主要基于以下几点考虑:
- 模型大小适中(1.5B参数),在保证质量的同时对硬件要求较低
- 支持长文本处理(最大8192 tokens)
- 在MTEB基准测试中表现优秀
- 可以通过Ollama方便地部署和管理
部署过程非常简单,使用Ollama的一行命令即可:
bash复制ollama pull nomic-embed-text
对于LLM部分,选择了deepseek-r1:1.5b模型。这是一个轻量级但性能不错的开源模型,适合本地部署。同样通过Ollama安装:
bash复制ollama pull deepseek-r1:1.5b
3. 核心依赖安装与配置
3.1 Python依赖安装
RAG系统需要多个功能组件的配合,以下是必须安装的核心依赖:
bash复制pip install pypdf python-docx langchain-community chromadb sentence-transformers langchain langchain-core langchain-text-splitters langchain-ollama
各主要依赖的作用如下表所示:
| 依赖包 | 用途 | 版本要求 |
|---|---|---|
| pypdf | PDF文档解析 | ≥3.0.0 |
| python-docx | Word文档解析 | ≥0.8.11 |
| langchain-community | LangChain社区扩展 | ≥0.0.20 |
| chromadb | 向量数据库 | ≥0.4.15 |
| sentence-transformers | 句子嵌入模型 | ≥2.2.2 |
| langchain-ollama | Ollama集成 | ≥0.1.0 |
3.2 文档加载器实现细节
文档加载是RAG系统的第一步,需要支持多种格式。我实现了多格式文档加载器,核心代码如下:
python复制def load_docs(doc_path):
documents = []
path = Path(doc_path)
# PDF加载
pdf_files = list(path.glob("*.pdf"))
for pdf_file in pdf_files:
try:
loader = PyPDFLoader(str(pdf_file))
documents.extend(loader.load())
except Exception as e:
print(f"PDF加载失败: {pdf_file} - {e}")
# Word加载
docx_files = list(path.glob("*.docx"))
for docx_file in docx_files:
try:
loader = Docx2txtLoader(str(docx_file))
documents.extend(loader.load())
except Exception as e:
print(f"DOCX加载失败: {docx_file} - {e}")
# 文本加载
txt_files = list(path.glob("*.txt"))
for txt_file in txt_files:
try:
loader = TextLoader(str(txt_file), encoding="utf-8")
documents.extend(loader.load())
except Exception as e:
print(f"TXT加载失败: {txt_file} - {e}")
return documents
注意:文本文件加载时务必指定encoding="utf-8",否则遇到中文文档容易出现乱码。
4. 文档处理与向量化流程
4.1 文本分块策略
文档加载后需要进行分块处理,这对检索质量至关重要。经过多次实验,最终采用的配置如下:
python复制text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块约500字符
chunk_overlap=50, # 块间重叠50字符
separators=["\n\n", "\n", "。", "!", "?", ";", ",", "、", ""]
)
分块大小的选择需要考虑以下因素:
- embedding模型的最大长度限制
- 检索精度与上下文的平衡
- 后续LLM处理的效率
4.2 向量数据库构建
ChromaDB作为轻量级向量数据库,非常适合本地部署场景。构建过程如下:
python复制db = Chroma.from_documents(
documents=split_documents,
embedding=embeddings,
persist_directory="./db/chroma_db"
)
关键参数说明:
- persist_directory:指定持久化目录,避免每次重启重新处理
- embedding:使用配置好的nomic-embed-text模型
- documents:分块后的文档列表
5. 问答链的实现与优化
5.1 提示词工程
设计了一个简洁有效的prompt模板:
python复制prompt = PromptTemplate(
template="""请根据以下上下文回答用户的问题,不知道就说不知道,不要编造:
上下文:
{context}
问题: {input}
回答:""",
input_variables=["context", "input"]
)
这个模板强调了几点:
- 严格基于上下文回答
- 不知道就明确说不知道
- 禁止幻觉和编造
5.2 检索与生成链
使用LangChain的新API构建问答链:
python复制# 文档链
question_answer_chain = create_stuff_documents_chain(
llm=llm,
prompt=prompt
)
# 检索链
qa_chain = create_retrieval_chain(
retriever=db.as_retriever(search_kwargs={"k": 3}),
combine_docs_chain=question_answer_chain
)
这里设置检索top_k=3,即每次检索最相关的3个文档片段,平衡响应速度和质量。
6. 系统部署与性能优化
6.1 流式输出实现
为了提升用户体验,实现了流式输出功能:
python复制llm = OllamaLLM(
model="deepseek-r1:1.5b",
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()]
)
这样用户可以看到答案逐步生成的过程,而不是长时间等待后一次性显示所有内容。
6.2 性能优化技巧
经过测试,总结出几个有效的优化点:
- 批量处理文档:一次性加载并处理所有文档,避免重复初始化
- 持久化向量库:构建完成后保存,下次直接加载
- 合理设置分块大小:500-1000字符的块大小在大多数场景表现最佳
- 限制检索范围:根据查询复杂度调整top_k值
7. 常见问题与解决方案
7.1 文档加载失败排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| PDF加载乱码 | 文件加密或特殊编码 | 尝试用专业PDF工具重新保存 |
| DOCX加载失败 | 文件损坏或版本不兼容 | 转换为DOC格式再试 |
| 中文显示异常 | 编码问题 | 确保所有加载器使用utf-8编码 |
7.2 检索质量优化
如果发现检索结果不理想,可以尝试:
- 调整分块策略(大小/重叠/分隔符)
- 尝试不同的embedding模型
- 增加检索的top_k值
- 对查询进行预处理(同义词扩展等)
8. 实际应用案例
8.1 技术文档问答
将公司API文档导入系统后,可以准确回答如:
"如何获取用户列表?需要哪些参数?"
这类具体问题,回答会直接引用文档中的相关段落。
8.2 内部知识查询
人事政策、报销流程等内部文档查询,系统能给出基于最新规定的准确回答,避免人工查询的延迟。
8.3 客户支持辅助
将产品FAQ和常见问题文档导入后,可以快速响应客户咨询,减轻客服团队压力。
9. 扩展与进阶方向
这个基础实现还可以进一步扩展:
- 多源知识整合:接入数据库、API等实时数据源
- 查询理解增强:加入查询重写和扩展
- 混合检索策略:结合关键词和向量检索
- 反馈学习机制:根据用户反馈优化检索结果
我在实际部署中发现,对于专业性强的领域,RAG相比纯LLM能提供更可靠的回答。特别是当文档更新时,只需重新处理文档而无需重新训练模型,维护成本大大降低。
