尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

LangChain RAG检索增强生成

LangChain RAG检索增强生成
📅 发布时间:2026/7/31 6:04:14

摘要

检索到的文档块不能直接扔给用户,得让模型读了资料再回答。本文讲怎么把Milvus转成Retriever、把文档块整理成上下文、写RAG专用的Prompt,以及最终拼成一条完整的RAG Chain。顺带处理一个问题:资料不足时让模型老实说"不知道"。

一、什么是RAG

RAG全称Retrieval-Augmented Generation,检索增强生成。

核心思路就三步:

用户提问 → 从知识库检索相关资料 → 把资料和问题一起交给模型生成答案

RAG不训练模型,不改模型参数,只是在调用时往Prompt里塞外部资料。

二、为什么需要RAG

模型不知道企业内部资料。DeepSeek不知道你们公司的考勤制度,你得把制度文档喂给它。

模型知识不是最新的。企业制度、产品价格随时在变,RAG用最新资料就能答最新问题。

模型可能编造答案。不知道的时候会硬编,RAG可以要求它"不知道就说不知道"。

回答需要展示来源。企业场景不仅要答案,还得告诉用户"这是来自员工手册第3页"。

三、RAG的两个阶段

索引阶段:加载文档→切分→向量化→存Milvus。跑一次就行,资料变了再重跑。

问答阶段:用户提问→检索相关块→组装Prompt→模型生成答案。每次问答都要走一遍。

第七章做了索引阶段,本章做问答阶段。

四、Retriever检索器

Retriever是LangChain里统一的检索接口。输入字符串问题,返回Document列表。

python

from langchain_milvus import Milvus from utils.model_factory import get_embeddings embeddings = get_embeddings() vector_store = Milvus( embedding_function=embeddings, connection_args={"host": "localhost", "port": "19530"}, collection_name="knowledge_base" ) retriever = vector_store.as_retriever(search_kwargs={"k": 3}) docs = retriever.invoke("员工迟到怎么处理") for doc in docs: print(doc.page_content) print(doc.metadata)

VectorStore负责存向量和相似度搜索,Retriever负责"根据问题返回文档"这个动作。RAG Chain通常依赖Retriever。

五、案例一:使用Retriever检索文档

python

from langchain_milvus import Milvus from utils.model_factory import get_embeddings embeddings = get_embeddings() vector_store = Milvus( embedding_function=embeddings, connection_args={"host": "localhost", "port": "19530"}, collection_name="knowledge_base" ) retriever = vector_store.as_retriever(search_kwargs={"k": 3}) question = "公司对迟到早退有什么规定" docs = retriever.invoke(question) for i, doc in enumerate(docs, 1): print(f"结果{i}") print(f"内容:{doc.page_content[:150]}...") print(f"来源:{doc.metadata.get('file_name')}") if doc.metadata.get("page") is not None: print(f"页码:{doc.metadata.get('page') + 1}") print("-" * 40)

六、整理文档上下文

Retriever返回的是Document列表,不能直接塞进Prompt。需要把多个文档块拼成一段上下文文本:

python

def format_documents(docs): if not docs: return "未找到相关资料。" parts = [] for i, doc in enumerate(docs, 1): source = doc.metadata.get("file_name", "未知来源") page = doc.metadata.get("page") if page is not None: source_info = f"【资料{i}】来自《{source}》第{page + 1}页" else: source_info = f"【资料{i}】来自《{source}》" parts.append(f"{source_info}\n{doc.page_content}") return "\n\n".join(parts)

七、编写RAG专用Prompt

RAG Prompt要告诉模型四件事:你是谁、用户问了什么、可以参考什么资料、资料不够怎么办。

python

from langchain.prompts import ChatPromptTemplate template = ChatPromptTemplate.from_messages([ ("system", """你是企业知识库助手。请严格根据以下参考资料回答用户问题。 参考资料: {context} 规则: 1. 只根据参考资料回答,不要使用自己的知识 2. 如果参考资料中没有相关信息,明确说"资料中没有提到" 3. 不要编造答案 4. 引用资料时注明来源文件名称"""), ("human", "{question}") ])

"资料不足时说不知道"这条规则很关键,能有效降低模型瞎编的概率。

八、案例二:手动完成一次RAG问答

先拆开步骤写,理解之后再上LCEL:

python

from langchain_milvus import Milvus from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from utils.model_factory import get_deepseek_model, get_embeddings embeddings = get_embeddings() model = get_deepseek_model(temperature=0.1) parser = StrOutputParser() vector_store = Milvus( embedding_function=embeddings, connection_args={"host": "localhost", "port": "19530"}, collection_name="knowledge_base" ) retriever = vector_store.as_retriever(search_kwargs={"k": 3}) def format_documents(docs): parts = [] for i, doc in enumerate(docs, 1): source = doc.metadata.get("file_name", "未知") parts.append(f"【资料{i}】来自《{source}》\n{doc.page_content}") return "\n\n".join(parts) template = ChatPromptTemplate.from_messages([ ("system", "根据参考资料回答问题。参考资料:\n{context}\n如果资料中没有相关信息,就说不知道。"), ("human", "{question}") ]) # 手动走三步 question = "员工每年有几天年假" docs = retriever.invoke(question) context = format_documents(docs) prompt = template.invoke({"context": context, "question": question}) response = model.invoke(prompt) answer = parser.invoke(response) print(answer)

三步走完:检索→格式化→生成。

九、使用LCEL组合RAG Chain

Retriever也是Runnable,可以接入LCEL管道:

python

from langchain_core.runnables import RunnablePassthrough from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from utils.model_factory import get_deepseek_model model = get_deepseek_model(temperature=0.1) parser = StrOutputParser() def format_documents(docs): parts = [] for i, doc in enumerate(docs, 1): source = doc.metadata.get("file_name", "未知") parts.append(f"【资料{i}】来自《{source}》\n{doc.page_content}") return "\n\n".join(parts) template = ChatPromptTemplate.from_messages([ ("system", "根据参考资料回答问题。参考资料:\n{context}\n如果资料中没有相关信息,就说不知道。"), ("human", "{question}") ]) chain = ( { "context": retriever | format_documents, "question": RunnablePassthrough() } | template | model | parser ) result = chain.invoke("员工每年有几天年假") print(result)

数据流动:输入问题→context分支走检索+格式化,question分支原样透传→合并成字典→进Prompt→进模型→进Parser。

十、LCEL写法的一个限制

上面这条链直接返回字符串答案,拿不到来源信息。如果还要展示来源,得再查一次,但两次检索结果可能不一样。

所以完整案例改用服务函数封装,答案和来源用同一次检索结果:

python

def rag_answer(question): docs = retriever.invoke(question) context = format_documents(docs) prompt = template.invoke({"context": context, "question": question}) response = model.invoke(prompt) answer = parser.invoke(response) sources = extract_sources(docs) return answer, sources

答案和来源用同一批docs,不会对不上。

十一、在回答中展示来源

从metadata里读来源,不要靠模型自己猜:

python

def extract_sources(docs): seen = set() sources = [] for doc in docs: file_name = doc.metadata.get("file_name", "未知文件") page = doc.metadata.get("page") key = f"{file_name}_{page}" if key not in seen: seen.add(key) if page is not None: sources.append(f"《{file_name}》第{page + 1}页") else: sources.append(f"《{file_name}》") return sources

用set去重,避免多个文档块来自同一文件同一页时重复展示。

十二、资料不足时拒绝编造

资料里没有的信息,让模型明确说不知道。Prompt里写清楚规则,然后测试:

python

question = "公司年终奖怎么发" # 知识库里没有 answer, sources = rag_answer(question) print(answer) # 预期输出类似"资料中没有提到年终奖的相关信息"

需要说明:大模型不是程序,不能保证100%遵守规则。正式项目还要结合相似度阈值、测试集验证等手段。

十三、企业案例:内部知识库问答系统

项目结构:

text

knowledge_base/ # 原始文档 build_index.py # 第七章,建索引 rag_service.py # RAG服务封装 main.py # 终端交互程序

rag_service.py:

python

from langchain_milvus import Milvus from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from utils.model_factory import get_deepseek_model, get_embeddings class RAGService: def __init__(self, collection_name="knowledge_base"): self.embeddings = get_embeddings() self.model = get_deepseek_model(temperature=0.1) self.parser = StrOutputParser() self.vector_store = Milvus( embedding_function=self.embeddings, connection_args={"host": "localhost", "port": "19530"}, collection_name=collection_name ) self.retriever = self.vector_store.as_retriever(search_kwargs={"k": 3}) self.template = ChatPromptTemplate.from_messages([ ("system", """你是企业知识库助手。严格根据参考资料回答用户问题。 参考资料: {context} 规则: 1. 只根据参考资料回答 2. 资料中没有相关信息时,明确说"资料中没有提到" 3. 不编造答案 4. 引用资料时注明来源文件"""), ("human", "{question}") ]) def _format_docs(self, docs): parts = [] for i, doc in enumerate(docs, 1): source = doc.metadata.get("file_name", "未知文件") parts.append(f"【资料{i}】来自《{source}》\n{doc.page_content}") return "\n\n".join(parts) def _extract_sources(self, docs): seen = set() sources = [] for doc in docs: file_name = doc.metadata.get("file_name", "未知文件") page = doc.metadata.get("page") key = f"{file_name}_{page}" if key not in seen: seen.add(key) if page is not None: sources.append(f"《{file_name}》第{page + 1}页") else: sources.append(f"《{file_name}》") return sources def ask(self, question): docs = self.retriever.invoke(question) context = self._format_docs(docs) prompt = self.template.invoke({"context": context, "question": question}) response = self.model.invoke(prompt) answer = self.parser.invoke(response) sources = self._extract_sources(docs) return answer, sources

main.py:

python

from rag_service import RAGService def main(): rag = RAGService() print("企业知识库问答系统已启动,输入exit退出") while True: question = input("\n问题:").strip() if question.lower() == "exit": break if not question: continue answer, sources = rag.ask(question) print(f"\n答案:{answer}") if sources: print(f"\n来源:{', '.join(sources)}") if __name__ == "__main__": main()

运行前确保已执行build_index.py建好索引。

十四、search_type调整检索方式

similarity:默认,按相似度从高到低取前k个。简单直接,但文档块内容重叠时容易返回重复信息。

MMR(Maximum Marginal Relevance):先多取一些候选(fetch_k),再从中挑选既相关又多样化的k个结果,避免重复。

python

retriever = vector_store.as_retriever( search_type="mmr", search_kwargs={"k": 3, "fetch_k": 10, "lambda_mult": 0.5} )

lambda_mult越靠近1越偏向相似度优先,越靠近0越偏向多样性优先。文档切片重叠多时用MMR,切片内容独立时用similarity。

十五、k值怎么设

k太小可能遗漏资料,k太大可能混入无关内容、增加token消耗。

从k=3或k=5开始试,根据真实问题效果调整。

十六、RAG调试顺序

回答不对时按顺序查:

打印检索结果,确认文档对不对:

python

docs = retriever.invoke(question) for doc in docs: print(doc.page_content)

打印格式化后的上下文,确认模型实际收到了什么:

python

context = format_documents(docs) print(context)

确认检索正确后再调Prompt。顺序是:数据→切分→检索→Prompt→模型,从前往后查。

常见问题

检索正确但回答错误

查Prompt是否明确要求根据资料回答、上下文是否完整传入、多个文档间是否有冲突。打印最终Prompt能看到模型实际收到了什么。

检索结果就是错的

问题在检索阶段:切分不合理、Embedding效果差、k值不对、文档缺少用户常用表达。改Prompt没用,得改索引。

文档更新后回答还是旧的

向量库里存的还是旧数据,重新跑build_index.py。

RAG能完全避免幻觉吗

不能。只能明显降低概率,还需要清晰的Prompt、高质量的检索、测试集验证和必要的人工审核。

相关新闻

  • AI写作助手如何提升本科生科研论文质量
  • Jackson树模型:JsonNode、ObjectNode与ArrayNode实战指南
  • Python turtle库图形编程:从基础绘制到多边形动画实战

最新新闻

  • 从RS-485故障解析上拉下拉电阻与推挽开漏输出的硬件设计核心
  • AI Copilot API调用工程化实践:从零构建稳定高效的大模型集成方案
  • 温控PID实战:单环与双环结构调试指南与参数整定技巧
  • 数字孪生技术在水电站全生命周期管理中的应用
  • C++ STL list双向链表:核心特性、性能对比与实战应用详解
  • 中空板哪里找:常见问题解答(2026最新版) - 汇聚至此

日新闻

  • 7步掌握KMS智能激活工具:Windows和Office永久激活完整方案
  • 如何在Windows上运行iOS应用:ipasim跨平台模拟器终极指南
  • 2026年重庆工伤赔偿律师口碑推荐:洪家木律师用专业赢得信赖 - 本地品牌推荐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号