在向类似 ChatGPT 这样的 LLM (Large language Model) 提问时经常遇到类似 “幻觉” 的问题或者需要让 LLM 访问私域的数据库,此时可以给 LLM 外挂一个数据库,通过从该数据库检索相关知识提供给 LLM 作为 prompt 以及用户的问题,可以得到更加准确的专业性更强的答案。这样的技术就是检索增强生成 (Retrieval Augmented Generation)。

检索增强生成

上面提到的检索增强生成的原理可以总结为下面这张图:

RAG 的原理

它从用户的问题开始。例如 “How do I do……?” 接着从知识库中检索与问题相关的知识,将相关知识与问题作为 prompt 同时交给 LLM 使得 LLM 能够基于私域知识库的内容生成回答。

为什么我们不能直接将文件 (私域知识库可能的形式) 丢给 LLM 而是要检索?
因为它无法很好地处理如此海量的信息。LLM 的 tooken 都是有长度限制的,包含提问和回答。例如,如果 tooken 是 100,提的问题有 50 个字,则回答就只剩下了 50 字的空间。因此提出的问题越长得到的回答质量就会越差。

为 LLM 提供私域知识回答问题

想要实现上述功能,相当于做这样一件事:

  • 告诉 LLM 下面的回答要全部基于给定的知识库;
  • 提供私域知识库;
  • 提出问题得到回答。

因此我们要实现利用私域知识库对 LLM 进行 “教育”。

通过系统 prompt 给 LLM 下达特定指令

给出像下面这样的系统提示给 LLM:

You are a Knowledge Bot. You will be given the extracted parts of a knowledge base (labeled with DOCUMENT) and a question. Answer the question using information from the knowledge base.

这相当于是告诉它只能基于给定的文件 (档) 给出回答。

为 LLM 提供知识来源

我们可以通过一些结构和格式来帮助它。

1
2
3
4
5
6
7
8
9
10
11
------------ DOCUMENT 1 -------------

This document describes the blah blah blah...

------------ DOCUMENT 2 -------------

This document is another example of using x, y and z...

------------ DOCUMENT 3 -------------

[more documents here...]

一旦我们格式化了文档,我们只需将其作为普通聊天消息发送给 LLM。再结合我们提出的问题,AI 往往就能提供比较准确优质的回答。

综合私域知识库提出问题

接下来就是最后的提问环节,我们将问题和私域知识库的内容一同发给 LLM 预期得到回答。以下是使用 OpenAI ChatCompletion API 在 Python 代码中的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
openai_response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "system",
"content": get_system_prompt(), # the system prompt as per above
},
{
"role": "system",
"content": get_sources_prompt(), # the formatted documents as per above
},
{
"role": "user",
"content": user_question, # the question we want to answer
},
],
)

如此,一个自定义系统提示,两条消息,我们就可以得到特定于上下文的答案!上述过程可以总结为下图:

提问原理

检索 —— 从数据库中得到相关信息

从本质上讲,检索是一种搜索操作 —— 我们希望根据用户的输入查找最相关的信息。就像搜索一样,有两个主要部分:

  • 索引:将您的知识库变成可以搜索 / 查询的内容;
  • 查询:从搜索词中提取最相关的知识。

可以总结为下图:

检索

事实上,任何搜索过程都可以作用为 “检索”。而当今大多数 RAG 系统都依赖于语义搜索,它使用人工智能技术的另一个核心部分:Embedding (嵌入)。

什么是 Embedding

如果你问一个人如何将单词转化为意义,他们可能会摸索并说出一些模糊且自我指涉的内容,例如 “因为我知道它们的意思”。在我们大脑深处的某个地方,有一个复杂的结构,它知道 “child” 和 “kid” 基本上是相同的,“红色” 和 “绿色” 都是颜色,“高兴”、“快乐” 和 “兴高采烈” 代表着相同的情绪,但程度不同。我们无法解释它是如何工作的,我们只是知道它。

语言模型对语言有类似的复杂理解,只不过,因为它们是计算机,所以它不在它们的大脑中,而是由数字组成。在大语言模型的世界中,任何人类语言都可以表示为数字向量。这个向量就是一个 Embedding。

LLM 技术的一个关键部分是从人类文字语言到人工智能数字语言的翻译器。我们将这个翻译器称为 “Embedding Machine”,尽管在幕后它只是一个 API 调用。人类语言输入,人工智能数字输出。

Embedding Machine

这些数字意味着什么?没有人知道!它们只对人工智能 “有意义”。但是,我们所知道的是,相似的单词最终会得到相似的向量。

我们可以在坐标轴上抽象地表示这种 Embedding 在向量空间的相关性。

Embedding in axes

在这个假设的语言空间中,两点彼此越接近,它们就越相似。事实上,我们并无法在二维平面上表示整个人类语言,在实际运用中,通常采用的是更高维度的空间,但是具体原理是相似的。OpenAI 的模型采用的 1536 维的语言空间。

建立私域知识库索引

要从私域知识库中找到相关性最强的知识,我们首先需要对知识库进行一系列复杂的处理。首先我们要对知识库进行分片,将其内部的繁多复杂的知识分类整理成一个个 “知识片”。

Splitting Machine

这其中包括了两个过程:

  • 加载:从通常存储的位置获取知识库的内容;
  • 分割:将知识分割成适合嵌入搜索的片段大小的块。

加载器是一个基础设施,它可以访问我的文档,找出可用的页面,然后拉取每个页面。加载程序完成后,它将输出单独的文档 (例如为网站的每一页生成一个单独的文档)。

Loader

这其中包含了许多复杂的操作,本文尽数略过。

接下来便是将这些文档分类切割成许多不同的部分,通常是按照标题 (如果有的话)。

Spliiter

这其中也包含了许多复杂的操作,本文尽数略过。

一旦我们有了文档片段,我们就将它们保存到我们的向量数据库中,如上所述,我们终于完成了!下面是索引知识库的完整图片:

Knowledge base Indexing

使用 Embedding 检索相关性最强的私域知识

完成知识库索引的建立之后我们就可以得到知识库索引对应的向量空间:

Knowledge space

之后我们将问题向量化:

Embedding of question

再将其绘制在知识库索引对应的向量空间中,得到相关性最强的知识:

Find the knowledge

以下是整个过程:

Whole procedure

查询本身涉及一些半复杂的数学 —— 通常使用称为余弦距离的东西,尽管还有其他计算方法。数学是您可以进入的整个空间,但超出了本文的范围,并且从实际角度来看,很大程度上可以转移到图书馆或数据库中。

回顾

我们可以用以下这张图概括整个 RAG 的过程:

RAG

首先,我们索引我们的知识库。我们获取知识并使用加载器将其转换为单独的文档,然后使用拆分器将其转换为一口大小的块或片段。一旦我们有了这些,我们就把它们传递给嵌入机,嵌入机将它们转换成可用于语义搜索的向量。我们将这些嵌入及其文本片段保存在我们的矢量数据库中。

接下来是检索。它从问题开始,然后通过相同的嵌入机发送并传递到我们的矢量数据库以确定最接近的匹配片段,我们将用它来回答问题。

最后,增强答案生成。我们获取知识片段,将它们与自定义系统提示和我们的问题一起格式化,最后得到我们上下文特定的答案。

注:本文参考
How do domain-specific chatbots work? An Overview of Retrieval Augmented Generation (RAG)
【译】私域聊天机器人如何工作?检索增强的内容生成(RAG)概述