跳到正文
LlamaIndex:产品、工程与评测·· 2023-05-28精选AI 评分61

LlamaIndex 推出 SQLAutoVectorQueryEngine,结合 Text-to-SQL 与语义搜索完成 RAG 查询

Combining Text-to-SQL with Semantic Search for Retrieval Augmented Generation

AI 导读

LlamaIndex 在博客中展示新查询引擎 SQLAutoVectorQueryEngine,可同时利用 SQL 数据库和向量数据库回答同时涉及结构化与非结构化数据的自然语言查询。

推荐理由

原文给出 SQLAutoVectorQueryEngine 的工作流程、示例代码和实验结果,读者可以照着复现结构化与非结构化数据的组合查询。

正文 · AI 翻译

摘要

在本文中,我们展示了 LlamaIndex 中一个强大的新查询引擎(SQLAutoVectorQueryEngine),它能够利用 SQL 数据库和向量存储来满足对结构化和非结构化数据组合的复杂自然语言查询。该查询引擎可以利用 SQL 对结构化数据的表达力,并将其与来自向量数据库的非结构化上下文相结合。我们在几个示例中展示了这个查询引擎,并表明它可以处理利用结构化/非结构化数据或其中任一数据的查询。

立即探索我们的免费和付费计划。

查看完整指南:https://gpt-index.readthedocs.io/en/latest/examples/query_engine/SQLAutoVectorQueryEngine.html。

背景

企业中的数据湖通常包含结构化和非结构化数据。结构化数据通常以表格形式存储在 SQL 数据库中,组织成具有预定义模式和实体之间关系的表。另一方面,数据湖中的非结构化数据缺乏预定义结构,无法整齐地适应传统数据库。这类数据包括文本文档,还包括其他多模态格式,如音频录音、视频等。

大型语言模型(LLM)能够从结构化和非结构化数据中提取见解。已经出现了一些用于处理这两种数据的初始工具和堆栈:

  • 文本到 SQL(结构化数据):给定一组表格模式,我们将自然语言转换为 SQL 语句,然后可以在数据库上执行。
  • 使用向量数据库的语义搜索(非结构化数据):将非结构化文档及其嵌入存储在向量数据库(例如 Pinecone、Chroma、Milvus、Weaviate 等)中。在查询时,通过嵌入相似性获取相关文档,然后放入 LLM 输入提示中以合成响应。

这些堆栈各自解决了特定的用例。

结构化数据上的文本到 SQL

在结构化设置中,SQL 是一种极具表达力的语言,用于操作表格数据——在分析的情况下,你可以获得聚合、跨多个表的连接信息、按时间戳排序等等。使用 LLM 将自然语言转换为 SQL 可以被视为程序合成的“作弊代码”——只需让 LLM 编译成正确的 SQL 查询,然后让数据库上的 SQL 引擎处理其余部分!

用例:文本到 SQL 查询非常适合分析用例,其中答案可以通过执行 SQL 语句找到。它们不适合需要比结构化表中更多细节的情况,或者需要比简单构造(如 WHERE 条件)更复杂的方式来确定与查询的相关性的情况。

适合文本到 SQL 的示例查询:

  • “北美城市的平均人口是多少”?
  • “每个大陆上最大的城市和人口是多少?”

非结构化数据上的语义搜索

在非结构化场景中,检索增强生成系统的行为是先执行检索,再进行合成。在检索过程中,我们首先通过嵌入相似度查找与查询最相关的文档。一些向量存储支持在检索时处理额外的元数据过滤器。我们可以选择手动指定所需的过滤器集合,或者让 LLM“推断”查询字符串和元数据过滤器应该是什么(参见 LlamaIndex 中的自动检索模块或 LangChain 的自查询模块)。

用例:检索增强生成非常适合那些答案可以在某些非结构化文本数据部分中找到的查询。大多数现有的向量存储(例如 Pinecone、Chroma)不提供类似 SQL 的接口;因此它们不太适合涉及聚合、连接、求和等操作的查询。

适合检索增强生成的示例查询

  • “告诉我柏林的历史博物馆”
  • “乔丹代表盖茨比向尼克提出了什么要求?”

结合这两种系统

对于某些查询,我们可能希望利用结构化表格以及向量数据库/文档存储中的知识,以便为查询提供最佳答案。理想情况下,这可以让我们兼得两者之长:对结构化数据的分析能力,以及对非结构化数据的语义理解。

以下是一个示例用例:

  • 你可以访问一个关于不同城市的文章集合,存储在向量数据库中
  • 你还可以访问一个包含每个城市统计数据的结构化表格。

给定这个数据集合,让我们举一个示例查询:“告诉我人口最多的城市的艺术和文化。”

回答这个问题的“正确”方式大致如下:

  • 在结构化表格中查询人口最多的城市。
SELECT city, population FROM city_stats ORDER BY population DESC LIMIT 1
  • 将原始问题转换为更详细的问题:“告诉我东京的艺术和文化。”
  • 在你的向量数据库上提出这个新问题。
  • 使用原始问题 + 对 SQL 数据库和向量数据库的中间查询/响应来合成答案。

让我们思考一下这样一个序列的一些高层含义:

  • 我们不是通过嵌入搜索(以及可选的元数据过滤器)来检索相关上下文,而是希望以某种方式将 SQL 查询作为第一个“检索”步骤。
  • 我们希望确保能够以某种方式将 SQL 查询的结果与存储在向量数据库中的上下文“连接”起来。目前没有现成的语言可以在 SQL 和向量数据库之间“连接”信息。我们必须自己实现这种行为。
  • 两个数据源都无法单独回答这个问题。结构化表格只包含人口信息。向量数据库包含城市信息,但没有简单的方法来查询人口最多的城市。

一个结合结构化分析和语义搜索的查询引擎

我们创建了一个全新的查询引擎( SQLAutoVectorQueryEngine ),它可以查询、连接、排序和组合来自 SQL 数据库的结构化数据以及来自向量数据库的非结构化数据,以合成最终答案。

该 SQLAutoVectorQueryEngine 通过传入一个 SQL 查询引擎( GPTNLStructStoreQueryEngine )以及一个使用我们的向量存储自动检索模块的查询引擎( VectorIndexAutoRetriever )来初始化。SQL 查询引擎和向量查询引擎都被包装为包含 name 和 description 字段的“Tool”对象。

提醒:VectorIndexAutoRetriever 将自然语言查询作为输入。在了解向量数据库的元数据模式后,自动检索器首先推断需要传入的其他查询参数(例如 top-k 值和元数据过滤器),并使用所有查询参数对向量数据库执行查询。

SQLAutoVectorQueryEngine 的流程图

在查询时,我们运行以下步骤:

  1. 选择器提示(同样用于我们的 RouterQueryEngine 中,参见指南)首先选择我们应该查询 SQL 数据库还是向量数据库。如果它选择使用向量查询引擎,那么函数执行的其余部分与使用 VectorIndexAutoRetriever 查询 RetrieverQueryEngine 相同。
  2. 如果它选择查询 SQL 数据库,它将针对数据库执行文本到 SQL 的查询操作,并(可选地)合成自然语言输出。
  3. 运行查询转换,根据 SQL 查询的结果将原始问题转换为更详细的问题。例如,如果原始问题是“告诉我人口最多的城市的艺术和文化。”,而 SQL 查询返回东京为人口最多的城市,那么新查询是“告诉我东京的艺术和文化。”唯一的例外是如果 SQL 查询本身足以回答原始问题;如果是这样,则函数执行返回 SQL 查询作为响应。
  4. 然后,新查询通过向量存储查询引擎运行,该引擎从向量存储中执行检索,然后进行 LLM 响应合成。我们强制使用 VectorIndexAutoRetriever 模块。这使我们能够根据 SQL 查询的结果自动推断正确的查询参数(查询字符串、top k、元数据过滤器)。例如,在上面的示例中,我们可能推断查询为 query_str="arts and culture" 和 filters={"title": "Tokyo"}。
  5. 原始问题、SQL 查询、SQL 响应、向量存储查询和向量存储响应被组合成一个提示,以合成最终答案。

退一步说,以下是对这种方法的一些一般性评论:

  • 使用我们的自动检索模块是我们模拟 SQL 数据库和向量数据库之间连接的方式。我们有效地使用 SQL 查询的结果来确定查询向量数据库的参数。
  • 这也意味着 SQL 数据库中的项目与向量数据库中的元数据之间不需要显式映射,因为我们可以依靠 LLM 为不同的项目提出正确的查询。不过,对结构化表和文档存储元数据之间的显式关系进行建模会很有趣;这样我们就不需要在自动检索步骤中花费额外的 LLM 调用来推断正确的元数据过滤器。

实验

那么这效果如何呢?它在广泛的查询中效果出奇地好,从可以利用结构化数据和非结构化数据的查询,到特定于结构化数据集合或非结构化数据集合的查询。

设置

我们的实验设置非常简单。我们有一个名为 city_stats 的 SQL 表,其中包含三个不同城市:多伦多、东京和柏林的城市、人口和国家。

我们还使用 Pinecone 索引来存储与这三个城市对应的维基百科文章。每篇文章被分块并存储为单独的“Node”对象;每个块还包含一个 title 元数据属性,其中包含城市名称。

然后我们从 Pinecone 向量索引中推导出 VectorIndexAutoRetriever 和 RetrieverQueryEngine。

from llama_index.indices.vector_store.retrievers import VectorIndexAutoRetriever
from llama_index.vector_stores.types import MetadataInfo, VectorStoreInfo
from llama_index.query_engine.retriever_query_engine import RetrieverQueryEngine


vector_store_info = VectorStoreInfo(
    content_info='articles about different cities',
    metadata_info=[
        MetadataInfo(
            name='city', 
            type='str', 
            description='The name of the city'),
    ]
)
vector_auto_retriever = VectorIndexAutoRetriever(vector_index, vector_store_info=vector_store_info)

retriever_query_engine = RetrieverQueryEngine.from_args(
    vector_auto_retriever, service_context=service_context
)

你也可以按如下方式获取 SQL 查询引擎

sql_query_engine = sql_index.as_query_engine()

SQL 查询引擎和向量查询引擎都可以被包装为 QueryEngineTool 对象。

sql_tool = QueryEngineTool.from_defaults(
    query_engine=sql_query_engine,
    description=(
        'Useful for translating a natural language query into a SQL query over a table containing: '
        'city_stats, containing the population/country of each city'
    )
)
vector_tool = QueryEngineTool.from_defaults(
    query_engine=query_engine,
    description=f'Useful for answering semantic questions about different cities',
)

最后,我们可以定义我们的 SQLAutoVectorQueryEngine

query_engine = SQLAutoVectorQueryEngine(
    sql_tool,
    vector_tool,
    service_context=service_context
)

结果

我们运行一些示例查询。

查询 1

query_engine.query(
  'Tell me about the arts and culture of the city with the highest population'
)

中间步骤:

最终响应:

Tokyo is the city with the highest population, with 13.96 million people. It is a vibrant city with a rich culture and a wide variety of art forms. From traditional Japanese art such as calligraphy and woodblock prints to modern art galleries and museums, Tokyo has something for everyone. There are also many festivals and events throughout the year that celebrate the city's culture and art. Some popular festivals and events in Tokyo include the Sannō Festival at Hie Shrine, the Sanja Festival at Asakusa Shrine, and the biennial Kanda Festivals. These events often feature parades with elaborately decorated floats and thousands of people. Additionally, an enormous fireworks display over the Sumida River takes place annually on the last Saturday of July, attracting over a million viewers. During spring, when cherry blossoms bloom, many residents gather in Ueno Park, Inokashira Park, and the Shinjuku Gyoen National Garden for picnics under the blossoms.

此查询会走完 SQLAutoVectorQueryEngine 的完整流程。它首先查询 SQL 数据库以找出人口最多的城市(“Tokyo”),然后用新的查询去查询向量数据库。结果被合并为最终响应。

查询 2

query_engine.query("Tell me about the history of Berlin")

中间步骤:

最终响应:

Berlin's history dates back to the early 13th century when it was founded as a small settlement. In 1618, the Margraviate of Brandenburg entered into a personal union with the Duchy of Prussia, and in 1701, they formed the Kingdom of Prussia with Berlin as its capital. The city grew and merged with neighboring cities, becoming a center of the Enlightenment under the rule of Frederick the Great in the 18th century.

The Industrial Revolution in the 19th century transformed Berlin, expanding its economy, population, and infrastructure. In 1871, it became the capital of the newly founded German Empire. The early 20th century saw Berlin as a hub for the German Expressionist movement and a major world capital known for its contributions to science, technology, arts, and other fields.

In 1933, Adolf Hitler and the Nazi Party came to power, leading to a decline in Berlin's Jewish community and the city's involvement in World War II. After the war, Berlin was divided into East and West Berlin, with the former under Soviet control and the latter under the control of the United States, United Kingdom, and France. The Berlin Wall was built in 1961, physically and ideologically dividing the city until its fall in 1989. Following the reunification of Germany in 1990, Berlin once again became the capital of a unified Germany and has since continued to grow and develop as a major global city.

此查询只需要向量数据库,不需要 SQL 数据库。初始选择器正确地识别出我们应当只查询向量数据库并返回结果。

查询 3

query_engine.query('Can you give me the country corresponding to each city?')

中间步骤

最终响应

 Toronto is in Canada, Tokyo is in Japan, and Berlin is in Germany.

此查询只需查询 SQL 数据库即可回答,不需要来自向量数据库的额外信息。查询转换步骤正确地将“None”识别为后续问题,表明原始问题已被回答。

结论

到目前为止,围绕 LLM + 非结构化数据和 LLM + 结构化数据的技术栈在很大程度上是彼此分离的。我们很期待看到,将 LLM 同时置于结构化和非结构化数据之上,能够以新颖而有趣的方式解锁新的检索/查询能力!

我们很希望你能试用 SQLAutoVectorQueryEngine,并告诉我们你的想法。

完整的 notebook 演练可在本指南中找到(相关 notebook)。

来源:LlamaIndex:产品、工程与评测 · llamaindex.ai