PROD欧洲主权BaaS平台打开仪表板 →

原生人工智能 · 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 访问权限。
  • 安全架构公开了一个 query_database(question) 工具,该工具委托给语法树验证器(仅限 SELECT、有界 LIMIT、隔离模式)而不是直接执行。
  • Aurabase 本机公开此验证器 (/nl2sql):将其重新用作工具的实现,避免了必须自己重新编码 SQL 验证。
  • 然后,提交的 SQL 通过 readOnly: true模式下的 aura.db.sql() 执行,这是一个实际的只读 Postgres 事务,而不是简单的文本过滤器。
  • Aurabase 的本机 /chat 端点尚不接受 tool 角色,也不接受 tools 参数(在代码中验证):代理循环当前通过 LLM 提供商的 SDK 运行,而不是通过 Aurabase 代理运行。
  • service_role 密钥在设计上绕过了 RLS:它绝不能离开您的后端,并且代理继承比典型的经过身份验证的用户更广泛的访问权限。
#
目的

你将构建什么

您将构建一个代理来回答有关 Postgres 项目中数据的自然语言问题,而无需让模型编写按原样执行的 SQL。该模型调用一个名为 query_database的工具,该工具将问题转换为通过 NL2SQL 验证的 SQL,然后执行此只读 SQL 并将行返回给模型,以便模型制定答案。

信息

本教程在服务器端使用 @aurabase/aurabase-js JavaScript SDK(切勿在浏览器端,service_role 密钥不得暴露给客户端)和用于代理循环的 OpenAI 函数调用 API。同样的原理也适用于 Anthropic 或 Gemini SDK。

#
在引擎盖下

为什么“运行此 SQL”工具很危险

大多数 Postgres 代理教程(包括一些官方指南)都定义了一个工具:一个 execute_sql 函数,它将 SQL 字符串作为参数并按原样执行。模型根据用户的问题和上下文中给出的模式自行编写此字符串。

tool-schema-dangereux.json (反模式)json
{
  "name": "execute_sql",
  "parameters": {
    "query": { "type": "string" }  // 模型直接写SQL
  }
}

这种选择将一项无法可靠履行的责任转移给模型。问题中的提示注入可能会产生破坏性的 SQL,该工具会不加区别地执行该 SQL,因为它不知道“合法”查询应该是什么样子。我们的专门文章详细介绍了此攻击向量:保护 NL2SQL 免受 SQL 注入。

本教程中构建的替代方案公开了一个更窄的工具 query_database(question)。该模型不能再直接编写 SQL:它只能在自己的工具调用中提出问题。 Aurabase NL2SQL 引擎 将此问题转换为 SQL,然后将其传递给语法树验证器(仅 SELECT,无子查询,授权的 10 个函数,有限的 LIMIT)。

系统提示并非安全检查

无论您的系统提示有多好,execute_sql(query: string) 工具都可以为模型提供完整的 SQL 访问权限。指令(“仅执行 SELECT”)仍然是模型可以遵循、误解或被用户问题中的注入所规避的指令。

#
步骤1

定义暴露给模型的工具的架构

Aurabase 的三个本地 LLM 提供商(OpenAI、Anthropic、Gemini)接受 JSON Schema 格式的工具定义表。对于这个代理来说,一个工具就足够了: query_database,它只接受自然语言的问题。该模型既看不到 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 进行两次调用。

第一个调用将问题转换为通过 aura.ai.nl2sql()验证的 SQL:仅限 SELECT,LIMIT 受限,无法访问系统目录。第二个执行已经通过 aura.db.sql()和 readOnly: true 选项验证的 SQL:然后 Postgres 本身拒绝此事务中的任何写入,独立于 NL2SQL 上游已经应用的文本验证。

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 验证相结合,代理具有两个独立的层:如果一个层有缺陷,另一层仍然有效。

#
步骤3

代理循环:调用供应商SDK端的函数

Aurabase 公开了三个本机 LLM 提供程序,但其 /chat 端点尚未中继 tools 参数或 tool角色。 ChatOptions 仅携带 temperature、 max_tokens 和 model,接受的角色仅限于 system、 user 和 assistant (在 llm/mod.rs 和 handlers/chat.rs中验证)。因此,函数调用循环现在直接通过提供商的 SDK 运行,而不是通过 Aurabase 代理。

目前的限制,不是最终的选择

只要 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

用真题进行测试

发送给代理商的问题:“本月有多少优质客户下了订单?” ”。模板按原样调用 query_database 来解决这个问题,没有看到或编写任何 SQL。这是该工具触发的两个内部调用的结果。

工具结果(摘录)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 以及十个 SQL 函数的封闭列表。自然需要子查询(“从未订购过的客户”)的问题必须重新表述或由第二个专用工具处理,而不是强制进入 NL2SQL。

目前,Aurabase /chat 代理内部不存在工具调用编排:此处描述的代理循环存在于您的应用程序代码中,而不是托管服务中。如果代理必须链接多个工具(例如数据库和文档 RAG),则您的后端将协调这两个调用。

#
走得更远

RAG 和函数调用相结合

本教程涵盖有关关系数据的结构化问题。对于有关非结构化内容(文档、票据、注释)的问题,同一代理可以公开连接到 Aurabase 的本机 RAG(pgvector、HNSW 搜索)的第二个工具。 Native AI on Postgres页面详细介绍了这两种功能及其关联。

#
常见问题解答

常见问题解答

我可以授予代理写入权限(插入/更新)吗?+
从技术上讲是可以的,通过删除 readOnly 选项并指向一个单独的工具,但这不是 NL2SQL 目前所做的:验证器只允许 SELECT 查询,而不管客户端选择的执行选项如何。写入代理请求一个单独的验证器,具有自己的函数白名单,并且可能在执行前进行人工确认。
它是否与 LangChain、LlamaIndex 或 Azure AI Agent 等服务兼容?+
是的:这些框架为您编排函数调用循环,但工具的实现仍然是您的。相同的处理程序(NL2SQL 然后只读执行)将自身连接为 LangChain 或 Azure 代理中声明的工具的函数,而不是让它们执行原始 SQL。

准备好部署了吗?

五分钟内完成您的后端。

无需信用卡 · 500 MB 免费 · 50,000 MAU