PROD欧州主権の BaaS プラットフォームダッシュボードを開く →

ネイティブAI · 10 分読み取り

関数呼び出しによる安全な Postgres エージェント (チュートリアル)

Affane Daylami · Fondateur · 2026年3月21日

ブログに戻る

Postgres にクエリを実行するエージェントは、コードの最初の行の前に、「モデルにどの関数を公開していますか?」という特定のセキュリティの質問をします。 LLM が呼び出すことができるツールが自身で作成した SQL を直接実行する場合、プロジェクト内のテーブルを読み取るにはあいまいな質問またはプロンプト インジェクションで十分です。

この英語のテキストはフランス語のオリジナルから自動的に生成されたもので、まだレビューされていません。
このページは自動翻訳されました。英語版が正式です。

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 関数という単一のツールが定義されています。モデルは、ユーザーの質問とコンテキストで与えられたスキーマに基づいて、この文字列自体を書き込みます。

tool-schema-dangereux.json (アンチパターン)json
{
  "name": "execute_sql",
  "parameters": {
    "query": { "type": "string" }  // モデルは SQL を直接書き込みます
  }
}

この選択により、モデルが確実に果たせない責任がモデルに移ることになります。質問にプロンプ​​トインジェクションが組み込まれると、ツールが「正当な」クエリがどのようなものであるべきかについての概念がないため、ツールが無差別に実行する破壊的な SQL が生成される可能性があります。この攻撃ベクトルについては、専用の記事で詳しく説明しています: SQL インジェクションから NL2SQL を保護する。

このチュートリアルに組み込まれている代替ツールでは、より範囲の狭いツール query_database(question)が公開されています。モデルは SQL を直接書くことができなくなり、独自のツール呼び出しで質問することしかできなくなります。 Aurabase NL2SQL エンジン は、この質問を構文ツリー検証ツール (SELECT のみ、サブクエリなし、許可される関数は 10 個、制限付き LIMIT) に渡す前に SQL に変換します。

システムプロンプトはセキュリティチェックではありません

execute_sql(query: string) ツールを使用すると、システム プロンプトの品質に関係なく、モデルに完全な SQL アクセスが与えられます。命令 (「SELECT のみを実行する」) は、モデルが従ったり、誤って解釈したり、ユーザーの質問に差し込まれたインジェクションによって回避されたりする可能性のある命令のままです。

#
ステップ1

モデルに公開されるツールのスキーマを定義する

Aurabase の 3 つのネイティブ LLM プロバイダ (OpenAI、Anthropic、Gemini) は、JSON スキーマ形式のツール定義のテーブルを受け入れます。このエージェントには query_databaseという 1 つのツールで十分です。このツールは自然言語のみで質問を受けます。モデルは、SQL スキーマも、モデル自身が入力できる query フィールドも認識しません。

lib/agent-tools.tstypescript
export const tools = [
  {
    type: 'function',
    function: {
      name: 'query_database',
      description:
        "Interroge les données du projet en langage naturel. N'accepte pas de SQL : posez une question.",
      parameters: {
        type: 'object',
        properties: {
          question: {
            type: 'string',
            description: 'Question en français sur les données du projet.'
          },
        },
        required: ['question'],
        additionalProperties: false
      },
    },
  },
]
#
ステップ2

ツールを実装します: NL2SQL を読み取り専用にします

ツール ハンドラーはブラウザーではなくバックエンドで実行されます。これにはプロジェクト キー service_roleが含まれていますが、これは仕様により RLS をバイパスするため、クライアントに公開されるべきではありません。 Aurabase SDK に対して 2 つの呼び出しを行います。

最初の呼び出しでは、質問が aura.ai.nl2sql()によって検証された SQL に変換されます: SELECT のみ、LIMIT 制限あり、システム カタログへのアクセスなし。 2 つ目は、 aura.db.sql()経由で検証済みの SQL を readOnly: true オプションを使用して実行します。その後、上流の NL2SQL によってすでに適用されているテキスト検証とは関係なく、Postgres 自体がこのトランザクションでの書き込みを拒否します。

server/tools/query-database.tstypescript
// クライアントはservice_roleキーで初期化されますが、ブラウザ側では初期化されません
import { aura } from '@/lib/aurabase'

export async function queryDatabase(question: string) {
  const { data: validated, error } = await aura.ai.nl2sql(
    question,
    undefined,
    { limit: 50 },
  )
  if (error) return { error: error.message }

  const { data: rows, error: execError } = await aura.db.sql(
    validated.sql,
    [],
    { readOnly: true },
  )
  if (execError) return { error: execError.message }

  return { sql: validated.sql, rows }
}
アストゥチェ

readOnly: true は、実際の読み取り専用 Postgres トランザクションをトリガーします。エンジンは書き込みを拒否します。これはリクエスト テキストに適用されるフィルターではありません。 NL2SQL の SELECT のみの検証と組み合わせると、エージェントには 2 つの独立した層があり、一方に欠陥があっても、もう一方には依然として問題が残ります。

#
ステップ3

エージェント ループ: サプライヤーの 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 呼び出しのままです。

server/agent.tstypescript
import OpenAI from 'openai'
import { tools } from './lib/agent-tools'
import { queryDatabase } from './tools/query-database'

const openai = new OpenAI()

export async function askAgent(question: string) {
  const messages = [{ role: 'user', content: question }]

  const first = await openai.chat.completions.create({
    model: 'gpt-4.1', messages, tools,
  })

  const call = first.choices[0].message.tool_calls?.[0]
  if (!call) return first.choices[0].message.content

  const args = JSON.parse(call.function.arguments)
  const result = await queryDatabase(args.question)

  const second = await openai.chat.completions.create({
    model: 'gpt-4.1',
    messages: [
      ...messages,
      first.choices[0].message,
      { role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) },
    ],
  })

  return second.choices[0].message.content
}

LangChain や Azure AI Agent などのサービスを使用してエージェントをオーケストレーションする場合でも、原則は変わりません。フレームワークで宣言されたツールは同じ query_databaseのままでなければならず、生の SQL エグゼキューターであってはなりません。私たちの比較では、LangChain と LlamaIndex が Postgres で実際の価値を提供する場所と、特に複雑さを増す場所を詳しく説明します: LangChain または LlamaIndex を備えた Postgres エージェント。

#
ステップ4

実際の質問でテストする

エージェントに送信された質問: 「今月注文したプレミアム顧客は何名ですか?」 "。テンプレートは、SQL を見たり書いたりすることなく、この質問をそのまま使用して query_database を呼び出します。ツールによってトリガーされた 2 つの内部呼び出しの結果は次のとおりです。

ツール結果(抜粋)json
{
  "sql": "SELECT count(*) FROM orders WHERE customer_plan = 'premium' AND created_at >= date_trunc('month', now()) LIMIT 50",
  "rows": [{ "count": 128 }]
}

モデルの最終的な答えは、推測ではなく、これらの実際の線に基づいています。ツールがゼロ行を返す場合、検証済みデータなしで応答するモデルに比べて、数字の幻覚が発生する可能性は大幅に低くなります。

#
セキュリティ

本番環境に入る前にエージェントを保護する

  • 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ページで詳しく説明されています。

#
よくある質問

よくある質問

エージェントに書き込みアクセス (INSERT/UPDATE) を与えることはできますか?+
技術的には、readOnly オプションを削除して別のツールを指定することで可能ですが、それは現在の NL2SQL では行われていません。クライアント側で選択された実行オプションに関係なく、バリデーターは SELECT クエリのみを許可します。書き込みエージェントは、独自の機能のホワイトリストとおそらく実行前の人間による確認を備えた別個のバリデーターを要求します。
LangChain、LlamaIndex、または Azure AI Agent などのサービスと互換性がありますか?+
はい: これらのフレームワークは関数呼び出しループを調整しますが、ツールの実装はユーザーが行います。同じハンドラー (NL2SQL の場合は読み取り専用実行) は、生の SQL を実行させるのではなく、LangChain または Azure エージェントで宣言されたツールの関数として自身を接続します。

導入の準備はできていますか?

5 分でバックエンドが完成します。

クレジット カードは不要 · 500 MB 無料 · 50,000 MAU