ITPub博客

首页 > 数据库 > 数据库开发技术 > 附代码|让检索增强生成(RAG)更快

附代码|让检索增强生成(RAG)更快

原创 数据库开发技术 作者:HelloTech技术派 时间:2024-02-14 22:00:00 0 删除 编辑

概述

大型语言模型 (LLMs) 是个很神奇的工具,但是一旦我们需要最新数据信息时,它们就毫无用处。其原因是 LLMs 的学习策略为所有“参数知识”。

“参数知识”是指在 LLM 训练阶段学习的信息。在训练过程中,LLM 学会将训练数据集中的信息编码到其内部模型参数中,即模型的参数化知识。

为了添加新的参数知识,我们必须微调 LLM 训练,无论是预训练(据报道GPT-4 的成本为1亿美元)还是微调,都是昂贵且缓慢的。昂贵和缓慢不是我们需要保持 LLM 知识更新时想要的特性。

检索增强生成(RAG)已成为我们解决这个问题的方法。不是通过微调将知识融入 LLM 中,而是将外部“知识库”插入到 LLM。而且这个知识库是可扩展的,我们可以像管理传统数据库一样管理它,而且它可能比微调更可靠。

RAG 的方法

检索增强生成 (RAG) 允许我们 LLMs 通过模型的“源知识”更新知识。源知识是指 LLM 通过输入提示输入的任何信息。

使用 RAG 和源知识,我们从外部数据源检索相关信息,用这些额外的源知识来增强我们的提示,并将该信息输入到 LLM。这种方法很有说服力,因为它使我们能够以最低的成本保持 LLM 知识的准确性和最新性。

以上描述了 “Naive RAG”。Naive RAG 是最常见的实现。它简单、高效、有效。

但是,还有其他方法可以进行 RAG。另一种流行的方法是使用能够访问 RAG 工具的 Agent。

从本质上讲,Agent 是一个 LLM 附加的逻辑,允许它对多个生成步骤进行推理,决定使用各种工具,并指定如何使用这些工具。

这种方法允许 Agent 回答更复杂的查询。它现在不需要在一个步骤中产生一个答案,而是可以制定和执行一个计划来构建最佳答案。

RAG 是让 Agent 访问外部知识的最常用工具之一。与 Naive RAG 方法相比,这种方法的优势主要源于 Agent 更高级的推理能力。它可以决定何时使用 RAG 以及如何使用它。Agent 可以根据用户的问题、参数知识和对话历史记录制定更好的搜索查询,还可以决定其他参数,例如要返回的结果数量或是否使用元数据过滤。

Agent 驱动的 RAG 的问题在于速度和成本。Agent 的每一步推理都是对 LLM 的调用——LLM是缓慢而昂贵的——所以我们将等待更长时间的响应,并支付更多的钱。

这两种 RAG 方法是最常见的。我们有 Naive RAG 和 Agent RAG。然而,第三种选择为我们提供了两者之间的中间地带,两者兼而有之。常被称为带护栏的 RAG。

带护栏的 RAG

我们可以使用护栏,而不是使用缓慢而昂贵的 LLM 调用来决定采取何种行动。护栏可以被描述为用户意图的分类器。我们可以创建一个护栏来标识那些表明有人在问问题的查询——当用户问问题时,护栏标识出这个意图并触发 RAG pipeline。

用户意图的类别称为“规范形式”。我们通过提供我们认为属于该规范形式的示例查询(话术)来定义规范形式。

如果我们有一个 RAG pipeline,其中包含有关 LLMs 的信息,我们可以定义一个规范形式来标识用户何时询问 LLMs。在上图中,我们用“define user ask llm”来定义这个规范形式。然后,我们提供了一些应触发此类型的示例查询:

"what is the llama 2 model?"
"tell me about Meta's new LLM"
"what are the differences between Falcon and Llama?"

每个话术都被编码到语义向量空间中,从而创建一个语义“映射”。该映射有助于我们将语义意义和规范形式对齐。

当新的用户查询传入时,我们会将其编码到与前面的话术示例相同的语义向量空间中。使用语义映射及其所属的规范形式,我们可以确定用户查询何时在语义上与这些话术示例相似。如果查询相似,我们将触发这些话术所属的规范形式。

这样一来,我们就可以在几毫秒内完成决策过程,而不是像 Agent 那样在几秒钟内做出决定。

将 RAG 与护栏一起使用确实有我们应该考虑的细微差别。首先,我们必须定义规范形式。我们可以从两个方面来看待定义规范形式的要求:(1)我们可以更确定什么应该触发特定动作,或者(2)我们失去了 LLM 天生的决策能力。

带有护栏的 RAG 允许我们将用户查询直接插入到操作中,但它无法改写查询、指定元数据过滤器或决定返回多少结果。另一方面,使用 Agent 使这些任务变得容易。但是,如果我们可以通过代码确定性地推断这些参数,则可以在不使用 LLM。

以上我们了解了带护栏的 RAG 的工作原理及其优缺点。接下来动手试一试。

使用护栏实现 RAG

要实现带有护栏的 RAG,我们将依赖 NVIDIA NeMo 护栏库。该库主要通过实施“护栏”作为防止不必要的交互的保护措施来关注 AI 安全。但是,我们也可以使用这些护栏来触发 RAG 之类的东西。

pip install -qU \
    nemoguardrails==0.4.0 \
    pinecone-client==2.2.2 \
    datasets==2.14.3 \
    openai==0.27.8

构建知识库

与每个 RAG 用例一样,我们必须首先创建知识库。为此,我们将使用存储在 Hugging Face 中的 Llama 2 相关 ArXiv 论文的小型数据集——下载数据集如下:

from datasets import load_dataset

data = load_dataset(
    "jamescalam/llama-2-arxiv-papers-chunked",
    split="train"
)
data
Dataset({
    features: ['doi', 'chunk-id', 'chunk', 'id', 'title', 'summary', 'source', 'authors', 'categories', 'comment', 'journal_ref', 'primary_category', 'published', 'updated', 'references'],
    num_rows: 4838
})
data[0]
{'doi': '1102.0183',
 'chunk-id': '0',
 'chunk': 'High-Performance Neural Networks\nfor Visual Object Classi\x0ccation\nDan C. Cire\x18 san, Ueli Meier, Jonathan Masci,\nLuca M. Gambardella and J\x7f urgen Schmidhuber\nTechnical Report No. IDSIA-01-11\nJanuary 2011\nIDSIA / USI-SUPSI\nDalle Molle Institute for Arti\x0ccial Intelligence\nGalleria 2, 6928 Manno, Switzerland\nIDSIA is a joint institute of both University of Lugano (USI) and University of Applied Sciences of Southern Switzerland (SUPSI),\nand was founded in 1988 by the Dalle Molle Foundation which promoted quality of life.\nThis work was partially supported by the Swiss Commission for Technology and Innovation (CTI), Project n. 9688.1 IFF:\nIntelligent Fill in Form.arXiv:1102.0183v1  [cs.AI]  1 Feb 2011\nTechnical Report No. IDSIA-01-11 1\nHigh-Performance Neural Networks\nfor Visual Object Classi\x0ccation\nDan C. Cire\x18 san, Ueli Meier, Jonathan Masci,\nLuca M. Gambardella and J\x7f urgen Schmidhuber\nJanuary 2011\nAbstract\nWe present a fast, fully parameterizable GPU implementation of Convolutional Neural\nNetwork variants. Our feature extractors are neither carefully designed nor pre-wired, but',
 'id': '1102.0183',
 'title': 'High-Performance Neural Networks for Visual Object Classification',
 'summary': 'We present a fast, fully parameterizable GPU implementation of Convolutional\nNeural Network variants. Our feature extractors are neither carefully designed\nnor pre-wired, but rather learned in a supervised way. Our deep hierarchical\narchitectures achieve the best published results on benchmarks for object\nclassification (NORB, CIFAR10) and handwritten digit recognition (MNIST), with\nerror rates of 2.53%, 19.51%, 0.35%, respectively. Deep nets trained by simple\nback-propagation perform better than more shallow ones. Learning is\nsurprisingly rapid. NORB is completely trained within five epochs. Test error\nrates on MNIST drop to 2.42%, 0.97% and 0.48% after 1, 3 and 17 epochs,\nrespectively.',
 'source': 'http://arxiv.org/pdf/1102.0183',
 'authors': ['Dan C. Cireşan',
  'Ueli Meier',
  'Jonathan Masci',
  'Luca M. Gambardella',
  'Jürgen Schmidhuber'],
 'categories': ['cs.AI', 'cs.NE'],
 'comment': '12 pages, 2 figures, 5 tables',
 'journal_ref': None,
 'primary_category': 'cs.AI',
 'published': '20110201',
 'updated': '20110201',
 'references': []}

数据已经针对此用例进行了预处理,因此我们无需担心分块 - 但是,我们需要重新格式化数据以仅保留知识库所需的内容。

data = data.map(lambda x: {
    'uid': f"{x['doi']}-{x['chunk-id']}"
})
data
Dataset({
    features: ['doi', 'chunk-id', 'chunk', 'id', 'title', 'summary', 'source', 'authors', 'categories', 'comment', 'journal_ref', 'primary_category', 'published', 'updated', 'references', 'uid'],
    num_rows: 4838
})

chunk 字段包含我们将编码并存储在 Pinecone 中的文本。为了对这些数据进行编码,我们需要使用 embedding 模型。这里使用 OpenAI 的 text-embedding-ada-002,因此我们必须使用 OpenAI API 密钥进行身份验证。

import os

# set your api key here
os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"

然后通过 openai 创建 embedding。Embedding.create 示例如下所示:

import openai

embed_model_id = "text-embedding-ada-002"

res = openai.Embedding.create(
    input=[
        "We would have some text to embed here",
        "And maybe another chunk here too"
    ], engine=embed_model_id
)

len(res['data'][0]['embedding']), len(res['data'][1]['embedding'])
(1536, 1536)

当我们将两个文本块输入到 embedding 函数中时,我们收到了两个嵌入(每个块一个)。请注意,每个都是 1536 维的。我们稍后在设置 Pinecone index 时需要这个数字。

创建向量索引

现在,我们需要一个地方来存储这些 embedding ,并能够通过它们进行有效的向量搜索。为此,我们使用 Pinecone 。我们可以获得一个免费的 API 密钥并在下面输入它,我们将在其中初始化与 Pinecone 的连接。

import pinecone

# initialize connection to pinecone (get API key at app.pinecone.io)
api_key = "YOUR_API_KEY"
# find your environment next to the api key in pinecone console
env = "YOUR_ENV"

pinecone.init(api_key=api_key, environment=env)

现在,我们创建向量索引:

import time

index_name = "nemo-guardrails-rag-with-actions"

# check if index already exists (it shouldn't if this is first time)
if index_name not in pinecone.list_indexes():
    # if does not exist, create index
    pinecone.create_index(
        index_name,
        dimension=len(res['data'][0]['embedding']),
        metric='cosine'
    )
    # wait for index to be initialized
    while not pinecone.describe_index(index_name).status['ready']:
        time.sleep(1)

# connect to index
index = pinecone.Index(index_name)

完成上面步骤,我们的知识库就准备好了。

带护栏的 RAG

我们首先需要设置当我们的护栏识别用户正在询问有关 LLMs。为此,我们将使用一个 retrieve 函数来执行 RAG 的检索组件,以及一个 rag 函数,它将所有这些和 RAG pipeline 逻辑的其余部分包装在一起。

async def retrieve(query: str) -> list:
    # create query embedding
    res = openai.Embedding.create(input=[query], engine=embed_model_id)
    xq = res['data'][0]['embedding']
    # get relevant contexts from pinecone
    res = index.query(xq, top_k=5, include_metadata=True)
    # get list of retrieved texts
    contexts = [x['metadata']['chunk'] for x in res['matches']]
    return contexts

async def rag(query: str, contexts: list) -> str:
    print("> RAG Called")  # we'll add this so we can see when this is being used
    context_str = "\n".join(contexts)
    # place query and contexts into RAG prompt
    prompt = f"""You are a helpful assistant, below is a query from a user and
    some relevant contexts. Answer the question given the information in those
    contexts. If you cannot find the answer to the question, say "I don't know".

    Contexts:
    {context_str}

    Query: {query}

    Answer: """

    # generate answer
    res = openai.Completion.create(
        engine="text-davinci-003",
        prompt=prompt,
        temperature=0.0,
        max_tokens=100
    )
    return res['choices'][0]['text']

请注意,这两个函数都是异步函数 — 我们需要它才能将它们与护栏集成 generate_async 稍后的函数。接下来,我们需要初始化我们的护栏配置文件。我们用它们来定义护栏、操作等。

yaml_content = """
models:
- type: main
  engine: openai
  model: text-davinci-003
"""


rag_colang_content = """
# define limits
define user ask politics
    "what are your political beliefs?"
    "thoughts on the president?"
    "left wing"
    "right wing"

define bot answer politics
    "I'm a personal assistant, I don't like to talk of politics."

define flow politics
    user ask politics
    bot answer politics
    bot offer help

# define RAG intents and flow
define user ask llms
    "tell me about llama 2?"
    "what is large language model"
    "where did meta's new model come from?"
    "what is the falcon model?"
    "have you ever meta llama?"

define flow llms
    user ask llms
    $contexts = execute retrieve(query=$last_user_message)
    $answer = execute rag(query=$last_user_message, contexts=$contexts)
    bot $answer
"""

在这里,我们定义了两个可能的对话流。第一种是典型的护栏。我们的聊天机器人避开政治话题是一种安全措施。第二个是我们启用了 RAG 的流程——当用户询问 LLM 时触发。

LLM flow(由用户 ask llms 规范形式定义)使用 contexts。然后,我们将 contexts 传递给我们的 rag 操作。为了初始化支持 RAG 的 Rail,我们执行以下操作:

from nemoguardrails import LLMRails, RailsConfig

# initialize rails config
config = RailsConfig.from_content(
    colang_content=rag_colang_content,
    yaml_content=yaml_content
)
# create rails
rag_rails = LLMRails(config)

每次我们在 Colang 配置文件中定义动作时,我们都必须注册它们——否则,我们的 rails 不知道如何执行、检索或执行 rag。我们按如下方式注册:

rag_rails.register_action(action=retrieve, name="retrieve")
rag_rails.register_action(action=rag, name="rag")

现在让我们试试看:

await rag_rails.generate_async(prompt="hello")

输出

Hi there! How can I help you today?'
await rag_rails.generate_async(prompt="tell me about llama 2")

> RAG Called

' Llama 2 is a collection of pretrained and fine-tuned large language models (LLMs) ranging in scale from 7 billion to 70 billion parameters. They are optimized for dialogue use cases and outperform open-source chat models on most benchmarks. They are also on par with some closed-source models, at least on the human evaluations performed. They are intended for commercial and research use in English and can be adapted for a variety of natural language generation tasks.'

从 > RAG Called 的打印语句中可以看出,第二个提示触发了 RAG 流水线。因此,我们的带护栏管道的 RAG 正在工作!

RAG 与无 RAG

一切看起来都不错。但是,看看 RAG 对我们的答案有什么影响会很有趣。让我们初始化一个新的 Rails 实例,该实例不包括对 RAG 的调用来比较结果。

no_rag_colang_content = """
# define limits
define user ask politics
    "what are your political beliefs?"
    "thoughts on the president?"
    "left wing"
    "right wing"

define bot answer politics
    "I'm a shopping assistant, I don't like to talk of politics."
    "Sorry I can't talk about politics!"

define flow politics
    user ask politics
    bot answer politics
    bot offer help
"""


# initialize rails config
config = RailsConfig.from_content(
    colang_content=no_rag_colang_content,
    yaml_content=yaml_content
)
# create rails
no_rag_rails = LLMRails(config)

让我们再问一下骆驼 2:

await no_rag_rails.generate_async(prompt="tell me about llama 2")

输出

"Llama 2 is a text-to-speech software developed by NVIDIA. It is designed to generate natural sounding speech from text and is used in a variety of applications such as virtual assistants, chatbots, and automated customer service. The software is powered by NVIDIA's AI platform and uses a deep learning model to generate the audio output."

这不是我们想要的 Llama 2——让我们尝试更多问题并比较 RAG 与无 RAG 的答案。

# no RAG
await no_rag_rails.generate_async(
    prompt="what was red teaming used for in llama 2 training?"
)

输出

'Red teaming was used in Llama 2 training to simulate enemy tactics and techniques. This allowed the trainees to practice dealing with realistic threats and build strategies to counter them.'

我们的无 RAG rails 提供了一个令人兴奋但完全错误的答案。让我们对支持 RAG 的 rails 进行同样的尝试:

# with RAG
await rag_rails.generate_async(
    prompt="what was red teaming used for in llama 2 training?"
)

输出

' Red teaming was used to identify risks and to measure the robustness of the model with respect to a red teaming exercise executed by a set of experts. It was also used to provide qualitative insights to recognize and target specific patterns in a more comprehensive way.'

一个完美的答案!我们支持 RAG 的 rails 更有能力回答问题,同时仅在需要时调用 RAG 操作,如我们的 actions.co 配置文件中所设置的那样。

通过以这种方式使用 Guardrails for RAG,我们设法在每次用户调用时实现 RAG 的轻量级但Naive 的方法与实现具有 RAG 工具访问权限的对话 Agent 的重量级和缓慢方法之间取得了平衡。

参考资料:

Making Retrieval Augmented Generation Fast | Pinecone(https://www.pinecone.io/learn/fast-retrieval-augmented-generation/)

[S. Yao, ReAct: Synergizing Reasoning and Acting in Language Models (2023), ICLR](https://arxiv.org/pdf/2210.03629.pdf)


原文作者:HelloTech技术派;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2017-12-05

  • 博文量
    181
  • 访问量
    73724