构建LangChain应用程序的示例代码:38、自主RAG的概念及其实现方法,使用LangChain和OpenAI工具从头开始构建一个结合检索和生成的系统

# 安装必要的库
! pip install langchain_community tiktoken langchain-openai langchainhub chromadb langchain langgraph

自主RAG (Self-RAG)

自主RAG是最近的一篇论文,介绍了一种用于主动RAG的有趣方法。

该框架训练单个任意的语言模型(如LLaMA2-7b, 13b)来生成控制RAG过程的标记:

  1. 是否从检索器检索 - Retrieve

    • 标记:Retrieve
    • 输入:x (问题)x (问题), y (生成的回答)
    • 决定何时使用R检索D个片段
    • 输出:yes, no, continue
  2. 检索到的片段D是否与问题x相关 - ISREL

    • 标记:ISREL
    • 输入:(x (问题), d (片段)) 对于每个dD
    • d提供解决x的有用信息
    • 输出:relevant, irrelevant
  3. 每个片段D生成的LLM回答是否与片段相关(如幻觉等) - ISSUP

    • 标记:ISSUP
    • 输入:x (问题), d (片段), y (生成的回答) 对于每个dD
    • y (生成的回答)中所有需要验证的陈述都由d支持
    • 输出:fully supported, partially supported, no support
  4. 每个片段D生成的LLM回答是否对x (问题)有用 - ISUSE

    • 标记:ISUSE
    • 输入:x (问题), y (生成的回答) 对于每个dD
    • y (生成的回答)是否对x (问题)有用
    • 输出:{5, 4, 3, 2, 1}

我们可以将其表示为一个图:

在这里插入图片描述

论文链接:https://arxiv.org/abs/2310.11511


让我们使用LangGraph从头开始实现这个过程。

检索器 (Retriever)

让我们索引三个博客文章。

from langchain.text_splitter import RecursiveCharacterTextSplitter  # 从LangChain导入递归字符文本分割器
from langchain_community.document_loaders import WebBaseLoader      # 从LangChain Community导入网页基础加载器
from langchain_community.vectorstores import Chroma                # 从LangChain Community导入Chroma向量存储
from langchain_openai import OpenAIEmbeddings                      # 从LangChain OpenAI导入OpenAI嵌入

# 定义需要索引的博客文章URL列表
urls = [
    "https://lilianweng.github.io/posts/2023-06-23-agent/",
    "https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/",
    "https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/",
]

# 加载每个URL的文档内容
docs = [WebBaseLoader(url).load() for url in urls]
# 将嵌套的文档列表展开为单个列表
docs_list = [item for sublist in docs for item in sublist]

# 使用递归字符文本分割器将文档分割成大小为250字符的块,且块之间没有重叠
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=250, chunk_overlap=0
)
doc_splits = text_splitter.split_documents(docs_list)

# 将分割后的文档添加到向量数据库
vectorstore = Chroma.from_documents(
    documents=doc_splits,            # 输入分割后的文档
    collection_name="rag-chroma",    # 指定集合名称
    embedding=OpenAIEmbeddings(),    # 使用OpenAI嵌入
)

# 将向量存储转化为检索器
retriever = vectorstore.as_retriever()

状态

我们将定义一个图。

我们的状态将是一个字典。

我们可以从任何图节点访问它,使用 state[‘keys’]。

from typing import Dict, TypedDict

from langchain_core.messages import BaseMessage


class GraphState(TypedDict):
    """
    Represents the state of an agent in the conversation.

    Attributes:
        keys: A dictionary where each key is a string and the value is expected to be a list or another structure
              that supports addition with `operator.add`. This could be used, for instance, to accumulate messages
              or other pieces of data throughout the graph.
    """

    keys: Dict[str, any]

节点和边

每个节点将简单地修改状态。

每条边将选择下一个要调用的节点。

我们可以将自助RAG表示为一个图:
在这里插入图片描述

import json
import operator
from typing import Annotated, Sequence, TypedDict

from langchain import hub
from langchain.output_parsers import PydanticOutputParser
from langchain.output_parsers.openai_tools import PydanticToolsParser
from langchain.prompts import PromptTemplate
from langchain_community.vectorstores import Chroma
from langchain_core.messages import BaseMessage, FunctionMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langgraph.prebuilt import ToolInvocation

### 节点 ###

def retrieve(state):
    """
    检索文档

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        dict: 向状态添加新键,documents,包含检索到的文档。
    """
    print("---RETRIEVE---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = retriever.invoke(question)
    return {"keys": {"documents": documents, "question": question}}

def generate(state):
    """
    生成答案

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        dict: 向状态添加新键,generation,包含生成的答案。
    """
    print("---GENERATE---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]

    # 提示模板
    prompt = hub.pull("rlm/rag-prompt")

    # LLM模型
    llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

    # 后处理
    def format_docs(docs):
        return "\n\n".join(doc.page_content for doc in docs)

    # 链
    rag_chain = prompt | llm | StrOutputParser()

    # 运行
    generation = rag_chain.invoke({"context": documents, "question": question})
    return {
        "keys": {"documents": documents, "question": question, "generation": generation}
    }

def grade_documents(state):
    """
    确定检索到的文档是否与问题相关。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        dict: 向状态添加新键,filtered_documents,包含相关文档。
    """

    print("---CHECK RELEVANCE---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]

    # 数据模型
    class grade(BaseModel):
        """相关性检查的二进制评分。"""

        binary_score: str = Field(description="相关性评分 'yes' 或 'no'")

    # LLM模型
    model = ChatOpenAI(temperature=0, model="gpt-4-0125-preview", streaming=True)

    # 工具
    grade_tool_oai = convert_to_openai_tool(grade)

    # LLM与工具绑定并强制调用
    llm_with_tool = model.bind(
        tools=[convert_to_openai_tool(grade_tool_oai)],
        tool_choice={"type": "function", "function": {"name": "grade"}},
    )

    # 解析器
    parser_tool = PydanticToolsParser(tools=[grade])

    # 提示模板
    prompt = PromptTemplate(
        template="""你是一个评估员,正在评估检索到的文档与用户问题的相关性。\n 
        这是检索到的文档:\n\n {context} \n\n
        这是用户问题:{question} \n
        如果文档包含与用户问题相关的关键词或语义,请将其评为相关。\n
        给出一个二进制评分 'yes' 或 'no',表示文档是否与问题相关。""",
        input_variables=["context", "question"],
    )

    # 链
    chain = prompt | llm_with_tool | parser_tool

    # 评分
    filtered_docs = []
    for d in documents:
        score = chain.invoke({"question": question, "context": d.page_content})
        grade = score[0].binary_score
        if grade == "yes":
            print("---GRADE: DOCUMENT RELEVANT---")
            filtered_docs.append(d)
        else:
            print("---GRADE: DOCUMENT NOT RELEVANT---")
            continue

    return {"keys": {"documents": filtered_docs, "question": question}}

def transform_query(state):
    """
    转换查询以生成更好的问题。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        dict: 保存新问题到state。
    """

    print("---TRANSFORM QUERY---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]

    # 创建一个包含格式指令和查询的提示模板
    prompt = PromptTemplate(
        template="""你正在生成针对检索进行优化的问题。\n 
        查看输入并尝试推理其潜在的语义意图。\n 
        这是初始问题:
        \n ------- \n
        {question} 
        \n ------- \n
        形成一个改进的问题:""",
        input_variables=["question"],
    )

    # 评分模型
    model = ChatOpenAI(temperature=0, model="gpt-4-0125-preview", streaming=True)

    # 提示模板
    chain = prompt | model | StrOutputParser()
    better_question = chain.invoke({"question": question})

    return {"keys": {"documents": documents, "question": better_question}}

def prepare_for_final_grade(state):
    """
    准备进行最终评分,状态透传。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        state (dict): 代理当前的状态,包括所有键。
    """

    print("---FINAL GRADE---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]
    generation = state_dict["generation"]

    return {
        "keys": {"documents": documents, "question": question, "generation": generation}
    }

### 边 ###

def decide_to_generate(state):
    """
    决定是生成答案还是重新生成问题。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        dict: 向状态添加新键,filtered_documents,包含相关文档。
    """

    print("---DECIDE TO GENERATE---")
    state_dict = state["keys"]
    question = state_dict["question"]
    filtered_documents = state_dict["documents"]

    if not filtered_documents:
        # 所有文档在检查相关性时都被过滤掉了
        # 我们将重新生成一个新查询
        print("---DECISION: TRANSFORM QUERY---")
        return "transform_query"
    else:
        # 我们有相关文档,所以生成答案
        print("---DECISION: GENERATE---")
        return "generate"

def grade_generation_v_documents(state):
    """
    确定生成的回答是否基于文档。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        str: 二进制决策评分。
    """

    print("---GRADE GENERATION vs DOCUMENTS---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]
    generation = state_dict["generation"]

    # 数据模型
    class grade(BaseModel):
        """相关性检查的二进制评分。"""

        binary_score: str = Field(description="支持评分 'yes' 或 'no'")

    # LLM模型
    model = ChatOpenAI(temperature=0, model="gpt-4-0125-preview", streaming=True)

    # 工具
    grade_tool_oai = convert_to_openai_tool(grade)

    # LLM与工具绑定并强制调用
    llm_with_tool = model.bind(
        tools=[convert_to_openai_tool(grade_tool_oai)],
        tool_choice={"type": "function", "function": {"name": "grade"}},
    )

    # 解析器
    parser_tool = PydanticToolsParser(tools=[grade])

    # 提示模板
    prompt = PromptTemplate(
        template="""你是一个评估员,正在评估答案是否基于/支持一组事实。\n 
        这里是事实:
        \n ------- \n
        {documents} 
        \n ------- \n
        这是答案:{generation}
        给出一个二进制评分 'yes' 或 'no',表示答案是否基于/支持一组事实。""",
        input_variables=["generation", "documents"],
    )

    # 链
    chain = prompt | llm_with_tool | parser_tool

    score = chain.invoke({"generation": generation, "documents": documents})
    grade = score[0].binary_score

    if grade == "yes":
        print("---DECISION: SUPPORTED, MOVE TO FINAL GRADE---")
        return "supported"
    else:
        print("---DECISION: NOT SUPPORTED, GENERATE AGAIN---")
        return "not supported"

def grade_generation_v_question(state):
    """
    确定生成的回答是否解决了问题。

    参数:
        state (dict): 代理当前的状态,包括所有键。

    返回:
        str: 二进制决策评分。
    """

    print("---GRADE GENERATION vs QUESTION---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]
    generation = state_dict["generation"]

    # 数据模型
    class grade(BaseModel):
        """相关性检查的二进制评分。"""

        binary_score: str = Field(description="有用评分 'yes' 或 'no'")

    # LLM模型
    model = ChatOpenAI(temperature=0, model="gpt-4-0125-preview", streaming=True)

    # 工具
    grade_tool_oai = convert_to_openai_tool(grade)

    # LLM与工具绑定并强制调用
    llm_with_tool = model.bind(
        tools=[convert_to_openai_tool(grade_tool_oai)],
        tool_choice={"type": "function", "function": {"name": "grade"}},
    )

    # 解析器
    parser_tool = PydanticToolsParser(tools=[grade])

    # 提示模板
    prompt = PromptTemplate(
        template="""你是一个评估员,正在评估答案是否有助于解决问题。\n 
        这是答案:
        \n ------- \n
        {generation} 
        \n ------- \n
        这是问题:{question}
        给出一个二进制评分 'yes' 或 'no',表示答案是否有助于解决问题。""",
        input_variables=["generation", "question"],
    )

    # 提示模板
    chain = prompt | llm_with_tool | parser_tool

    score = chain.invoke({"generation": generation, "question": question})
    grade = score[0].binary_score

    if grade == "yes":
        print("---DECISION: USEFUL---")
        return "useful"
    else:
        print("---DECISION: NOT USEFUL---")
        return "not useful"

Graph

import pprint

from langgraph.graph import END, StateGraph

# 定义工作流状态图
workflow = StateGraph(GraphState)

# 添加节点
workflow.add_node("retrieve", retrieve)  # 检索
workflow.add_node("grade_documents", grade_documents)  # 评估文档
workflow.add_node("generate", generate)  # 生成答案
workflow.add_node("transform_query", transform_query)  # 转换查询
workflow.add_node("prepare_for_final_grade", prepare_for_final_grade)  # 准备最终评分,透传状态

# 构建图
workflow.set_entry_point("retrieve")  # 设置入口点为检索节点
workflow.add_edge("retrieve", "grade_documents")  # 检索后接评估文档节点
workflow.add_conditional_edges(
    "grade_documents",
    decide_to_generate,
    {
        "transform_query": "transform_query",  # 评估文档后,根据决定转到转换查询或生成答案节点
        "generate": "generate",
    },
)
workflow.add_edge("transform_query", "retrieve")  # 转换查询后返回检索节点
workflow.add_conditional_edges(
    "generate",
    grade_generation_v_documents,
    {
        "supported": "prepare_for_final_grade",  # 生成答案后,根据决定转到准备最终评分或重新生成答案
        "not supported": "generate",
    },
)
workflow.add_conditional_edges(
    "prepare_for_final_grade",
    grade_generation_v_question,
    {
        "useful": END,  # 准备最终评分后,根据决定结束或返回转换查询
        "not useful": "transform_query",
    },
)

# 编译工作流
app = workflow.compile()

# 导入 pprint 模块,用于美化打印输出
import pprint

# 定义输入,包含一个问题
inputs = {"keys": {"question": "Explain how the different types of agent memory work?"}}

# 运行编译好的工作流应用
for output in app.stream(inputs):
    # 遍历每个节点的输出
    for key, value in output.items():
        # 打印节点名称
        pprint.pprint(f"Output from node '{key}':")
        pprint.pprint("---")
        # 打印输出的详细信息
        pprint.pprint(value["keys"], indent=2, width=80, depth=None)
    # 打印分隔符,区分不同节点的输出
    pprint.pprint("\n---\n")

import pprint

# 定义输入,包含一个问题
inputs = {"keys": {"question": "Explain how chain of thought prompting works?"}}

# 运行编译好的工作流应用
for output in app.stream(inputs):
    # 遍历每个节点的输出
    for key, value in output.items():
        # 打印节点名称
        pprint.pprint(f"Output from node '{key}':")
        pprint.pprint("---")
        # 打印输出的详细信息
        pprint.pprint(value["keys"], indent=2, width=80, depth=None)
    # 打印分隔符,区分不同节点的输出
    pprint.pprint("\n---\n")

扩展知识

1. RAG (Retrieval-Augmented Generation)

RAG是一种结合了检索和生成的技术,首先从一个大型文档集合中检索相关内容,然后使用生成模型基于这些内容生成回答。这种方法能够有效结合外部知识库和生成模型的能力,提高回答的准确性和信息丰富性。

2. LangChain

LangChain是一个用于构建语言模型应用的框架,支持多种文档加载、文本分割、嵌入计算和向量存储方式。它简化了从数据准备到模型应用的整个流程。

3. OpenAI

OpenAI提供了强大的语言模型如GPT-3和GPT-4,这些模型能够理解和生成自然语言文本,广泛应用于各种NLP任务,如问答、翻译、文本生成等。

4. Chroma

Chroma是一个高效的向量数据库,支持快速的向量检索,常用于结合嵌入技术进行相似性搜索。它在处理大型文档集合时表现出色。

总结

在本文中,我们介绍了自主RAG的概念及其实现方法,使用LangChain和OpenAI工具从头开始构建一个结合检索和生成的系统。通过对多个博客文章进行索引和处理,我们展示了如何利用这些工具进行高效的信息检索和回答生成。本文还补充了相关的技术知识,帮助读者更好地理解和应用这些技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/723733.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

科技赋能冷链园区:可视化带来全新体验

应用图扑可视化技术,冷链园区能够更加直观地监控和管理资源,优化运作流程,提高运营效率与服务质量。

机器学习课程复习——支持向量机

支持向量机是一种二类分类模型。基本模型:定义在特征空间上的间隔最大的线性分类器模型升级:线性分类器+核技巧=非线性分类器学习策略:间隔最大化(可形式化为求解凸二次规划的问题;可等价于正则化的合页损失函数的最小化问题)学习算法:求解图二次规划的最优化算法支持向…

三次输错密码后,系统是怎么做到不让我继续尝试的?

1故事背景 忘记密码这件事,相信绝大多数人都遇到过,输一次错一次,错到几次以上,就不允许你继续尝试了。 但当你尝试重置密码,又发现新密码不能和原密码重复: 图片 相信此刻心情只能用一张图形容: 图片 虽然,但是,密码还是很重要的,顺便我有了一个问题:三次输错密…

python安装系列问题

python3.4版本以上安装了python之后自带安装python。 1、换源 以Windows,清华源为例: 直接在user目录中创建一个pip目录,例如:C:\Users\xx\pip,新建文件pip.ini,内容如下: [global] index-url https:/…

Adobe Premiere 视频编辑软件下载安装,pr 全系列资源分享!

Adobe Premiere以其强大的功能、灵活的操作和卓越的性能,成为视频编辑领域的佼佼者。 在剪辑方面,Adobe Premiere提供了强大而灵活的工具集。用户可以在直观的时间线上对视频进行精细的裁剪、剪辑和合并操作。无论是快速剪辑短片,还是精心打造…

运维篇-配置高级

黑马程序员Spring Boot2 文章目录 1、临时属性1.1 命令行中使用临属性1.2 开发环境设定临时属性 2、配置文件分类3、自定义配置文件 1、临时属性 1.1 命令行中使用临属性 使用jar命令启动Sprigboot工程师可以使用临时属性替换配置文件中的属性临时属性添加方式:ja…

护眼台灯选购:台灯怎么选对眼睛好?

如今孩子们的学习压力越来越大,熬夜学习已成常态,视力问题也日益凸显。尽管没有详细的地域数据,但整体而言,中国青少年的近视率已经高居世界第一位,且不同地区的近视率可能存在一定的差异。眼睛健康愈发受到关注&#…

《QT从基础到进阶·七十二》基于Qt开发的文件保险柜工具并支持文件各种加密和解密

1、概述 源码放在文章末尾 该项目实现了文件各种加密和解密的功能,能够有效的保障文件的安全,主要包含如下功能: 1、支持所有 AES 密钥长度; AES_128 AES_192 AES_256 2、支持ECB、CBC、CFB、OFB四种模式; 3、支持ZER…

JasperReport-Variable变量使用和求总计等计算

背景 在制作报表时,会经常看到Variables变量,里边有几个默认值,默认值不支持修改。其中用得最多的就是PAGE_NUMBER和PAGE_COUNT。下边接着介绍一下自定义变量的一些用法。 内置变量 Variable Name 变量名 Description 描述 PAGE_NUMBER Co…

爆赞!GitHub首本Python开发实战背记手册,标星果然百万名不虚传

Python (发音:[ paiθ(ə) n; (US) paiθɔn ] n. 蟒蛇,巨蛇 ),是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大而完善的通用型语言,已经具有十多年的发展历史,成熟且稳定。Python 具有脚本语言中最丰富…

Java 诊断神器 Arthas使用笔记

Arthas 是一款开源在线 Java 诊断工具,采用命令行交互模式,支持 web 端在线诊断,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。得益于 Arthas 强大且丰富的功能。 1.JDK原生定位工具 平时开发中会用到JDK中…

韩顺平0基础学java——第23

p460-483 常用类 包装类Wrapper 针对8种几种数据类型相应的引用类型——包装类 包装类和基本数据类型的转换 jdk5之前的手动装箱和拆箱: jdk5之后的自动装箱和拆箱: 三元运算符是一个整体: 这个三元运算符里,精度最高的是doubl…

【区块链】以太坊白皮书深度解读:构建智能合约的分布式平台

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 以太坊白皮书深度解读:构建智能合约的分布式平台引言1. 以太坊的诞生…

机能学实验通过ZL-620C一体化信息化生物信号采集系统具体呈现

ZL-621大屏教学试教系统为了实施机能学实验的教学改革,大力减轻教师的实验教学负担,主要功能电子白板,同步教学、控制、过程仿真、虚拟现实、三维动画、管理、音视频广播、PPT教材等于一体,大屏教学试教系统并能同时实现屏幕监视和…

权利利益方格

权利利益方格 一、定义二、分类与策略权力高、利益高(双高)权力高、利益低权力低、利益高权力低、利益低(双低) 三、应用四、总结 在项目管理中,权力利益方格是一种重要的工具,用于评估项目相关方的权力和利…

树莓派4B_OpenCv学习笔记9:图片的腐蚀与膨胀

今日继续学习树莓派4B 4G:(Raspberry Pi,简称RPi或RasPi) 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1: 图像的膨胀与腐蚀一般用于灰度图或者二值图,今日便来学习…

头歌资源库(13)背包问题

一、 问题描述 二、算法思想 这是一个背包问题,可以使用动态规划算法来解决。具体思路如下: 定义一个二维数组dp,dp[i][j]表示前i个物品在背包容量为j时能获取的最大价值。初始化dp数组的第一行和第一列为0,表示当只有一个物品或…

这13个常用电路基础公式,每一位电子工程师都要牢记

计算电阻电路中电流、电压、电阻和功率之间的关系。 欧姆定律解释了电压、电流和电阻之间的关系,即通过导体两点间的电流与这两点间的电势差成正比。说明两点间的电压差、流经该两点的电流和该电流路径电阻之间关系的定律。该定律的数学表达式为V IR,其中…

Qt调用第三方库的通用方式(静态链接库.a或.lib、动态链接库.dll)

目录 一、前提 二、如何引用静态链接库 三、如何引用动态链接库 四、示例代码资源 在开发项目中经常会存在需要调用第三方库的时候,对于Qt如何来调用第三方库,为了方便自己特意记录下详细过程。 一、前提 1. window 10操作系统 2. 已安装了Qt6.7.…

申万宏源:消费税改或是财政改革第一枪

消费税征收环节后移可能带来年化千亿的税收收入增长,地方财政压力的缓和程度取决于中央确定保留的消费税基数。申万宏源认为,财政改革不仅仅只涉及消费税和央地分配,而稳定扩大需求才是下一步改革核心。 主要内容 财政现实呼唤改革。紧迫性…