构建LangChain应用程序的示例代码:37、基于LangGraph的文档检索与答案生成系统教程

news2024/10/5 18:29:49

这示例它实现了一个基于LangGraph的系统,用于处理文档检索和生成答案的过程。
好的,我会按照Markdown格式完整翻译并保留文件结构和格式:


! pip install langchain_community tiktoken langchain-openai langchainhub chromadb langchain langgraph tavily-python

CRAG

Corrective-RAG 是一篇最新的论文,介绍了一种有趣的主动 RAG 方法。

该框架根据问题对检索到的文档进行评分:

  1. 正确的文档 -

    • 如果至少有一个文档超过了相关性的阈值,则继续生成。
    • 在生成之前,它会进行知识细化。
    • 这会将文档分成“知识条带”。
    • 它对每个条带进行评分,并过滤掉无关的条带。
  2. 含糊或错误的文档 -

    • 如果所有文档都低于相关性阈值或评分器不确定,则框架会寻找额外的数据源。
    • 它会使用网络搜索来补充检索。
    • 论文中的图表还表明,这里使用了查询重写。

在这里插入图片描述

论文链接:https://arxiv.org/pdf/2401.15884.pdf


让我们使用 LangGraph 从头开始实现这一点。

我们可以做一些简化:

  • 作为初步尝试,让我们跳过知识细化阶段。如果需要,可以将其添加回节点中。
  • 如果任何文档不相关,我们选择使用网络搜索来补充检索。
  • 我们将使用 Tavily Search 进行网络搜索。
  • 我们将使用查询重写来优化网络搜索查询。

设置 TAVILY_API_KEY

检索器

让我们索引3篇博客文章。

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

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/",
]

docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]

text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=250, chunk_overlap=0
)
doc_splits = text_splitter.split_documents(docs_list)

# Add to vectorDB
vectorstore = Chroma.from_documents(
    documents=doc_splits,
    collection_name="rag-chroma",
    embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()

状态

我们将定义一个图。
我们的状态将是 dict 。
我们可以从任何图形节点 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]

节点和边

每个 node 将简单地修改 state 。
每个 edge 将选择接下来调用哪个 node 。
它将遵循上面显示的图表。
在这里插入图片描述

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

# 导入langchain相关模块
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.schema import Document
from langchain_community.tools.tavily_search import TavilySearchResults
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 = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, streaming=True)

    # 后处理函数
    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'")

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

    # 工具
    grade_tool_oai = convert_to_openai_tool(grade)

    # 绑定工具和强制调用的语言模型
    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 = []
    search = "No"  # 默认不进行网络搜索来补充检索
    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---")
            search = "Yes"  # 进行网络搜索
            continue

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

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

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

    返回:
        dict: 保存新的问题。
    """
    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 web_search(state):
    """
    使用Tavily进行网络搜索。

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

    返回:
        state (dict): 将网络搜索结果附加到文档中。
    """
    print("---WEB SEARCH---")
    state_dict = state["keys"]
    question = state_dict["question"]
    documents = state_dict["documents"]

    tool = TavilySearchResults()
    docs = tool.invoke({"query": question})
    web_results = "\n".join([d["content"] for d in docs])
    web_results = Document(page_content=web_results)
    documents.append(web_results)

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

### 边函数 ###

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"]
    search = state_dict["run_web_search"]

    if search == "Yes":
        # 所有文档已被过滤
        # 我们将重新生成一个新的查询
        print("---DECISION: TRANSFORM QUERY and RUN WEB SEARCH---")
        return "transform_query"
    else:
        # 我们有相关文档,所以生成回答
        print("---DECISION: GENERATE---")
        return "generate"

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("web_search", web_search)  # 网络搜索

# 构建图
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", "web_search")
workflow.add_edge("web_search", "generate")
workflow.add_edge("generate", END)

# 编译
app = workflow.compile()

# 运行
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")

# 对不在上下文中的问题进行修正
inputs = {"keys": {"question": "What is the approach taken in the AlphaCodium paper?"}}
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. LangChain:是一个用于构建语言模型应用的Python库,提供了文本分割、文档加载、向量存储、嵌入和检索等功能。
  2. Tavily Search:是一个网络搜索引擎,可以用于补充检索过程中的数据源。
  3. RecursiveCharacterTextSplitter:用于将长文本分割成更小的块,以便更好地处理和索引。
  4. Chroma:是一个向量数据库,可以存储和检索文档的嵌入表示。
  5. OpenAIEmbeddings:使用OpenAI的模型来生成文档的嵌入表示。
  6. StateGraph:是一个用于构建和执行状态图的类,状态图是一种用于控制流程的有向图。

总结:

本文介绍了一个使用LangGraph实现的系统,该系统通过文档检索、文档评估、问题转换和网络搜索等步骤,来生成针对特定问题的答案。系统的核心是一个状态图,它定义了各个节点和边,通过这些节点和边来控制整个检索和生成流程。代码中使用了多个库,包括langchainlangchain_communitylangchain_openai等,这些库为系统提供了文本分割、文档加载、向量存储、嵌入和检索等功能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聚类分析 #数据挖掘 #Python

聚类分析(Cluster Analysis)是一种无监督机器学习方法,主要用于数据挖掘和数据分析中,它的目标是将一组对象或观测值根据它们之间的相似性或相关性自动分组,形成不同的簇或类别。聚类分析并不预先知道每个观测值的具体…

cloudflare worker访问自己的网站显示521问题解决

写在前面:如果你的网站不是在80端口上运行的,开一下80端口可能就行了… 1.在cloudlare上添加域名 前文搭建了自己的DNS服务器(DNS服务器搭建),现在想通过自己的DNS服务器解析域名,需要四步: 添…

2Y0A21 GP2Y0A21YK0F 红外测距传感器 arduino使用教程

三根线 正极负极信号线 确认自己的三根线分别代表什么,我们的颜色可能不一样 附一张我买的传感器他们的说明图 正极 接 开发板5V 负极 接开发板GND 信号线 接A0 代码arduino ide上面写 // Infračerven senzor vzdlenosti Sharp 2Y0A21// připojen potře…

vscode-关闭ts与js语义校验

1.ts与js语义校验 TypeScript(TS)和JavaScript(JS)在语义校验方面有很大的不同。TypeScript是一种静态类型检查的编程语言,它是JavaScript的一个超集,为JavaScript添加了类型系统和其他一些特性。而JavaScr…

软件测试技术(一):软件测试流程

软件测试流程 软件测试流程如下: 测试计划测试设计测试执行 单元测试集成测试确认测试系统测试验收测试回归测试验证活动 测试计划 测试计划由测试负责人来编写,用于确定各个测试阶段的目标和策略。这个过程将输出测试计划,明确要完成的测…

有个网友问Webview2如何另存为mhtml

有个网友问Webview2如何另存为mhtml 。俺查了一下,Webview2没有直接的saveas函数。然后我查到 之后我就使用 webview2 capture 这2个关键字去查询,果然搜到了 一段代码 然后我把这段代码 改成成C#的, string data await webView21.CoreWebV…

这四个有意思的工具,很香

提醒英雄 提醒英雄应用是一款能够帮助用户彻底解决健忘症的应用程序。该应用创建的事项会完全同步到通知中心,并且持续保持在锁屏界面上,只要打开手机,用户就会看到之前设置的提醒事项。这种设计确保了用户在任何时候都能及时收到提醒&#…

[C#] opencvsharp对Mat数据进行序列化或者反序列化以及格式化输出

【简要介绍】 在OpenCVSharp中,FileStorage类用于将数据(包括OpenCV的Mat类型数据)序列化为XML或YAML格式的文件,以及从这些文件中反序列化数据。以下是关于FileStorage类用法的详细说明: 写入数据(序列化…

铠侠全面复产:NAND价格还会涨吗?

近期,日本经济新闻(Nikkei)报道指出,经历长达20个月的产能削减后,全球第四大三维NAND闪存制造商铠侠已全面恢复生产。这一转变不仅标志着铠侠再次全力投入到市场份额的争夺中,也可能预示着闪存市场价格即将…

深入探究RTOS的任务调度

阅读引言: 此文将会从一个工程文件, 一步一步的分析RTOS的任务调度实现, 这里选用FreeRTOS分析, 别的也差不多的, 可能在细节上有少许不一样。 目录 1, 常见嵌入式实时操作系统 2, 任务调度的…

数据库系统概述选择简答概念复习

目录 一、组成数据库的三要素 二、关系数据库特点 三、三级模式、二级映像 四、视图和审计提供的安全性 审计(Auditing) 视图(Views) 五、grant、revoke GRANT REVOKE 六、三种完整性 实体完整性 参照完整性 自定义完整性 七、事务的特性ACDI 原子性(Atomicity)…

基于卷积变分自编码器的心电信号异常检测

代码较为简单,运行代码如下: # Built-in libraries # import os import time import random import pandas as pd import numpy as np from tqdm import tqdm # ------------------------------- # Visualization libraries # import matplotlib.p…

179海关接口源码并实践:打造具备跨境报关功能的多平台商城

一、跨境电商的发展与挑战 随着全球化的快速发展,跨境电商成为了各国商家开拓市场的重要方式。然而,跨境电商在面临海关报关等复杂流程时,常常遇到各种挑战。为了解决这些问题,许多商家开始关注179海关接口源码的使用&#xff0c…

mkv文件怎么转成mp4?教你四种常见的转换方法!

mkv文件怎么转成mp4?大家在使用mkv文件的时候有没有遇到过下面这些缺点,首先是mkv的兼容性不行,这体验在它不方便分享上面,很有可能我们分享出去但是对方根本无法进行接受,这就导致我们需要进行额外的操作才能分享&…

qt登录和闹钟实现

qt实现登录 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 去掉头部this->setWindowFlag(Qt::FramelessWindowHint);// 去掉空白部分this->s…

汽车IVI中控开发入门及进阶(二十七):车载摄像头vehicle camera

前言: 在车载IVI、智能座舱系统中,有一个重要的应用场景就是视频。视频应用又可分为三种,一种是直接解码U盘、SD卡里面的视频文件进行播放,一种是手机投屏,就是把手机投屏软件已视频方式投屏到显示屏上显示,另外一种就是对视频采集设备(主要就是摄像头Camera)的视频源…

反激开关电源保险丝以及热敏电阻的选型

保险丝(2A/250V) 保险丝的选型及计算 1、保险丝的作用就是在电路出现故障造成过流甚至短路时能及时切断电路电源的联系。( 保护后 级电路,一旦出现故障,由于电流过大温度过高,保险丝熔断 ) 2、…

硫碳复合材料可用作固态电池正极材料 锂硫电池是重要下游

硫碳复合材料可用作固态电池正极材料 锂硫电池是重要下游 硫碳复合材料,是半固态电池、固态电池的正极材料,主要用于金属硫电池制造领域,在锂硫电池应用中研究热度最高。 锂硫电池,一种二次电池,以硫元素为正极&#x…

【多模态】39、HRVDA | 基于高分辨率输入的高效文档助手(CVPR2024)

论文:HRVDA: High-Resolution Visual Document Assistant 代码:暂无 出处:中国科学技术大学 | 腾讯优图 贡献点: 作者提出了高分辨率视觉文档助手 HRVDA,能直接处理高分辨率图像输入作者提出了内容过滤机制和指令过…

【Linux环境下Hadoop部署】— 报错“bash: myhadoop.sh: command not found“

项目场景: 执行 “myhadoop.sh stop” 命令。 问题描述 bash: myhadoop.sh: command not found 原因分析: 查看我们的系统配置,发现没有myhadoop.sh文件存放的路径。 解决方案: 1、执行 “sudo vim /etc/profile” 命令&#xff…