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

RAG学习(一)

**声明:**本人是通过Github的文章All-in-rag学习有关rag的内容,原文链接:https://datawhalechina.github.io/all-in-rag/#/,github链接:https://github.com/datawhalechina/all-in-rag。其中github仓库中提供了完整代码。

第一节 RAG

1.1 RAG核心定义

检索增强生成(Retrieval-Augmented Generation) 是一种将信息检索与文本生成相结合的AI架构范式。其核心定义包含三个关键要素:
RAG=LLM生成能力⊕知识库检索系统\text{RAG} = \text{LLM}_{\text{生成能力}} \oplus \text{知识库}_{\text{检索系统}} RAG=LLM生成能力知识库检索系统

  • 突破传统LLM的知识局限:通过外部知识源扩展模型的知识边界
  • 解决"知识截止"问题:可动态更新知识库而不需重新训练模型
  1. 核心特征

    • 可验证性:答案来源可追溯(基于检索结果)
    • 动态性:知识库可实时更新
    • 抗幻觉:显著降低模型虚构事实的概率(相比纯生成模型降低40-60%)
  2. 本质目标

    “将世界知识存储与语言理解能力分离,使模型专注于其最擅长的语言生成任务”

1.2 RAG的技术原理

RAG系统由三大核心组件构成,形成完整的处理闭环:

在这里插入图片描述

  • 关键组件
    1. 索引(Indexing) 📑:将非结构化文档(PDF/Word等)分割为片段,通过嵌入模型转换为向量数据。
    2. 检索(Retrieval) 🔍️:基于查询语义,从向量数据库召回最相关的文档片段(Context)。
    3. 生成(Generation) ✨:将检索结果作为上下文输入LLM,生成自然语言响应。

1.3 技术演进与分类

RAG 架构经历 朴素版(Naive RAG)、高级版(Advanced RAG)、模块化版(Modular RAG) 的演进:

  • Naive RAG“索引→检索→生成” 线性流程 构建基础能力,无中间优化;

  • Advanced RAG 新增 预检索(Query Rewrite、HyDE 优化查询)后检索(Rerank 重排、Filter 结果提纯),强化中间环节精度;

  • Modular RAG

    进一步解耦为

    七大核心模块(索引、预检索、检索、后检索、生成、编排、知识引导)

    ,每个模块细分技术子层:

    • 索引 支持 Chunk 优化(小→大 Chunk 扩展)与结构化组织(知识图谱 / 文档层级建模);
    • 预检索 覆盖查询变换(假设答案 / 查询生成)、扩展(子查询分解)、构造(Text-to-Cypher/SQL)及路由分发(硬 / 软 Prompt 管道);
    • 检索 实现检索器微调(LM 监督训练)、多源检索(句子 / Chunk / 子图)与策略选择(嵌入 / 关键词 / 混合检索);
    • 后检索 集成重排(多模态文档重排)、压缩(Long LLM Lingua 精简)、验证(知识冲突检测);
    • 生成 支持 Generator FT(结合外部知识 / 大模型增强);
    • 编排 动态调度流程(检索必要性判断、重查决策);
    • 知识引导 通过知识图谱规划推理路径,辅助检索与生成。

模块化设计赋能 多场景适配(结构化数据、多模态处理)、细粒度优化(Chunk 切分、检索器调优)与动态流程控制(路由、拒答机制),推动 RAG 向更智能、可靠的方向发展。

在这里插入图片描述

第二节 配置环境

2.1 API申请

  1. 访问 Deepseek 开放平台打开浏览器,访问 Deepseek 开放平台。
  2. 登录或注册账号 如果你已有账号,请直接登录。如果没有,请点击页面上的注册按钮,使用邮箱或手机号完成注册。
  3. 创建新的 API 密钥 登录成功后,在页面左侧的导航栏中找到并点击 API Keys。在 API 管理页面,点击 创建 API key 按钮。输入一个跟其他api key不重复的名称后点击创建
  4. 保存 API Key 系统会为你生成一个新的 API 密钥。请立即复制并将其保存在一个安全的地方。

注意:你需要使用你的钞能力才能使用它,要不然你会出现:

openai.APIStatusError: Error code: 402 - {'error': {'message': 'Insufficient Balance', 'type': 'unknown_error', 'param': None, 'code': 'invalid_request_error'}}

这个错误。当然,如果你不想使用这个付费的api key,你也可以本地下载cache_model在本地直接使用下载好的模型。或者你可以找不需要付费的api key。

2.2 虚拟环境的创建、配置与代码的拉取

首先我们需要创建虚拟环境,在windows下,我们打开事先安装好的anaconda中的anaconda prompt,输入以下代码:

conda create -n [环境名] python=3.12.7

举个例子,我想让我的环境名叫做“all-in-rag”,则我需要输入conda create -n all-in-rag python=3.12.7。接下来的过程中再输入一个[y]就可以了,随后就是激活环境:

conda activate [环境名]

我们创建的虚拟环境空空如也,里面能用于rag的python包少之又少,所以我们需要配置我们的虚拟环境,使用pip install来下载需要的包。作者已经在源码中给大家一个requirements.txt文件,里面有所有需要安装的包。首先我们需要拉取项目代码。如果你的电脑有git,那么你可以直接使用以下代码来直接克隆项目代码:

git clone https://github.com/datawhalechina/all-in-rag.git

如果没有,你可以上GitHub直接下载项目代码。代码的文件结构如下:

all-in-rag/
├── docs/           # 教程文档
├── code/           # 代码示例
├── data/           # 示例数据
├── models/         # 预训练模型
└── README.md       # 项目说明

其中,配置文件位于code中,我们同样在anaconda prompt,输入:

cd all-in-rag(可以替换为你本地存放代码的文件地址)

如果你发现cd+文件位置无法跳转,不妨试一试:

cd /d+文件地址

注意/d+文件地址是需要连在一起的,例如/dF:\Python Project\Project\Natural Language Processing\RAG\all-in-rag\code

随后执行:

pip install -r requirements.txt

就可以了。

2.3配置api key的环境变量

在windows中,找到“此电脑”,点击属性,在“相关链接”中找到“高级系统设置”,进入点击“环境变量”,选择用户变量下的新建按钮,变量名为“DEEPSEEK_API_KEY”.变量值为你之前申请的api密钥。

第三节 代码运行测试与分析

接下来你就可以通过vscode或者bash直接运行python代码:

python 01_langchain_example.py

示例代码:

import os
# hugging face镜像设置,如果国内环境无法使用启用该设置
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from dotenv import load_dotenv
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeekload_dotenv()markdown_path = r"F:\Python Project\Project\Natural Language Processing\RAG\all-in-rag\data\C1\markdown\easy-rl-chapter1.md"# 加载本地markdown文件
loader = UnstructuredMarkdownLoader(markdown_path)
docs = loader.load()# 文本分块
text_splitter = RecursiveCharacterTextSplitter()
chunks = text_splitter.split_documents(docs)# 中文嵌入模型
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5",model_kwargs={'device': 'cpu'},encode_kwargs={'normalize_embeddings': True}
)# 构建向量存储
vectorstore = InMemoryVectorStore(embeddings)
vectorstore.add_documents(chunks)# 提示词模板
prompt = ChatPromptTemplate.from_template("""请根据下面提供的上下文信息来回答问题。
请确保你的回答完全基于这些上下文。
如果上下文中没有足够的信息来回答问题,请直接告知:“抱歉,我无法根据提供的上下文找到相关信息来回答此问题。”上下文:
{context}问题: {question}回答:""")# 配置大语言模型
llm = ChatDeepSeek(model="deepseek-chat",temperature=0.7,max_tokens=2048,api_key=os.getenv("DEEPSEEK_API_KEY")
)# 用户查询
question = "文中举了哪些例子?"# 在向量存储中查询相关文档
retrieved_docs = vectorstore.similarity_search(question, k=3)
docs_content = "\n\n".join(doc.page_content for doc in retrieved_docs)answer = llm.invoke(prompt.format(question=question, context=docs_content))
print(answer)

得到的结果如下所示:

content='根据提供的上下文,文中举了以下例子:\n\n1. **自然界中的羚羊**:羚羊出生后通过试错学习站立和奔跑,适应环境。\n2. **股票交易**:通过买卖股票并根据市场反馈学习最大化奖励。\n3. **雅达利游戏(如Breakout和Pong)**:通过试错学习游戏策略以通关或获胜。\n4. **AlphaGo**:强化学习算法击败人类顶尖棋手 ,展示超人类表现。\n5. **选择餐馆**:利用(去已知喜欢的餐馆)与探索(尝试新餐馆)的权衡。\n6. **做广告**:利用(采取已知最优广告策略)与探索(尝试新广告策略)。\n7. **挖油**:利用(在已知地点挖油)与探索(在新地点挖油,可能发现大油田)。\n8. **玩游戏(如《街头霸王》)**:利用(固定策略如蹲角落出脚)与探索(尝试新招式如“大招”)。\n\n这些例子分别用于说明强化学习的应用场景、 探索与利用的权衡,以及其与监督学习的区别。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 231, 'prompt_tokens': 5549, 'total_tokens': 5780, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 5504}, 'prompt_cache_hit_tokens': 5504, 'prompt_cache_miss_tokens': 45}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0623_fp8_kvcache', 'id': '296a05f3-5105-446b-8290-27c15798c4e7', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--742a5a71-bd27-4d2e-88fd-9d35bd66e341-0' usage_metadata={'input_tokens': 5549, 'output_tokens': 231, 'total_tokens': 5780, 'input_token_details': {'cache_read': 5504}, 'output_token_details': {}}

有点难看懂,解释一下:

  • content(核心回答内容):模型生成的最终回答文本
  • additional_kwargs(附加参数):refusal表示模型是否拒绝回答,如果值为True,表示模型认为问题不合适而拒绝回答
  • response_metadata(响应元数据):
'token_usage': {'completion_tokens': 231,     # 生成回答消耗的token数'prompt_tokens': 5549,        # 输入提示消耗的token数'total_tokens': 5780,         # 总token数 = 5549 + 231'completion_tokens_details': None,'prompt_tokens_details': {'audio_tokens': None,     # 音频token数(本请求未使用)'cached_tokens': 5504     # 从缓存复用的token数},'prompt_cache_hit_tokens': 5504,  # 提示缓存命中的token数'prompt_cache_miss_tokens': 45    # 提示缓存未命中的token数
}

其他元数据:

  • model_name: 使用的模型名称(‘deepseek-chat’)
  • system_fingerprint: 系统指纹标识(用于追踪模型版本和配置)
  • id: 本次请求的唯一ID(可用于日志追踪)
  • service_tier: 服务等级(None表示默认等级)
  • finish_reason: 生成结束原因('stop’表示正常结束)
  • logprobs: token概率信息(None表示未返回)

我们可以将上面代码的流程概括为以下几点:

  1. 文档分割: D→d1,d2,...,dnD → {d_1, d_2, ..., d_n}Dd1,d2,...,dn
  2. 向量映射: ∀di→vi=f(di)∀d_i → v_i = f(d_i)divi=f(di)
  3. 查询处理: q=f(question)q = f(question)q=f(question)
  4. 相似检索: R=arg⁡max⁡i∈[1,n]k(q⋅vi)R = \arg\max_{i \in [1,n]}^k \ (q \cdot v_i)R=argmaxi[1,n]k (qvi)
  5. 上下文构建: C=⨁r∈Rtext(r)C = \bigoplus_{r \in R} \text{text}(r)C=rRtext(r)
  6. 提示工程: P=Template(C,question)P = \text{Template}(C, question)P=Template(C,question)
  7. 生成回答: answer=LLM(P)\text{answer} = \text{LLM}(P)answer=LLM(P)

其中我们系要注意以下几点:

  1. 无论输入文本长度如何(100字或1000字),输出都是固定维度的向量
  2. 向量库V={(vi,texti,metadatai)}i=1nV = \{ (v_i, \text{text}_i, \text{metadata}_i) \}_{i=1}^nV={(vi,texti,metadatai)}i=1n中所有向量维度相同:768维
  3. 相似度计算:similarity(q,vi)=cos⁡(θ)=q⋅vi∣∣q∣∣⋅∣∣vi∣∣\text{similarity}(q, v_i) = \cos(\theta) = \frac{q \cdot v_i}{||q|| \cdot ||v_i||}similarity(q,vi)=cos(θ)=∣∣q∣∣∣∣vi∣∣qvi,由于归一化,则简化为similarity(q,vi)=q⋅vi=∑j=1768qjvij\text{similarity}(q, v_i) = q \cdot v_i = \sum_{j=1}^{768} q_j v_{ij}similarity(q,vi)=qvi=j=1768qjvij
  4. 最近邻搜索:Retrieve(q,V,k)={vi∣rank(q⋅vi)≤k}\text{Retrieve}(q, V, k) = \{ v_i \ | \ \text{rank}(q \cdot v_i) \leq k \}Retrieve(q,V,k)={vi  rank(qvi)k}

在这一节中,我们提到四步构建最小可行系统分别是数据准备、索引构建、检索优化和生成集成。接下来将围绕这四个方面来实现一个基于LangChain框架的RAG应用。

3.1 初始化设置

首先进行基础配置,包括导入必要的库、加载环境变量以及下载嵌入模型。

import os
# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from dotenv import load_dotenv
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_deepseek import ChatDeepSeek# 加载环境变量
load_dotenv()

3.2 数据准备

  • 加载原始文档: 先定义Markdown文件的路径,然后使用TextLoader加载该文件作为知识源。

    markdown_path = "../../data/C1/markdown/easy-rl-chapter1.md"
    loader = TextLoader(markdown_path)
    docs = loader.load()
    
  • 文本分块 (Chunking): 为了便于后续的嵌入和检索,长文档被分割成较小的、可管理的文本块(chunks)。这里采用了递归字符分割策略,使用其默认参数进行分块。当不指定参数初始化 RecursiveCharacterTextSplitter() 时,其默认行为旨在最大程度保留文本的语义结构:

    • 默认分隔符与语义保留: 按顺序尝试使用一系列预设的分隔符 ["\n\n" (段落), "\n" (行), " " (空格), "" (字符)] 来递归分割文本。这种策略的目的是尽可能保持段落、句子和单词的完整性,因为它们通常是语义上最相关的文本单元,直到文本块达到目标大小。
    • 保留分隔符: 默认情况下 (keep_separator=True),分隔符本身会被保留在分割后的文本块中。
    • 默认块大小与重叠: 使用其基类 TextSplitter 中定义的默认参数 chunk_size=4000(块大小)和 chunk_overlap=200(块重叠)。这些参数确保文本块符合预定的大小限制,并通过重叠来减少上下文信息的丢失。

我们来看一下使用chunk_size=1000chunk_overlap=50参数的结果,使得检索到的内容更宏观,代码修改为:

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=50
)

结果:

content='根据提供的上下文,文中举了以下例子:\n\n1. **DeepMind 研发的走路的智能体**:智能体学习在曲折道路上行走,通过举手保持平衡以更快前进,并能适应环境扰动。  \n2. **机械臂抓取**:通过强化学习训练机械臂抓取不同形状的物 体,避免传统算法需对每个物体单独建模的问题。  \n3. **OpenAI 的机械臂翻魔方**:机械臂先在虚拟环境中训练,再应用到现实,实现灵活操作魔方的能力。  \n4. **穿衣服的智能体**:训练智能体完成穿衣服的精细操作,并能抵抗扰动(尽管可 能失败)。  \n\n此外,在“探索和利用”部分还补充了其他生活化例子:  \n- 选择餐馆(利用已知餐馆 vs 探索新餐馆)  \n- 做广告(利用现有策略 vs 尝试新策略)  \n- 挖油(在已知地点开采 vs 勘探新油田)  \n- 玩游戏(固定策略 vs 尝试新招式)。  \n\n这些例子均用于说明强化学习的不同应用场景或核心概念(如奖励、探索与利用)。

3.3 索引构建

数据准备完成后,接下来构建向量索引:

  • 初始化中文嵌入模型: 使用HuggingFaceEmbeddings加载之前在初始化设置中下载的中文嵌入模型。配置模型在CPU上运行,并启用嵌入归一化 (normalize_embeddings: True)。

    embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5",model_kwargs={'device': 'cpu'},encode_kwargs={'normalize_embeddings': True}
    )
    
  • 构建向量存储: 将分割后的文本块 (texts) 通过初始化好的嵌入模型转换为向量表示,然后使用InMemoryVectorStore将这些向量及其对应的原始文本内容添加进去,从而在内存中构建出一个向量索引。

    vectorstore = InMemoryVectorStore(embeddings)
    vectorstore.add_documents(texts)
    

3.4 检索与查询

索引构建完毕后,便可以针对用户问题进行查询与检索:

  • 定义用户查询: 设置一个具体的用户问题字符串。
  • 在向量存储中查询相关文档: 使用向量存储的similarity_search方法,根据用户问题在索引中查找最相关的 k (此处示例中 k=3) 个文本块。
  • 准备上下文: 将检索到的多个文本块的页面内容 (doc.page_content) 合并成一个单一的字符串,并使用双换行符 ("\n\n") 分隔各个块,形成最终的上下文信息 (docs_content) 供大语言模型参考。

注意:使用 "\n\n" (双换行符) 而不是 "\n" (单换行符) 来连接不同的检索文档块,主要是为了在传递给大型语言模型(LLM)时,能够更清晰地在语义上区分这些独立的文本片段。双换行符通常代表段落的结束和新段落的开始,这种格式有助于LLM将每个块视为一个独立的上下文来源,从而更好地理解和利用这些信息来生成回答。

3.5 生成集成

最后一步是将检索到的上下文与用户问题结合,利用大语言模型(LLM)生成答案:

  • 构建提示词模板: 使用ChatPromptTemplate.from_template创建一个结构化的提示模板。此模板指导LLM根据提供的上下文 (context) 回答用户的问题 (question),并明确指出在信息不足时应如何回应。
  • 配置大语言模型: 初始化ChatDeepSeek客户端,配置所用模型 (deepseek-chat)、生成答案的温度参数 (temperature=0.7)、最大Token数 (max_tokens=2048) 以及API密钥 (从环境变量加载)。
  • 调用LLM生成答案并输出: 将用户问题 (question) 和先前准备好的上下文 (docs_content) 格式化到提示模板中,然后调用ChatDeepSeek的invoke方法获取生成的答案。
prompt = ChatPromptTemplate.from_template("""请根据下面提供的上下文信息来回答问题。
请确保你的回答完全基于这些上下文。
如果上下文中没有足够的信息来回答问题,请直接告知:“抱歉,我无法根据提供的上下文找到相关信息来回答此问题。”上下文:
{context}问题: {question}回答:""")
#-------------------------------------------
llm = ChatDeepSeek(model="deepseek-chat",temperature=0.7,max_tokens=2048,api_key=os.getenv("DEEPSEEK_API_KEY")
)
#-------------------------------------------
answer = llm.invoke(prompt.format(question=question, context=docs_content))
print(answer)

n}

回答:“”"
)
#-------------------------------------------
llm = ChatDeepSeek(
model=“deepseek-chat”,
temperature=0.7,
max_tokens=2048,
api_key=os.getenv(“DEEPSEEK_API_KEY”)
)
#-------------------------------------------
answer = llm.invoke(prompt.format(question=question, context=docs_content))
print(answer)


http://www.dtcms.com/a/335868.html

相关文章:

  • 在职老D渗透日记day19:sqli-labs靶场通关(第26a关)get布尔盲注 过滤or和and基础上又过滤了空格和注释符 ‘)闭合
  • Google Earth Engine | (GEE)逐月下载的MODIS叶面积指数LAI
  • 好看的个人导航系统多模板带后台
  • 二叉搜索树的模拟实现
  • 【MySQL学习|黑马笔记|Day7】触发器和锁(全局锁、表级锁、行级锁、)
  • Golang 后台技术面试套题 1
  • 天地图应用篇:增加全屏、图层选择功能
  • 2023年全国研究生数学建模竞赛华为杯E题出血性脑卒中临床智能诊疗建模求解全过程文档及程序
  • multiboot 规范实践分析
  • STM32—OTA-YModem
  • Linux设备模型深度解析
  • RISC-V汇编新手入门
  • Java项目中短信的发送
  • 判断回文数的两种高效方法(附Python实现)
  • Webflux核心概念、适用场景分析【AI Chat类项目选型优势】
  • 数据链路层(2)
  • MySQL的事务基础概念:
  • 显式编程(Explicit Programming)
  • 深入解析函数指针及其数组、typedef关键字应用技巧
  • Go面试题及详细答案120题(21-40)
  • Pycharm Debug详解
  • C++ vector的使用
  • 自动驾驶中的传感器技术34——Lidar(9)
  • 前端项目练习-王者荣耀竞赛可视化大屏 -Vue纯前端静态页面项目
  • Springboot项目3种视图(JSP、Thymeleaf、Freemarker)演示
  • 图解直接插入排序C语言实现
  • 3.逻辑回归:从分类到正则化
  • pyecharts可视化图表组合组件_Grid:打造专业数据仪表盘
  • 矿物分类案列 (一)六种方法对数据的填充
  • C#WPF实战出真汁13--【营业查询】