首页>AI>正文

RAG工程化落地:检索链路构建与生产优化实践

Feng 12 阅读 AI

RAG要解决的问题很具体:模型答得流畅,但答错了;或者企业私有文档、最新行业动态它压根没读过。检索增强生成的做法是把外部知识库接在模型前面,先找证据再组织答案。它不是什么新算法,本质是工程链路设计。

两条链路,各自独立迭代

整套系统拆成离线建库和在线问答两段。离线段把原始文档变成机器能快速匹配的形态:加载、分块、向量化、入库。在线段接收用户问题,向量化后去库里找相似片段,拼进Prompt交给LLM生成。两段解耦的好处是知识更新只动离线段,模型权重完全不用碰。

这里有个容易搞反的认知:RAG的效果上限由检索决定,不由生成决定。检索捞回来的片段不相关,Prompt写得再严谨也是白搭。

和微调、Prompt硬塞的区别

微调把私有数据压进权重,回答自然,但成本高、周期长,知识一变就得重训。Prompt硬塞简单,可上下文窗口有限,塞不进大规模文档,检索效率也谈不上。RAG把知识放在外部库,更新只改库,检索后只传相关片段,长度限制和实时性两个问题一起解决。

技术栈怎么选

原型阶段图快:LangChain Document Loaders 加 Chroma,嵌入模型用 all-MiniLM-L6-v2,LLM 走 Ollama 本地跑 Llama 3,链路用 LangChain Chains 串起来。这套组合全开源、零联网、部署简单。

生产环境要换血:文档处理上 Unstructured 适配表格和图片;嵌入模型换 BGE-large-en/v1.5 提升语义精度;向量库换 Milvus 或 Pinecone,要分片、副本、水平扩容;LLM 用 GPT-4o 或本地化 Qwen-max,在推理能力和成本间取平衡;链路监控接 LangSmith 做追踪和指标评估。

本地原型:Ollama + Chroma

先装依赖,Ollama 本地部署好并拉取模型(ollama pull llama3):

pip install langchain langchain-ollama langchain-chroma langchain-text-splitters
pip install pypdf sentence-transformers

注意 langchain-community 里的 HuggingFaceEmbeddings 依赖 sentence-transformers,漏装会在初始化时报 ImportError,这是新手最常踩的坑之一。

离线建库的代码骨架:

from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_chroma import Chroma

loader = PyPDFLoader("enterprise_knowledge.pdf")
documents = loader.load()

text_splitter = RecursiveCharacterTextSplitter(
 chunk_size=500,
 chunk_overlap=50,
 length_function=len,
 separators=["\n\n", "\n", ". ", " ", ""]
)
text_chunks = text_splitter.split_documents(documents)

embeddings = HuggingFaceEmbeddings(
 model_name="all-MiniLM-L6-v2",
 model_kwargs={"device": "cpu"}
)

vector_db = Chroma.from_documents(
 documents=text_chunks,
 embedding=embeddings,
 persist_directory="./rag_chroma_db"
)

retriever = vector_db.as_retriever(search_kwargs={"k": 5})

Chroma 的持久化目录有个细节:进程重启后不要再用 from_documents 重建,那会重复写入。正确做法是 Chroma(persist_directory="./rag_chroma_db", embedding_function=embeddings) 直接加载已有库。如果换 Milvus 单机版,docker compose 拉起后先确认 etcd、minio、milvus 三个容器都是 healthy,否则客户端连接会超时,报错信息通常只显示连接失败,根因在依赖服务没起来。

在线链路。LangChain 0.2.x 之后 RetrievalQA 已归入 langchain.chains.legacy,新项目建议用 create_retrieval_chain 加 create_stuff_documents_chain 组合,行为一致但接口是官方维护路径:

from langchain_ollama import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOllama(model="llama3", temperature=0.1, max_tokens=512)

prompt = ChatPromptTemplate.from_template("""
你是企业知识库问答助手,严格遵守以下规则:
1. 仅使用提供的检索内容作为回答依据,严禁编造信息;
2. 若检索内容无相关答案,直接回复“未查询到对应知识库内容”;
3. 回答需简洁专业,分点说明复杂问题,保留关键数据。

检索到的知识库内容:
{context}

用户问题:
{question}
""")

combine_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, combine_chain)

result = rag_chain.invoke({"input": "本企业2025年核心产品的研发进度是?"})
print(result["answer"])
for doc in result["context"][:3]:
 print(doc.metadata.get("page"), doc.page_content[:100])

参数怎么调,先验证再动手

分块大小看文档句子长度:长句多调到800,短句碎调到300,原则是单块别混进多个主题。Top-k 取3到5,太大引入噪声,太小漏关键内容。温度问答场景压到0.0到0.2。

调参之前先建验证集:挑20到30个真实问题,人工标注每个问题对应的正确片段(gold chunk),跑一遍检索看 Top-k 命中率。命中率低于预期时,优先调 chunk_overlap 和分块策略,而不是急着换嵌入模型——多数召回问题出在切分把语义切断了。这个验证方法比凭感觉调参靠谱得多。

生产环境四个方向

检索精度。单一向量检索遇到语义模糊就飘。混合检索把向量语义和 Elasticsearch 关键词检索结合,语义保相关性,关键词滤噪声。检索后加一层重排,Cross-Encoder 或 Cohere Rerank 对初筛结果重新打分,Top-3 准确率通常有明显提升。

性能。向量库换 Milvus 或 Pinecone,开分片副本支持水平扩容;高频查询结果用 Redis 缓存。文档加载和向量化丢进 Celery 异步队列,别阻塞主线程;多用户并发用连接池限流,防止 LLM 过载。

稳定性。接 LangSmith 监控检索准确率、回答相关性、幻觉率。检索失败返回预设提示,LLM 调用超时自动重试,向量库异常切备用节点。

知识更新。用增量写入接口只加新文档向量,不重建历史数据。给知识库打版本标签支持回滚,避免错误文档污染全库。

场景边界与延伸

RAG 适合知识频繁变动、需要溯源、数据不能出内网的场景。反过来,如果任务要求模型深度理解领域语义并做风格化输出,微调仍然更合适;如果知识量小到能塞进上下文窗口,直接 Prompt 反而省事。RAG 的短板在于检索链路本身引入延迟,对毫秒级响应场景不友好。

延伸方向包括多模态 RAG(图片、表格转向量做图文混合问答)、智能体加 RAG(用 LangGraph 让系统自主决定是否检索、检索几轮)、跨语言 RAG(多语言嵌入模型支持中英混合检索)。原型升级到服务时,分布式部署保并发,权限管控保私有知识库安全访问,这两件事优先级最高。

ID:7757

Feng
这位作者很神秘,还没有填写简介。