生成AIを用いた自然言語からSQLを生成(Text-to-SQL)するアプリケーションの実現

本記事では、生成AIを用いて自然言語からSQLを作成する技術について紹介します。

著者

山﨑 祐太

CEO

2024-12-20

2024-12-20

山﨑 祐太

生成AIを用いた自然言語からSQLを生成(Text-to-SQL)するアプリケーションの実現

#AI

#Engineering


概要

本記事では、生成AIを用いて自然言語からSQLを作成する技術について紹介します。

データベースからデータを取得するなどの操作のためには、SQLを書いて実行する必要があります。日常的にSQLに慣れ親しんだエンジニアであれば、「データベースから〜のデータを抽出して確認しよう」ということが簡単にできます。

しかしSQLに慣れ親しんだエンジニアでなければ、SQLを用いたデータの取得を含む集計業務はどうしても時間がかかります。

したがって今回はなるべく多くの人がデータ抽出をしやすい環境を実現するために、生成AIを用いて自然言語から対応するSQLを生成することに挑戦します。

Text-to-SQLの概要

Text-to-SQLは生成AIが発展する前から研究されている分野のひとつであり、自然言語で表現されたクエリをSQL文に変換することを指します。

大規模言語モデルを活用したText-to-SQLのプロンプト構成

大規模言語モデルを活用したText-to-SQLのシンプルなプロンプトとしては、下記のような構成のものが多く使われています。この次のセクションで記載する先行の技術調査で、LangChainやPinterest社の取り組みを見ていても、同様のプロンプト(Few Shot Promptingはどちらもない)で構成されています。

  • Text-to-SQLのタスク説明
  • ユーザーのクエリ
  • テーブルスキーマ
  • Few Shot Prompting(ユーザーのクエリと期待するSQLのペアを数件)

Text-to-SQLアプリケーションの実運用における課題

Text-to-SQLの技術自体はテーブル数が一定制限されているなどがあり、それなりの精度で止めて良いのであれば、特別難しいことではありません。しかしながらアプリケーションとしてユーザーが無邪気に使用することを想定するのであれば、セキュリティやユーザー体験など、考慮すべき事項がいくつかあります。

不正確なSQLの生成

生成AIが出力するSQLは常に正しいものにはなりません。対象となるテーブルやカラム名を間違えて実行不可能である可能性や、複雑なクエリであれば生成されたSQL文自体が誤っている可能性などがあります。

テーブル名やカラム名のミスについては、SQLAlchemy などのORMを用いてテーブルメタデータからルールベースのバリデーションをしていくことで対応できます。SQLの構文自体については、sqlparse などのライブラリを用いて、適切なSQL文になっているかどうかを確認する処理を挟んで対応します。

SQLインジェクション

SQLインジェクションで意図しないデータ操作が起こるリスクは避けなければなりません。データ分析用と限定するのであれば、データベースやデータに変更が加わるような、DDL、またINSERT、UPDATE、DELETEなどの処理を制限する必要があります。

LangChainではDO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.というプロンプトにより、この問題に対処しようとしていました。

最終的なバリデーションにおいては、sqlparse などのライブラリを使用することで、SQL文が安全なものであるかを確認する必要があります。

機密情報や個人情報の漏洩(認可制御)

このアプリケーションを使用するユーザーの権限等により、参照できるデータの範囲を制限する必要があります。いわゆるWebアプリケーションなどであれば、バックエンドのコードで認可制御をすることが多くなりますが、分析用にBIツール的に使用できるものであれば、行レベルセキュリティ などを活用したDBレイヤーでの認可制御が必要になることがあります。

非効率なクエリによるパフォーマンス低下

生成AIが作成するSQLのパフォーマンスが必ずしもパフォーマンスに優れるものとは限りません。生成AIのエージェント的な機構の中でパフォーマンスの良いクエリを作成することを指示するだけではなく、そもそもの取り扱えるデータを制約するなどして、一定のパフォーマンスを確保する必要があります。

精度面の課題

Text-to-SQLの精度面における課題としては、下記の4点があります。

  1. 自然言語の複雑さと曖昧さ
  2. スキーマの理解と表現における難しさ
  3. 頻度の少ないSQLの適切な生成
  4. 異なるドメインにおける精度担保の難しさ

自然言語の複雑さと曖昧さ

自然言語自体は非常に複雑であり曖昧で、よほど注意深く質問を投げないことには、正確に一対一でSQLに変換可能な表現にはなりません。

スキーマの理解と表現における難しさ

正確なSQLを生成するには、データベースのスキーマを詳細に把握する必要があります。また全てを表現しようにも、Context Windowの制約などもあり、効率的にスキーマを伝えなければなりません。

頻度の少ないSQLの適切な生成

サブクエリ、JOIN、Window関数などの複雑なSQLについては、学習データなどに多く含まれているわけではないので、適切な生成に難儀します。この場合はFew Shot PromptingやRAGなどを用いて、精度向上を目指す必要があります。

異なるドメインにおける精度担保の難しさ

ドメインが異なれば、質問文やSQL文に出てくる単語が大きく異なります。したがって学習データと異なる入出力を多く期待しなければならないので、どのように精度を担保するかという課題があります。

先行の技術調査

LangChainにおけるText-to-SQL

LangChainにはcommunityとして、SQLを自然言語から生成する機能が提供されています。具体的には create_sql_agentという関数で、必要な一連の処理をAgentとして取得して実行できます。

では、このコードがどのような処理を実行しているのかを見ていきます。

いくつか処理のパターンがあるのですが、最もリッチに処理をする場合、下記のような処理で構成されます。

  1. ユーザーのプロンプトを受け取る
  2. sql_db_list_tables: DBにあるテーブルの一覧を名称のみ取得し、プロンプトに追加する
  3. sql_db_schema: クエリから最も関連するテーブルに関するスキーマと3行のサンプルデータを取得し、プロンプトに追加する
  4. sql_db_query: SQLを生成して、DBにクエリを実行する
  5. 結果を元に回答を自然言語で生成して返す

サンプルのデータベースを提供している https://github.com/lerocha/chinook-database のリポジトリを使用して、create_sql_agentによる一連の処理を実行してみます。

データベースにはSQLiteを使用するため、下記を実行してSQLiteのファイルを取得します。

curl -s https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql | sqlite3 Chinook.db

下記のPythonコードを実行します。

import os from langchain_community.agent_toolkits import create_sql_agentfrom langchain_community.utilities import SQLDatabasefrom langchain_openai import ChatOpenAI os.environ["OPENAI_API_KEY"] = "sk-proj-xxx" if __name__ == "__main__": db = SQLDatabase.from_uri("sqlite:///Chinook.db") llm = ChatOpenAI(model="gpt-4o-mini") agent_executor = create_sql_agent(llm, db=db, agent_type="openai-tools", verbose=True) r = agent_executor.invoke("How many employees are there?") print(r)

上記コードを実行すると、従業員数が8名であるという結果が得られました。

{'input': 'How many employees are there?', 'output': 'There are 8 employees in the database.'}

実際にデータベースの中身をのぞいて見ると、確かに8名のデータがあるようです。

(1, 'Adams', 'Andrew', 'General Manager', None, '1962-02-18 00:00:00', '2002-08-14 00:00:00', '11120 Jasper Ave NW', 'Edmonton', 'AB', 'Canada', 'T5K 2N1', '+1 (780) 428-9482', '+1 (780) 428-3457', 'andrew@chinookcorp.com')
(2, 'Edwards', 'Nancy', 'Sales Manager', 1, '1958-12-08 00:00:00', '2002-05-01 00:00:00', '825 8 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 2T3', '+1 (403) 262-3443', '+1 (403) 262-3322', 'nancy@chinookcorp.com')
(3, 'Peacock', 'Jane', 'Sales Support Agent', 2, '1973-08-29 00:00:00', '2002-04-01 00:00:00', '1111 6 Ave SW', 'Calgary', 'AB', 'Canada', 'T2P 5M5', '+1 (403) 262-3443', '+1 (403) 262-6712', 'jane@chinookcorp.com')
(4, 'Park', 'Margaret', 'Sales Support Agent', 2, '1947-09-19 00:00:00', '2003-05-03 00:00:00', '683 10 Street SW', 'Calgary', 'AB', 'Canada', 'T2P 5G3', '+1 (403) 263-4423', '+1 (403) 263-4289', 'margaret@chinookcorp.com')
(5, 'Johnson', 'Steve', 'Sales Support Agent', 2, '1965-03-03 00:00:00', '2003-10-17 00:00:00', '7727B 41 Ave', 'Calgary', 'AB', 'Canada', 'T3B 1Y7', '1 (780) 836-9987', '1 (780) 836-9543', 'steve@chinookcorp.com')
(6, 'Mitchell', 'Michael', 'IT Manager', 1, '1973-07-01 00:00:00', '2003-10-17 00:00:00', '5827 Bowness Road NW', 'Calgary', 'AB', 'Canada', 'T3B 0C5', '+1 (403) 246-9887', '+1 (403) 246-9899', 'michael@chinookcorp.com')
(7, 'King', 'Robert', 'IT Staff', 6, '1970-05-29 00:00:00', '2004-01-02 00:00:00', '590 Columbia Boulevard West', 'Lethbridge', 'AB', 'Canada', 'T1K 5N8', '+1 (403) 456-9986', '+1 (403) 456-8485', 'robert@chinookcorp.com')
(8, 'Callahan', 'Laura', 'IT Staff', 6, '1968-01-09 00:00:00', '2004-03-04 00:00:00', '923 7 ST NW', 'Lethbridge', 'AB', 'Canada', 'T1H 1Y8', '+1 (403) 467-3351', '+1 (403) 467-8772', 'laura@chinookcorp.com')

またここで実際にどのようなプロンプトが送信されたのかを確認します。

初期プロンプトは、 https://github.com/langchain-ai/langchain/blob/master/libs/community/langchain_community/agent_toolkits/sql/prompt.py に定義されています。このコードがエージェントの種別により何通りかの組み合わされ方をしますが、ざっくりとこのような情報からSQLを生成しています。

SQL_PREFIX = """You are an agent designed to interact with a SQL database.Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.You can order the results by a relevant column to return the most interesting examples in the database.Never query for all the columns from a specific table, only ask for the relevant columns given the question.You have access to tools for interacting with the database.Only use the below tools. Only use the information returned by the below tools to construct your final answer.You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again. DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database. If the question does not seem related to the database, just return "I don't know" as the answer.""" SQL_SUFFIX = """Begin! Question: {input}Thought: I should look at the tables in the database to see what I can query. Then I should query the schema of the most relevant tables.{agent_scratchpad}""" SQL_FUNCTIONS_SUFFIX = """I should look at the tables in the database to see what I can query. Then I should query the schema of the most relevant tables."""

sql_db_list_tablesでは、DBに存在するテーブルの一覧を取得しています。下記のようにDB上にあるテーブルの名称のみを取得しています。

"Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track”

この取得したテーブル名の一覧を、初期プロンプトに挿入しています。下記がプロンプトの一部抜粋。

Then I should query the schema of the most relevant tables.\nAI: \nTool: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track

ここからmost relevantなテーブルとして、Employeeが選択され、その詳細情報をsql_db_schemaから取得します。sql_db_schemaでは、テーブルスキーマとデータの例を3行分取得します。

https://github.com/langchain-ai/langchain/blob/c2f1d022a2e55dfddd313e54d01250d3f64c6eb2/libs/community/langchain_community/utilities/sql_database.py#L300 に処理が記載されており、今回のケースでは下記の出力をプロンプトに加えます。

"CREATE TABLE "Employee" (
"EmployeeId" INTEGER NOT NULL,
"LastName" NVARCHAR(20) NOT NULL,
"FirstName" NVARCHAR(20) NOT NULL,
"Title" NVARCHAR(30),
"ReportsTo" INTEGER,
"BirthDate" DATETIME,
"HireDate" DATETIME,
"Address" NVARCHAR(70),
"City" NVARCHAR(40),
"State" NVARCHAR(40),
"Country" NVARCHAR(40),
"PostalCode" NVARCHAR(10),
"Phone" NVARCHAR(24),
"Fax" NVARCHAR(24),
"Email" NVARCHAR(60),
PRIMARY KEY ("EmployeeId"),
FOREIGN KEY("ReportsTo") REFERENCES "Employee" ("EmployeeId")
)
/*
3 rows from Employee table:
EmployeeId LastName FirstName Title ReportsTo BirthDate HireDate Address City State Country PostalCode Phone Fax Email
1 Adams Andrew General Manager None 1962-02-18 00:00:00 2002-08-14 00:00:00 11120 Jasper Ave NW Edmonton AB Canada T5K 2N1 +1 (780) 428-9482 +1 (780) 428-3457 andrew@chinookcorp.com
2 Edwards Nancy Sales Manager 1 1958-12-08 00:00:00 2002-05-01 00:00:00 825 8 Ave SW Calgary AB Canada T2P 2T3 +1 (403) 262-3443 +1 (403) 262-3322 nancy@chinookcorp.com
3 Peacock Jane Sales Support Agent 2 1973-08-29 00:00:00 2002-04-01 00:00:00 1111 6 Ave SW Calgary AB Canada T2P 5M5 +1 (403) 262-3443 +1 (403) 262-6712 jane@chinookcorp.com
*/"

上記をさらにプロンプトに加えます。

System: You are an agent designed to interact with a SQL database.\nGiven an input question, create a syntactically correct sqlite query to run, then look at the results of the query and return the answer.\nUnless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 10 results.\nYou can order the results by a relevant column to return the most interesting examples in the database.\nNever query for all the columns from a specific table, only ask for the relevant columns given the question.\nYou have access to tools for interacting with the database.\nOnly use the below tools. Only use the information returned by the below tools to construct your final answer.\nYou MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.\n\nDO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.\n\nIf the question does not seem related to the database, just return \"I don't know\" as the answer.\n\nHuman: How many employees are there?\nAI: I should look at the tables in the database to see what I can query. Then I should query the schema of the most relevant tables.\nAI: \nTool: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track\nAI: \nTool: \nCREATE TABLE \"Employee\" (\n\t\"EmployeeId\" INTEGER NOT NULL, \n\t\"LastName\" NVARCHAR(20) NOT NULL, \n\t\"FirstName\" NVARCHAR(20) NOT NULL, \n\t\"Title\" NVARCHAR(30), \n\t\"ReportsTo\" INTEGER, \n\t\"BirthDate\" DATETIME, \n\t\"HireDate\" DATETIME, \n\t\"Address\" NVARCHAR(70), \n\t\"City\" NVARCHAR(40), \n\t\"State\" NVARCHAR(40), \n\t\"Country\" NVARCHAR(40), \n\t\"PostalCode\" NVARCHAR(10), \n\t\"Phone\" NVARCHAR(24), \n\t\"Fax\" NVARCHAR(24), \n\t\"Email\" NVARCHAR(60), \n\tPRIMARY KEY (\"EmployeeId\"), \n\tFOREIGN KEY(\"ReportsTo\") REFERENCES \"Employee\" (\"EmployeeId\")\n)\n\n/*\n3 rows from Employee table:\nEmployeeId\tLastName\tFirstName\tTitle\tReportsTo\tBirthDate\tHireDate\tAddress\tCity\tState\tCountry\tPostalCode\tPhone\tFax\tEmail\n1\tAdams\tAndrew\tGeneral Manager\tNone\t1962-02-18 00:00:00\t2002-08-14 00:00:00\t11120 Jasper Ave NW\tEdmonton\tAB\tCanada\tT5K 2N1\t+1 (780) 428-9482\t+1 (780) 428-3457\tandrew@chinookcorp.com\n2\tEdwards\tNancy\tSales Manager\t1\t1958-12-08 00:00:00\t2002-05-01 00:00:00\t825 8 Ave SW\tCalgary\tAB\tCanada\tT2P 2T3\t+1 (403) 262-3443\t+1 (403) 262-3322\tnancy@chinookcorp.com\n3\tPeacock\tJane\tSales Support Agent\t2\t1973-08-29 00:00:00\t2002-04-01 00:00:00\t1111 6 Ave SW\tCalgary\tAB\tCanada\tT2P 5M5\t+1 (403) 262-3443\t+1 (403) 262-6712\tjane@chinookcorp.com\n*/

この後は実際にこのプロンプトからSQLを生成して実行することになるのですが、今回の主題であるSQLを自然言語から生成する文脈からは外れるので割愛します。

LangChainの処理はDBに実際に接続して様々な情報を付与しながら、自然言語からSQLを生成しています。実用を考える上では、データスキーマに大きな変更がない場合などは、事前にテーブル一覧やサンプルデータを静的ファイルなどに書き出しておき、SQL生成の過程ではDBへの接続をしないというケースも考えられそうです。

PinterestにおけるText-to-SQL

Pinterest社では How we built Text-to-SQL at Pinterest という記事において、自社のText-to-SQLの取り組みを紹介しています。

Pinterest社の初期のText-to-SQLの大きな仕組みは、LangChainとほぼ同様

  1. 関連するテーブルスキーマをテーブルメタデータから取得
  2. 「質問、Dialect(MySQL, PostgreSQLなど)、テーブルスキーマ」をプロンプトにする
  3. プロンプトをLLMに送信し、SQLを応答として受け取る

プロンプトに含めるテーブルスキーマ

  • Table name: テーブル名
  • Table description: テーブルの説明
  • Columns: カラム
  • Column name: カラム名
  • Column type: データ型
  • Column description: カラムの説明

を取り込んでいるようです。

Cadinalityの低いデータの取り扱い

選択式などのカラムにおいて、platform=’WEB’を意図していても、platform=’web’という生成結果になることがあります。そのような少数の選択肢の情報を、テーブルスキーマの中に投入しているようです。上記のスキーマ一覧の中であれば、Column descriptionの中に入れているのではないかと想定されます。

Context Windowの制限への対処

テーブルスキーマが大きくなると、LLMのContext Windowの制限を上回ることがあります。そのためpinterest社では、下記の対応を実施しているようです。

  1. テーブルスキーマの最小バージョンを使用する(テーブル名、列名、データ型のみなど)
  2. 不要な列の情報を除外する

初期に使用していたプロンプト

なぜか4がなくて5と6に飛んでいるのが気になりますが、このプロンプトを使用していたそうです。

You are a {dialect} expert.
Please help to generate a {dialect} query to answer the question. Your response should ONLY be based on the given context and follow the response guidelines and format instructions.
===Tables
{table_schemas)
===Original Query
{original_query}
===Response Guidelines
1. If the provided context is sufficient, please generate a valid query without any explanations for the question. The query should start with a comment containing the question being asked.
2. If the provided context is insufficient, please explain why it can't be generated.
3. Please use the most relevant table(s).
5. Please format the query before responding.
6. Please always respond with a valid well-formed JSON object with the following format
===Response Format
{{
"query": "A generated SQL query when context is sufficient.",
"explanation": "An explanation of failing to generate the query."
}}
===Question
{question}

精度評価

Spiderデータセットによる評価を実施し、文献内と同様の精度を確認できたようです。しかしながら実際に取り扱うアプリケーションより簡単な評価データセットのため、実運用における初回で適切なSQLを生成できた割合を確認しながら、適宜精度向上の取り組みをしていたようです。

またText-to-SQLの仕組みにより、タスク完了までの速度が35%向上していることを確認したとも記載されています。

テーブル選択にRAGを導入する

Pinterest社の数十万件あるDWHのテーブルから、適切なテーブルをユーザーが選択することは大きな課題であり、RAGを導入してユーザーがタスクに適したテーブルを選択しやすくする仕組みを、第二弾では導入したようです。

  1. テーブル概要とSQLクエリのログのそれぞれのEmbeddingを取り、OpenSearchに保存する
  2. ユーザーの質問からEmbeddingを取り、上位N件の適切なテーブルを取得する
  3. 「上位N件のテーブル、テーブルスキーマ、質問」から、LLMが最も関連性の高い上位K件のテーブルを選択する
  4. 上位K件のテーブルがユーザーに提示される
  5. ユーザーが選択したテーブルから、以前と同様のText-to-SQLの仕組みが動く

Pinterest社のText-to-SQLにおける課題

SQLのバリデーション

現在はLLMが生成したSQLをそのままユーザーに返しており、期待通りの動作が保証されていない状態であり、constrained beam searchなどを使用して更なる保証をユーザーに提供することを想定しているようです。

ユーザーフィードバック

テーブル検索はSQL生成結果に対するユーザーフィードバックを反映させやすいインターフェースを提供し、人と協働しながらSQLを生成してパフォーマンスを向上させることを想定しているとのことです。

Spider

Text-to-SQLに関する多くの研究がSpiderというデータセットで評価されています。最新の最も精度が良いモデル・研究については、 https://yale-lily.github.io/spider のリーダーボードをチェックしていけば良いでしょう。

シンプルなText-to-SQLの実装

LangChainは現時点では、必ずしも本番環境のアプリケーションとして実装するのに適したものであるとは言えません。ここでは https://github.com/openai/openai-python を主に使用し、Text-to-SQLプリケーションのコア部分を実装します。

import contextlibimport jsonimport os import openaiimport pydanticimport sqlalchemyimport sqlparsefrom sqlalchemy import ormfrom sqlalchemy.schema import CreateTablefrom sqlparse.tokens import DML, DDL engine = sqlalchemy.create_engine("sqlite:///Chinook.db")Session = orm.sessionmaker(bind=engine) @contextlib.contextmanagerdef session_scope(): session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close() def is_safe_sql(sql_statement: str) -> bool: # 本番稼働にはさらに厳密なチェックが必要 if sql_statement == "わかりません。": return False disallowed_types = (DML, DDL) parsed = sqlparse.parse(sql_statement) for statement in parsed: for token in statement.tokens: if token.ttype in disallowed_types: if token.value.upper() in [ "INSERT", "UPDATE", "DELETE", "CREATE", "ALTER", "DROP", "TRUNCATE", "REPLACE", "GRANT", "REVOKE", "MERGE", "CALL", "EXPLAIN", "LOCK", "UNLOCK", ]: return False # 不正なSQL文 return True # 安全なSQL文 class SQLOutput(pydantic.BaseModel): sql_statement: str if __name__ == "__main__": metadata = sqlalchemy.MetaData() metadata.reflect(bind=engine) table_names: set[str] = set(metadata.tables.keys()) # テーブル名の一覧 # 各テーブルのCREATE文を生成 create_statements = {} for table_name, table in metadata.tables.items(): # CreateTableオブジェクトを生成し、SQL文字列を取得 create_statement = str(CreateTable(table).compile(engine)) create_statements[table_name] = create_statement system_prompt: str = f""" あなたはSQLデータベースと対話するように設計されたエージェントです。 入力された質問に対して、構文的に正しいSQLiteクエリを作成してください。 データベースに対してDML文(INSERT、UPDATE、DELETE、DROPなど)を作成しないでください。 質問がデータベースと関係なさそうであれば、「わかりません。」を回答として返してください。 このデータベースのテーブルの一覧: {table_names} 各テーブルのスキーマ: {create_statements} """ user_prompt: str = "従業員は何名いますか?" os.environ["OPENAI_API_KEY"] = "sk-proj-xxx" client = openai.OpenAI() messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ] r = client.beta.chat.completions.parse( model="gpt-4o-mini", messages=messages, response_format=SQLOutput, ) response = json.loads(r.choices[0].message.content) messages.append({"role": "system", "content": response["sql_statement"]}) print(f"ユーザークエリ: {user_prompt}") print(f"生成されたSQL文: {response['sql_statement']}") if not is_safe_sql(response["sql_statement"]): print("不正なSQL文です。") exit(1) with session_scope() as session: r = session.execute(sqlalchemy.text(response["sql_statement"])).fetchall() print(f"SQL実行結果: {r}") analysis_prompt: str = f""" あなたはユーザーのクエリから、データベースの情報に基づき、適切な情報分析を行い、その報告を行うように設計されたエージェントです。 ユーザーのクエリに対して、データベースの情報を分析し、その結果を報告してください。 質問: {user_prompt} 質問に対するSQLクエリ: {response['sql_statement']} データベースの情報: {r} """ messages.append({"role": "user", "content": analysis_prompt}) r = client.beta.chat.completions.parse( model="gpt-4o-mini", messages=messages, ) print(f"分析結果: {r.choices[0].message.content}")

このコードを実行した結果、下記のような結果が得られました。

$ rye run python src/text2sql.pyユーザークエリ: 従業員は何名いますか?生成されたSQL文: SELECT COUNT(*) FROM Employee;SQL実行結果: [(8,)]分析結果: 従業員の人数は8名です。

分析に適さない質問が来た場合は、適切に「わかりません」という回答を出力し、分析しないような制御ができています。

user_prompt: str = "今日の昼ご飯はなんですか。"
$ rye run python src/text2sql.pyユーザークエリ: 今日の昼ご飯はなんですか。生成されたSQL文: わかりません。不正なSQL文です。

まとめ

本記事では、生成AIを活用して自然言語から適切なSQLを生成する方法について詳しく解説しました。LangChainの実装を分析し、自前でText-to-SQLを実装する手順を紹介することで、SQLに不慣れなユーザーでもデータベースから必要な情報を容易に取得できる可能性を示しました。

アプリケーション化する際の注意点として、今回は不正確なSQLの生成とSQLインジェクションに対する一部の対応を実施しました。実際にはさらなる対応をした上で、安全かつユーザー体験を損なわない制御を提供する必要があります。

当社では、最新のAI技術を活用したソリューション開発に注力しており、データ活用の課題解決や業務効率化を支援しています。生成AIによるText-to-SQLの導入にご興味がある方は、ぜひお気軽にお問い合わせください。

お問い合わせはこちらから

Share


xのアイコンfacebookのアイコンこのエントリーをはてなブックマークに追加

Author


著者

山﨑 祐太

CEO

神⼾⼤学と神⼾⼤学⼤学院にて深層学習に関する研究を⾏い、⼤阪のAI ベンチャーで機械学習エンジニアとして従事。株式会社Digeonを創業。


共に働く仲間を募集しています

Digeonは意欲のある方を積極的に採用しています。
神戸発のAIベンチャーでAIの社会実装を一緒に進めませんか?

採用ページはこちら
logo
Engineering Portal
ディジョンのエンジニア情報ポータルサイト
©株式会社Digeon All Rights Reserved.