RAG 案例

下一个例子,我们将构建一个检索增强生成(RAG)链,以便在回答问题时添加一些上下文。

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
# 在运行前,需要安装以下包:
# pip install langchain docarray tiktoken

from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai import OpenAIEmbeddings

vectorstore = DocArrayInMemorySearch.from_texts(
["harrison worked at kensho", "bears like to eat honey"],
embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()

setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser

chain.invoke("where did harrison work?")

在上例中,是这样组合这个链的:

1
chain = setup_and_retrieval | prompt | model | output_parser

下面,解释一下这种用法,首先我们来看看这个提示词模版,它有两个参数 context、question 需要被替换。在构建这个提示词模版之前,我们需要先检索相关文档,把他们作为上下文包含进来。

作为测试案例,我们这里使用了一个可提供查询检索的内存存储工具作为检索器,这个检索器同时也是一个可运行的组件,可以与其他组件链接运行,也可以单独运行:

1
2
retriever.invoke("where did harrison work?")
# > [Document(page_content='harrison worked at kensho'), Document(page_content='bears like to eat honey')]

然后,我们使用 RunnableParallel 来对 prompt 模版中的参数进行组合,该对象将作为 prompt 的输入参数,它接收一个文档检索器来进行文档搜索,以及一个 RunnablePassthrough 来传递用户的原始问题。

1
2
3
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)

最后,完整的链是这样的:

1
2
3
chain = setup_and_retrieval | prompt | model | output_parser
# 这里也可以简写成这样
chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | model | output_parser

整体流程如下:

  1. 首先,创建一个 RunnableParallel 对象,它接收一个包含两个条目的字典。条目一 context 包含检索器检索到的文档结果。条目 二 question 包含用户的原始问题。为了传递原始问题(在后续invoke传入),需要使用 RunnablePassthrough 来复制该条目。
  2. 将上一步的字典传递给 prompt 组件。这样,它即可将用户输入(即 question)以及检索到的文档(即 context)用于构建提示词,并输出 PromptValue。
  3. model 组件接收生成的提示词,并将其传递给 OpenAI LLM 模型进行评估推理。然后生成一个 ChatMessage 对象输出。
  4. 最后,output_parser 组件接收 ChatMessage,将其转换成 Python 字符串,并从 后续的 invoke 方法调用返回。