🔖

text-to-sql を試してみた

2024/06/14に公開

はじめに

text-to-sql は自然言語で SQL クエリに変換するということです。今回は Amazon Bedrock を使って、簡単に text-to-sql を試せる Streamlit アプリを作りました。

こんな感じ

イメージはこのようなものです。
text-to-sql-image

できること

  • Instruction の部分に指示を入力してクエリすると、SQL が生成され、データベースにクエリの結果が DataFrame として表示されます。
  • 生成された SQL を自然言語で説明します。生成されたものが意図に沿うものかを簡単に判別するためです。
  • 生成された SQL の表示と編集、編集後のもので再度クエリできます。
  • データベーススキーマを表示します。

コード

コードはこちらをご参照ください。試しにやってみただけなので割と適当なのですが、試せます。
最新の Bedrock Converse API を使っていますので、モデルを簡単に変えたり比較したりできます。
(DB 接続文字列 と AWS CLI のセットアップが必要です)
https://github.com/arvehisa/text-to-sql-bedrock/blob/main/text-to-sql-bedrock.py

Streamlit を使用して、データベーススキーマの表示、自然言語による SQL クエリの生成、生成された SQL の説明、SQL の編集と再実行などの機能を提供するシンプルなアプリケーションを実装しています。

工夫する点

精度をあげること、編集やチューニングしやすくするためにいくつか工夫している点があります。

1. データベーススキーマをプロンプトで渡すこと

当たり前ですが、スキーマを渡しています。

SELECT table_name, column_name, data_type 
FROM information_schema.columns 
WHERE table_schema = 'public'
ORDER BY table_name, column_name

こちらでデータベースのスキーマを抽出して、プロンプトで渡しています。
存在しないテーブル名やカラム名が捏造されないように、精度があがりエラー頻度を下げることができます。

2. 使用しているデータベースエンジンを指定すること

MySQL と PostgreSQL の構文は若干異なるため、エンジンを指定したほうが構文エラーを減らせます。
私は PostgreSQL を使っているため、プロンプトに PostgreSQL を明記しています。

割と適当に書いたのですが、プロンプトはこのようなシンプルなものです。

Your task is to generate SQL query based on provided database schema and the instruction.
The database engine is PostgreSQL.

Only generate the SQL itself, no any markdown like "```sql" or "```" or other formatting.

here is the database schema:
{schema_str}

here is the instruction:
{instruction}

3. 生成された SQL の意味を確認できるようにする

こちらは精度をあげるものというよりは、指示をチューニングしやすくするものです。

自然言語は曖昧なので想定しているものと違う結果が出ることも多いです。その場合は、どういうロジックで SQL 書かれてるかがわからないとそもそも「使えない」と思って諦めてしまいがちです。

SQL に慣れていればひと目で意味をつかめるかもしれませんが、実際 SQL や DB 構造をそこまで理解していない人にとっては呪文にしか見えないです。

生成された SQL をまた自然言語で簡単に解釈してあげることにより、SQL がわからない人でも生成された SQL が自分の意図に沿ったものなのか、ある程度確認できます。

sql-explanation

4. 生成された SQL の確認と編集をできるようにする

構文エラーを含めて、ひと目でわかるような問題を手動で修正したいときなどのために、編集して再度クエリできるようにしておきました。
modify-sql

5. データベーススキーマを表示しておくこと

一応どんなデータが取得可能かを参考までに表示させておくことで、指示の参考にもなります。

感想

ここからは自然言語での SQL 生成をやってみた感想になります。実際、自分のデータで試してほしいのですが、指示の出し方や選んでるモデルによってかなり異なる結果が出ました。

どこまでデータを意識できるか

生成 AI 全般に言えることですが、指示の出し方、プロンプトチューニングが重要です。
実際以下の点をイメージできるとより明確な指示を出せるようになり、意図に沿った結果を得られます。

  • どんなデータを出せるか(DBにあるデータ、ないデータの把握)
  • 手動でデータを取り出す場合、どのような計算ロジックを使うか

例えば、タイムスタンプを取っていないのに「最後に更新したもの」とかを指示するとそれが正しく出るわけがありません。

ただ、それが自分で DB のデータ構造に詳しくない、自分でデータの抽出や分析をしない場合、最初から意識して指示を出すことが難しいと思います。

自然言語の曖昧さ

プログラミング言語と異なり、自然言語は曖昧です。その曖昧さは、データ分析者への指示をする際でも人間が口頭でのやり取りで解消できることもありますが、それでも誤解が生じることがあります。

自然言語で SQL を生成することは比較的単純に見える部分もありますが、一言で指示を出そうとするとその曖昧さが増し、生成された SQL が意図と異なるケースも多々あります。

実際やってみて、明確だと思っていた言葉は、これだけの曖昧さが含まれているんだ、という目からウロコな瞬間もあります。ただ、どうやって曖昧さを排除しようとしても、全く汲み取ってくれないときもあります。

SQL 書ける人向け

本来は SQL がわからない人向けに自然言語でデータ抽出とデータ分析にできると良いと思ったのですが、SQL わからない人には結果の正確さの判断が難しく、また意図に沿わないときには諦めてしまいがちのように感じました。

それは一般的なコード生成も同じように思います。ある程度理解をしてると、実はあと一歩だ、ということがわかりチューニングしようと思えます。

私自身はデータ分析で SQL をかなり書いていた時期があり、デーブル数が多くテーブル間の依存関係が複雑な場合、それを読み解くのにかなり時間がかかっていました。しかし、このようにスキーマを LLM に渡して生成してもらう場合、人間では敵わないスピードで読み解いてくれるので、かなり楽になるのは間違いないです。

ただ、指示内容にはよりますが結果がガチャ気味なので、複雑なクエリの場合一発で正しい回答を期待するのではなく出力結果をみて指示を補足して調整すること、自分で編集することが現実的でしょう。

また、実際生成された内容が本当に意図に沿うものか念入りに確認した方が良いでしょう。

LLM 精度の影響

もちろん、生成された SQL がどれくらい意図を汲み取ってくれるかは使用している LLM の精度も影響します。
ここで詳細比較はしませんが、LLM によっては意図の汲み取り具合がかなり異なります。

また、モデルごとに癖があるため、1つのプロンプトを複数モデルに使い回さないことが重要です。例えば、デフォルトで Markdown でコードブロックを出しがちなモデルもありますし、なぞに最初に空行をいれるモデルもあります。使用しているモデルに合わせてプロンプトチューニングした方が良い結果が得られることをあらためて実感しました。

text-to-sql をプロダクトへの組み込み

最後に、本来 text-to-sql はもう少しアドバイスドに例えばチャットボットで自然言語で欲しい商品を出させるユースケースで利用するイメージもありますが(もともとそこに懐疑的ですが)、実際やってみてガチャ要素が強いのでそこで text-to-sql という手段を使うのは現実的ではないと思いました。

かなりクエリ範囲を狭めるとできるかもしれませんがその場合そもそも普通のキーワード検索をさせたほうが 100 倍安定しますので、text-to-sql をプロダクトに組み込む良さそうなユースケースはあまり想像できませんでした。

GitHubで編集を提案

Discussion