This tutorial builds an agent with function calling where the tool exposed to the model never executes arbitrary SQL. It combines two mechanisms already verified in the Aurabase code: the NL2SQL validator and a read-only Postgres transaction, two bricks of thenative AI integrated into thebackend. Prerequisites: an Aurabase project, its service_rolekey, and an account with one of the three native LLM providers (OpenAI, Anthropic, Gemini).
必需品
- 本当のリスクはそれ自体を呼び出す関数ではなく、モデルに公開されるツールです。つまり、生の
execute_sql(query)によってモデルに完全な SQL アクセスが与えられます。 - 安全なアーキテクチャは、直接実行ではなく構文ツリー検証ツール (SELECT のみ、制限付き LIMIT、分離スキーマ) に委任する
query_database(question)ツールを公開します。 - Aurabase は、このバリデータをネイティブに公開します (
/nl2sql)。これをツールの実装として再利用することで、SQL 検証を自分でコーディングし直す必要がなくなります。 - コミットされた SQL は、単純なテキスト フィルターではなく、実際の読み取り専用 Postgres トランザクションである
readOnly: trueモードのaura.db.sql()を通じて実行されます。 - Aurabase のネイティブ
/chatエンドポイントは、toolロールもtoolsパラメータもまだ受け入れません (コードで確認)。エージェント ループは現在、Aurabase プロキシ経由ではなく、LLM プロバイダーの SDK 経由で実行されます。 service_roleキーは設計により RLS をバイパスします。バックエンドを離れることはできず、エージェントは通常の認証されたユーザーよりも広範なアクセスを継承します。
何を構築するか
そのまま実行される SQL をモデルに記述させることなく、Postgres プロジェクト内のデータに関する自然言語の質問に答えるエージェントを構築します。モデルは query_databaseという名前のツールを呼び出します。このツールは質問を NL2SQL で検証された SQL に変換し、この読み取り専用 SQL を実行してモデルに行を返し、答えを定式化します。
このチュートリアルでは、サーバー側で @aurabase/aurabase-js JavaScript SDK (ブラウザー側では決して使用しないでください。service_role キーはクライアントに公開しないでください) と、エージェント ループの API を呼び出す OpenAI 関数を使用します。同じ原則が Anthropic または Gemini SDK にも当てはまります。
「この SQL を実行」ツールが危険な理由
一部の公式ガイドを含むほとんどの Postgres エージェント チュートリアルでは、SQL 文字列を引数として受け取り、それをそのまま実行する execute_sql 関数という単一のツールが定義されています。モデルは、ユーザーの質問とコンテキストで与えられたスキーマに基づいて、この文字列自体を書き込みます。
この選択により、モデルが確実に果たせない責任がモデルに移ることになります。質問にプロンプトインジェクションが組み込まれると、ツールが「正当な」クエリがどのようなものであるべきかについての概念がないため、ツールが無差別に実行する破壊的な SQL が生成される可能性があります。この攻撃ベクトルについては、専用の記事で詳しく説明しています: SQL インジェクションから NL2SQL を保護する。
このチュートリアルに組み込まれている代替ツールでは、より範囲の狭いツール query_database(question)が公開されています。モデルは SQL を直接書くことができなくなり、独自のツール呼び出しで質問することしかできなくなります。 Aurabase NL2SQL エンジン は、この質問を構文ツリー検証ツール (SELECT のみ、サブクエリなし、許可される関数は 10 個、制限付き LIMIT) に渡す前に SQL に変換します。
execute_sql(query: string) ツールを使用すると、システム プロンプトの品質に関係なく、モデルに完全な SQL アクセスが与えられます。命令 (「SELECT のみを実行する」) は、モデルが従ったり、誤って解釈したり、ユーザーの質問に差し込まれたインジェクションによって回避されたりする可能性のある命令のままです。
モデルに公開されるツールのスキーマを定義する
Aurabase の 3 つのネイティブ LLM プロバイダ (OpenAI、Anthropic、Gemini) は、JSON スキーマ形式のツール定義のテーブルを受け入れます。このエージェントには query_databaseという 1 つのツールで十分です。このツールは自然言語のみで質問を受けます。モデルは、SQL スキーマも、モデル自身が入力できる query フィールドも認識しません。
ツールを実装します: NL2SQL を読み取り専用にします
ツール ハンドラーはブラウザーではなくバックエンドで実行されます。これにはプロジェクト キー service_roleが含まれていますが、これは仕様により RLS をバイパスするため、クライアントに公開されるべきではありません。 Aurabase SDK に対して 2 つの呼び出しを行います。
最初の呼び出しでは、質問が aura.ai.nl2sql()によって検証された SQL に変換されます: SELECT のみ、LIMIT 制限あり、システム カタログへのアクセスなし。 2 つ目は、 aura.db.sql()経由で検証済みの SQL を readOnly: true オプションを使用して実行します。その後、上流の NL2SQL によってすでに適用されているテキスト検証とは関係なく、Postgres 自体がこのトランザクションでの書き込みを拒否します。
readOnly: true は、実際の読み取り専用 Postgres トランザクションをトリガーします。エンジンは書き込みを拒否します。これはリクエスト テキストに適用されるフィルターではありません。 NL2SQL の SELECT のみの検証と組み合わせると、エージェントには 2 つの独立した層があり、一方に欠陥があっても、もう一方には依然として問題が残ります。
エージェント ループ: サプライヤーの SDK 側での関数呼び出し
Aurabase は 3 つのネイティブ LLM プロバイダを公開していますが、その /chat エンドポイントはまだ tools パラメータまたは toolロールを中継していません。 ChatOptions は temperature、 max_tokens 、 modelのみをサポートし、受け入れられるロールは system、 user 、 assistant に限定されます ( llm/mod.rs および handlers/chat.rsで検証)。したがって、関数呼び出しループは現在、Aurabase プロキシ経由ではなく、プロバイダーの SDK 経由で直接実行されます。
Aurabase がツール呼び出しをネイティブに調整しない限り、バックエンドは OpenAI、Anthropic、または Gemini SDK を使用してループ自体を管理する必要があります。 NL2SQL と SQL の実行は、このループ内での従来の Aurabase 呼び出しのままです。
LangChain や Azure AI Agent などのサービスを使用してエージェントをオーケストレーションする場合でも、原則は変わりません。フレームワークで宣言されたツールは同じ query_databaseのままでなければならず、生の SQL エグゼキューターであってはなりません。私たちの比較では、LangChain と LlamaIndex が Postgres で実際の価値を提供する場所と、特に複雑さを増す場所を詳しく説明します: LangChain または LlamaIndex を備えた Postgres エージェント。
実際の質問でテストする
エージェントに送信された質問: 「今月注文したプレミアム顧客は何名ですか?」 "。テンプレートは、SQL を見たり書いたりすることなく、この質問をそのまま使用して query_database を呼び出します。ツールによってトリガーされた 2 つの内部呼び出しの結果は次のとおりです。
モデルの最終的な答えは、推測ではなく、これらの実際の線に基づいています。ツールがゼロ行を返す場合、検証済みデータなしで応答するモデルに比べて、数字の幻覚が発生する可能性は大幅に低くなります。
本番環境に入る前にエージェントを保護する
service_roleキーは、モデルに送信されるプロンプト、ログ、クライアント側の環境変数など、バックエンドから離れることはありません。- プロジェクトがアプリケーションの他の場所に書き込む必要がある場合でも、
readOnly: trueは、この特定のツールのaura.db.sql()上でアクティブなままになります。 service_roleは設計により RLS をバイパスします。質問しているユーザーに応じてエージェントが異なる応答をする必要がある場合は、SQL で明示的にフィルターするか、RLS を尊重する従来の PostgREST エンドポイントにフォールバックします。 マルチテナント RLS 分離を参照してください。- 各ツール呼び出しをログに記録します (質問された質問、検証された SQL、行数)。これは、質問によって予期しない結果が生じた場合に使用できる唯一のトレースです。
- Aurabase のレート制限と月次割り当ては、
/nl2sqlのプロジェクトごとにすでに適用されています。おしゃべりなエージェントが黙って AI 予算を超えることはできません。
注意すべき電流制限
query_database ツールは、NL2SQL バリデーターの制限をすべて継承しています。つまり、サブクエリなし、CTE/WITH なし、UNION なし、および 10 個の SQL 関数のクローズド リストです。必然的にサブクエリを必要とする質問 (「注文したことのない顧客」) は、NL2SQL に強制的に組み込むのではなく、再定式化するか、2 番目の専用ツールで処理する必要があります。
現在、Aurabase /chat プロキシ内にはツール呼び出しのオーケストレーションは存在しません。ここで説明されているエージェント ループは、マネージド サービス内ではなく、アプリケーション コード内に存在します。エージェントが複数のツール (データベースとドキュメンタリー RAG など) をチェーンする必要がある場合、2 つの呼び出しを調整するのはバックエンドです。
RAG と関数呼び出しの組み合わせ
このチュートリアルでは、リレーショナル データに関する構造化された質問について説明します。非構造化コンテンツ (ドキュメント、チケット、メモ) に関する質問については、同じエージェントが Aurabase のネイティブ RAG (pgvector、HNSW 検索) に接続された 2 番目のツールを公開できます。 2 つの機能とその表現については、 Postgres のネイティブ AIページで詳しく説明されています。