二、检索链

为了能够正确回答原始问题 (“how can langsmith help with testing?”), 我们需要向 LLM 提供额外的上下文信息。这里,我们可以通过检索来实现这一点。当有太多数据无法直接传递给 LLM 时,检索就非常有用了。您可以使用检索器来获取最相关的内容并将其传递进去。

在这个过程中,我们将从检索器中查找相关的文档,然后将其传递到提示词Prompt中。检索器的背后可以是任何东西 - SQL数据表、互联网等端。下面,我们将使用一个向量存储组件并将其用作检索器。

首先,我们需要加载想要索引的数据。为了实现这一点,我们将使用 WebBaseLoader。在使用它之前,先要安装 BeautifulSoup:

1
pip install beautifulsoup4

然后,我们可以引入WebBaseLoader、实例化并加载文档。

1
2
3
4
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")

docs = loader.load()

接下来,我们需要将这些数据索引到一个向量存储中。为了完成这个步骤,需要引入几个组件,主要是嵌入模型和向量存储。

对于嵌入模型,我们需要使用跟之前的LLM对应的嵌入模型,比如OpenAI,我们可以加载如下嵌入模型。

1
2
3
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()

如果我们使用的是Ollama,则需要用如下代码加载嵌入模型。

1
2
3
from langchain_community.embeddings import OllamaEmbeddings

embeddings = OllamaEmbeddings()

现在,我们就可以使用这个嵌入模型将文档导入向量存储中。为了简单起见,我们将使用一个轻量的本地向量存储:FAISS。

首先,我们需要安装所需的包:

1
pip install faiss-cpu

然后使用如下代码建立索引:

1
2
3
4
5
6
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)

运行上述代码之后,即可将数据索引到了向量存储中。接下来,我们将创建一个检索链。这个链将接收一个传入的问题,查找相关文档,然后将这些文档以及原始问题一并传递给 LLM 并让它回答原始问题。

首先,让我们创建一个链,它接收一个问题以及检索到的文档,并生成一个答案。

1
2
3
4
5
6
7
8
9
10
11
from langchain.chains.combine_documents import create_stuff_documents_chain

prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:

<context>
{context}
</context>

Question: {input}""")

document_chain = create_stuff_documents_chain(llm, prompt)

创建完之后,即可运行该链,并直接将文档传给它:

1
2
3
4
5
6
from langchain_core.documents import Document

document_chain.invoke({
"input": "how can langsmith help with testing?",
"context": [Document(page_content="langsmith can let you visualize test results")]
})

但大部分时候,我们想要的是先使用刚刚创建的检索器的文档,而不是直接传入一个文档。这样,对于给定的问题,我们才可以使用检索器动态选择最相关的文档并传递进去。

1
2
3
4
from langchain.chains import create_retrieval_chain

retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)

运行这条链,它将返回一个字典,LLM返回的内容则存在这个字典的”answer”这个key中。

1
2
3
4
response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])

# LangSmith offers several features that can help with testing:...

现在,这个答复应该更准确了。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain

from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings

from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS

from langchain_text_splitters import RecursiveCharacterTextSplitter


llm = ChatOpenAI()

loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")
docs = loader.load()

embeddings = OpenAIEmbeddings()

text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)

prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:

<context>
{context}
</context>

Question: {input}""")

document_chain = create_stuff_documents_chain(llm, prompt)

# document_chain.invoke({
# "input": "how can langsmith help with testing?",
# "context": [Document(page_content="langsmith can let you visualize test results")]
# })

retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)

response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])