RAG概述
一个典型的RAG应用程序,它有两个主要组件:
- 索引:从源中获取数据并对其进行索引的管道。这通常在脱机情况下发生。
- 检索和生成:在运行时接受用户查询,并从索引中检索相关数据,然后将其传递给模型。
从原始数据到答案的完整序列如下所示:
索引
- 加载:首先我们需要加载我们的数据。我们将为此使用DocumentLoaders。
- 拆分:文本拆分器将大型文档拆分成更小的块。这对于索引数据和将数据传递给模型都很有用,因为大块更难搜索,也不适合模型的有限上下文窗口。
- 信息存储:我们需要一个地方来存储和索引我们的拆分,以便以后可以搜索它们。这通常使用VectorStore和Embeddings模型来完成。
检索和生成
- 检索:给定用户输入,使用Retriever从存储中检索相关拆分。
- 生成:ChatModel/LLM使用包含问题和检索到的数据的提示生成答案。
代码实例
依赖加载
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
#实例化大模型
llm = Ollama(model="llama2")
#添加向量化
embeddings = OllamaEmbeddings()
# 加载数据
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()
#我们这里查看具体的下载内容
print(docs)
查看具体的下载内容
加载数据的拆分和灌库
#添加数据的拆分 每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)
构建检索生成
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)
加载数据链
#RunnablePassthrough是Langchain库中的一个类,它允许您传递未更改的输入或带有附加键的输入。
#它可以与RunnableParallel一起使用,将数据传递到映射中的新键。它还可以用于通过assign()方法向链状态添加值
rag_chain = ({"context": retriever | format_docs, "question": RunnablePassthrough()} #检索加强| prompt #提示词| llm #大模型| StrOutputParser() #输出结果样式定义
)
#进行对话
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标记是相关的,所以我们将删除所有其他标记。
数据加载
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()
print(docs)
print(len(docs[0].page_content))
#43131
print(docs[0].page_content[:500])
"""LLM Powered Autonomous AgentsDate: June 23, 2023 | Estimated Reading Time: 31 min | Author: Lilian WengBuilding 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
"""
文本拆分
from langchain_text_splitters import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200, add_start_index=True
)
all_splits = text_splitter.split_documents(docs)
RecursiveCharacterTextSplitter 通过递归地查看字符来拆分文本。 递归地尝试按不同的字符进行拆分,以找到一个有效的字符。 创建一个新的TextSplitter。
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模型将所有文档分割嵌入并存储在一个命令中。
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:
"""
矢量存储是用于有效存储和查询矢量嵌入的数据结构,矢量嵌入是数据点的高维数值表示。向量存储通常用于机器学习应用程序中的任务,如相似性搜索、异常检测和聚类。
在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)
检索与生成
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:”)]
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中自动跟踪我们的链,从而获得流式、异步和批量调用
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthroughdef 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()
)
for chunk in rag_chain.stream("What is Task Decomposition?"):print(chunk, end="", flush=True)
模型选择
#构建提示词模版
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 的总体流程。
感谢阅读。