如今,每个人都在谈论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
每一行包含股票的 _日期_、当天的 _开盘价_、_最高价_ 和 _最低价_、_收盘价_、交易的股票 _数量_,以及股票 _名称_——例如,美国航空集团公司 (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 %2Fstocks%2F_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 %2Fstocks%2F_mapping 返回的结果)和用人类语言表达的查询(例如:返回 2017 年的前 10 个文档)替换。
当然,ChatGPT 是有限制的,在某些情况下它无法回答问题。我们发现,大多数情况下,这是因为提示中使用的句子过于笼统或含糊不清。为了解决这种情况,我们需要使用更多细节来改进提示。这个过程称为迭代,它需要多个步骤来定义要使用的适当句子。
如果您想尝试 ChatGPT 如何将搜索句子转换成 Elasticsearch DSL 查询(甚至是 SQL),可以使用dsltranslate.com。
整合所有内容
使用 OpenAI 提供的 ChatGPT API 和 Elasticsearch 的映射和搜索 API,我们将所有内容整合到一个用于 PHP 的实验库中。
此库公开了一个具有以下 API 的 `search()` 函数:
search(string $index, string $prompt, bool $cache = true)
其中 `$index` 是要使用的索引名称,`$prompt` 是用人类语言表达的查询,`$bool` 是一个可选参数,用于使用缓存(默认情况下启用)。
此函数的过程如下图所示:
输入是 _索引_ 和 _提示_(左侧)。索引用于从 Elasticsearch 检索映射(使用获取映射 API)。结果是一个 JSON 格式的映射,用于构建要使用以下 API 代码发送到 ChatGPT 的查询字符串。我们使用了 OpenAI 的gpt-3.5-turbo模型,该模型能够进行代码翻译。
ChatGPT 的结果包含一个 Elasticsearch DSL 查询,我们用它来查询 Elasticsearch。然后将结果返回给用户。为了查询 Elasticsearch,我们使用了官方的elastic%2Felasticsearch-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%2Ftest.php。
要执行此 examples%2Ftest.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的可能性。
在本博文中,我们可能使用了第三方生成式AI工具,这些工具由其各自的所有者拥有和运营。Elastic对第三方工具没有任何控制权,我们对其内容、操作或使用,以及因您使用此类工具而可能造成的任何损失或损害不承担任何责任。使用AI工具处理个人、敏感或机密信息时,请务必谨慎。您提交的任何数据都可能用于AI训练或其他目的。我们不能保证您提供的信息将被安全或保密地保存。在使用任何生成式AI工具之前,您应该熟悉其隐私惯例和使用条款。
Elastic、Elasticsearch和相关标记是Elasticsearch N.V.在美国和其他国家的商标、标识或注册商标。所有其他公司和产品名称是其各自所有者的商标、标识或注册商标。
Elasticsearch与行业领先的生成式AI工具和提供商具有原生集成。查看我们关于超越RAG基础知识或构建可用于生产环境的应用程序的网络研讨会Elastic向量数据库。