RAG技术与应用
学习目标大模型应用开发的三种模式范式RAG技术概述RAG核心原理与流程NativeRAG实现步骤LangChain快速搭建本地知识库三大阶段有效提升RAG质量的方法1. 大模型应用开发的三种模式2. 什么是 RAG RAGRetrieval-Augmented Generation检索增强生成是一种结合信息检索Retrieval和文本生成Generation的技术RAG技术通过实时检索相关文档或信息并将其作为上下文输入到生成模型中从而提高生成结果的时效性和准确性。2.1. RAG 的优势是什么解决知识时效性问题大模型的训练数据通常是静态的无法涵盖最新信息而RAG可以检索外部知识库实时更新信息减少模型幻觉通过引入外部知识RAG能够减少模型生成虚假或不准确内容的可能性提升专业领域回答质量RAG能够结合垂直领域的专业知识库生成更具专业深度的回答生成内容的溯源可解释性2.2. RAG 的核心原理与流程Step1数据预处理构建索引库知识库构建收集并整理文档、网页、数据库等多源数据构建外部知识库文档分块将文档切分为适当大小的片段chunks以便后续检索。分块策略需要在语义完整性与检索效率之间取得平衡向量化处理使用嵌入模型如BGE、M3E、Chinese-Alpaca-2等将文本块转换为向量并存储在向量数据库中Step2检索阶段查询处理将用户输入的问题转换为向量并在向量数据库中进行相似度检索找到最相关的文本片段重排序对检索结果进行相关性排序选择最相关的片段作为生成阶段的输入Step3生成阶段上下文组装将检索到的文本片段与用户问题结合形成增强的上下文输入生成回答大语言模型基于增强的上下文生成最终回答划重点RAG 本质上就是重构了一个新的 Prompt3. NativeRAGNativeRAG的步骤Indexing 如何更好地把知识存起来。Retrieval 如何在大量的知识中找到一小部分有用的给到模型参考。Generation 如何结合用户的提问和检索到的知识让模型生成有用的答案。划重点上面三个步骤虽然看似简单但在 RAG 应用从构建到落地实施的整个过程中涉及较多复杂的工作内容4. LangChain快速搭建本地知识库检索4.1. 环境准备本地安装好 Conda 环境conda create -n chapter-2 python3.11 ipykernel推荐使用阿里大模型平台百炼https://bailian.console.aliyun.com/百炼平台使用注册登录申请api key4.2. 搭建流程文档加载并按一定条件切割成片段将切割的文本片段灌入检索引擎封装检索接口构建调用流程Query - 检索 - Prompt - LLM - 回复# !pip install pypdf2 # !pip install dashscope # !pip install langchain # !pip install langchain-openai # !pip install langchain-community # !pip install faiss-cpuimport os import logging import pickle from PyPDF2 import PdfReader from langchain.chains.question_answering import load_qa_chain from langchain_openai import OpenAI, ChatOpenAI from langchain_openai import OpenAIEmbeddings from langchain_community.embeddings import DashScopeEmbeddings from langchain_community.callbacks.manager import get_openai_callback from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import FAISS from typing import List, Tuple def extract_text_with_page_numbers(pdf) - Tuple[str, List[int]]: 从PDF中提取文本并记录每行文本对应的页码 参数: pdf: PDF文件对象 返回: text: 提取的文本内容 page_numbers: 每行文本对应的页码列表 text page_numbers [] for page_number, page in enumerate(pdf.pages, start1): extracted_text page.extract_text() if extracted_text: text extracted_text page_numbers.extend([page_number] * len(extracted_text.split(\n))) else: logging.warning(fNo text found on page {page_number}.) return text, page_numbers def process_text_with_splitter(text: str, page_numbers: List[int], save_path: str None) - FAISS: 处理文本并创建向量存储 参数: text: 提取的文本内容 page_numbers: 每行文本对应的页码列表 save_path: 可选保存向量数据库的路径 返回: knowledgeBase: 基于FAISS的向量存储对象 # 创建文本分割器递归字符文本分割器用于将长文本分割成小块 text_splitter RecursiveCharacterTextSplitter( separators [\n\n, \n, ., , ], chunk_size 512, chunk_overlap 128, length_function len, ) # 分割文本 chunks text_splitter.split_text(text) # logging.debug(fText split into {len(chunks)} chunks.) print(f文本被分割成 {len(chunks)} 个块。) # 创建嵌入模型OpenAI嵌入模型配置环境变量 OPENAI_API_KEY # embeddings OpenAIEmbeddings() # 调用阿里百炼平台文本嵌入模型配置环境变量 DASHSCOPE_API_KEY embeddings DashScopeEmbeddings( model text-embedding-v4 ) # 从文本块创建知识库 knowledgeBase FAISS.from_texts(chunks, embeddings) print(已从文本块创建知识库...) # 存储每个文本块对应的页码信息 page_info {chunk: page_numbers[i] for i, chunk in enumerate(chunks)} knowledgeBase.page_info page_info # 如果提供了保存路径则保存向量数据库和页码信息 if save_path: # 确保目录存在 os.makedirs(save_path, exist_okTrue) # 保存FAISS向量数据库 knowledgeBase.save_local(save_path) print(f向量数据库已保存到: {save_path}) # 保存页码信息到同一目录 with open(os.path.join(save_path, page_info.pkl), wb) as f: pickle.dump(page_info, f) print(f页码信息已保存到: {os.path.join(save_path, page_info.pkl)}) return knowledgeBase def load_knowledge_base(load_path: str, embeddings None) - FAISS: 从磁盘加载向量数据库和页码信息 参数: load_path: 向量数据库的保存路径 embeddings: 可选嵌入模型。如果为None将创建一个新的DashScopeEmbeddings实例 返回: knowledgeBase: 加载的FAISS向量数据库对象 # 如果没有提供嵌入模型则创建一个新的 if embeddings is None: embeddings DashScopeEmbeddings( modeltext-embedding-v4 ) # 加载FAISS向量数据库添加allow_dangerous_deserializationTrue参数以允许反序列化 knowledgeBase FAISS.load_local(load_path, embeddings, allow_dangerous_deserializationTrue) print(f向量数据库已从 {load_path} 加载。) # 加载页码信息 page_info_path os.path.join(load_path, page_info.pkl) if os.path.exists(page_info_path): with open(page_info_path, rb) as f: page_info pickle.load(f) knowledgeBase.page_info page_info print(页码信息已加载。) else: print(警告: 未找到页码信息文件。) return knowledgeBase # 读取PDF文件 pdf_reader PdfReader(./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf) # 提取文本和页码信息 text, page_numbers extract_text_with_page_numbers(pdf_reader) textimport os # 设置 DashScope API Key os.environ[DASHSCOPE_API_KEY] apikey print(f提取的文本长度: {len(text)} 个字符。) # 处理文本并创建知识库同时保存到磁盘 save_dir ./vector_db knowledgeBase process_text_with_splitter(text, page_numbers, save_pathsave_dir) # 处理文本并创建知识库 knowledgeBase process_text_with_splitter(text, page_numbers) knowledgeBase# 设置查询问题 # query 客户经理被投诉了投诉一次扣多少分 query 客户经理每年评聘申报时间是怎样的 if query: # 执行相似度搜索找到与查询相关的文档 docs knowledgeBase.similarity_search(query) # 初始化对话大模型 chatLLM ChatOpenAI( # 若没有配置环境变量请用百炼API Key将下行替换为api_keysk-xxx, api_key os.getenv(DASHSCOPE_API_KEY), base_url https://dashscope.aliyuncs.com/compatible-mode/v1, model deepseek-v3 ) # 加载问答链 chain load_qa_chain(chatLLM, chain_typestuff) # 准备输入数据 input_data {input_documents: docs, question: query} # 使用回调函数跟踪API调用成本 with get_openai_callback() as cost: # 执行问答链 response chain.invoke(inputinput_data) print(f查询已处理。成本: {cost}) print(response[output_text]) print(来源:) # 记录唯一的页码 unique_pages set() # 显示每个文档块的来源页码 for doc in docs: text_content getattr(doc, page_content, ) source_page knowledgeBase.page_info.get( text_content.strip(), 未知 ) if source_page not in unique_pages: unique_pages.add(source_page) print(f文本块页码: {source_page})# 设置查询问题 query 客户经理每年评聘申报时间是怎样的 if query: # 设置 API Key确保已设置 import os os.environ[DASHSCOPE_API_KEY] apikey # 导入必要的模块 from langchain_openai import ChatOpenAI from langchain.chains.question_answering import load_qa_chain from langchain_community.callbacks.manager import get_openai_callback # 创建嵌入模型 embeddings DashScopeEmbeddings( modeltext-embedding-v4 ) # 从磁盘加载向量数据库 try: loaded_knowledgeBase FAISS.load_local(./vector_db, embeddings) except: loaded_knowledgeBase FAISS.load_local( folder_path./vector_db, embeddingsembeddings ) # 使用加载的知识库进行查询 docs loaded_knowledgeBase.similarity_search(query, k3) # 限制为3个最相关结果 # 1. 先显示找到的文档 print(f查询问题: {query}) print(f找到 {len(docs)} 个相关文档块) print(- * 50) for i, doc in enumerate(docs, 1): print(f\n文档块 {i}:) print(doc.page_content[:150] ...) # 只显示前150个字符 print(- * 30) print(\n * 50) # 2. 使用大模型问答链生成精确答案 try: # 创建大模型使用通义千问 # 如果没有ChatOpenAI可以安装: pip install langchain-openai # 注意这里需要使用支持DashScope的模型 llm ChatOpenAI( modelqwen-max, # 或使用其他模型 temperature0, openai_api_keyos.environ[DASHSCOPE_API_KEY], openai_api_basehttps://dashscope.aliyuncs.com/compatible-mode/v1 ) # 加载问答链 chain load_qa_chain(llm, chain_typestuff) # 准备输入数据 input_data {input_documents: docs, question: query} # 使用回调函数跟踪API调用成本 with get_openai_callback() as cost: # 执行问答链 response chain.invoke(inputinput_data) print(f查询已处理。成本: {cost}) print(\n * 50) print( 答案总结:) print( * 50) print(response[output_text]) # 3. 显示来源页码 print(\n * 50) print( 来源:) print( * 50) # 记录唯一的页码 unique_pages set() # 显示每个文档块的来源页码 for doc in docs: text_content getattr(doc, page_content, ) # 注意这里需要确保 knowledgeBase.page_info 存在 source_page loaded_knowledgeBase.page_info.get( text_content.strip(), 未知 ) if source_page not in unique_pages: unique_pages.add(source_page) print(f文本块页码: {source_page}) except ImportError as e: print(f警告: 缺少必要模块 - {e}) print(\n使用简单提取模式...) # 简单模式直接从文档中提取相关信息 print(\n * 50) print( 从文档中提取的信息:) print( * 50) for doc in docs: content doc.page_content if 每年 in content and 月份 in content: # 提取包含时间信息的句子 sentences content.split(。) for sentence in sentences: if 每年 in sentence and 月份 in sentence: print(f- {sentence.strip()}。) break # 显示来源页码 print(\n来源页码:) unique_pages set() for doc in docs: text_content getattr(doc, page_content, ) source_page loaded_knowledgeBase.page_info.get(text_content.strip(), 未知) if source_page not in unique_pages: unique_pages.add(source_page) print(f- 页码: {source_page}) except Exception as e: print(f问答链处理出错: {e}) print(\n使用备用方案...) # 备用方案简单显示相关内容 print(\n相关文档内容:) for i, doc in enumerate(docs[:2], 1): # 只显示前2个 content doc.page_content if 每年 in content or 月份 in content or 申报 in content: # 清理内容 clean_content content.replace(\n, ).replace( , ).strip() if len(clean_content) 100: clean_content clean_content[:100] ... print(f{i}. {clean_content})小结1. PDF文本提取与处理使用PyPDF2库的PdfReader从PDF文件中提取文本在提取过程中记录每行文本对应的页码便于后续溯源使用RecursiveCharacterTextSplitter将长文本分割成小块便于向量化处理2. 向量数据库构建使用OpenAIEmbeddings / DashScopeEmbeddings将文本块转换为向量表示使用FAISS向量数据库存储文本向量支持高效的相似度搜索为每个文本块保存对应的页码信息实现查询结果溯源3. 语义搜索与问答链基于用户查询使用similarity_search在向量数据库中检索相关文本块使用文本语言模型和load_qa_chain构建问答链将检索到的文档和用户问题作为输入生成回答4. 成本跟踪与结果展示使用get_openai_callback跟踪API调用成本展示问答结果和来源页码方便用户验证信息5. 三大阶段有效提升RAG质量方法5.1. 数据准备阶段5.1.1. 常见问题1构建完整的数据准备流程2智能文档技术数据质量差企业大部分数据尤其是非结构化数据缺乏良好的数据治理未经标记/评估的非结构化数据可能包含敏感、过时、矛盾或不正确的信息。多模态信息提取、定义和理解文档中的不同内容元素如标题、配色方案、图像和标签等存在挑战。复杂的PDF提取PDF是为人类阅读而设计的机器解析起来非常复杂。5.1.2. 如何提升数据准备阶段的质量构建完整的数据准备流程采用智能文档技术1.数据评估与分类数据审计全面审查现有数据识别敏感、过时、矛盾或不准确的信息。数据分类按类型、来源、敏感性和重要性对数据进行分类便于后续处理。2.数据清洗去重删除重复数据纠错修正格式错误、拼写错误等更新替换过时信息确保数据时效性一致性检查解决数据矛盾确保逻辑一致3.敏感信息处理识别敏感数据使用工具或正则表达式识别敏感信息如个人身份信息4.数据标记与标注元数据标记为数据添加元数据如来源、创建时间等内容标注对非结构化数据进行标注便于后续检索和分析5.数据治理框架制定政策明确数据管理、访问控制和更新流程责任分配指定数据治理负责人确保政策执行监控与审计定期监控数据质量进行审计阿里文档智能https://www.aliyun.com/product/ai/docmind?spma2c4g.11174283.0.0.bfe667a8tIVMdG微软 LayoutLMv3https://www.microsoft.com/en-us/research/articles/layoutlmv3/5.2. 知识检索阶段5.2.1. 常见问题内容缺失当检索过程缺少关键内容时系统会提供不完整、碎片化的答案 降低RAG的质量错过排名靠前的文档用户查询相关的文档时被检索到但相关性极低导致答案不能满足用户需求这是因为在检索过程中用户通过主观判断决定检索“文档数量”。理论上所有文档都要被排序并考虑进一步处理但在实践中通常只有排名top k的文档才会被召回而k值需要根据经验确定。不在上下文中从数据库中检索出包含答案的文档但未能包含在生成答案的上下文中。这种情况通常发生在返 回大量文件时需要进行整合以选择最相关的信息。5.2.2. 如何提升知识检索阶段的质量通过查询转换澄清用户意图明确用户意图提高检索准确性。采用混合检索和重排策略确保最相关的文档被优先处理生成更准确的答案。1通过查询转换澄清用户意图场景用户询问 “如何申请信用卡”问题用户意图可能模糊例如不清楚是申请流程、所需材料还是资格条件。解决方法通过查询转换明确用户意图。实现步骤意图识别使用自然语言处理技术识别用户意图。例如识别用户是想了解流程、材料还是资格。查询扩展根据识别结果扩展查询。例如如果用户想了解流程查询扩展为“信用卡申请的具体步骤”如果用户想了解材料查询扩展为“申请信用卡需要哪些材料”如果用户想了解资格查询扩展为“申请信用卡的资格条件”检索使用扩展后的查询检索相关文档示例用户输入“如何申请信用卡”系统识别意图为流程扩展查询为信用卡申请的具体步骤检索结果包含详细的申请步骤文档系统生成准确答案2混合检索和重排策略场景用户询问“信用卡年费是多少”问题直接检索可能返回大量文档部分相关但排名低导致答案不准确。解决方法采用混合检索和重排策略。步骤混合检索结合关键词检索和语义检索。比如关键词检索“信用卡年费”。语义检索使用嵌入模型检索与“信用卡年费”语义相近的文档。重排对检索结果进行重排。生成答案从重排后的文档中生成答案。示例用户输入“信用卡年费是多少”系统进行混合检索结合关键词和语义检索。重排后最相关的文档如“信用卡年费政策”排名靠前。系统生成准确答案“信用卡年费根据卡类型不同普通卡年费为100元金卡为300元白金卡为1000元。”5.3. 答案生成阶段5.3.1. 常见问题未提取答案与所提供的上下文相符但大语言模型却无法准确提取。这种情况通常发生在上下文中存在过多噪音或相互冲突的信息时。不完整尽管能够利用上下文生成答案但信息缺失会导致对用户查询的答复不完整。格式错误当prompt中的附加指令格式不正确时大语言模型可能误解或曲解这些指令从而导致错误的答案。幻觉大模型可能会产生误导性或虚假性信息。5.3.2. 如何提升答案生成阶段的质量改进提示词模板实施动态防护栏1改进提示词模板场景原始提示词改进后的提示词用户询问“如何申请信用卡”“根据以下上下文回答问题如何申请信用卡”“根据以下上下文提取与申请信用卡相关的具体步骤和所需材料如何申请信用卡”用户询问“信用卡的年费是多少”“根据以下上下文回答问题信用卡的年费是多少”“根据以下上下文详细列出不同信用卡的年费信息并说明是否有减免政策信用卡的年费是多少”用户询问“什么是零存整取”“根据以下上下文回答问题什么是零存整取”“根据以下上下文准确解释零存整取的定义、特点和适用人群确保信息真实可靠什么是零存整取”如何对原有的提示词进行优化可以通过DeepSeek-R1或QWQ的推理链对提示词进行优化信息提取从原始提示词中提取关键信息。需求分析分析用户的需求明确用户希望获取的具体信息。提示词优化根据需求分析的结果优化提示词使其更具体、更符合用户的需求。2实施动态防护栏动态防护栏Dynamic Guardrails是一种在生成式AI系统中用于实时监控和调整模型输出的机制旨在确保生成的内容符合预期、准确且安全。它通过设置规则、约束和反馈机制动态地干预模型的生成过程避免生成错误、不完整、不符合格式要求或含有虚假信息幻觉的内容。在RAG系统中动态防护栏的作用尤为重要因为它可以帮助解决以下问题未提取确保模型从上下文中提取了正确的信息。不完整确保生成的答案覆盖了所有必要的信息。格式错误确保生成的答案符合指定的格式要求。幻觉防止模型生成与上下文无关或虚假的信息。场景1防止未提取用户问题“如何申请信用卡”上下文包含申请信用卡的步骤和所需材料。动态防护栏规则检查生成的答案是否包含“步骤”和“材料”。如果缺失提示模型重新生成。示例错误输出“申请信用卡需要提供一些材料。”防护栏触发检测到未提取具体步骤提示模型补充。场景2防止不完整用户问题“信用卡的年费是多少”上下文包含不同信用卡的年费信息。动态防护栏规则检查生成的答案是否列出所有信用卡的年费。如果缺失提示模型补充。示例错误输出“信用卡A的年费是100元。”防护栏触发检测到未列出所有信用卡的年费提示模型补充。场景3防止幻觉用户问题“什么是零存整取”上下文包含零存整取的定义和特点。动态防护栏规则检查生成的答案是否与上下文一致。如果不一致提示模型重新生成。示例错误输出“零存整取是一种贷款产品。防护栏触发检测到与上下文不一致提示模型重新生成。如何实现动态防护栏技术事实性校验规则在生成阶段设置规则验证生成内容是否与检索到的知识片段一致。例如可以使用参考文献验证机制确保生成内容有可靠来源支持避免输出矛盾或不合理的回答。如何制定事实性校验规则当业务逻辑明确且规则较为固定时可以人为定义一组规则比如规则1生成的答案必须包含检索到的知识片段中的关键实体如“年费”、“利率”。规则2生成的答案必须符合指定的格式如步骤列表、表格等。实施方法使用正则表达式或关键词匹配来检查生成内容是否符合规则。例如检查生成内容是否包含“年费”这一关键词或者是否符合步骤格式如“1. 登录2. 设置”。6. RAG在不同阶段提升质量的实践数据准备环节阿里云考虑到文档具有多层标题属性且不同标题之间存在关联性提出多粒度知识提取方案按照不同标题级别对文档进行拆分然后基于Qwen14b模型和RefGPT训练了一个面向知识提取任务的专属模型对各个粒度的chunk进行知识提取和组合并通过去重和降噪的过程保证知识不丢失、不冗余。最终将文档知识提取成多个事实型对话提升检索效果知识检索环节哈啰出行采用多路召回的方式主要是向量召回和搜索召回。其中向量召回使用了两类一类是大模型的向量、另一类是传统深度模型向量搜索召回也是多链路的包括关键词、ngram等。通过多路召回的方式可以达到较高的召回查全率。答案生成环节中国移动为了解决事实性不足或逻辑缺失采用FoRAG两阶段生成策略首先生成大纲然后基于大纲扩展生成最终答案。7. QA如果LLM可以处理无限上下文了RAG还有意义吗效率与成本LLM处理长上下文时计算资源消耗大响应时间增加。RAG通过检索相关片段减少输入长度。知识更新LLM的知识截止于训练数据无法实时更新。RAG可以连接外部知识库增强时效性。可解释性RAG的检索过程透明用户可查看来源增强信任。LLM的生成过程则较难追溯。定制化RAG可针对特定领域定制检索系统提供更精准的结果而LLM的通用性可能无法满足特定需求。数据隐私RAG允许在本地或私有数据源上检索避免敏感数据上传云端适合隐私要求高的场景。结合LLM的生成能力和RAG的检索能力可以提升整体性能提供更全面、准确的回答。8. 学习打卡8.1 结合你的业务场景创建本地知识检索Step1收集整理知识库Step2从PDF中提取文本并记录每行文本对应的页码Step3处理文本并创建向量存储Step4执行相似度搜索找到与查询相关的文档Step5使用问到链对用户问题进行回答Step6显示每个文档块的来源页码8.2. 理解有效提升RAG质量的方法如何提升数据准备阶段的质量如何提升知识检索阶段的质量如何提升答案生成阶段的质量