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는 인덱스 매핑을 검색하기 위해 get mapping API를 제공합니다. 실험에서는 여기에서 이용 가능한 주가 지수 데이터 세트를 사용했습니다. 이 데이터 세트에는 2013년 2월부터 2018년 2월까지 Fortune지 선정 500개 기업의 5년간 주가가 포함되어 있습니다.

여기에는 데이터 세트가 포함된 CSV 파일의 처음 5줄이 보고되었습니다.

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용 실험 라이브러리에 통합했습니다.

이 라이브러리는 다음 API를 사용하여 search() 함수를 노출합니다.

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

여기서 $index는 사용할 인덱스 이름이고, $prompt은 인간의 언어로 표현된 쿼리이고 $bool은 캐시 사용을 위한 선택적 매개변수입니다(기본적으로 활성화됨).

이 기능의 프로세스는 다음 다이어그램에 보고되어 있습니다.

Elasticsearch OpenAI 다이어그램

입력은 indexprompt(왼쪽)입니다. 인덱스는 Elasticsearch에서 매핑을 검색하는 데 사용됩니다(get mapping API 사용). 결과는 다음 API 코드를 사용하여 ChatGPT에 보낼 쿼리 스트링을 작성하는 데 사용되는 JSON의 매핑입니다. 코드로 변환할 수 있는 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입니다.

이 example/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이 어떻게 되어야 하는지에 대한 설명을 사용하여 표현되어야 합니다.

결론

이 포스팅에서는 자연어 검색 문장을 Elasticsearch DSL 쿼리로 번역하기 위한 ChatGPT의 실험적 사용 사례를 제시했습니다. OpenAI API를 사용하여 내부적으로 쿼리를 번역하고 캐싱 시스템도 제공하기 위해 PHP에서 간단한 라이브러리를 개발했습니다.

답변의 정확성에 한계가 있음에도 불구하고 실험 결과는 긍정적이었습니다. 앞으로도 Elastic은 ChatGPT와 점점 더 대중화되고 있는 다른 LLM 모델을 사용하여 자연어로 Elasticsearch를 쿼리할 수 있는 가능성에 대해 확실히 더 조사할 것입니다.

Elasticsearch와 AI의 가능성에 대해 자세히 알아보세요.



이 블로그 포스팅에서, Elastic은 각 소유자가 소유하고 운영하는 서드파티 생성형 AI 도구를 사용했을 수 있습니다. Elastic은 서드파티 도구에 대한 어떠한 통제권도 없으며 당사는 그 내용, 작동 또는 사용에 대한 책임이나 법적 의무가 없고 이러한 도구의 사용으로 인해 발생할 수 있는 손실 또는 손상에 대해 책임을 지지 않습니다. 개인 정보, 민감한 정보 또는 기밀 정보와 함께 AI 도구를 사용할 때 주의하세요. 제출하신 모든 데이터는 AI 교육을 위해 또는 다른 목적으로 사용될 수 있습니다. 제공하시는 정보가 안전하게 유지되거나 기밀로 유지된다는 보장은 없습니다. 사용 전에 생성형 AI 도구의 개인 정보 보호 관행 및 사용 약관을 숙지하셔야 합니다.  

Elastic, Elasticsearch 및 관련 상표는 미국 및 기타 국가에서 Elasticsearch N.V.의 상표, 로고 또는 등록 상표입니다. 기타 모든 회사 및 제품 이름은 해당 소유자의 상표, 로고 또는 등록 상표입니다.