当前位置: 首页 > news >正文

手把手教你用LangChain搭建企业级RAG问答系统,解决大模型知识幻觉难题

你有没有遇到过这种情况:让大模型回答公司内部文档的问题,它要么编答案,要么说"我不清楚"?RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这个问题的——先从你的文档里检索出相关内容,再让模型基于这些内容回答。

RAG是目前AI落地最成熟的场景,没有之一。客服系统、文档问答、企业知识库、法律助手……底层都是RAG。今天这篇文章,我带你用LangChain 1.0从零搭一个完整的RAG系统。

RAG的核心思路

一句话说清楚:先把文档切成小块存进向量数据库,用户提问时检索出最相关的几块,然后把这些块塞进提示词让模型回答。

流程图:

用户问题 ↓[Embedding模型把问题转成向量] ↓[在向量数据库中检索最相似的文档块] ↓[把检索结果+用户问题一起发给大模型] ↓[大模型基于检索到的内容生成回答]

第一步:加载和切分文档

RAG的第一步是把你的文档读进来并切成小块。为什么不能整篇喂给模型?两个原因:一是模型的上下文窗口有限,装不下整篇文档;二是小块检索精度更高,整篇检索噪音太多。

文档加载

from langchain_community.document_loaders import TextLoader, PyPDFLoader# 加载txt文件loader = TextLoader("company_faq.txt", encoding="utf-8")docs = loader.load()# 加载PDF文件loader = PyPDFLoader("product_manual.pdf")docs = loader.load()

LangChain支持几十种文档格式:Word、Markdown、HTML、CSV、Notion、飞书文档……在langchain-community里基本都能找到对应的Loader。

文本切分

from langchain_text_splitters import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每块最多500个字符 chunk_overlap=50, # 相邻块之间重叠50个字符(避免关键信息被切断) separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""], # 按优先级切分)chunks = splitter.split_documents(docs)print(f"文档被切成了 {len(chunks)} 块")

几个要点:

  • chunk_size不是越小越好——太小会丢失上下文,太大检索噪音多。500-1000字符是比较通用的范围
  • chunk_overlap建议设为chunk_size的10%左右,防止关键信息正好被切断
  • separators的顺序很重要:优先在段落、句子级别切分,不到万不得已别在词中间切

第二步:向量化并存入数据库

把文本块转成向量(一串数字),然后存进向量数据库。向量数据库能根据语义相似度快速检索。

from langchain_openai import OpenAIEmbeddingsfrom langchain_community.vectorstores import Chroma# 选择Embedding模型embeddings = OpenAIEmbeddings(model="text-embedding-3-small")# 创建向量数据库并存入文档块vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db", # 持久化存储目录)# 测试检索results = vectorstore.similarity_search("产品退货流程是什么?", k=3)for doc in results: print(doc.page_content) print("---")

向量数据库怎么选?

数据库特点适用场景
Chroma轻量、本地运行、开箱即用开发测试、小规模数据
FAISSMeta开源、纯内存、速度极快大规模数据、不需要持久化
Milvus分布式、高性能、生产级企业级大规模部署
Pinecone全托管云服务、免运维不想自己管基础设施
QdrantRust实现、性能好、过滤能力强需要元数据过滤的场景

我的建议:开发阶段用Chroma或FAISS,快且省事;上生产换Milvus或Pinecone。Embedding模型用OpenAI的text-embedding-3-small就行,性价比高,国内可以用智谱或百度的Embedding。

第三步:构建RAG链

有了向量数据库,接下来把检索和生成串起来。

基础版RAG链

from langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.runnables import RunnablePassthrough# 检索器retriever = vectorstore.as_retriever( search_type="similarity", # 相似度搜索 search_kwargs={"k": 3}, # 返回最相似的3个文档块)# 提示词模板prompt = ChatPromptTemplate.from_template("""基于以下参考资料回答用户问题。如果参考资料中没有相关信息,请说"根据现有资料无法回答"。参考资料:{context}用户问题:{question}回答:""")# 模型model = ChatOpenAI(model="gpt-4o-mini")# 构建RAG链def format_docs(docs): return"\n\n".join(doc.page_content for doc in docs)rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model | StrOutputParser())# 使用answer = rag_chain.invoke("产品退货流程是什么?")print(answer)

这段LCEL链的意思是:

  1. 用户问题同时传给retriever(检索相关文档)和question(原样传递)
  2. 检索结果格式化后和问题一起塞进提示词
  3. 发给模型生成回答
  4. 解析输出

进阶版:带来源引用的RAG

让模型回答时标注信息来源,方便用户验证:

from langchain_core.runnables import RunnableParallel# 提示词加上来源引用要求prompt_with_source = ChatPromptTemplate.from_template("""基于以下参考资料回答用户问题。回答时请标注信息来源的编号,如[1][2]。参考资料:{context}用户问题:{question}回答格式:回答内容[来源编号]""")rag_chain_with_source = RunnableParallel( {"context": retriever | format_docs, "question": RunnablePassthrough()}) | prompt_with_source | model | StrOutputParser()

第四步:把RAG封装成Agent工具

上面的RAG链是固定流程,但你可能想让Agent自己判断什么时候该查文档、什么时候直接回答。这时候可以把RAG封装成工具:

from langchain.agents import create_agentdef search_knowledge_base(query: str) -> str: """搜索公司知识库,获取产品、流程、政策等相关信息。 当用户询问公司内部信息时应使用此工具。""" results = rag_chain.invoke(query) return resultsagent = create_agent( model="gpt-4o-mini", tools=[search_knowledge_base, search_web], # 知识库 + 网络搜索 system_prompt="""你是一个智能客服助手。- 公司内部信息(产品、流程、政策等)用search_knowledge_base查询- 实时信息、新闻、外部知识用search_web查询- 不要编造答案,查不到就说不知道""",)

这样Agent就能根据问题类型自己选检索方式了——内部文档走知识库,实时信息走搜索。

高级RAG策略

基础RAG能覆盖60%的场景,剩下40%需要一些高级策略:

1. 查询改写

用户的提问往往不是最优的检索query。比如用户问"怎么退货",但文档里写的是"退换货流程"。让模型先改写query再检索:

query_rewrite_prompt = ChatPromptTemplate.from_template( "请将以下问题改写为更适合检索的查询词:\n原始问题:{question}\n改写后:")rewrite_chain = query_rewrite_prompt | model | StrOutputParser()# 用户问"怎么退货" → 改写为"退换货流程 退货条件 退货步骤" → 检索更准

2. 混合检索

纯向量检索擅长语义匹配,但对关键词精确匹配不太行。混合检索结合了向量搜索和关键词搜索:

# Chroma支持的混合搜索配置retriever = vectorstore.as_retriever( search_type="mmr", # Maximum Marginal Relevance,兼顾相关性和多样性 search_kwargs={"k": 5, "fetch_k": 10},)

3. 父文档检索

有时候切太碎了,检索到的小块缺乏上下文。父文档检索的思路是:检索小块,返回小块所属的大块:

from langchain.retrievers import ParentDocumentRetrieverfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_community.storage import InMemoryStore# 子块切分器(用于检索)child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)# 父块切分器(用于返回)parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=InMemoryStore(), child_splitter=child_splitter, parent_splitter=parent_splitter,)

我踩过的RAG坑

1. 文档切分粒度不对,检索效果差

这个得调。建议先用500字符的chunk_size试,如果检索不到相关内容,试着调大到800;如果检索到了但回答跑偏,试着调小到300。

2. Embedding模型和生成模型的语言不匹配

如果你的文档是中文的,但用了英文为主的Embedding模型(如早期的text-embedding-ada-002),检索效果会很差。推荐用text-embedding-3-small或中文Embedding模型(如BGE系列)。

3. 检索到的内容与问题无关

这往往是因为query太模糊。加一个查询改写步骤,或者用混合检索,效果会好很多。

4. 模型不按检索内容回答,自己编

在提示词里加上"仅基于参考资料回答,不要编造信息"的约束,并且明确告诉模型"如果参考资料中没有相关信息,请回答’根据现有资料无法回答’"。

假如你从2026年开始学大模型,按这个步骤走准能稳步进阶。

接下来告诉你一条最快的邪修路线,

3个月即可成为模型大师,薪资直接起飞。

阶段1:大模型基础

阶段2:RAG应用开发工程

阶段3:大模型Agent应用架构

阶段4:大模型微调与私有化部署

配套文档资源+全套AI 大模型 学习资料,朋友们如果需要可以微信扫描下方二维码免费领取【保证100%免费】👇👇


配套文档资源+全套AI 大模型 学习资料,朋友们如果需要可以微信扫描下方二维码免费领取【保证100%免费】👇👇

http://www.jsqmd.com/news/1153349/

相关文章:

  • 桌面版炼丹炉,开箱即训!零环境配置 · 图形化训练 · 自动打标 · 实时监控——无需折腾,专注素材与效果。
  • 计算机毕业设计之临床信息管理系统
  • 质谱级修饰胰蛋白酶的分子特性与蛋白质组学应用价值
  • Windows“此电脑“图标清理终极指南:如何彻底删除顽固快捷方式?
  • 设计系统主题切换架构:从CSS Variables到运行时Token注入完整方案
  • Certum 开源代码签名证书 2024 申请实战:3 步验证与 1 年 €25 成本详解
  • ClickHouse MergeTree 引擎:排序键设计决定查询天花板
  • 新能源车低压电源管理:基于SOC阈值的4种DCDC电压策略(12.3V-15.5V)
  • 打破次元壁:当Blender遇见MikuMikuDance,MMD Tools如何重塑3D创作边界
  • 如何永久保存B站缓存视频?m4s-converter开源工具3分钟搞定
  • 广州增城标识标牌生产生产商哪家强
  • Wwise 2023.1 离线安装与Unity集成避坑指南
  • PHP 文件上传漏洞防御:3 类服务端校验策略与 10+ 安全编码示例
  • d2s-editor:基于Vue.js的暗黑破坏神2存档解析与编辑技术实现
  • 计算机毕业设计之基于ssm框架智能制造从业人员招聘系统
  • AI 音乐工具的可控性设计:用户意图如何转化为生成参数
  • Agentic AI:从聊天机器人到自主执行系统-9588
  • DVWA从入门到精通(十四):CSP Bypass(内容安全策略绕过)
  • 食品加工厂代工预制菜需要提供配方吗?力晟解答关键点
  • 环形制冷片为何是808脱毛仪标配?
  • 3C电子产品自动化测试怎么选?2026靠谱智能终端测试厂家深度盘点
  • B站缓存视频一键转换终极指南:m4s-converter让珍贵视频永久保存
  • C盘飘红实操:系统清理到工具选择
  • 3步解决B站缓存失效难题:m4s-converter开源工具让视频永久属于你
  • 【万字干货】2026年工业自动化智能控制阀门选型避坑指南及优质电动阀门厂家揭秘(附Python通讯源码)
  • 计算机毕业设计之宿舍零食小铺管理系统设计与实现
  • frepple 9.17.0 Docker 部署实战:PostgreSQL 16 数据库独立配置 3 步避坑
  • 抖音内容采集的终极解决方案:告别繁琐的手工操作,一键自动化批量下载
  • 偶遇维特根斯坦:原来我没疯,只是那些关于痛苦与生命的逻辑,早被看穿了
  • DDrawCompat架构深度解析:DirectX 1-7兼容层设计与性能调优实战