every Tech Blog

株式会社エブリーのTech Blogです。

OpenAI Assistants APIを使って分析用SQLを生成してみる

こんにちは。開発本部のデータ&AIチームでマネージャー兼データサイエンティストをしている伊藤です。

この記事は every Tech Blog Advent Calendar 12日目の記事です。

今回は、先日発表されたOpenAI Assistants APIを使って分析用のSQLの生成を試してみた取り組みについて紹介します。

分析用SQLの生成

最近1年ほどでLLMが注目されるようになり、ChatGPTをはじめLLMを使ってより効率的に処理できる作業が増えてきています。

自分自身も、社内のChatAppにコーディングを手伝ってもらったり、ちょっとした文章の整形や知識の深掘りに活用しています。

中でも、データサイエンティストとしてLLMで効率化したい作業の一つにデータ分析、特にSQLの作成があると思います。

LLMによってSQLが生成できるようになると、データサイエンティストをはじめ分析者がより効率的に分析できるようになるのはもちろん、 普段SQLを書かない社員が分析する際の助けにもなります。

SQLの生成自体は現在のLLMでも可能ではありますが、世の中にリリースされているLLMは自社独自のデータ構造等のドメイン知識は学習していないため、 社内での分析利用を目指すのであれば、そういったドメイン知識をいかに扱うかが重要となります。

直近 Amazon Q Generative SQLのプレビュー版が発表されるなど、ドメイン知識を活用したSQL生成はLLM活用の中でも関心の高い領域となっていそうです。

今回紹介する取り組みは、11月の中旬に開催された挑戦weekで実施したもので、 当時リリースされたばかりだった OpenAI Assistants API を使った内容を紹介できればと思います。

OpenAI Assistants APIについて

OpenAI Assistants APIは、チャットボットのようなアプリケーションにCode InterpreterやRetrieval、Function callingといった"Tool"を 簡単に組み込める機能を提供しています。

https://platform.openai.com/docs/assistants/overview

詳しい使い方は公式ドキュメントに委ねますが、主要な構成要素として

  • ドメイン知識などのデータが記録されているFile
  • 入力に対してFileを使った処理を行うTool
  • 入力に対するレスポンスとして生成されるMessage
  • 過去やり取りされたMessage群を一連の会話として保持しているThread
  • FileやToolを利用し、入力に対してMessageを作成するAssistant

があり、それらは

  1. AssistantにFile、Toolを紐づける(それぞれ複数指定可能)
  2. Threadを立ててユーザーからMessageを入力する
  3. ThreadをAssistantに渡すと、LLMからのレスポンスがMessageとしてThreadに追加される

といった関係性で動作します。

Assistants API 概念図

開発時には、Fileの作成やToolの選定を適切に実施するだけで、目的に応じたAssistantの作成が可能です。

方針

今回作成するSQL生成アプリケーション(以下SQLジェネレーターと呼びます)は、社員が誰でも利用できるようなツールを目指しています。

現状エブリーのデータ基盤のうち、社員全員が利用できる部分ではRedashからTreasureData (Presto) を参照しているため、 TreasureData上で実行可能なSQLの生成を目標とします。

要件を整理すると、

  1. ユーザーから知りたい内容の質問を受け取り
  2. 質問内容を集計できるTreasureData (Presto) で動作するSQLを返す

ような方針で設計を行いました。

SQLジェネレーター構成

まずToolの選定ですが、入力に基づいてFileを参照し、必要なドメイン知識を抽出する役割が必要なため、Retrievalを採用しました。

本来、外部のドメイン知識を扱う際には、それらを適切な長さのチャンクに分割し、ベクトルデータベース等に格納しておく必要がありますが、 Retrieval Toolではその辺りをOpenAI側で処理しているため、深く考慮せずとも実装が可能です。

ドメイン知識に関しては、テーブル情報を記載した表や特殊な関数の使い方をまとめたものをPDFファイルとしてアップロードしました。

ユーザーの質問をAssistantに渡す部分では、LLMがタスク内容を解釈しやすいように、入力するプロンプト内で

  • ユーザーから要求されている分析内容を集計できるSQLを作成する
  • 利用するデータソースやカラム、独自関数を使った期間指定の方法等はファイルを参考にする
  • TreasureDataでそのまま実行できるSQLを出力する

といった内容を記述しています。

実験

まずは、何もドメイン知識を与えない状態で、社内ChatApp (GPT-4-turbo) にSQLを生成させてみます。

SQL生成例1

当然ですが、ドメイン知識を何も渡していないため、このままではTreasureData上で実行できません *1

続いて、 SQLジェネレーターに同じ質問を投げかけてみます。

SQL生成例2

今度は、検索データのevent_searchテーブルを参照した上で、適切なカラムのselect、search_typeやtag_idなどの条件指定、TreasureDataの独自関数 (TD_TIME_FORMAT、TD_TIME_RANGE) の使用が適切で、 実際に使用できるSQLとなっています*2

実行例

実際の分析でよく利用されるデモグラフィックデータとの突合も試してみます。

SQL生成例3

検索データのevent_searchに加えて年代情報のあるuser_masterテーブルとカラムを正しく指定できており、join操作含めて正しいSQLになっています。

他にも、ウインドウ関数を使った移動平均の集計なども対応でき、ドメイン知識を取り込んだSQLの生成が簡単に実装できました。

ドメイン知識を適切に扱えないケース

前節ではうまく生成できた結果をピックアップして紹介しましたが、もちろん間違ったSQLが出てくることも多くあります。

間違え方としては、SQLの文法自体が誤っているケースはほとんどなく、主に独自関数の使い方に集中していました。

特に、期間指定に用いる独自関数の中でも、TD_TIME_RANGEよりもTD_INTERVALという関数の扱い方は間違っている割合が高く、 ファイルに記載しているにも関わらず、入力パラメータや出力値の型を間違って認識していました。

試しに、ドメイン知識を与えない状態でTD_TIME_RANGE、TD_INTERVALそれぞれの意味を質問したところ、以下のような回答が返ってきました。

TreasureData独自関数に関する回答

回答では、TD_TIME_FORMATについては正しい内容が書かれてますが、TD_INTERVALは間違った回答(Hallucination)になっています*3

SQLジェネレーターにおいて、TD_INTERVALの使い方は必ず間違えるわけではなく、正解する場面も見られたため、 プロンプトや確率的な変数など何かしらの要素が起因してファイル中の知識よりもGPTが持っている知識体系にバイアスを受けている可能性がありそうでした。

このようなLLM自体が持つバイアスへの対処は、ファイル中のドメイン知識の書き方やプロンプトエンジニアリングなどで解消できる可能性はあるため、今後の課題と考えています。

今後の展望

今回作ったSQLジェネレーターは、改善点は多く残されてはいますが、社内ChatAppの拡張機能としてベータ版を展開予定です。

今後の取り組みとしては、レスポンス速度や精度の改善、SQLが間違っている場合に修正しやすい仕組みの提供といった機能的な改善はもちろん、 社員が普段見ているドキュメントをAssistantsのドメイン知識にどのように転換させるかといった運用的な改善も考えられます。

今後もLLMを中心とした業務効率の改善に、チーム全体として取り組んでいければと思います。

*1:入力プロンプト内にドメイン知識を記述して生成させる手段はありますが、今回はユーザーから直接ドメイン知識を与えない状況を想定しているため考えないものとします。

*2:具体的な値にはぼかしを入れています

*3:公式ドキュメント https://docs.treasuredata.com/display/public/PD/Supported+Presto+and+TD+Functions#SupportedPrestoandTDFunctions-TD_INTERVAL