零基础搭建RAG知识库:LangChain+Milvus实战教程

📅 2026/7/30 11:37:57 👁️ 阅读次数 📝 编程学习
零基础搭建RAG知识库:LangChain+Milvus实战教程

这次我们来看一个面向零基础的RAG技术实战教程。RAG(Retrieval-Augmented Generation)作为当前AI大模型应用的热门方向,能够有效解决大模型知识滞后、幻觉问题,让AI回答更准确可靠。本文将从最基础的概念讲起,手把手带你完成RAG知识库的完整搭建流程。

最值得关注的是,这个教程不需要深厚的技术背景,只要掌握基本的Python编程就能上手。我们将使用完全开源的技术栈,包括LangChain、Milvus向量数据库和BGE嵌入模型,所有组件都可以在普通配置的电脑上运行。硬件门槛方面,8GB内存的机器就能流畅运行基础版本,如果使用GPU加速,显存占用在2-4GB左右。

本文将带你完成从环境准备到实战部署的全过程,重点演示如何构建一个可用的企业知识库系统。我们会涵盖向量数据库配置、文档处理、相似度检索和回答生成等核心环节,最后还会提供API接口封装和批量处理方案。

1. 核心能力速览

能力项具体说明
技术栈LangChain + Milvus + BGE嵌入模型 + 开源大模型
硬件需求CPU版本需8GB内存,GPU加速版需2-4GB显存
部署方式本地部署,支持Docker和原生安装
核心功能文档检索、语义搜索、智能问答、知识管理
接口支持RESTful API,支持批量文档处理
适用场景企业知识库、智能客服、文档检索系统

2. RAG技术原理与价值

RAG技术的核心思想很直观:当用户提出问题时,系统首先从知识库中检索相关信息,然后将这些信息与大模型的生成能力结合,产生更准确的回答。这种方法相比直接使用大模型有三大优势:

首先是解决知识时效性问题。大模型的训练数据有截止时间,无法获取最新信息。通过RAG,我们可以随时更新知识库,让模型能够回答最新的问题。

其次是降低幻觉风险。大模型有时会"编造"看似合理但实际错误的信息。RAG通过提供准确的参考文档,约束模型的生成内容,提高回答的可信度。

最后是成本效益。相比微调大模型,RAG方案的实施成本更低,维护更简单,特别适合中小型企业快速搭建智能问答系统。

从技术架构看,一个完整的RAG系统包含四个关键组件:文档加载与处理、向量化与索引、相似度检索、答案生成。每个环节都有多种技术选型,本文选择的是经过实践验证的稳定组合。

3. 环境准备与工具选型

在开始实战之前,我们需要准备好开发环境。以下是推荐的基础配置:

操作系统要求

  • Windows 10/11, macOS 10.14+, 或 Ubuntu 18.04+
  • 建议使用Linux系统获得最佳性能

Python环境

# 创建虚拟环境 python -m venv rag_env source rag_env/bin/activate # Linux/macOS # 或 rag_env\Scripts\activate # Windows # 安装基础包 pip install langchain langchain-community milvus pymilvus sentence-transformers

向量数据库选型我们选择Milvus作为向量数据库,因为它具有以下优势:

  • 支持大规模向量检索
  • 内存占用相对较小
  • 有活跃的社区支持
  • 提供Python SDK方便集成

嵌入模型选择BGE(BAAI General Embedding)模型在中文场景表现优异,特别是bge-large-zh版本,在多项评测中领先。如果资源有限,可以选择bge-small-zh降低计算需求。

4. Milvus向量数据库部署

Milvus的部署有多种方式,我们推荐使用Docker Compose进行快速部署:

# docker-compose.yml version: '3.5' services: etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.5 environment: - ETCD_AUTO_COMPACTION_MODE=revision - ETCD_AUTO_COMPACTION_RETENTION=1000 - ETCD_QUOTA_BACKEND_BYTES=4294967296 - ETCD_SNAPSHOT_COUNT=50000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd healthcheck: test: ["CMD", "etcdctl", "endpoint", "health"] interval: 30s timeout: 20s retries: 3 minio: container_name: milvus-minio image: minio/minio:RELEASE.2023-03-20T20-16-18Z environment: MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 milvus: container_name: milvus-standalone image: milvusdb/milvus:v2.3.4 command: ["milvus", "run", "standalone"] environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] interval: 30s startup_period: 90s timeout: 20s retries: 3 ports: - "19530:19530" - "9091:9091" depends_on: - "etcd" - "minio"

启动命令:

docker-compose up -d

验证部署:

from pymilvus import connections # 连接Milvus connections.connect("default", host="localhost", port="19530") # 检查连接状态 print(f"Milvus版本: {connections.get_connection_addr('default')}")

5. 文档处理与向量化流程

文档处理是RAG系统的第一步,直接影响后续检索效果。我们使用LangChain提供的文档加载器:

from langchain.document_loaders import TextLoader, PyPDFLoader, UnstructuredFileLoader from langchain.text_splitter import RecursiveCharacterTextSplitter def process_documents(file_path): # 根据文件类型选择加载器 if file_path.endswith('.pdf'): loader = PyPDFLoader(file_path) elif file_path.endswith('.txt'): loader = TextLoader(file_path, encoding='utf-8') else: loader = UnstructuredFileLoader(file_path) documents = loader.load() # 文本分割 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, length_function=len, ) splits = text_splitter.split_documents(documents) return splits # 示例使用 documents = process_documents("企业知识文档.pdf") print(f"共处理 {len(documents)} 个文本块")

接下来进行向量化处理,使用BGE模型生成文本嵌入:

from langchain.embeddings import HuggingFaceEmbeddings # 初始化嵌入模型 embedding_model = HuggingFaceEmbeddings( model_name="BAAI/bge-large-zh", model_kwargs={'device': 'cpu'}, # 使用GPU可改为 'cuda' encode_kwargs={'normalize_embeddings': True} ) # 生成向量 texts = [doc.page_content for doc in documents] embeddings = embedding_model.embed_documents(texts) print(f"生成 {len(embeddings)} 个向量,维度: {len(embeddings[0])}")

6. 构建向量索引与检索系统

在Milvus中创建集合并建立索引:

from pymilvus import Collection, CollectionSchema, FieldSchema, DataType def create_milvus_collection(collection_name, dimension=1024): # 定义字段 fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dimension), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=255), FieldSchema(name="chunk_index", dtype=DataType.INT64) ] schema = CollectionSchema(fields, description="RAG知识库") collection = Collection(name=collection_name, schema=schema) # 创建索引 index_params = { "index_type": "IVF_FLAT", "metric_type": "L2", "params": {"nlist": 1024} } collection.create_index("embedding", index_params) return collection # 创建集合 collection = create_milvus_collection("enterprise_knowledge_base")

插入文档数据:

def insert_documents(collection, documents, embeddings): # 准备插入数据 texts = [doc.page_content for doc in documents] sources = [doc.metadata.get('source', 'unknown') for doc in documents] chunk_indices = list(range(len(documents))) data = [ texts, embeddings, sources, chunk_indices ] # 插入数据 mr = collection.insert(data) collection.flush() print(f"成功插入 {mr.insert_count} 条记录") return mr.primary_keys

7. 相似度检索与答案生成

实现核心的检索功能:

from langchain.llms import Ollama # 或使用其他LLM接口 from langchain.chains import RetrievalQA def setup_retrieval_qa(collection, embedding_model, llm_model): # 创建检索器 from langchain.vectorstores import Milvus vector_store = Milvus( collection_name="enterprise_knowledge_base", embedding_function=embedding_model, connection_args={"host": "localhost", "port": "19530"} ) # 创建QA链 qa_chain = RetrievalQA.from_chain_type( llm=llm_model, chain_type="stuff", retriever=vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 3} ), return_source_documents=True ) return qa_chain # 初始化LLM(以Ollama为例) llm = Ollama(model="qwen:7b") # 需要提前安装Ollama并下载模型 # 创建QA系统 qa_system = setup_retrieval_qa(collection, embedding_model, llm)

测试检索效果:

# 测试问答 question = "我们公司的产品有哪些主要功能?" result = qa_system({"query": question}) print(f"问题: {question}") print(f"回答: {result['result']}") print("\n参考文档:") for i, doc in enumerate(result['source_documents']): print(f"{i+1}. {doc.page_content[:200]}...")

8. 批量任务处理优化

对于企业级应用,我们需要支持批量文档处理:

import os from concurrent.futures import ThreadPoolExecutor def batch_process_documents(directory_path, batch_size=10): """批量处理目录下的所有文档""" supported_extensions = ['.pdf', '.txt', '.docx', '.md'] # 收集所有文档文件 document_files = [] for root, dirs, files in os.walk(directory_path): for file in files: if any(file.endswith(ext) for ext in supported_extensions): document_files.append(os.path.join(root, file)) print(f"找到 {len(document_files)} 个文档文件") # 分批处理 results = [] for i in range(0, len(document_files), batch_size): batch_files = document_files[i:i+batch_size] with ThreadPoolExecutor(max_workers=4) as executor: batch_results = list(executor.map(process_single_document, batch_files)) results.extend(batch_results) print(f"已完成批次 {i//batch_size + 1}/{(len(document_files)-1)//batch_size + 1}") return results def process_single_document(file_path): """处理单个文档""" try: documents = process_documents(file_path) embeddings = embedding_model.embed_documents([doc.page_content for doc in documents]) return { 'file_path': file_path, 'documents': documents, 'embeddings': embeddings, 'status': 'success' } except Exception as e: return { 'file_path': file_path, 'error': str(e), 'status': 'failed' } # 使用示例 batch_results = batch_process_documents("./knowledge_docs") success_count = sum(1 for r in batch_results if r['status'] == 'success') print(f"批量处理完成,成功: {success_count}, 失败: {len(batch_results)-success_count}")

9. API接口封装与部署

将RAG系统封装为Web API,方便其他系统集成:

from flask import Flask, request, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) # 全局变量(实际生产环境应使用更好的方式管理) qa_system = None @app.route('/api/ask', methods=['POST']) def ask_question(): """问答接口""" data = request.json question = data.get('question', '') top_k = data.get('top_k', 3) if not question: return jsonify({'error': '问题不能为空'}), 400 try: result = qa_system({ 'query': question, 'top_k': top_k }) response = { 'question': question, 'answer': result['result'], 'sources': [ { 'content': doc.page_content, 'source': doc.metadata.get('source', 'unknown'), 'page': doc.metadata.get('page', 0) } for doc in result['source_documents'] ] } return jsonify(response) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/health', methods=['GET']) def health_check(): """健康检查接口""" return jsonify({'status': 'healthy', 'service': 'RAG API'}) if __name__ == '__main__': # 初始化系统(实际部署时应单独初始化) app.run(host='0.0.0.0', port=5000, debug=False)

使用curl测试API:

curl -X POST http://localhost:5000/api/ask \ -H "Content-Type: application/json" \ -d '{ "question": "如何申请年假?", "top_k": 2 }'

10. 性能优化与资源管理

RAG系统的性能优化主要集中在以下几个方面:

向量检索优化

  • 调整IVF索引的nlist参数,平衡精度和速度
  • 使用HNSW索引替代IVF_FLAT获得更好的检索性能
  • 设置合理的top_k值,避免检索过多无关文档

内存管理

# 分批处理大文档 def process_large_document(file_path, max_chunk_size=1000): documents = process_documents(file_path) # 监控内存使用 import psutil process = psutil.Process() for i in range(0, len(documents), max_chunk_size): chunk = documents[i:i+max_chunk_size] embeddings = embedding_model.embed_documents( [doc.page_content for doc in chunk] ) # 插入数据库后立即释放内存 insert_documents(collection, chunk, embeddings) del embeddings del chunk memory_info = process.memory_info() print(f"内存使用: {memory_info.rss / 1024 / 1024:.2f} MB")

缓存策略对常见问题建立答案缓存,减少重复计算:

import hashlib import json from datetime import datetime, timedelta class AnswerCache: def __init__(self, ttl_hours=24): self.cache = {} self.ttl = timedelta(hours=ttl_hours) def get_cache_key(self, question, top_k=3): """生成缓存键""" content = f"{question}_{top_k}" return hashlib.md5(content.encode()).hexdigest() def get(self, question, top_k=3): key = self.get_cache_key(question, top_k) if key in self.cache: cached_data = self.cache[key] if datetime.now() - cached_data['timestamp'] < self.ttl: return cached_data['answer'] return None def set(self, question, answer, top_k=3): key = self.get_cache_key(question, top_k) self.cache[key] = { 'answer': answer, 'timestamp': datetime.now() } # 使用缓存 cache = AnswerCache() def cached_ask(question, top_k=3): cached_answer = cache.get(question, top_k) if cached_answer: return cached_answer # 正常处理 result = qa_system({'query': question, 'top_k': top_k}) cache.set(question, result, top_k) return result

11. 常见问题与解决方案

问题1:Milvus连接失败

错误信息:ConnectivityError - Failed to connect to Milvus 解决方案: 1. 检查Milvus服务是否启动:docker ps | grep milvus 2. 验证端口是否开放:telnet localhost 19530 3. 检查防火墙设置

问题2:嵌入模型加载慢

# 解决方案:预加载模型 embedding_model = HuggingFaceEmbeddings( model_name="BAAI/bge-large-zh", model_kwargs={'device': 'cuda' if torch.cuda.is_available() else 'cpu'}, encode_kwargs={'normalize_embeddings': True} ) # 首次运行时预热模型 embedding_model.embed_documents(["预热文本"])

问题3:检索结果不准确调整检索参数和文本分割策略:

# 优化文本分割 text_splitter = RecursiveCharacterTextSplitter( chunk_size=300, # 减小块大小 chunk_overlap=100, # 增加重叠 separators=["\n\n", "\n", "。", "!", "?", ";", ",", "、", ""] ) # 优化检索参数 retriever = vector_store.as_retriever( search_type="mmr", # 使用最大边际相关度 search_kwargs={ "k": 5, "fetch_k": 20, # 初始检索更多文档 "lambda_mult": 0.5 # 多样性权重 } )

问题4:内存占用过高实施内存监控和自动清理:

import gc import psutil def memory_optimized_processing(documents, batch_size=50): results = [] for i in range(0, len(documents), batch_size): batch = documents[i:i+batch_size] batch_results = process_batch(batch) results.extend(batch_results) # 强制垃圾回收 del batch del batch_results gc.collect() # 内存检查 if psutil.virtual_memory().percent > 85: print("内存使用过高,暂停处理") break return results

12. 企业级部署建议

对于生产环境部署,建议采用以下架构:

高可用架构

  • 使用Milvus集群替代单机部署
  • 部署多个API实例负载均衡
  • 设置数据库定期备份机制

安全考虑

# API认证中间件 from functools import wraps from flask import request, jsonify def require_api_key(f): @wraps(f) def decorated_function(*args, **kwargs): api_key = request.headers.get('X-API-Key') if not api_key or not validate_api_key(api_key): return jsonify({'error': '无效的API密钥'}), 401 return f(*args, **kwargs) return decorated_function @app.route('/api/ask', methods=['POST']) @require_api_key def protected_ask_question(): # 受保护的问答接口 pass

监控与日志集成Prometheus监控和ELK日志系统:

import logging from prometheus_client import Counter, Histogram # 定义指标 REQUEST_COUNT = Counter('rag_requests_total', 'Total requests') REQUEST_DURATION = Histogram('rag_request_duration_seconds', 'Request duration') @app.route('/api/ask', methods=['POST']) @REQUEST_DURATION.time() def monitored_ask_question(): REQUEST_COUNT.inc() # 正常处理逻辑 pass

这个RAG系统从零开始搭建,涵盖了从基础概念到企业级部署的全流程。关键是要理解每个组件的作用和调优方法,特别是向量检索参数和文本处理策略对最终效果的影响最大。

实际部署时建议先从小规模开始,逐步优化各个模块的性能。记得定期更新知识库内容,监控系统运行状态,才能保证RAG系统长期稳定运行。