このレッスンのロックを解除するには、まず「基礎:AIエージェントがどのように知覚し、計画し、行動するか」を完了してください。前のレッスンに戻る

はじめに

AIエージェントは、ユーザーの代わりにリレーショナルデータベースと直接やり取りできる場合に、はるかに有用になります。「前四半期のトップセラープロダクトは何ですか?」や「この顧客の配送住所を更新してください」といった質問に、人間が手動でSQLを書くことなく答えられます。課題は、エージェントに実際のデータベース権限を与えながら、破壊的または安全でないクエリの実行を防ぐことです。このレッスンでは実践的な構成要素を順を追って説明します。エージェントにスキーマを認識させ、SQLを生成し、そのSQLを検証し、それから初めて実際のデータベースに対して実行する手順です。

エージェントにスキーマ認識を付与する

存在するテーブルやカラムがわからなければ、エージェントは正しいSQLを書けません。標準的な手法は、データベーススキーマの簡潔な説明をシステムプロンプトの一部として、または取得したコンテキストブロックとしてモデルに供給することです。PostgreSQLデータベースの場合、information_schemaを使ってテーブルとカラムを出力し、モデルに渡す前にその出力を絞り込みます。

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

返された行は、エージェントがクエリを構築する際に参照できるテキスト要約になります。これがないと、モデルがカラム名を幻覚(hallucination)し、何度も失敗します。

エージェントがユーザーの質問を受け取り、データベースメタデータからスキーマコンテキストを取得し、SQL候補を生成し、SQLを検証し、その後実行または拒否する様子を示すフローチャート

SQLの生成と検証

エージェントがスキーマコンテキストを取得したら、ユーザーリクエストに応じて単一のSQLステートメントを生成するよう指示します。その出力をパーサーでラップし、実行前に検査できるようにします。堅牢なパイプラインは次の3つを順番に行います:(1) 単一ステートメントでないものをすべて拒否する、(2) 先頭の動詞が拒否リスト(DROP、TRUNCATE、WHEREなしDELETE、WHEREなしUPDATE、GRANTなど)にあるステートメントを拒否する、(3) 意図に応じて読み取り専用またはパラメータ制限付きの接続でステートメントを実行する。

python
import sqlparse
FORBIDDEN = {"drop", "truncate", "grant", "revoke"}
def safe_sql(raw: str) -> str:
parsed = sqlparse.parse(raw)
if len(parsed) != 1:
raise ValueError("Must be exactly one statement")
stmt = parsed[0]
first_token = stmt.get_type().lower()
head = stmt.token_first(skip_cm=True).value.lower()
if head in FORBIDDEN:
raise ValueError(f"Forbidden verb: {head}")
return str(stmt)

この検証レイヤーは、SQLがデータベースに触れる*前*に実行されます。パイプライン全体で最も重要な保護層です。

制限された接続での実行

検証があっても、多層防御は重要です。すでに権限が制限されているユーザーとしてデータベースに接続します。分析用の質問には読み取り専用ロールを、アプリが必要とする特定のINSERT/UPDATE権限のみを持つ別のロールを用意します。セッションレベルで短いステートメントタイムアウトを設定し、暴走したクエリがテーブルを長時間ロックできないようにします。

sql
SET statement_timeout = '3s';
SET idle_in_transaction_session_timeout = '5s';

これらのタイムアウトと組み合わせて、生成されたSELECTに行数制限を追加します(例えば、ユーザーが明示的にオプトアウトしない限り、常にLIMIT 100を追加する)。これにより、エージェントが100万行のテーブルをメモリに取り込んで停止することがなくなります。

2つのデータベース接続ロール — 読み取り専用の分析ロールとスコープ付きの書き込みロール — を比較し、それぞれがどのSQLステートメントの実行を許可されているかを示すブロック図

よくある落とし穴

最も多い失敗は、エージェントがSQLを生のままチャットにエコーバックし、実際には実行しないことです。ユーザーはそれをコピーして自分で実行するため、監査証跡が失われます。もう一つは、モデルに同じプロンプト内で「クエリが安全かどうかを判断する」ことを信頼することです。自己ポリシングは信頼できないため、必ずコード内で検証を実施してください。最後に、大規模なデータベースの完全なスキーマを一度に渡すのは避けてください。モデルは決して使わないテーブルでコンテキストを浪費するため、リクエストごとに関連テーブルのみに絞り込むか、埋め込みを使って一致するテーブルのみを取得してください。

まとめ

エージェントをSQLに接続することは、多層的な問題です。まずスキーマメタデータを公開してモデルが実際のSQLを書けるようにし、次に実行前にコード内ですべてのステートメントを生成、解析、検証します。バリデーターをロールベースの権限と接続レベルのタイムアウトでバックアップし、ミスや悪意のあるプロンプトがテーブルを削除できないようにします。スキーマコンテキスト、コードレベル検証、制限されたロール、タイムアウトという4つの層があれば、エージェントはデータベースを危険にさらすことなく、データ質問に答えたり安全な書き込みを行ったりできます。

レッスンの練習

レッスンのチェックポイント

学んだ内容を復習し、回答へのフィードバックを受けましょう。

0/8
01スキーマメタデータをエージェントに供給することが、正確なSQL生成の前提条件となるのはなぜですか?
02コンテキストに関わらず、検証レイヤーによって明示的に拒否すべきSQL動詞はどれですか?
03safe_sql Python関数におけるlen(parsed) != 1チェックの目的は何ですか?
04コード内でのSQL検証だけでなく、セッションレベルでstatement_timeoutを設定するのはなぜですか?
05大規模データベースの完全なスキーマをエージェントのプロンプトに渡すと、どのような問題が発生しますか?
06エージェントにプロンプト内で自身のSQLを「自己ポリシング」させるのが信頼できないと見なされるのはなぜですか?
07このセットアップにおける読み取り専用データベースユーザーの役割は何ですか?
08エージェント駆動SQLのための多層防御を最もよく表す順序はどれですか?
回答済み · 0/8