为什么要使用RAG?
RAG和LLM之间存在着互补关系。RAG可以被视为一种扩展了功能的LLM,它通过引入额外的检索步骤,使得LLM能够有效利用外部知识。这两种技术结合使用,可以在许多复杂的自然语言处理任务中取得更好的效果,为开发更强大的NLP系统提供了新的可能性。
检索: 当接收到用户查询时,使用检索索引找到最相关的文档。具体来讲,对用户输入的查询信息Embedding转化为向量,再到向量数据库中检索其他匹配的上下文信息。通过这种相似性搜索,可以找到向量数据库中最匹配的相关数据。
增强: 将向量数据库中检索到的信息与用户查询信息放到我们自定义的提示模板中。
生成: 最后将上面经过检索以及增强后的提示内容输入到LLM中,LLM根据上述信息生成最终结果。
加载:加载指定的数据,不同的文件可以通过不同的文档加载器完成。
拆分:通过 文本拆分器将内容拆分成小块内容。这对于索引数据和将数据传递到模型都很有用,因为大块内容更难搜索,并且不适合模型的有限上下文窗口。
嵌入:利用Embedding技术可以将高维度的数据(例如文字、图片、 音频)映射到低维度空间,即把图片、音频和文字最终转化为向量来表示。其中,向量是一组数值,可以表示一个点在多维空间中的位置。
存储:需要一个向量数据库用来存储和索引分割后的向量,便于日后快速的检索数据。
在这一部分,我们将展示如何利用Python 结合 OpenAI 的大语言模型、Weaviate的向量数据库以及 OpenAI 的嵌入模型来实现一个检索增强生成(RAG)流程。在这个过程中,我们将使用 LangChain 来进行整体编排。
iii. 对分块内容进行嵌入,并存储这些块
首先,加载指定的数据。数据类型支持文本、PDF、Word、Excel、CSV、HTML等不同的文件。下面例子中使用TextLoader来加载文本。针对不同文件类型可以使用不同的加载器,如CSVLoader加载CSV文件、WebBaseLoader通过URL方式加载网页数据,而PDF也有许多加载方式如PDFMinerLoader是其中之一。
import requests
from langchain.document_loaders import TextLoader
url ="https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs/modules/state_of_the_union.txt"
res = requests.get(url)
withopen("state_of_the_union.txt","w")as f:
f.write(res.text)
loader = TextLoader('./state_of_the_union.txt')
documents = loader.load()
其次,需要对文档进行分块。分块(chunking)是将大块文本分解成小段的过程。分块可以帮助我们优化从向量数据库被召回的内容的准确性。LangChain 也提供了许多文本分割工具,对于这个的示例,可以使用 CharacterTextSplitter来进行分割。设置片段大小 chunk_size 为 500,并且设置重叠token数量 chunk_overlap 为 50,以确保文本块之间的连贯性。
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = text_splitter.split_documents(documents)
最后,通过Embedding技术将其转为向量并存储这些文本块 。为了实现让LLM理解文件中的内容,需要为分块后的数据Embedding,并将它们存储起来。有多种方式可以Embedding,在本例中使用 OpenAI 的嵌入模型;最后使用Weaviate 向量数据库将向量保存。在LangChain中通过执行 .from_documents() 操作,就可以自动将这些块向量填充进向量数据库中。
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Weaviate
import weaviate
from weaviate.embedded import EmbeddedOptions
client = weaviate.Client(
embedded_options = EmbeddedOptions()
)
vectorstore = Weaviate.from_documents(
client = client,
documents = chunks,
embedding = OpenAIEmbeddings(),
by_text =False
)
检索:在前面已经将文件转为向量数据并写入到向量数据库中,接下来就可以将它设定为检索组件。这个组件能够根据用户查询与已嵌入的文本块之间的语义相似度,来检索出额外的上下文信息。
retriever = vectorstore.as_retriever()
增强:准备一个提示模板,通过预设的提示信息,再加上检索后的上下文信息来增强原始的提示。
from langchain.prompts import ChatPromptTemplate
template ="""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: {question}
Context: {context}
Answer:
"""
prompt = ChatPromptTemplate.from_template(template)
print(prompt)
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
rag_chain =(
{"context": retriever,"question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
query ="What did the president say about Justice Breyer"
rag_chain.invoke(query)
"总统还提到,他提名了法官凯坦吉·布朗·杰克逊来接替布雷耶法官,以延续后者的卓越遗产。"
总结
确保 LLM 可以回答最新,最准确的内容。并且用户可以访问模型内容的来源,确保可以检查其声明的准确性并最终可信。
通过将 LLM建立在一组外部的、可验证的事实数据之上,该模型将信息提取到其参数中的机会更少。这减少了 LLM 泄露敏感数据或“幻觉”不正确或误导性信息的机会。