langchain系列(四)- LangChain 的RAG原理与代码实现

news/2025/2/24 8:15:00

导读
环境:OpenEuler、Windows 11、WSL 2、Python 3.12.3 langchain 0.3

背景:前期忙碌的开发阶段结束,需要沉淀自己的应用知识,过一遍LangChain

时间:20250223

说明:技术梳理,使用LangChain实现rag,当前主流实现均使用LangGraph,此处使用LangChain的目的是加强LangChain的熟练度

原理与问题

背景

最初源于2020年Facebook的一篇论文——《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》

概念

RAG(Retrieval-Augmented Generation)检索增强生成,即大模型LLM在回答问题或生成文本时,会先从大量的文档中检索出相关信息,然后基于这些检索出的信息进行回答或生成文本,从而可以提高回答的质量,而不是任由LLM来发挥。

解决问题

在大模型的快速发展过程中,以下四点比较突出:

幻觉问题:大语言模型基于概率推理,所以LLMs有时候会一本正经的胡说八道,编造看似合理的答案。
知识缺乏问题:大模型都是预训练,就拿ChatGPT3.5来说训练数据是2021年,但是对于2021年之后的事情,它将一无所知。另外还可能会产生过时的知识和缺乏一些特定领域的知识。
数据安全问题:对于企业来说,企业的经营数据,商业机密数据都是非常重要的,直接使用大模型可能会有数据安全泄露风险。
可信度问题:不透明、无法追踪的推理过程,导致回答问题可信度问题。

与微调对比

以上问题,微调也可以解决,下面是对比信息

RAG、微调优缺点对比
微调   RAG
有点针对特定任务调整预训练模型。优点是可针对特定任务优化结合检索系统和生成模型。优点是能利用最新信息,提高答案质量,具有更好的可解释性和适应性
缺点但缺点是更新成本高,对新信息适应性较差是可能面临检索质量问题和曾加额外计算资源需求
RAG、微调特性对比
特性RAG微调
知识更新实时更新检索库,适合动态数据,无需频繁重训存储静态信息,更新知识需要重新训练
外部知识高效利用外部资源,适合各类数据库可对齐外部知识,但对动态数据源不够灵活
数据处理数据处理需求低 需构建高质量数据集,数据限制可能影响性能
模型定制化专注于信息检索和整合,定制化程度低可定制行为,风格及领域知识
可解释性答案可追溯,解释性高解释性相对低
计算资源需要支持检索的计算资源,维护外部数据源需要训练数据集和微调资源
延迟要求数据检索可能增加延迟微调后的模型反应更快
减少幻觉基于实际数据,幻觉减少通过特定域训练可减少幻觉,但仍然有限
道德与隐私处理外部文本数据时需要考虑隐私和道德问题训练数据的敏感内容可能引发隐私问题

原理图详解

图片地址:Retrieval-Augmented Generation for Large Language Models: A Survey

该论文综述中,将RAG技术按照复杂度分为Naive RAGAdvanced RAGModular RAG

初级 RAG 在检索质量、响应生成质量以及增强过程中存在多个挑战,高级 RAG 范式随后被提出,高级RAG在数据索引、检索前和检索后都进行了额外处理。通过更精细的数据清洗、设计文档结构和添加元数据等方法提升文本的一致性、准确性和检索效率。随着 RAG 技术的进一步发展和演变,新的技术突破了传统的 初级 RAG 检索 — 生成框架,基于此我们提出模块化 RAG 的概念。在结构上它更加自由的和灵活,引入了更多的具体功能模块,例如查询搜索引擎、融合多个回答。技术上将检索与微调、强化学习等技术融合。流程上也对 RAG 模块之间进行设计和编排,出现了多种的 RAG 模式。

本文主讲LangChain中的RAG,故而此处简要介绍原始RAG的原理,高级和模块化的均基于naive RAG

Naive RAG

该RAG的流程包括索引、检索和生成三个步骤,既把问答内容输入到数据库中,给定query,可以直接去数据库中搜索,搜索完成后把查询结果和query拼接起来送给模型去生成内容。图示如下:

索引

索引阶段是将文本、图片、音视频等格式的内容进行解析、分割、向量化处理,并最终向量化后的内容存储到向量数据库,具体包含四个步骤:数据加载、文本分块、文本嵌入、创建索引

数据加载

将外部数据进行清理和提取,将CSV、 PDF、HTML、Word、Markdown 等不同格式的文件转换成纯文本,这里可以借助LangChain内置的加载器来实现。LangChain内置的加载器是LangChain中最有用的部分之一,例如加载CSV的CSVLoader,加载PDF的PyPDFLoader,加载HTML的UnstructuredHTMLLoader,加载Word的:UnstructuredWordDocumentLoader,加载MarkDown的:UnstructuredMarkdownLoader等

文本分块

一方面Transformer模型有固定的输入序列长度,即使输入context很大,一个句子或几个句子的向量也比几页文本的平均向量更好地代表它们的语义含义,另一方面,我们将文档分割成适合搜索的小块,使其更适合进行嵌入搜索,从而提升片段召回的准确性。

LangChain中集成了不少分块工具,如下:

from langchain import text_splitter
文本嵌入

亦称之为向量化,是将文本内容通过embedding嵌入模型转化为多维向量的过程

可以这样认为:将不同的人类语言生成的向量绘制到多维坐标系中,发现在这个假设的语言空间中,两个点越接近,它们所表达的语义就越相似。

创建索引

将原始语料块和嵌入以键值对形式存储到向量数据库,以便于未来进行快速且频繁的搜索

常用的向量数据库有:ChromaWeaviate、 FAISSES、Milvus

检索

检索是RAG框架中的重要组成部分,根据用户的查询,快速检索到与之最相关的知识,并将其融入提示词(Prompt)中。这个过程一般分两步:

1、根据用户的输入,采用与索引创建相同的编码模型将查询内容转换为向量。
2、计算问题向量与语料库中文档块向量之间的相似性,并根据相似度水平选出最相关的前 K 个文档块作为当前问题的补充背景信息。

常见的检索方法:分层索引检索混合检索HyDE方案

生成

将用户的问题与知识库被检索出的文本块相结合, 用prompt的形式传递给大语言模型的上下文,使大模型更好理解用户意图,生成用户想要的结果

以上就是RAG框架的整体流程,接下来将用代码实现一个完整的RAG Demo。

代码实现 

1、索引

创建索引需要执行以下四个步骤

数据加载

将如下内容写入到pdf中,后续使用pdf实现数据加载

王二狗,1990年出生于中国西南部一个风景秀丽的小山村——云南省大理白族自治州的一个小村庄。从小,他就对周围的世界充满了好奇心,尤其对电子设备和计算机表现出浓厚的兴趣。尽管家庭条件并不富裕,但父母的支持与鼓励为他的成长奠定了坚实的基础。

2008年,18岁的王二狗以优异的成绩考入了清华大学计算机科学与技术专业,成为了村里第一个进入这所著名学府的学生。大学期间,他不仅在学业上取得了显著成就,还积极参与各种学术研究和社会实践活动。他曾作为团队核心成员参加了全国大学生智能车竞赛,并荣获一等奖。

毕业后,王二狗加入了一家知名的互联网公司,专注于人工智能领域的发展。在这里,他迅速崭露头角,凭借出色的编程能力和创新思维,参与了多个重要项目,为公司的技术进步做出了突出贡献。

工作之余,王二狗也不忘回馈社会。他经常回到家乡,给村里的孩子们讲述外面世界的精彩,鼓励他们勇敢追求梦想。如今,35岁的王二狗已经成为行业内的一名杰出专家,但他依旧保持着谦逊和学习的态度,继续探索科技的无限可能。他的故事激励着无数年轻人勇敢追梦,不畏艰难,用知识改变命运。

 实现数据加载的代码

# 数据加载
pdf_path = "/home/jack/langchain_test/langchain_rag/static/test_xx.pdf"
text = ""
pdf_reader = PdfReader(pdf_path)
for page in pdf_reader.pages:
    text += page.extract_text()
print(text)

输出 

 '王二狗,1990 年出生于中国西南部一个风景秀丽的小山村 ——云南省大理白族自治州\n的一个小村庄。从小,他就对周围的世界充满了好奇心,尤其对电子设备和计算机表\n现出浓厚的兴趣。尽管家庭条件并不富裕,但父母的支持与鼓励为他的成长奠定了坚\n实的基础。 \n \n2008年,18岁的王二狗以优异的成绩考入了清华大学计算机科学与技术专业,成为了\n村里第一个进入这所著名学府的学生。大学期间,他不仅在学业上取得了显著成就,\n还积极参与各种学术研究和社会实践活动。他曾作为团队核心成员参加了全国大学生\n智能车竞赛,并荣获一等奖。  \n \n毕业后,王二狗加入了一家知名的互联网公司,专注于人工智能领域的发展。在这\n里,他迅速崭露头角,凭借出色的编程能力和创新思维,参与了多个重要项目,为公\n司的技术进步做出了突出贡献。 \n \n工作之余,王二狗也不忘回馈社会。他经常回到家乡,给村里的孩子们讲述外面世界\n的精彩,鼓励他们勇敢追求梦想。如今,35岁的王二狗已经成为行业内的一名杰出专\n家,但他依旧保持着谦逊和学习的态度,继续探索科技的无限可能。他的故事激励着\n无数年轻人勇敢追梦,不畏艰难,用知识改变命运。 '

文本分块

# 文本分块
text_spliter = CharacterTextSplitter(separator="\n", chunk_size=100, chunk_overlap=20, length_function=len)
content_chunks = text_spliter.split_text(text)

输出

 ['王二狗,1990 年出生于中国西南部一个风景秀丽的小山村 ——云南省大理白族自治州\n的一个小村庄。从小,他就对周围的世界充满了好奇心,尤其对电子设备和计算机表', '现出浓厚的兴趣。尽管家庭条件并不富裕,但父母的支持与鼓励为他的成长奠定了坚\n实的基础。 \n \n2008年,18岁的王二狗以优异的成绩考入了清华大学计算机科学与技术专业,成为了', '村里第一个进入这所著名学府的学生。大学期间,他不仅在学业上取得了显著成就,\n还积极参与各种学术研究和社会实践活动。他曾作为团队核心成员参加了全国大学生\n智能车竞赛,并荣获一等奖。', '智能车竞赛,并荣获一等奖。  \n \n毕业后,王二狗加入了一家知名的互联网公司,专注于人工智能领域的发展。在这\n里,他迅速崭露头角,凭借出色的编程能力和创新思维,参与了多个重要项目,为公', '司的技术进步做出了突出贡献。 \n \n工作之余,王二狗也不忘回馈社会。他经常回到家乡,给村里的孩子们讲述外面世界\n的精彩,鼓励他们勇敢追求梦想。如今,35岁的王二狗已经成为行业内的一名杰出专', '家,但他依旧保持着谦逊和学习的态度,继续探索科技的无限可能。他的故事激励着\n无数年轻人勇敢追梦,不畏艰难,用知识改变命运。']

分为六个元素 

文本嵌入并创建索引

# 文本嵌入并创建索引
vectorstore = Chroma.from_texts(texts=content_chunks, embedding=embedding_model)

创建检索器

retriever = vectorstore.as_retriever(search_kwargs={"k": 2})

 使用检索器检索上下文

context_docs = retriever.invoke(question)
context = "\n".join([doc.page_content for doc in context_docs])

使用问题和上下文设置提示的格式

formatted_prompt = prompt.format(text=question, context=context)

 使用格式化提示调用 LLM

llm_response = qa.invoke({"query": formatted_prompt})
answer = llm_response["result"]

输出

'王二狗出生于1990年,所以如果按当前年份2023年计算,他应为33岁。但实际上,具体年龄会根据他的出生月份和当前月份来确切决定。不过,基于提供的信息,我们可以推测他大约是33岁。' 

整体代码 

from langchain_community.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from PyPDF2 import PdfReader
from langchain_openai import AzureOpenAIEmbeddings, ChatOpenAI
from langchain.chains import retrieval_qa, RetrievalQA
from langchain.prompts import ChatPromptTemplate

embedding_model = AzureOpenAIEmbeddings(
    “请输入自己的新”
)

llm = ChatOpenAI(
    “请使用自己的信息”
    )

def rag_test_pdf(question):
    # 数据加载
    pdf_path = "/home/jack/langchain_test/langchain_learn/static/test_cui.pdf"
    text = ""
    pdf_reader = PdfReader(pdf_path)
    for page in pdf_reader.pages:
        text += page.extract_text()

    # 文本分块
    text_spliter = CharacterTextSplitter(separator="\n", chunk_size=100, chunk_overlap=20, length_function=len)
    content_chunks = text_spliter.split_text(text)
    
    # 文本嵌入并创建索引
    vectorstore = Chroma.from_texts(texts=content_chunks, embedding=embedding_model)

    # 创建检索器
    retriever = vectorstore.as_retriever(search_kwargs={"k": 2})

    # 创建提示词
    prompt = ChatPromptTemplate.from_messages(
        [("system", "你是一个智能助手"), ("user","{text}")]
    )
    qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever)

    # 使用检索器检索上下文
    context_docs = retriever.invoke(question)
    context = "\n".join([doc.page_content for doc in context_docs])

    # 使用问题和上下文设置提示的格式
    formatted_prompt = prompt.format(text=question, context=context)

    # 使用格式化提示调用 LLM
    llm_response = qa.invoke({"query": formatted_prompt})
    answer = llm_response["result"]
    print(answer)
rag_test_pdf("王二狗今年多大")

代码写的太粗糙,有时间再细化,未完待续


http://www.niftyadmin.cn/n/5864108.html

相关文章

【漫话机器学习系列】101.特征选择法之Lasso(Lasso For Feature Selection)

Lasso 特征选择法详解 1. Lasso 回归简介 Lasso(Least Absolute Shrinkage and Selection Operator,最小绝对收缩和选择算子)是一种基于 L1 范数正则化的线性回归方法。它不仅能够提高模型的泛化能力,还可以自动进行特征选择&am…

用AI写游戏3——deepseek实现kotlin android studio greedy snake game 贪吃蛇游戏

项目下载 https://download.csdn.net/download/AnalogElectronic/90421306 项目结构 就是通过android studio 建空项目,改下MainActivity.kt的内容就完事了 ctrlshiftalts 看项目结构如下 核心代码 MainActivity.kt package com.example.snakegame1// MainA…

Logback:强大的Java日志框架

文章目录 引言什么是Logback?Logback的主要特点Logback的配置在项目中使用Logback总结 引言 在软件开发中,日志记录是一个不可或缺的部分。它不仅帮助开发者在调试时追踪问题,还能在生产环境中监控应用程序的运行状态。Java生态中有多种日志…

sentinel小记

sentinel小记 1、被处理的接口 /*** 分页获取题目列表&#xff08;封装类&#xff09;** param questionQueryRequest* param request* return*/PostMapping("/list/page/vo")public BaseResponse<Page<QuestionVO>> listQuestionVOByPage(RequestBody …

Docker启动ES容器打包本地镜像

文章目录 1、安装 Docker2、下载镜像3、查看已下载的镜像4、 保存和加载镜像5、.tar 文件与 Docker 镜像的关系6、如何从 .tar 文件加载 Docker 镜像7、为什么需要 .tar 文件&#xff1f;8、ES 8.x版本无法启动8.1 问题原因8.2 解决方案8.3 提交容器为新镜像 1、安装 Docker 如…

Vue2 和 Vue3 的响应式原理对比

Object.defineProperty 与 Proxy 对比 前言一、Vue2 的响应式原理二、Vue3 的响应式原理三、性能优化总结 前言 响应式系统是 Vue 框架的核心机制之一&#xff0c;通俗易懂的来说 vue2需要手动登记&#xff0c;只有被用到的才会被记录&#xff0c;vue3全自动监控。 一、Vue2 …

一周学会Flask3 Python Web开发-Jinja2模板基本使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们平台开发web系统&#xff0c;必须用到网页&#xff0c;单纯的静态网页无法满足我们的需求。我们可以使用模版引擎技术&am…

智能优化算法:莲花算法(Lotus flower algorithm,LFA)介绍,提供MATLAB代码

一、 莲花算法 1.1 算法原理 莲花算法&#xff08;Lotus flower algorithm&#xff0c;LFA&#xff09;是一种受自然启发的优化算法&#xff0c;其灵感来源于莲花的自清洁特性和授粉过程。莲花的自清洁特性&#xff0c;即所谓的“莲花效应”&#xff0c;是由其叶片表面的微纳…