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

RAG学习-基于 LangChain 框架的 RAG 实现

第三节、四步构建RAG

一、启动虚拟环境

conda activate all-in-rag
# 假设当前在 all-in-rag 项目的根目录下cdcode/C1

二、运行RAG示例代码

python 01_langchain_example.py

代码运行后,可以看到类似下面的输出(格式化后):

Downloading Model from https://www.modelscope.cn to directory: Path\to\all-in-rag\models\bge-small-zh-v1.52025-06-08 02:36:19,318 - modelscope - INFO - Target directory already exists, skipping creation.content=' 文中举了以下例子: 1. **自然界中的羚羊**:刚出生的羚羊通过试错学习站立和奔跑,适应环境。 2. **股票交易**:通过买卖股票并根据市场反馈调整策略,最大化奖励。 3. **雅达利游戏(如Breakout和Pong)**:通过不断试错学习如何通关或赢得游戏。 4. **选择餐馆**:利用(去已知喜欢的餐馆)与探索(尝试新餐馆)的权衡。 5. **做广告**:利用(采取已知最优广告策略)与探索(尝试新广告策略)。 6. **挖油**:利用(在已知地点挖油)与探索(在新地点挖油,可能发现大油田)。 7. **玩游戏(如《街头霸王》)**:利用(固定策略如蹲角落出脚)与探索(尝试新招式如“大招”)。 这些例子用于说明强化学习中的核心概念(如探索与利用、延迟奖励等)及其在实际场景中的应用。 'additional_kwargs={'refusal':None}response_metadata={'token_usage':{'completion_tokens':209,'prompt_tokens':5576,'total_tokens':5785,'completion_tokens_details':None,'prompt_tokens_details':{'audio_tokens':None,'cached_tokens':5568},'prompt_cache_hit_tokens':5568,'prompt_cache_miss_tokens':8},'model_name':'deepseek-chat','system_fingerprint':'fp_8802369eaa_prod0425fp8','id':'67a0580d-78b1-44d6-bccf-f654ae0e9bba','service_tier':None,'finish_reason':'stop','logprobs':None}id='run--919cedcd-771e-4aed-8dfd-cf436795792e-0'usage_metadata={'input_tokens':5576,'output_tokens':209,'total_tokens':5785,'input_token_details':{'cache_read':5568},'output_token_details':{}}

输出参数解析:

  • content: 这是最核心的部分,即大型语言模型(LLM)根据你的问题和提供的上下文生成的具体回答。

  • additional_kwargs: 包含一些额外的参数,在这个例子中是{'refusal': None},表示模型没有拒绝回答。

  • response_metadata
    包含了关于LLM响应的元数据。
    • token_usage: 显示了本次调用消耗的token数量,包括完成(completion_tokens)、提示(prompt_tokens)和总量(total_tokens)。
    • model_name: 使用的LLM模型名称,当前是deepseek-chat
    • system_fingerprint,id,service_tier,finish_reason,logprobs: 这些是更详细的API响应信息,例如finish_reason: 'stop'表示模型正常完成了生成。
  • id: 本次运行的唯一标识符。

  • usage_metadata: 与response_metadata中的token_usage类似,提供了输入和输出token的统计。

一句话总结
这是一个最简 RAG(检索增强生成)流程:加载本地 Markdown 文档 → 分块 → 向量化 → 检索 → 交给 LLM 生成回答,用 LangChain 框架实现。

三、基于LangChain框架的RAG实现

3.1 初始化设置

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

importos# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'fromdotenvimportload_dotenvfromlangchain_community.document_loadersimportTextLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_huggingfaceimportHuggingFaceEmbeddingsfromlangchain_core.vectorstoresimportInMemoryVectorStorefromlangchain_core.promptsimportChatPromptTemplatefromlangchain_deepseekimportChatOpenAI# 加载环境变量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(块重叠)。这些参数确保文本块符合预定的大小限制,并通过重叠来减少上下文信息的丢失。
text_splitter=RecursiveCharacterTextSplitter()texts=text_splitter.split_documents(docs)

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查询与检索

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

定义用户查询: 设置一个具体的用户问题字符串。

question="文中举了哪些例子?"

在向量存储中查询相关文档: 使用向量存储的similarity_search方法,根据用户问题在索引中查找最相关的k(此处示例中k=3) 个文本块。

retrieved_docs=vectorstore.similarity_search(question,k=3)

准备上下文: 将检索到的多个文本块的页面内容 (doc.page_content) 合并成一个单一的字符串,并使用双换行符 ("\n\n") 分隔各个块,形成最终的上下文信息 (docs_content) 供大语言模型参考。

docs_content="\n\n".join(doc.page_contentfordocinretrieved_docs)

3.5生成集合

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

构建提示词模板: 使用ChatPromptTemplate.from_template创建一个结构化的提示模板。此模板指导LLM根据提供的上下文 (context) 回答用户的问题 (question),并明确指出在信息不足时应如何回应。

prompt=ChatPromptTemplate.from_template("""请根据下面提供的上下文信息来回答问题。 请确保你的回答完全基于这些上下文。 如果上下文中没有足够的信息来回答问题,请直接告知:“抱歉,我无法根据提供的上下文找到相关信息来回答此问题。” 上下文: {context} 问题: {question} 回答:""")

配置大语言模型: 初始化ChatOpenAI客户端,配置所用模型(glm-4.7-flash-free)、生成答案的温度参数(temperature=0.7)、最大Token数 (max_tokens=2048) 以及API密钥(从环境变量加载)和 url。

llm=ChatOpenAI(model="glm-4.7-flash-free",temperature=0.7,max_tokens=2048,api_key=os.getenv("DEEPSEEK_API_KEY")base_url="https://aihubmix.com/v1")

调用LLM生成答案并输出: 将用户问题 (question) 和先前准备好的上下文 (docs_content) 格式化到提示模板中,然后调用ChatDeepSeek的invoke方法获取生成的答案。

answer=llm.invoke(prompt.format(question=question,context=docs_content))print(answer)

这是一个 Naive RAG 的最小化实现(对应 C1 RAG 入门章节),使用 LangChain 框架 完整演示了 RAG 的核心流程:加载 → 分块 → 嵌入 → 检索 → 生成。
逐段功能解析

  1. 环境准备与导入(第1-12行)
    from dotenv import load_dotenv
    from langchain_community.document_loaders import UnstructuredMarkdownLoader

load_dotenv()

  • 加载 .env 文件(虽然此脚本实际未使用环境变量中的 key)
  • 引入 LangChain 生态的 6 个核心组件,覆盖整个 RAG 流水线
  1. 文档加载(第14-18行)
    markdown_path = “…/…/data/C1/markdown/easy-rl-chapter1.md”
    loader =UnstructuredMarkdownLoader(markdown_path)
    docs = loader.load()
  • 从项目共享数据目录加载一个 Markdown 文件(强化学习教材第一章)
  • 注意:路径 …/…/data/ 是相对路径,脚本必须从 code/C1/ 目录运行,否则路径错误(常见踩坑点)
  1. 文本分块(第20-22行)
    text_splitter =RecursiveCharacterTextSplitter()
    chunks = text_splitter.split_documents(docs)
  • 使用 RecursiveCharacterTextSplitter 将长文档切分成小块(默认参数:chunk_size=1000, chunk_overlap=200)
  • 这是 C2 数据准备 中详细讲解的技术,决定了后续检索的粒度
  1. 中文嵌入模型(第24-29行)
    embeddings = HuggingFaceEmbeddings(
    model_name=“BAAI/bge-small-zh-v1.5”,
    model_kwargs={‘device’: ‘cpu’},
    encode_kwargs={‘normalize_embeddings’: True}
    )
  • 加载本项目默认的中文嵌入模型 BAAI/bge-small-zh-v1.5(512维,CPU推理)
  • normalize_embeddings=True 启用归一化,使余弦相似度等价于内积
  1. 向量存储(第31-33行)
    vectorstore = InMemoryVectorStore(embeddings)
    vectorstore.add_documents(chunks)
  • 使用 InMemoryVectorStore(内存向量存储),适合学习演示
  • 生产环境会替换为 FAISS / Chroma / Milvus(见 C3 向量数据库章节)
  1. 提示词模板(第35-46行)
    prompt = ChatPromptTemplate.from_template(“”“请根据下面提供的上下文信息来回答问题。…”“”)
  • 构建约束性指令:强制 LLM 只基于检索到的上下文回答
  • 包含容错机制:当上下文不足时,LLM 被告知直接说"找不到相关信息"
  • 模板变量:{context}(检索结果)和 {question}(用户问题)
  1. LLM 配置(第49-66行)
    llm = ChatOpenAI(
    model=“gpt-5.5-free”,
    api_key=“sk-…”,
    base_url=“https://aihubmix.com/v1”
    )

  2. 执行查询(第68-75行)
    retrieved_docs = vectorstore.similarity_search(question, k=3)
    answer = llm.invoke(prompt.format(question=question, context=docs_content))
    print(answer)

  • similarity_search(question, k=3):检索与问题最相似的 3 个文档块
  • 将检索到的文档拼接为 docs_content,填入提示模板
  • 调用 LLM 生成最终答案并打印
    整体流程图
    Markdown文件 → UnstructuredMarkdownLoader → RecursiveCharacterTextSplitter

    文档块 (chunks)

    HuggingFaceEmbeddings(bge-small-zh-v1.5)

    ┌──────────────── InMemoryVectorStore ────────────────┐
    │ 添加文档块,构建向量索引 │
    └────────────────────┬────────────────────────────────┘

    用户问题 ──────→ similarity_search(k=3) ──→ 检索到3个相关块

    拼接 context + question 填入模板

    ChatOpenAI(gpt-5.5-free) 生成回答

    打印答案

附:虚拟环境安装

一、

powershell-ExecutionPolicyByPass-c"irm https://astral.sh/uv/install.ps1 | iex"

二、

安装成功后,按照提示输入以下命令添加环境变量。这里注意,不同的人的安装路径不同,请按照提示自行复制粘贴命令。

$env:Path="C:\Users\michaelbradley\.local\bin;$env:Path"

三、

uv venv rag--python3.12.7

四、

rag\Scripts\activate
http://www.jsqmd.com/news/1067592/

相关文章:

  • 3分钟免费安装!VideoDownloadHelper视频下载神器终极指南
  • 怪物猎人世界智能辅助工具HunterPie:游戏数据实时监控与战斗效率提升指南
  • 四川LED显示屏维修源头厂家有哪些
  • ARM处理器与RTOS集成:i.MX平台AMX实时内核开发实践
  • AVR32时钟控制器(CLKCTRL)配置与中断管理实战详解
  • UiPath Studio 21.10.4 安装教程:新手从零搭建 RPA 机器人开发环境
  • wiliwili:让你的游戏机变身全能B站客户端,一键开启跨平台追番体验
  • 利用ATtiny3227 Curiosity Nano板载调试器编程外部MCU实战指南
  • SAM G51微控制器:Cortex-M4内核在物联网中的性能与功耗平衡实践
  • 低成本汽车LIN从机方案:基于68HC08QT/QY系列MCU的软硬件设计实践
  • ATF1508AS(L) CPLD深度解析:从宏单元架构到开发调试实战
  • GitHub周趋势2026W25 | Headroom 压缩 95% Token、NVIDIA 开源 AI Agent 安全扫描器、Apple macOS 原生 Linux 容器、…
  • 3步快速完成Honey Select 2完整汉化:免费终极补丁指南
  • 2010年-2024年上市公司参与数字技术标准制定情况
  • e6500处理器L2缓存分区与错误处理机制实战解析
  • 全网小说一键下载终极指南:novel-downloader让离线阅读更简单
  • QorIQ P1022嵌入式开发:从硬件架构到Linux BSP构建实战
  • ATtiny85 EEPROM数据丢失排查:低电压与时钟频率的致命影响
  • AVR32SD硬件联动:CCL连接AC与ADC实现纳秒级响应
  • ATF1508AS(L) CPLD开发全解析:从硬件架构到JTAG调试实战
  • 微电网光伏发电经逆变器带负载模型模型研究(Simulink仿真实现)
  • DSP56303主机接口与ESSI编程:异构系统通信与音频处理实战
  • FitGirl游戏启动器:一站式管理你的游戏收藏库
  • ViPER4Windows音频驱动修复工具:3步解决Windows 10/11音效兼容性问题终极指南
  • 九江一站式团建服务指南:吃喝玩乐全包含攻略
  • 哔咔漫画下载器:打造您的个人离线漫画图书馆
  • AVR单片机TCA/TCB定时器中断配置与调试实战指南
  • ATA6824C电机驱动芯片:H桥驱动、电荷泵与热保护机制详解
  • MCF528x嵌入式网关开发:集成以太网与CAN的工业控制核心
  • 【CANdelaStudio-从入门到深入到实战】50 从“硬复位”到“软着陆”:0x34/0x36/0x37 窗口下载的流量控制艺术