IA: Agentes de IA não devem ter acesso direto a bancos de dados. Como projetar um circuito de ação seguro com FastAPI e PostgreSQL

IA: Agentes de IA não devem ter acesso direto a bancos de dados. Como projetar um circuito de ação seguro com FastAPI e PostgreSQL

A integração de agentes de IA em processos de negócios exige cautela. Este artigo explora como criar um sistema seguro onde a IA propõe ações, mas um backend robusto as valida e executa, garantindo controle e auditoria.

MundiX News·12 de junho de 2026·10 min de leitura·👁 8 views

Recentemente, tenho encontrado com frequência a seguinte afirmação: o mundo dos negócios jamais concederá a um agente de IA acesso direto a bancos de dados de clientes, pedidos, pagamentos, CRMs ou documentos internos. À primeira vista, isso soa lógico. Se um agente cometer um erro, confundir o contexto ou executar uma ação incorreta, o dano pode ser bastante real. No entanto, acredito que aqui frequentemente se confundem duas coisas distintas. Conceder acesso direto ao banco de dados a um agente é, de fato, inaceitável. Por outro lado, permitir que ele opere através de um circuito de ações restrito, verificável e auditável é perfeitamente viável. De maneira análoga, não concedemos aos usuários acesso direto ao PostgreSQL, mas permitimos que eles cliquem em botões na interface que acionam uma lógica de negócios predefinida.

Neste artigo, pretendo detalhar como tal circuito pode se apresentar na prática: sem mágica, sem a premissa de que "o agente resolverá tudo sozinho", sem SQL bruto gerado pelo modelo e sem a crença de que um bom prompt substitui uma arquitetura adequada. A ideia central é que o agente atue como um planejador não confiável, propondo ações que serão validadas e executadas por um backend seguro e controlado.

O Problema do Acesso Direto

Imagine uma situação simples. Temos um CRM interno. Ele armazena informações de clientes, status de negociações, comentários de gerentes, histórico de interações, documentos e indicadores como nível de risco ou valor do contrato. Surge a ideia de conectar um agente de IA para auxiliar os colaboradores em tarefas como:

  • Encontrar um cliente específico com base em uma descrição.
  • Gerar um resumo conciso de uma negociação.
  • Sugerir o próximo passo em um processo.
  • Criar uma tarefa para um gerente.
  • Atualizar um comentário no cadastro do cliente.
  • Preparar um e-mail.
  • Verificar quais pedidos estão há muito tempo sem processamento.

Do ponto de vista da utilidade, o agente pode, de fato, economizar tempo. Contudo, se lhe for concedido acesso direto ao banco de dados, surgem imediatamente questões incômodas:

  • O que impedirá o agente de ler registros indevidos?
  • O que o impedirá de alterar um campo de um cliente errado?
  • O que acontecerá se o usuário solicitar a exportação de todo o banco de dados?
  • Como rastrear posteriormente quem iniciou uma ação?
  • Como distinguir uma sugestão do agente de uma operação realmente executada?
  • Como reverter uma ação errônea?
  • Onde será armazenado o log de raciocínio, se necessário?
  • Quem é responsável pela mutação dos dados?

Neste ponto, geralmente surge a conclusão: "Agentes de IA não devem ter acesso ao perímetro de negócios". Eu formularia de outra maneira: um agente de IA não deve ser um executor confiável. Ele deve ser visto como um planejador não confiável, que pode propor uma ação, mas não deve executá-la diretamente.

Princípio Fundamental: O Agente Propõe, o Sistema Executa

Eu dividi o esquema em três camadas:

  1. Camada de Solicitação do Usuário: O humano escreve: "Mostre-me os clientes com os quais não há contato há muito tempo", ou "Gere um resumo para o João", ou "Adicione um comentário ao cadastro do cliente".
  2. Camada do Agente: Este é o próprio agente. Ele analisa a intenção do usuário e a transforma não em uma consulta SQL, mas em uma descrição estruturada da ação. Por exemplo: obter o cadastro do cliente, criar um comentário, definir uma tarefa, solicitar confirmação.
  3. Camada de Backend Confiável: É esta camada que verifica permissões, aplica políticas, decide se a ação pode ser executada automaticamente, se requer confirmação ou se deve ser proibida.

De forma simplificada, o esquema se apresenta da seguinte maneira:

Usuário ↓ Backend API ↓ LLM / Agente como Planejador ↓ ActionSpec: Ação Estruturada ↓ Policy Gateway ↓ Fila de Aprovação / Executor ↓ PostgreSQL / CRM / Serviços Externos

Neste esquema, o agente não obtém a senha do banco de dados, não envia SQL arbitrário e não acessa serviços internos à vontade. Ele apenas formula a intenção em um formato predefinido.

Por Que um Prompt Não é uma Proteção

Uma falha comum reside na ordem incorreta e na tentativa de resolver a segurança por meio de uma "instrução" textual genérica:

"Você é um agente corporativo. Nunca mostre dados pessoais. Nunca exclua registros. Nunca execute comandos perigosos."

Tal instrução é útil como uma camada adicional, mas não deve ser a única proteção. Um prompt pode ser mal formulado, contornado, pode haver um conflito acidental de instruções, ou um caso específico pode simplesmente não ser previsto. Para um desenvolvedor backend, isso deve soar familiar. Não escrevemos em comentários de API: "por favor, não envie o user_id de outra pessoa". Verificamos as permissões no servidor. Com agentes, a lógica é a mesma. O modelo pode ser uma interface conveniente para ações, mas a verificação de permissões deve residir no código comum.

Formato Mínimo de Ação

Eu começaria não com uma grande plataforma de agente, mas com um pequeno formato de ação. Por exemplo:

python
from enum import Enum
from typing import Any, Literal

from pydantic import BaseModel, Field


class Operation(str, Enum):
    READ = "read"
    CREATE = "create"
    UPDATE = "update"
    DELETE = "delete"


class Resource(str, Enum):
    CUSTOMER = "customer"
    CUSTOMER_NOTE = "customer_note"
    TASK = "task"
    REPORT = "report"


class RiskLevel(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"


class ActionSpec(BaseModel):
    resource: Resource
    operation: Operation
    arguments: dict[str, Any] = Field(default_factory=dict)
    reason: str
    risk_level: RiskLevel

Este não é um modelo industrial final, mas uma ideia. O agente deve retornar não "execute este SQL", mas um objeto:

json
{
  "resource": "customer_note",
  "operation": "create",
  "arguments": {
    "customer_id": 42,
    "text": "O cliente pediu para retornar à discussão do contrato na sexta-feira"
  },
  "reason": "O usuário pediu para adicionar um comentário ao cadastro do cliente",
  "risk_level": "medium"
}

Agora, o backend tem algo que ele sabe como verificar. Ele pode analisar o papel do usuário, o recurso, o tipo de operação, os argumentos e o nível de risco.

Policy Gateway

A próxima camada é o gateway de políticas. Sua tarefa não é ser inteligente. Sua tarefa é ser chato e previsível.

Exemplo de um conjunto simples de regras:

python
from dataclasses import dataclass


@dataclass(frozen=True)
class UserContext:
    user_id: int
    role: str
    department_id: int


@dataclass(frozen=True)
class PolicyDecision:
    allowed: bool
    require_approval: bool
    reason: str


def evaluate_policy(user: UserContext, action: ActionSpec) -> PolicyDecision:
    if action.operation == Operation.DELETE:
        return PolicyDecision(
            allowed=False,
            require_approval=False,
            reason="Exclusão via agente proibida"
        )

    if action.resource == Resource.CUSTOMER and action.operation == Operation.READ:
        return PolicyDecision(
            allowed=True,
            require_approval=False,
            reason="Leitura do cadastro do cliente permitida após verificação de escopo de acesso"
        )

    if action.resource == Resource.CUSTOMER_NOTE and action.operation == Operation.CREATE:
        return PolicyDecision(
            allowed=True,
            require_approval=True,
            reason="Criação de comentário requer confirmação do usuário"
        )

    if action.resource == Resource.REPORT and user.role != "manager":
        return PolicyDecision(
            allowed=False,
            require_approval=False,
            reason="Relatórios disponíveis apenas para gerentes"
        )

    return PolicyDecision(
        allowed=False,
        require_approval=False,
        reason="Nenhuma regra adequada para a ação"
    )

Aqui não há magia de rede neural. É lógica de servidor comum, que pode ser testada, revisada e explicada aos especialistas em segurança. É importante notar que allowed=True ainda não significa "executar imediatamente". Para algumas ações, é possível ativar a confirmação: mostrar ao usuário um diff, o texto do comentário, a lista de objetos afetados e um botão "Confirmar".

Não Gerar SQL, Mas Chamar Operações Predefinidas

O ponto mais perigoso, na minha opinião, é a tentação de um júnior ou trabalhador inexperiente de permitir que o agente escreva SQL. Parece conveniente: o usuário pergunta, o agente gera a consulta, o banco de dados responde. Mas para o perímetro de negócios, isso é arriscado demais. Eu nem daria ao agente a capacidade de escrever SQL arbitrário. Em vez disso, é melhor criar um catálogo de operações permitidas.

Por exemplo, o agente pode solicitar:

json
{
  "resource": "customer",
  "operation": "read",
  "arguments": {
    "customer_id": 42
  },
  "reason": "Preciso mostrar ao usuário informações resumidas sobre o cliente",
  "risk_level": "low"
}

E o backend já chama uma função segura:

python
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession


async def get_customer_summary(
    session: AsyncSession,
    user: UserContext,
    customer_id: int,
) -> dict | None:
    query = text("""
        select id, full_name, status, manager_id
        from customers
        where id = :customer_id
          and department_id = :department_id
        limit 1
    """)

    result = await session.execute(
        query,
        {
            "customer_id": customer_id,
            "department_id": user.department_id,
        },
    )

    row = result.mappings().first()
    return dict(row) if row else None

Mesmo que o agente solicite um cliente de outra pessoa, o backend limitará a seleção por department_id. Mesmo que o usuário tente enganar o agente, o backend ainda verificará o escopo de acesso.

Auditoria é Mais Importante Que uma Resposta Bonita

Se o agente estiver trabalhando com dados de negócios, é necessário registrar não apenas a resposta final. É preciso salvar a cadeia de ações de forma compreensível para auditoria. Eu armazenaria, no mínimo, as seguintes entidades:

sql
create table agent_actions (
    id bigserial primary key,
    user_id bigint not null,
    resource text not null,
    operation text not null,
    arguments jsonb not null,
    risk_level text not null,
    status text not null,
    policy_reason text not null,
    created_at timestamptz not null default now()
);

Os status podem ser simples:

planned denied waiting_approval approved executed failed cancelled

O principal é separar o que o agente propôs do que foi realmente executado. Essa é uma diferença importante. Em uma situação de disputa, é preciso ter a capacidade de responder às perguntas:

  • Quem foi o usuário?
  • Que ação o agente propôs?
  • Qual política foi acionada?
  • Houve confirmação?
  • O que exatamente foi alterado?
  • Quando isso ocorreu?

Sem um log como esse, o agente se transforma em uma caixa preta, na qual ou se deve acreditar, ou se deve proibi-lo completamente de trabalhar com dados importantes.

Exemplo de Endpoint no FastAPI

Em uma versão mínima, pode-se criar um endpoint que recebe uma ação, verifica a política e ou a rejeita, ou a envia para aprovação, ou a executa.

python
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

router = APIRouter()


@router.post("/agent/actions")
async def handle_agent_action(
    action: ActionSpec,
    session: AsyncSession = Depends(get_session),
    user: UserContext = Depends(get_current_user),
):
    decision = evaluate_policy(user, action)

    await save_action_log(
        session=session,
        user=user,
        action=action,
        status="denied" if not decision.allowed else "planned",
        policy_reason=decision.reason,
    )

    if not decision.allowed:
        raise HTTPException(status_code=403, detail=decision.reason)

    if decision.require_approval:
        approval_id = await create_approval_request(
            session=session,
            user=user,
            action=action,
            policy_reason=decision.reason,
        )
        return {
            "status": "waiting_approval",
            "approval_id": approval_id,
            "reason": decision.reason,
        }

    result = await execute_action(
        session=session,
        user=user,
        action=action,
    )

    return {
        "status": "executed",
        "result": result,
    }

O importante aqui não é que o código seja perfeito. Em um projeto real, surgiriam transações, outbox, retries, tarefas em segundo plano, papéis, escopos, rate limits, chaves de idempotência e tratamento de erros normal. O importante é outro: o agente não executa a ação diretamente. Entre o agente e o banco de dados, sempre há uma camada de código backend comum.

Por Que a Aprovação Deve Ser Contextual

Se for exibido ao usuário apenas um botão "Permitir que o agente execute a ação", isso é uma proteção fraca. O usuário rapidamente se acostumará a clicar nele automaticamente. É melhor mostrar um diff concreto! Por exemplo, não assim:

"O agente quer atualizar o cadastro do cliente. Permitir?"

Mas sim assim:

"O agente propõe adicionar o seguinte comentário ao cliente #42:

'O cliente pediu para retornar à discussão do contrato na sexta-feira'

Serão alterados:

  • Tabela customer_notes
  • Uma nova entrada será criada
  • Os campos existentes do cliente não serão alterados"

Se a ação afetar vários objetos, é preciso mostrar a quantidade de objetos e uma amostra. Se a ação for arriscada, é melhor enviá-la não ao próprio usuário, mas a um gerente ou administrador.

O Que Fazer Com Dados Pessoais

Uma questão separada aqui é: quanta informação fornecer ao modelo. Mesmo que o acesso seja tecnicamente permitido, isso não significa que se deva enviar tudo no prompt. Eu usaria o princípio do contexto mínimo. Se o usuário pedir "lembre-me brevemente sobre o cliente X", o modelo não precisa do passaporte completo da negociação, todos os documentos e todo o histórico de correspondência. Ele precisa de um resumo pré-compilado.

Por exemplo, o backend pode compilar um contexto seguro:

python
def build_customer_context(customer: dict, notes: list[dict]) -> dict:
    return {
        "customer_id": customer["id"],
        "status": customer["status"],
        "last_notes": [
            {
                "created_at": note["created_at"].isoformat(),
                "text": note["text"],
            }
            for note in notes[-5:]
        ],
    }

E este objeto restrito é passado ao agente. O modelo não deve receber mais dados do que o necessário para a operação específica.

Erros Que Eu Não Incorporaria na Arquitetura

O primeiro erro é um único usuário técnico para todas as ações. Se todas as operações forem feitas em nome de ai_agent, a auditoria se torna quase inútil. É preciso salvar o iniciador real: usuário, papel, departamento, fonte da solicitação.

O segundo erro é o acesso livre a ferramentas. Se o agente puder chamar qualquer endpoint HTTP, qualquer comando shell ou qualquer SQL, ele não é um assistente, mas um ponto de entrada descontrolado na infraestrutura interna.

O terceiro erro é a falta de um modo somente leitura (read-only). Para o primeiro lançamento, é melhor dar ao agente apenas permissão de leitura e geração de rascunhos. Mutações podem ser adicionadas posteriormente, uma operação por vez, com confirmações e logs.

O quarto erro é permitir a mistura do raciocínio do agente e da decisão de negócios. O agente pode propor: "o cliente parece problemático". Mas a decisão de "bloquear o cliente" deve passar pelo processo de negócios normal.

O quinto erro é não testar as políticas separadamente. O Policy Gateway deve ser coberto por testes tanto quanto qualquer lógica de negócios crítica.

Conjunto Mínimo de Testes

Mesmo para um pequeno protótipo, eu escreveria testes para as políticas:

python
def test_delete_is_denied():
    user = UserContext(user_id=1, role="manager", department_id=10)
    action = ActionSpec(
        resource=Resource.CUSTOMER,
        operation=Operation.DELETE,
        arguments={"customer_id": 42},
        reason="O usuário pediu para excluir o cliente",
        risk_level=RiskLevel.HIGH,
    )

    decision = evaluate_policy(user, action)

    assert decision.allowed is False
    assert decision.require_approval is False


def test_customer_note_requires_approval():
    user = UserContext(user_id=1, role="manager", department_id=10)
    action = ActionSpec(
        resource=Resource.CUSTOMER_NOTE,
        operation=Operation.CREATE,
        arguments={
            "customer_id": 42,
            "text": "Ligar de volta na sexta-feira",
        },
        reason="O usuário pediu para adicionar um comentário",
        risk_level=RiskLevel.MEDIUM,
    )

    decision = evaluate_policy(user, action)

    assert decision.allowed is True
    assert decision.require_approval is True

Esses testes parecem chatos, mas são eles que transformam a conversa sobre segurança de IA de filosofia em prática de engenharia.

O Que Resulta no Final

Não acredito que o mundo dos negócios concederá em massa acesso direto dos agentes de IA a bancos de dados de clientes. E fará bem. Mas isso não significa que os agentes não possam trabalhar com dados importantes. Simplesmente, eles devem trabalhar não como um funcionário onipotente com a senha do banco de dados, mas como um planejador não confiável dentro de um circuito de backend normal.

Para mim, a fórmula básica seria:

  • O agente propõe uma ação.
  • O backend verifica as permissões.
  • As políticas determinam o nível de risco.
  • O usuário confirma as mutações.
  • A camada executora chama apenas operações previamente permitidas.
  • Todas as ações são registradas em auditoria.

Nesta arquitetura, não pedimos ao negócio para "acreditar na IA". Oferecemos um modelo de engenharia compreensível: menos confiança no modelo, mais controle no código. E, estranhamente, é isso que pode ser a maneira mais realista de implementar agentes de IA em fluxos de trabalho onde existem bancos de dados de clientes, documentos, dinheiro e responsabilidade.

📤 Compartilhar & Baixar