PRODPlataforma BaaS europeia soberanaAbra o painel →

IA nativa · 10 minutos de leitura

Agente Postgres seguro com chamada de função (tutorial)

Affane Daylami · Fondateur · 21 de março de 2026

Voltar ao blog

Um agente consultando o Postgres faz uma pergunta de segurança específica antes da primeira linha do código: Qual função você está expondo ao modelo? Se a ferramenta que o LLM pode chamar executa diretamente o SQL que ela mesma escreveu, uma pergunta ambígua ou uma injeção de prompt é suficiente para ler qualquer tabela do projeto.

Este texto em inglês foi gerado automaticamente a partir do original em francês e ainda não foi revisado.
Esta página foi traduzida automaticamente. A versão em inglês é oficial.

Este tutorial constrói um agente com chamada de função onde a ferramenta exposta ao modelo nunca executa SQL arbitrário. Ele combina dois mecanismos já verificados no código Aurabase: o validador NL2SQL e uma transação Postgres somente leitura, dois blocos da IA ​​nativaintegrados ao backend. Pré-requisitos: um projeto Aurabase, sua chave service_rolee uma conta com um dos três provedores nativos de LLM (OpenAI, Anthropic, Gemini).

O essencial

  • O risco real não é a função que chama a si mesma, mas a ferramenta exposta ao modelo: um execute_sql(query) bruto fornece acesso SQL completo.
  • A arquitetura segura expõe uma ferramenta query_database(question) que delega para um validador de árvore de sintaxe (somente SELECT, LIMIT limitado, esquema isolado) em vez de execução direta.
  • Aurabase expõe este validador nativamente (/nl2sql): reutilizá-lo como uma implementação da ferramenta evita ter que recodificar você mesmo a validação SQL.
  • O SQL confirmado é então executado por meio de aura.db.sql() no modo readOnly: true, uma transação real somente leitura do Postgres, não um simples filtro textual.
  • O endpoint /chat nativo do Aurabase ainda não aceita uma função tool nem um parâmetro tools (verificado no código): o loop do agente atualmente é executado por meio do SDK do provedor LLM, não por meio do proxy Aurabase.
  • A chave service_role ignora o RLS por design: ela nunca deve sair do seu back-end e o agente herda um acesso mais amplo do que um usuário autenticado típico.
#
Objetivo

O que você vai construir

Você construirá um agente que responde a perguntas de linguagem natural sobre dados em um projeto Postgres, sem nunca deixar o modelo escrever SQL que é executado como está. O modelo chama uma ferramenta chamada query_database, esta ferramenta traduz a pergunta em SQL validado via NL2SQL, depois executa esse SQL somente leitura e retorna as linhas para o modelo para que ele formule sua resposta.

Informações

Este tutorial usa o SDK JavaScript @aurabase/aurabase-js no lado do servidor (nunca no lado do navegador, a chave service_role não deve ser exposta ao cliente) e a função OpenAI que chama a API para o loop do agente. O mesmo princípio se aplica ao SDK Anthropic ou Gemini.

#
Sob o capô

Por que uma ferramenta “executar este SQL” é perigosa

A maioria dos tutoriais do agente Postgres, incluindo alguns guias oficiais, definem uma única ferramenta: uma função execute_sql que recebe uma string SQL como argumento e a executa como está. O próprio modelo escreve essa string, com base na pergunta do usuário e no esquema fornecido a ele no contexto.

tool-schema-dangereux.json (antipadrão)json
{
  "name": "execute_sql",
  "parameters": {
    "query": { "type": "string" }  // o modelo escreve SQL diretamente
  }
}

Esta escolha transfere para o modelo uma responsabilidade que este não pode cumprir de forma confiável. Uma injeção imediata inserida na questão pode produzir SQL destrutivo que a ferramenta executa indiscriminadamente, uma vez que não tem noção de como deveria ser uma consulta "legítima". Nosso artigo dedicado detalha este vetor de ataque: protegendo NL2SQL contra injeção de SQL.

A alternativa criada neste tutorial expõe uma ferramenta mais restrita, query_database(question). O modelo não pode mais escrever SQL diretamente: ele só pode fazer uma pergunta em sua própria chamada de ferramenta. É o motor Aurabase NL2SQL que traduz esta questão em SQL, antes de passá-la através de um validador de árvore sintática (SELECT sozinho, sem subconsultas, dez funções autorizadas, LIMIT limitado).

O prompt do sistema não é uma verificação de segurança

Uma ferramenta execute_sql(query: string) fornece ao modelo acesso SQL completo, independentemente da qualidade do prompt do sistema. Uma instrução ("executa apenas SELECTs") continua sendo uma instrução que o modelo pode seguir, interpretar mal ou ser contornada por uma injeção inserida na pergunta do usuário.

#
Passo 1

Defina o esquema da ferramenta exposta ao modelo

Os três provedores LLM nativos da Aurabase (OpenAI, Anthropic, Gemini) aceitam uma tabela de definições de ferramentas no formato JSON Schema. Para este agente basta uma única ferramenta: query_database, que responde uma pergunta em linguagem natural e nada mais. O modelo não vê o esquema SQL nem um campo query que ele mesmo possa preencher.

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
      },
    },
  },
]
#
Etapa 2

Implemente a ferramenta: NL2SQL então somente leitura

O manipulador de ferramentas é executado em seu back-end, nunca no navegador. Ele carrega a chave do projeto service_role, que ignora o RLS por design e, portanto, nunca deve ser exposta a um cliente. Faz duas chamadas para o SDK Aurabase.

A primeira chamada traduz a pergunta em SQL validado via aura.ai.nl2sql(): somente SELECT, LIMIT limitado, sem acesso ao catálogo do sistema. O segundo executa esse SQL já validado via aura.db.sql(), com a opção readOnly: true: o próprio Postgres então recusa qualquer escrita nesta transação, independente da validação textual já aplicada pelo upstream do NL2SQL.

server/tools/query-database.tstypescript
// Cliente inicializado com a chave service_role, nunca no lado do navegador
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 }
}
Astuce

readOnly: true aciona uma transação Postgres real somente leitura: o mecanismo recusa a gravação, não é um filtro aplicado ao texto da solicitação. Combinado com a validação somente SELECT do NL2SQL, o agente possui duas camadas independentes: se uma tiver uma falha, a outra ainda será válida.

#
Etapa 3

O loop do agente: chamada de função no SDK do fornecedor

Aurabase expõe três provedores LLM nativos, mas seu endpoint /chat ainda não retransmite um parâmetro tools ou função tool. ChatOptions carrega apenas temperature, max_tokens e model, e as funções aceitas são limitadas a system, user e assistant (verificadas em llm/mod.rs e handlers/chat.rs). O loop de chamada de função, portanto, é executado hoje diretamente através do SDK do provedor, não através do proxy Aurabase.

Limitação atual, não uma escolha definitiva

Contanto que o Aurabase não orquestre nativamente as chamadas de ferramentas, seu back-end deve gerenciar o próprio loop com o SDK OpenAI, Anthropic ou Gemini. A execução de NL2SQL e SQL permanecem chamadas clássicas do Aurabase dentro deste loop.

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
}

O princípio permanece o mesmo se você orquestrar o agente com LangChain ou um serviço como o Azure AI Agent: a ferramenta declarada na estrutura deve permanecer a mesma query_database, nunca um executor SQL bruto. Nossa comparação detalha onde LangChain e LlamaIndex fornecem valor real no Postgres e onde eles adicionam complexidade especialmente: Agentes Postgres com LangChain ou LlamaIndex.

#
Etapa 4

Teste com uma pergunta real

Pergunta enviada ao agente: “Quantos clientes premium fizeram um pedido este mês?” ". O modelo chama query_database com esta pergunta como está, sem nunca ver ou escrever nenhum SQL. Aqui está o resultado das duas chamadas internas acionadas pela ferramenta.

resultado da ferramenta (extrato)json
{
  "sql": "SELECT count(*) FROM orders WHERE customer_plan = 'premium' AND created_at >= date_trunc('month', now()) LIMIT 50",
  "rows": [{ "count": 128 }]
}

A resposta final do modelo é baseada nessas linhas reais, não em uma suposição. Se a ferramenta retornar zero linhas, uma alucinação numérica se tornará significativamente menos provável do que com um modelo que responderia sem dados verificados.

#
Segurança

Proteja o agente antes de entrar em produção

  • A chave service_role nunca sai do seu backend: nem no prompt enviado ao modelo, nem em um log, nem em uma variável de ambiente do lado do cliente.
  • readOnly: true permanece ativo em aura.db.sql() para esta ferramenta específica, mesmo se seu projeto precisar ser escrito em outro lugar no aplicativo.
  • service_role ignora RLS por design. Se o agente responder de forma diferente dependendo do usuário que faz a pergunta, filtre explicitamente no SQL ou volte para os endpoints PostgREST clássicos, que respeitam o RLS. Consulte isolamento RLS multilocatário.
  • Registrar cada chamada de ferramenta (pergunta feita, SQL validada, número de linhas): este é o único rastreamento utilizável se uma pergunta produzir um resultado inesperado.
  • A limitação de taxa e a cota mensal do Aurabase já se aplicam por projeto em /nl2sql: um agente falante não pode exceder silenciosamente seu orçamento de IA.
#
Honestidade

Limites atuais a serem observados

A ferramenta query_database herda todas as limitações do validador NL2SQL: sem subconsultas, sem CTE/WITH, sem UNION e uma lista fechada de dez funções SQL. Uma questão que naturalmente exige uma subconsulta (“clientes que nunca fizeram pedidos”) deve ser reformulada ou processada por uma segunda ferramenta dedicada, em vez de forçada ao NL2SQL.

Nenhuma orquestração de chamadas de ferramenta existe hoje dentro do proxy Aurabase /chat: o loop do agente descrito aqui reside no código do seu aplicativo, não em um serviço gerenciado. Se o agente precisar encadear diversas ferramentas (banco de dados e RAG documental, por exemplo), é o seu backend que orquestra as duas chamadas.

#
Vá mais longe

RAG e chamada de função combinadas

Este tutorial cobre questões estruturadas sobre dados relacionais. Para dúvidas sobre conteúdos não estruturados (documentos, tickets, notas), o mesmo agente pode expor uma segunda ferramenta conectada ao RAG nativo do Aurabase (pgvector, busca HNSW). As duas capacidades e sua articulação estão detalhadas na página Native AI on Postgres.

#
Perguntas frequentes

Perguntas frequentes

Posso conceder ao agente acesso de gravação (INSERT/UPDATE)?+
Tecnicamente sim, removendo a opção readOnly e apontando para uma ferramenta separada, mas não é isso que o NL2SQL faz hoje: o validador só permite consultas SELECT, independente da opção de execução escolhida no lado do cliente. Um agente de gravação solicita um validador separado, com sua própria lista de permissões de funções e provavelmente confirmação humana antes da execução.
É compatível com LangChain, LlamaIndex ou um serviço como Azure AI Agent?+
Sim: essas estruturas orquestram o loop de chamada de função para você, mas a implementação da ferramenta permanece sua. O mesmo manipulador (NL2SQL e execução somente leitura) se conecta em função da ferramenta declarada em LangChain ou no agente do Azure, em vez de permitir que eles executem SQL bruto.

PRONTO PARA IMPLEMENTAR?

Seu back-end em cinco minutos.

Não é necessário cartão de crédito · 500 MB grátis · 50.000 MAU