2025年,RAG(检索增强生成)已成为企业落地的首选AI架构。本文从技术实战角度,详细介绍如何从零搭建一个生产级RAG系统。
目录
为什么需要RAG
传统问答系统的痛点
问题1:大模型的知识盲区
大语言模型(LLM)的训练数据有截止日期,且无法访问企业内部数据。
用户:"公司的差旅政策是什么?"
LLM:"抱歉,我不知道贵公司的具体政策。"
问题2:幻觉问题
LLM可能编造不存在的信息。
用户:"XX产品的技术参数是什么?"
LLM:(自信地编造了一堆错误数据)
问题3:无法溯源
LLM生成的答案无法引用来源,降低了可信度。
RAG的解决方案
**RAG(Retrieval-Augmented Generation)**的核心理念:
用户提问 → 检索相关文档 → 将文档作为上下文 → LLM生成答案
优势:
- ✅ 回答基于真实文档,减少幻觉
- ✅ 可引用来源,提高可信度
- ✅ 可访问企业私有知识
- ✅ 无需训练模型,降低成本
RAG架构原理
核心流程
用户提问
↓
文档检索(向量搜索)
↓
文档片段排序(重排序)
↓
提示词构建(注入检索结果)
↓
LLM生成答案
↓
返回答案+来源
关键组件
| 组件 | 作用 | 主流方案 |
|---|---|---|
| 文档解析 | 提取文本、表格、图片 | PyPDF2, Unstructured, OCR |
| 文本切分 | 将文档切成合适大小的片段 | LangChain TextSplitter |
| 向量化 | 将文本转换为向量嵌入 | OpenAI Embedding, 通义千问 |
| 向量数据库 | 存储和检索向量 | Milvus, Pinecone, Chroma |
| 检索器 | 查找相关文档片段 | 向量相似度搜索 + 重排序 |
| LLM | 生成最终答案 | GPT-4, 通义千问, DeepSeek |
技术选型
LLM选择
国内企业推荐方案:
| LLM | 优势 | 适用场景 | 参考价格 |
|---|---|---|---|
| 通义千问 | 中文理解强,API稳定 | 通用问答 | ¥0.008/千tokens |
| DeepSeek | 性价比高,开源友好 | 成本敏感场景 | ¥0.001/千tokens |
| 百川 | 中文生成能力强 | 内容创作 | ¥0.012/千tokens |
| 智谱 | 逻辑推理强 | 复杂问答 | ¥0.012/千tokens |
私有化部署:
- 支持国产LLM是政企刚需
- 华为昇腾、寒武纪芯片适配
- 质数科技AI Stack提供全栈国产化方案
向量数据库选择
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Milvus | 开源,功能强大 | 运维复杂 | 大规模部署 |
| Chroma | 轻量,易用 | 功能有限 | 小规模试点 |
| Pinecone | 托管服务,简单 | 需翻墙 | 海外业务 |
| PGVector | 基于PostgreSQL | 性能一般 | 已有PG基础设施 |
推荐:
- 试点阶段:Chroma
- 生产环境:Milvus
- 国产化:Milvus + 国产向量引擎
从零搭建实战
第一步:环境准备
# 创建虚拟环境
python -m venv rag_env
source rag_env/bin/activate # Windows: rag_env\Scripts\activate
# 安装依赖
pip install langchain langchain-community
pip install chromadb
pip install unstructured[pdf]
pip install dashscope # 阿里通义千问
第二步:文档处理
from unstructured.partition.pdf import partition_pdf
from langchain.text_splitter import RecursiveCharacterTextSplitter
def process_documents(file_path):
"""解析PDF文档并切分"""
# 解析PDF
elements = partition_pdf(file_path)
# 提取文本
text = "\n".join([el.text for el in elements])
# 切分文本
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ",", " ", ""]
)
chunks = text_splitter.split_text(text)
return chunks
# 使用示例
chunks = process_documents("company_policy.pdf")
print(f"切分出 {len(chunks)} 个文档片段")
第三步:向量化存储
import chromadb
from dashscope import TextEmbedding
class VectorStore:
def __init__(self, collection_name="docs"):
# 初始化Chroma
self.client = chromadb.Client()
self.collection = self.client.get_or_create_collection(
name=collection_name
)
def embed_text(self, text):
"""使用通义千问进行向量化"""
response = TextEmbedding.call(
model="text-embedding-v2",
input=text
)
return response["output"]["embeddings"][0]
def add_documents(self, chunks, metadata=None):
"""添加文档到向量库"""
embeddings = [self.embed_text(chunk) for chunk in chunks]
self.collection.add(
embeddings=embeddings,
documents=chunks,
metadatas=metadata or [{}] * len(chunks),
ids=[f"doc_{i}" for i in range(len(chunks))]
)
def search(self, query, top_k=3):
"""搜索相关文档"""
query_embedding = self.embed_text(query)
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
return results["documents"][0], results["metadatas"][0]
# 使用示例
vector_store = VectorStore()
vector_store.add_documents(chunks)
docs, metas = vector_store.search("差旅政策有哪些?")
第四步:RAG问答
import dashscope
from dashscope import Generation
def rag_answer(question, vector_store):
"""RAG问答主函数"""
# 1. 检索相关文档
docs, metas = vector_store.search(question, top_k=3)
# 2. 构建提示词
context = "\n\n".join([f"文档{i+1}:{doc}" for i, doc in enumerate(docs)])
prompt = f"""你是一个专业的企业知识问答助手。
请根据以下文档内容回答用户问题。
【参考文档】
{context}
【用户问题】
{question}
【回答要求】
1. 仅基于参考文档回答,不要编造信息
2. 如果文档中没有相关信息,明确告知
3. 回答要简洁、准确
4. 引用具体的文档来源
回答:"""
# 3. 调用LLM生成答案
response = Generation.call(
model="qwen-turbo",
prompt=prompt,
api_key="your-dashscope-api-key"
)
answer = response["output"]["text"]
# 4. 返回答案+来源
return {
"answer": answer,
"sources": docs
}
# 使用示例
result = rag_answer("差旅费报销标准是什么?", vector_store)
print(result["answer"])
print("\n来源文档:", result["sources"])
第五步:完整应用
import streamlit as st
st.title("企业知识库问答系统")
# 侧边栏:上传文档
with st.sidebar:
st.header("文档管理")
uploaded_file = st.file_uploader("上传文档", type=["pdf", "txt", "md"])
if uploaded_file and st.button("处理文档"):
with st.spinner("处理中..."):
chunks = process_documents(uploaded_file)
vector_store.add_documents(chunks)
st.success(f"已处理 {len(chunks)} 个文档片段")
# 主界面:问答
st.header("提问")
question = st.text_input("请输入你的问题")
if st.button("提问") and question:
with st.spinner("思考中..."):
result = rag_answer(question, vector_store)
st.subheader("答案")
st.write(result["answer"])
st.subheader("参考文档")
for i, doc in enumerate(result["sources"], 1):
st.text(f"文档{i}:{doc[:100]}...")
生产环境优化
优化1:混合检索
纯向量检索可能遗漏关键词匹配,建议混合检索:
def hybrid_search(question, top_k=5):
"""混合检索:向量+关键词"""
# 向量检索
vector_docs, _ = vector_store.search(question, top_k=10)
# 关键词检索(如BM25)
keyword_docs = bm25_search(question, top_k=10)
# 合并去重
all_docs = list(set(vector_docs + keyword_docs))
# 重排序(使用Rerank模型)
reranked_docs = rerank(question, all_docs)
return reranked_docs[:top_k]
优化2:重排序
检索后使用专门的重排序模型提升准确度:
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('BAAI/bge-reranker-v2-m3')
def rerank(question, docs):
"""对检索结果重排序"""
pairs = [[question, doc] for doc in docs]
scores = reranker.predict(pairs)
# 按分数排序
ranked = sorted(zip(docs, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, score in ranked]
优化3:查询改写
用户提问可能不够精确,需要进行改写:
def rewrite_query(question):
"""查询改写:让问题更精确"""
prompt = f"""你是一个查询优化专家。
请将用户的提问改写为更适合检索的形式。
原问题:{question}
改写要求:
1. 保持原意
2. 添加相关关键词
3. 使表述更清晰
改写后的问题:"""
response = Generation.call(
model="qwen-turbo",
prompt=prompt
)
return response["output"]["text"].strip()
优化4:缓存机制
相同问题直接返回缓存结果:
import hashlib
import json
def get_cache_key(question):
return hashlib.md5(question.encode()).hexdigest()
def cached_rag(question, vector_store):
cache_key = get_cache_key(question)
# 检查缓存
if cache_key in cache:
return cache[cache_key]
# 执行RAG
result = rag_answer(question, vector_store)
# 存入缓存
cache[cache_key] = result
return result
企业级部署
部署架构
┌─────────────────────────────────────────┐
│ 前端(Web/App) │
└─────────────────┬───────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ API网关(认证、限流) │
└─────────────────┬───────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ RAG服务 │
│ ┌──────────┐ ┌──────────┐ │
│ │ 文档处理 │ │ 问答服务 │ │
│ └──────────┘ └──────────┘ │
└─────────────────┬───────────────────────┘
│
┌───────┴───────┐
↓ ↓
┌──────────────┐ ┌──────────────┐
│ 向量数据库 │ │ LLM服务 │
│ (Milvus) │ │ (私有化) │
└──────────────┘ └──────────────┘
私有化部署要点
1. LLM私有化
# 使用Docker部署本地LLM
docker pull ghcr.io/mudler/ollama:latest
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama
# 下载国产模型
ollama pull qwen2:7b
2. 向量数据库私有化
# 部署Milvus
docker-compose -f docker-compose.yml up -d
3. 国产化适配
- 硬件:华为昇腾、寒武纪
- 操作系统:统信UOS、麒麟
- LLM:通义千问、DeepSeek
- 向量引擎:Milvus国产化版本
质数科技AI Stack提供开箱即用的私有化RAG解决方案,包括:
- ✅ 全栈国产化适配
- ✅ 一键部署脚本
- ✅ 企业级权限管理
- ✅ 监控和运维工具
性能基准
典型性能数据
| 指标 | 数值 | 备注 |
|---|---|---|
| 检索延迟 | 100-300ms | Milvus,100万文档 |
| LLM生成延迟 | 1-3s | 通义千问turbo |
| 端到端延迟 | 2-5s | 完整RAG流程 |
| 准确率 | 85-95% | 取决于数据质量 |
| 并发能力 | 100+ QPS | 需优化和缓存 |
成本估算
以100万文档、1000并发用户为例:
| 组件 | 年度成本 |
|---|---|
| LLM API | ¥10万 |
| 向量数据库 | ¥5万(云托管) |
| 服务器 | ¥20万(私有化) |
| 运维 | ¥15万 |
| 总计 | ¥30-50万/年 |
私有化部署2-3年回本。
常见问题
Q1:如何提升检索准确率?
方法:
- 优化文本切分策略(按语义而非固定长度)
- 使用混合检索(向量+关键词)
- 添加重排序模型
- 优化文档预处理(清理噪音)
Q2:如何处理多模态内容?
方案:
- 表格:使用专门的表格解析器(TableTransformer)
- 图片:OCR + 图像描述模型(如BLIP)
- 视频:提取关键帧后处理
Q3:如何保证数据安全?
措施:
- 私有化部署(数据不出域)
- 访问权限控制(文档级别)
- 审计日志(记录所有查询)
- 数据脱敏(敏感信息处理)
Q4:如何持续优化?
方法:
- 收集用户反馈(点赞/点踩)
- 分析bad case,针对性优化
- 定期更新文档库
- A/B测试不同策略
总结
RAG系统的核心价值在于将企业知识转化为智能问答能力。
关键要点:
- 架构简单:文档→向量→检索→生成
- 技术成熟:开源工具链完善
- 成本可控:无需训练模型
- 效果可验证:可溯源,易调试
实践建议:
- 从小场景开始(1-2个文档类型)
- 重视数据质量(垃圾进,垃圾出)
- 持续收集反馈优化
- 逐步扩展到全企业
厦门质数科技有限公司是专注AI应用解决方案的科技公司,我们提供的企业知识图谱系统(数问引擎)就是基于RAG架构的生产级解决方案。
如果你需要:
- ✅ 企业级RAG系统咨询
- ✅ 私有化部署方案
- ✅ 定制化开发
欢迎交流讨论!
你搭建过RAG系统吗?遇到过哪些坑?欢迎在评论区分享经验!