如何使用 Elasticsearch 以自然语言提示 ChatGPT

blog-thumb-elasticsearch-gears-light-blue.png

最近,人人都在谈论 ChatGPT。这个大型语言模型 (LLM) 的一项很酷的功能是能够生成代码。因此,我们使用它来生成 Elasticsearch DSL 查询。我们的目标是能够在 Elasticsearch® 中搜索类似“给我股票指数中 2017 年的前 10 个文档”这样的句子。 这一实验表明,这是可行的,但同时也有一些局限性。在本篇博文中,我们将介绍这一实验的情况,以及我们针对这个用例发布的开源库

ChatGPT 可以生成 Elasticsearch DSL 吗?

我们的实验先从一些测试入手,重点测试了 ChatGPT 生成 Elasticsearch DSL 查询的能力。在这个范围内,您需要向 ChatGPT 提供一些关于要搜索的数据结构的上下文。

在 Elasticsearch 中,数据都存储在索引中,这类似于关系数据库中的“表”。它有一个定义多个字段及其类型的映射。这意味着,我们需要提供要查询的索引映射信息。这样,ChatGPT 就能够获得将查询转换为 Elasticsearch DSL 所需的上下文。

Elasticsearch 提供了一个获取映射 API,以检索索引的映射。在实验中,我们使用了此处提供的股票指数数据集。这个数据集包含《财富》500 强公司五年的股票价格,时间跨度为 2013 年 2 月至 2018 年 2 月。

下面我们报告了包含该数据集的 CSV 文件的前五行:

date,open,high,low,close,volume,name
2013-02-08,15.07,15.12,14.63,14.75,8407500,AAL
2013-02-11,14.89,15.01,14.26,14.46,8882000,AAL
2013-02-12,14.45,14.51,14.1,14.27,8126000,AAL
2013-02-13,14.3,14.94,14.25,14.66,10259500,AAL

每一行都包含股票日期、当日开盘价、最高价和最低价、收盘价、股票成交以及股票名称 — 例如,American Airlines Group Inc.(AAL)。

与股票指数关联的映射如下:

{
  "stocks": {
    "mappings": {
      "properties": {
        "close": {"type":"float"},
        "date" : {"type":"date"},
        "high" : {"type":"float"},
        "low"  : {"type":"float"},
        "name" : {
          "type": "text",
          "fields": {
            "keyword":{"type":"keyword", "ignore_above":256}
          }
        },
        "open"  : {"type":"float"},
        "volume": {"type":"long"}
      }
    }
  }
}

我们可以使用 GET /stocks/_mapping API 从 Elasticsearch 中检索映射。

[相关文章:ChatGPT 和 Elasticsearch:OpenAI 遇见私有数据]

构建用于查找的提示

为了将以人类语言表达的查询转换成 Elasticsearch DSL,我们需要给 ChatGPT 找到正确的提示。这是整个过程中最难的部分:使用正确的问题格式(换句话说,就是正确的提示)对 ChatGPT 进行实际编程。

经过反复迭代,我们最终得到了以下看起来效果还不错的提示:

Given the mapping delimited by triple backticks ```{mapping}``` translate the text delimited by triple quotes in a valid Elasticsearch DSL query """{query}""". Give me only the json code part of the answer. Compress the json output removing spaces.

提示中的 {mapping} {query} 值是两个占位符,需要替换为映射 json 字符串(例如,上例中 GET /stocks/_mapping 返回的映射)和用人类语言表达的查询(例如:返回 2017 年的前 10 个文档)。

当然,ChatGPT 也有局限性,在有些情况下它无法回答问题。我们发现,大多数时候,出现这种情况是因为提示中使用的句子过于笼统或含糊不清。要解决这种情况,我们就需要使用更多的细节来强化提示语。这个过程称为迭代,需要经过多个步骤才可定义出适用的句子。

如果您想试试 ChatGPT 如何转换 Elasticsearch DSL 查询(甚至是 SQL)中的搜索句子,可以使用 dsltranslate.com

将所有内容整合到一起

我们使用 OpenAI 提供的 ChatGPT API 和 Elasticsearch API 进行映射和搜索,并将其整合到了一个 PHP 实验库中。

这个库公开了一个 search() 函数,其 API 如下所示:

search(string $index, string $prompt, bool $cache = true)

其中,$index 是要使用的索引名称,$prompt 是以人类语言表达的查询,$bool 是使用缓存的可选参数(默认启用)。

这个函数的运行过程如下图所示:

elasticsearch openai 图

输入是 index(索引)和 prompt(提示)(位于左边)。索引用于从 Elasticsearch 中检索映射(请参考获取映射 API)。结果是一个 JSON 格式的映射,用来构建查询字符串,以使用下面的 API 代码发送给 ChatGPT。我们使用的是 OpenAI 的 gpt-3.5-turbo 模型;这个模型能够进行代码转换。

来自 ChatGPT 的结果包含一个我们用来查询 Elasticsearch 的 Elasticsearch DSL 查询。然后,将结果返回给用户。为了查询 Elasticsearch,我们使用了官方的 elastic/elasticsearch-php 客户端。

为了优化响应时间并降低使用 ChatGPT API 的成本,我们使用了一个基于文件的简单缓存系统。我们使用缓存进行以下操作:

  • 存储 Elasticsearch 返回的映射 JSON:我们将这个 JSON 保存在一个以索引命名的文件中。这样,我们无需额外调用 Elasticsearch,就可以检索映射信息。
  • 存储由 ChatGPT 生成的 Elasticsearch DSL:为了缓存生成的 Elasticsearch DSL,我们使用所用提示的哈希值 (MD5) 命名了缓存文件。通过这种方法,我们可以重复使用之前为相同查询生成的 Elasticsearch DSL,从而无需再次调用 ChatGPT API。

此外,我们还增加了使用 getLastQuery() 函数以编程方式检索 Elasticsearch DSL 的可能性。

使用财务数据进行实验

我们使用 Elastic Cloud 来存储此处报告的股票值。我们特别使用了一个简单的批量脚本来读取 CSV 格式的股票文件,并使用批量 API 将其发送到 Elasticsearch。

有关如何设置 Elastic Cloud 和检索 API 密钥的详细信息,请阅读文档

存储股票指数后,我们使用了一个简单的 PHP 脚本来测试一些用英语表达的查询。我们使用的脚本是 examples/test.php

要执行这个 examples/test.php 脚本,我们需要设置三个环境变量:

  • OPENAI_API_KEY:OpenAI 的 API 密钥
  • ELASTIC_CLOUD_ENDPOINT:Elasticsearch 实例的 URL
  • ELASTIC_CLOUD_API_KEY:Elastic Cloud 的 API 密钥

使用股票映射,我们测试了以下记录所有 Elasticsearch DSL 响应的查询:

Query: Return the first 10 documents of 2017

Result:
{
  "query":{
    "range":{
      "date":{
        "gte":"2017-01-01",
        "lte":"2017-12-31"
      }
    }
  },
  "size":10
}
Query: Return the first 30 names of all the different stock names

Result:
{
  "size":0,
  "aggs":{
    "unique_names":{
      "terms":{
        "field":"name.keyword",
        "size":30
      }
    }
  }
}
Query: Return the max value of the field "high" for each stock in 2015

Result: 
{
  "aggs":{
    "stocks":{
      "terms":{"field":"name.keyword","size":100},
      "aggs":{
        "max_high":{"max":{"field":"high"}}
      }
    }
  },
  "query":{
    "bool":{
      "must":[{
        "range":{
          "date":{
            "gte":"2015-01-01",
            "lt":"2016-01-01"
          }
        }
      }]
    }
  }
}
Query: Return the average value of the field "high" for each stock in 2015

Result:
{
  "size":0,
  "aggs":{
    "stocks":{
      "terms":{
        "field":"name.keyword",
        "size":10000
      },
      "aggs":{
        "2015":{
          "filter":{
            "range":{
              "date":{
                "gte":"2015-01-01",
                "lt":"2016-01-01"
              }
            }
          },
          "aggs":{
            "avg_high":{
              "avg":{
                "field":"high"
              }
            }
          }
        }
      }
    }
  }
}
Query: Return the max value of the field "high" for all the documents with name MON in 2014

Result:

{
  "query":{
    "bool":{
      "must":[
        {
          "match":{
            "name.keyword":"MON"
          }
        },
        {
          "range":{
            "date":{
              "gte":"2014-01-01",
              "lt":"2015-01-01"
            }
          }
        }
      ]
    }
  },
  "size":0,
  "aggs":{
    "max_high":{
      "max":{
        "field":"high"
      }
    }
  }
}
Query: Return the documents that have the difference between close and open fields > 20

Response:
{
  "query":{
    "bool":{
      "must":[
        {
          "script":{
            "script":{
              "lang":"painless",
              "source":"doc['close'].value - doc['open'].value > 20"
            }
          }
        }
      ]
    }
  }
}

如您所看到的,结果相当不错。最后一个关于结束字段和开始字段之间区别的结果令人印象深刻!

所有请求均已被转换为有效的 Elasticsearch DSL 查询,正确对应以自然语言表达的问题。

用您所说的语言查询!

ChatGPT 的一大特色就是可用不同的语言指定问题。

这意味着,您可以使用这个库,以不同的自然语言(如意大利语、西班牙语、法语、德语等)指定查询。

下面是一个示例:

# English
$result = $chatGPT->search('stocks', 'Return the first 10 documents of 2017');
# Italian
$result = $chatGPT->search('stocks', 'Restituisci i primi 10 documenti del 2017');
# Spanish
$result = $chatGPT->search('stocks', 'Devuelve los 10 primeros documentos de 2017');
# French
$result = $chatGPT->search('stocks', 'Retourner les 10 premiers documents de 2017');
# German
$result = $chatGPT->search('stocks', 'Senden Sie die ersten 10 Dokumente des Jahres 2017 zurück');

之前的所有搜索都获得了相同的结果,大致生成了以下 Elasticsearch 查询:

{"size":10,"query":{"range":{"date":{"gte":"2017-01-01","lt":"2018-01-01"}}}}

重要说明:ChatGPT 是一个针对英语进行过优化的 LLM,这意味着使用英语输入的查询可以获得最佳结果。

LLM 的局限性

遗憾的是,ChatGPT 和 LLM 一般无法从语义角度验证答案的正确性。它们给出的答案从统计学角度来看是正确的。这意味着,我们无法检验 ChatGPT 生成的 Elasticsearch DSL 查询是否正确转换了以自然语言输入的查询。

当然,这是目前的一大局限。在其他一些用例中,比如数学运算,我们可以使用外部插件(如 ChatGPT 的 Wolfram 插件)来解决正确性问题。在这种情况下,ChatGPT 的结果会使用 Wolfram 引擎,通过数学符号模型检查响应的正确性。

除了正确性限制(这意味着我们应始终检查 ChatGPT 的答案)之外,在 Elasticsearch DSL 查询中转换人类语句的能力也有局限性。

例如,使用之前的股票数据集,如果我们提出以下问题:

Query: Return the first 10 documents of 2017 and 2015

Result:
{
  "from":0,
  "size":10,
  "query":{
    "bool":{
      "must":[
        {
          "range":{
            "date":{
              "gte":"2015-01-01",
              "lte":"2015-12-31",
              "format":"yyyy"
            }
          }
        },
        {
          "range":{
            "date":{
              "gte":"2017-01-01",
              "lte":"2017-12-31",
              "format":"yyyy"
            }
          }
        }
      ]
    }
  }
}

ChatGPT 生成的 DSL 查询无效,导致出现下面的 Elasticsearch 错误:

无法解析格式为 [yyyy] 的日期字段 [2015-01-01]。

如果我们使用更具体的信息重新表述这句话,消除关于日期格式的明显歧义,就能检索到正确的答案,如下所示:

Query: Return the first 10 documents with year 2017 and 2015 in "date" field

Result:
{
  "size":10,
  "query":{
    "bool":{
      "should":[
        {
          "term":{
            "date":"2017"
          }
        },
        {
          "term":{
            "date":"2015"
          }
        }
      ]
    }
  }
}

基本上,这个句子必须使用描述 Elasticsearch DSL 应该是怎样的来表达,并不是一个真正的人类语句。

总结

在本篇博文中,我们介绍了 ChatGPT 将自然语言搜索句子转换成 Elasticsearch DSL 查询的实验用例。我们开发了一个简单的 PHP 库,用于使用 OpenAI API 在底层转换查询,同时还提供了一个缓存系统。

尽管答案的正确性有所限制,但实验结果还是令人鼓舞的。不过,我们一定会进一步研究使用 ChatGPT 以及其他日益普及的 LLM 模型通过自然语言查询 Elasticsearch 的可能性。

详细了解 Elasticsearch 和 AI 的可能性



在本博文中,我们可能使用了第三方生成式 AI 工具,这些工具由其各自所有者拥有和运营。Elastic 对第三方工具没有任何控制权,对其内容、操作或使用不承担任何责任或义务,对您使用此类工具可能造成的任何损失或损害也不承担任何责任或义务。在 AI 工具中使用个人、敏感或机密信息时,请务必谨慎。您提交的任何数据都可能用于 AI 训练或其他目的。Elastic 不保证您所提供信息的安全性或保密性。在使用任何生成式 AI 工具之前,您都应自行熟悉其隐私惯例和使用条款。

Elastic、Elasticsearch 及相关标志为 Elasticsearch N.V. 在美国和其他国家/地区的商标、徽标或注册商标。所有其他公司和产品名称均为其相应所有者的商标、徽标或注册商标。