LangChain入门2 RAG详解

RAG概述

一个典型的RAG应用程序,它有两个主要组件:

  • 索引:从源中获取数据并对其进行索引的管道。这通常在脱机情况下发生。
  • 检索和生成:在运行时接受用户查询,并从索引中检索相关数据,然后将其传递给模型。

从原始数据到答案的完整序列如下所示:

索引

  • 加载:首先我们需要加载我们的数据。我们将为此使用DocumentLoaders。
  • 拆分:文本拆分器将大型文档拆分成更小的块。这对于索引数据和将数据传递给模型都很有用,因为大块更难搜索,也不适合模型的有限上下文窗口。
  • 信息存储:我们需要一个地方来存储和索引我们的拆分,以便以后可以搜索它们。这通常使用VectorStore和Embeddings模型来完成。

检索和生成

  • 检索:给定用户输入,使用Retriever从存储中检索相关拆分。
  • 生成:ChatModel/LLM使用包含问题和检索到的数据的提示生成答案。

代码实例

依赖加载

python 复制代码
from langchain_community.llms import Ollama
import bs4
from langchain import hub
from langchain_community.document_loaders import WebBaseLoader
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_community.embeddings import OllamaEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
python 复制代码
#实例化大模型
llm = Ollama(model="llama2")
#添加向量化
embeddings = OllamaEmbeddings()
python 复制代码
# 加载数据
loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("post-content", "post-title", "post-header")
        )
    ),
)
docs = loader.load()
python 复制代码
#我们这里查看具体的下载内容
print(docs)

查看具体的下载内容

加载数据的拆分和灌库

python 复制代码
#添加数据的拆分 每1000个为一组并重叠200个字符
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
#拆分下载的数据
splits = text_splitter.split_documents(docs)
#灌入向量数据库
vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)

构建检索生成

python 复制代码
retriever = vectorstore.as_retriever()
#下载预制的提示词
prompt = hub.pull("rlm/rag-prompt")

#调整文本
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

加载数据链

python 复制代码
#RunnablePassthrough是Langchain库中的一个类,它允许您传递未更改的输入或带有附加键的输入。
#它可以与RunnableParallel一起使用,将数据传递到映射中的新键。它还可以用于通过assign()方法向链状态添加值
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()} #检索加强
    | prompt #提示词
    | llm #大模型
    | StrOutputParser() #输出结果样式定义
)
python 复制代码
#进行对话
rag_chain.invoke("What is Task Decomposition?")

返回内容

详细说明

首先加载博客文章的内容。我们可以为此使用DocumentLoaders,它们是从源加载数据并返回文档列表的对象。Document是一个具有一些page_content(str)和元数据(dict)的对象。

在这种情况下,我们将使用WebBaseLoader,它使用urllib从web URL加载HTML,并使用BeautifulSoup将其解析为文本。我们可以通过bs_kwargs将参数传递给BeautifulSoup解析器来自定义HTML->文本解析(请参阅Beautiful Soup文档)。在这种情况下,只有类为"post-content"、"posttitle"或"post-header"的HTML标记是相关的,所以我们将删除所有其他标记。

数据加载

python 复制代码
import bs4
from langchain_community.document_loaders import WebBaseLoader

# Only keep post title, headers, and content from the full HTML.
bs4_strainer = bs4.SoupStrainer(class_=("post-title", "post-header", "post-content"))
loader = WebBaseLoader(
    web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
    bs_kwargs={"parse_only": bs4_strainer},
)
docs = loader.load()
python 复制代码
print(docs)
python 复制代码
print(len(docs[0].page_content))
#43131
print(docs[0].page_content[:500])
"""
      LLM Powered Autonomous Agents
    
Date: June 23, 2023  |  Estimated Reading Time: 31 min  |  Author: Lilian Weng


Building agents with LLM (large language model) as its core controller is a cool concept. Several proof-of-concepts demos, such as AutoGPT, GPT-Engineer and BabyAGI, serve as inspiring examples. The potentiality of LLM extends beyond generating well-written copies, stories, essays and programs; it can be framed as a powerful general problem solver.
Agent System Overview#
In
"""

文本拆分

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200, add_start_index=True
)
all_splits = text_splitter.split_documents(docs)

RecursiveCharacterTextSplitter 通过递归地查看字符来拆分文本。 递归地尝试按不同的字符进行拆分,以找到一个有效的字符。 创建一个新的TextSplitter。

python 复制代码
print(len(all_splits))
#66
print(len(all_splits[0].page_content))
#969
print(all_splits[10].metadata)
#{'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/',
#'start_index': 7056}

索引:存储

现在我们需要对66个文本块进行索引,以便在运行时对它们进行搜索。最常见的方法是嵌入每个文档分割的内容,并将这些嵌入插入到矢量数据库(或矢量存储)中。当我们想搜索分割时,我们采用文本搜索查询,嵌入它,并执行某种"相似性"搜索,以识别嵌入与查询嵌入最相似的存储分割。最简单的相似性度量是余弦相似性------我们测量每对嵌入(它们是高维向量)之间的角度的余弦。

我们可以使用Chroma矢量存储和OpenAIEmbeddings模型将所有文档分割嵌入并存储在一个命令中。

python 复制代码
from langchain_chroma import Chroma
from langchain_community.embeddings import OllamaEmbeddings
#添加向量化
embeddings = OllamaEmbeddings()
#灌库
vectorstore = Chroma.from_documents(documents=all_splits, embedding=embeddings)

检索与生成:检索

现在让我们来编写实际的应用程序逻辑。我们想要创建一个简单的应用程序,该应用程序接受用户问题,搜索与该问题相关的文档,将检索到的文档和初始问题传递给模型,并返回答案。

首先,我们需要定义搜索文档的逻辑。LangChain定义了一个Retriever接口,该接口封装了一个索引,该索引可以在给定字符串查询的情况下返回相关文档。

最常见的Retriever类型是VectorStoreRetriever,它使用向量存储的相似性搜索功能来促进检索。使用VectorStore.as_Retriever(),任何VectorStore都可以很容易地转换为Retriever:

python 复制代码
"""
矢量存储是用于有效存储和查询矢量嵌入的数据结构,矢量嵌入是数据点的高维数值表示。向量存储通常用于机器学习应用程序中的任务,如相似性搜索、异常检测和聚类。
在Langchain的上下文中,VectorStore类是用于处理向量存储的接口。它提供了添加和查询矢量的方法,以及执行各种操作的方法,例如计算矢量之间的距离和根据某些标准过滤矢量。
"""
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 6})
retrieved_docs = retriever.invoke("What are the approaches to Task Decomposition?")
print(len(retrieved_docs))
#6
print(retrieved_docs[0].page_content)

检索与生成

python 复制代码
from langchain import hub
#加载的提示词
prompt = hub.pull("rlm/rag-prompt")

example_messages = prompt.invoke(
    {"context": "filler context", "question": "filler question"}
).to_messages()
print(example_messages)

返回

[HumanMessage(content="You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\nQuestion: filler question \nContext: filler context \nAnswer:")]

python 复制代码
print(example_messages[0].content)

You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.

Question: filler question

Context: filler context

LCEL

我们将使用LCEL Runnable协议来定义链,使我们能够以透明的方式将组件和函数管道连接在一起,在LangSmith中自动跟踪我们的链,从而获得流式、异步和批量调用

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough


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


rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)
python 复制代码
for chunk in rag_chain.stream("What is Task Decomposition?"):
    print(chunk, end="", flush=True)

模型选择

python 复制代码
#构建提示词模版
from langchain_core.prompts import PromptTemplate
#提示词
template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
Use three sentences maximum and keep the answer as concise as possible.
Always say "thanks for asking!" at the end of the answer.

{context}

Question: {question}

Helpful Answer:"""
#模版加载
custom_rag_prompt = PromptTemplate.from_template(template)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | custom_rag_prompt
    | llm
    | StrOutputParser()
)
#结果输出
rag_chain.invoke("What is Task Decomposition?")

以上是整体使用LangChain 构建RAG 的总体流程。

感谢阅读。

相关推荐
Hello.Reader26 分钟前
StarRocks实时分析数据库的基础与应用
大数据·数据库
执键行天涯28 分钟前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
yanglamei196238 分钟前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
工作中的程序员1 小时前
ES 索引或索引模板
大数据·数据库·elasticsearch
严格格1 小时前
三范式,面试重点
数据库·面试·职场和发展
营赢盈英1 小时前
TypeError: expected string or buffer - Langchain, OpenAI Embeddings
langchain·azure·embeddings·openai api·rag
微刻时光1 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
单字叶2 小时前
MySQL数据库
数据库·mysql
mqiqe2 小时前
PostgreSQL 基础操作
数据库·postgresql·oracle
just-julie2 小时前
MySQL面试题——第一篇
数据库·mysql