您是否有兴趣与自己的文档聊天,无论是文本文件、PDF 还是网站?LangChain 使您可以轻松地使用文档进行问答。在这篇博文中,我们将探讨四种不同的问答方法,以及您可以为案例考虑的各种选项。

在我们实现问答之前,您可能想知道:什么是 LangChain?简单来说,LangChain 是一种快速与语言模型交互和构建应用程序的开源框架,方便集成许多主流的 LLM 和组件扩展。

现在让我们开始使用 LangChain 实现文档问答功能。

设置 OpenAI API

在 OpenAI 上创建一个帐户并创建一个API密钥:https://platform.openai.com/account 。您需要创建 Key 才能使用 OpenAI API。由于它太火爆了,因此使用起来不太理想。

1
2
import os 
os.environ["OPENAI_API_KEY"] = "COPY AND PASTE YOUR API KEY HERE"

当然还有一个对我们更友好的选择是 Azure OpenAI:https://azure.microsoft.com/zh-cn/products/cognitive-services/openai-service/ ,虽然也需要开通帐户付费,但它有很好的稳定性保障,还可以结合 Azure Functions 快速构建网络服务增加我们的应用能力。

1
2
3
4
os.environ["OPENAI_API_TYPE"] = "azure"
os.environ["OPENAI_API_BASE"] = "https://your-resource-name.openai.azure.com"
os.environ["OPENAI_API_VERSION"] = "RELEASED VERSION BASE ON YOUR MODEL"
os.environ["OPENAI_API_KEY"] = "COPY AND PASTE YOUR Azure OpenAI API KEY HERE"

或者,您可以使用 HuggingFace Hub 或其他地方的模型。

加载文档

LangChain 支持许多文档加载器,如 Notion,YouTube 和 Figma。在此示例中,我想与我的 PDF 文件聊天。因此,我使用PyPDFLoader 来加载我的文件。

1
2
3
4
# load document
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("materials/example.pdf")
documents = loader.load()

方法一 load_qa_chain

load_qa_chain 提供用于回答问题的最通用界面。您可以输入问题,针对文档中的任意文本进行 QA。

image

它还允许您对一组文档进行 QA:

1
2
3
4
5
### For multiple documents 
loaders = [....]
documents = []
for loader in loaders:
    documents.extend(loader.load())

但是,如果我的文档超长以至于超过了 Token 限制怎么办?

有两种方法可以修复它:

解决方案1:Chain Type

默认 chain_type=“stuff” 会使用文档中的所有文本。它实际上不适用于我们的示例,因为它超出了 Token 限制并导致 rate-limiting 错误。这就是为什么在这个例子中,我们不得不使用其他 Chain Type,例如 “map_reduce”。其他 Chain Type 有哪些?

  • map_reduce: 它将文本分成批(例如,您可以在 llm=OpenAI(batch_size=5) 中定义批大小),将每个批次的问题分别提供给 LLM,并根据每批的答案提出最终答案。
  • refine: 它将文本分成几批,将第一批提供给 LLM,并将答案和第二批提供给 LLM。它通过遍历所有批次来细化答案。
  • map-rerank: 它将文本分成几批,将每批提供给 LLM,返回它回答问题的完整程度的分数,并根据每批的高分答案提出最终答案。

解决方案2: RetrievalQA

使用所有文本的一个问题是,它可能非常昂贵,因为您将所有文本提供给OpenAI API,并且API按 Token 数量收费。更好的解决方案是先检索相关文本块,然后仅在语言模型中使用相关文本块。接下来,我将详细介绍 RetrievalQA。

方法二 RetrievalQA

RetrievalQA Chain 实际上在 load_qa_chain 下使用。我们检索最相关的文本块,并将其提供给语言模型。

以下是它的工作原理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from langchain.chains import RetrievalQA
from langchain.indexes import VectorstoreIndexCreator
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# split the documents into chunks
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
# select which embeddings we want to use
embeddings = OpenAIEmbeddings()
# create the vectorestore to use as the index
db = Chroma.from_documents(texts, embeddings)
# expose this index in a retriever interface
retriever = db.as_retriever(search_type="similarity", search_kwargs={"k":2})
# create a chain to answer questions 
qa = RetrievalQA.from_chain_type(
    llm=OpenAI(), chain_type="stuff", retriever=retriever, return_source_documents=True)
query = "How many AI publications in 2021?"
result = qa({"query": query})

在结果中,我们可以看到答案和两个源文档,因为我们将 k 定义为 2,这意味着我们只对获取两个相关的文本块感兴趣。 image

可选

在此过程中,有多种选项供您选择:

  • embeddings: 在示例中,我们使用了 OpenAI 嵌入。但是还有许多其他嵌入选项,例如来自特定模型的 Cohere Embeddings 和 HuggingFace Embeddings。
  • TextSplitter: 我们在文本按单个字符拆分的示例中使用了字符文本拆分器。您还可以使用本文档中提到的不同文本拆分器和不同标记。
  • VectorStore: 我们使用 Chroma 作为我们的 Vector 数据库,我们在其中存储我们的嵌入的 Vector。另外受欢迎的选择是 FAISS,Milvus 和 Pinecone。
  • Retrievers: 我们使用了 VectoreStoreRetriver,它由 VectorStore 支持。要检索文本,您可以选择两种 search_type 搜索类型:“similarity” 或 “mmr”。search_type=“similarity” 在检索器对象中使用相似性搜索,在其中选择与问题向量最相似的文本块向量。search_type=“mmr” 使用最大边际相关性搜索,其中优化相似性以查询所选文档之间的多样性。
  • Chain Type: 与方法1相同。您还可以将链类型定义为四个选项之一: “stuff”, “map reduce”, “refine”, “map_rerank”。

方法三 VectorstoreIndexCreator

VectorstoreIndexCreator 是上述功能的包装器。它只是公开了一个更高级别的接口,让您从三行代码开始: image

当然,您也可以在此包装器中指定不同的选项: image

方法四 ConversationalRetrievalChain

ConversationalRetrievalChain 与方法二 RetrievalQA 非常相似。它添加了一个附加参数 chat_history 来传入聊天记录,可用于后续问题。

ConversationalRetrievalChain = conversation memory + RetrievalQAChain

如果希望语言模型具有上一个对话的记忆,请使用此方法。在下面的例子中,我询问了AI出版物的数量,并得到了500,000的结果。然后我要求 LLM 将这个数字除以2。由于它有所有的聊天记录,模型知道我所指的数字是 500,000,返回的结果是 250,000。 image

小结

现在您知道了在 LangChain 中使用 LLM 进行问答的四种方法。总之,load_qa_chain 使用所有文本并接受多个文档。检索 QA 在后台使用 load_qa_chain,但首先检索相关的文本块。VectorstoreIndexCreator 与 RetrievalQA 相同,具有更高级别的接口。当您想要将聊天记录传递给模型时,ConversationalRetrievalChain 非常有用。