Catalisa.Building Blocks
Catálogo/Inteligência/AI Engine

AI Engine

Produção

Tarefas de IA sobre texto, imagem e áudio numa API só, na nuvem ou na sua rede

10
Endpoints
0
Entidades
3
Provedores
Tenant
Escopo
3020
Porta
2026-02
Desde

Você não integra a API de um fabricante de IA em cada serviço que escreve. Você chama "classifique este texto" ou "transcreva este áudio" e decide, por configuração, se isso roda num provedor externo ou dentro da sua própria rede.

Para quem é
  • Fintechs e financeiras que precisam ler texto livre (mensagem, e-mail, laudo) dentro de uma esteira automatizada
  • Operações de atendimento com volume alto de mensagem e áudio que hoje são lidas por pessoas
  • Times de produto que querem usar IA sem amarrar o código a um fabricante específico de modelo
Substitui
  • Integração direta com o SDK de um fabricante de LLM repetida em cada serviço
  • Camada caseira de prompt, retry e parsing de JSON escrita por cada time
  • Serviço separado de transcrição de áudio
O que não é
  • Um agente autônomo ou orquestrador de fluxo de IA (isso é o Decision Platform)
  • Um pipeline de extração de documento com OCR e fila (isso é o Data Extraction)
  • Um sistema de treinamento ou ajuste fino de modelo
O que dá para fazer

11 endpoints em 5 recursos.

Explorar a API →
01

Resumo executivo

O AI Engine é a porta única por onde os outros building blocks e as suas aplicações pedem trabalho de inteligência artificial. Em vez de cada serviço integrar o SDK de um fabricante de modelo, todos chamam operações com nome de negócio: resumir, traduzir, classificar, medir sentimento, extrair campos, descrever uma imagem e transcrever um áudio.

Na prática isso resolve o dia em que a diretoria decide que os dados de cliente não podem mais sair da rede da empresa. Você troca AI_ENGINE_DEFAULT_PROVIDER de anthropic para ollama, aponta para um modelo rodando na sua própria infraestrutura, e nenhuma linha do código que chama a API muda.

Está em produção desde fevereiro de 2026, roda no stack de produção em ai-engine.bb.catalisa.app e não guarda nada: o AI Engine é sem estado. Não há tabela, não há histórico de prompt e não há modelo Prisma — o que entra é processado e devolvido.

AtributoValor
Identificadorai-engine
CategoriaInteligência
EscopoTenant (exige organizationId no token nas oito rotas de execução)
Porta (standalone)3020
Path alias@ai-engine
Prefixo HTTP/ai-engine
StatusProdução desde 2026-02
Depende deIAM (token). Nenhum banco, nenhum Redis.

02

O problema

negócio

O cenário. Uma operação recebe texto livre em volume: mensagem de WhatsApp, e-mail de reclamação, observação digitada pelo analista, áudio de dois minutos que o cliente gravou. Alguém precisa ler tudo isso e transformar em campo estruturado para o sistema decidir. Hoje quem lê é uma pessoa, ou um if com lista de palavras-chave.

O que trava hoje.

  • Cada serviço integra o fabricante por conta própria. Um time usa um SDK, outro usa fetch direto, um terceiro esquece o timeout. Quando o fabricante muda o nome do modelo, você descobre quantos lugares tinham a string escrita à mão.
  • A chave de API circula. Toda aplicação que quer chamar o modelo precisa da credencial do fabricante em variável de ambiente. Revogar uma chave vazada significa mexer em todos.
  • O modelo devolve texto, não dado. Pedir JSON a um LLM e receber JSON de verdade exige prompt cuidadoso, tolerância a cerca de markdown e um parser defensivo. Cada time reescreve esse mesmo parser, com qualidade diferente.
  • Dado sensível sai da empresa sem decisão explícita. Mandar o texto de um cliente para uma API externa é uma decisão de LGPD. Quando a integração está espalhada, ninguém sabe dizer quais dados saem, para onde, e por quanto tempo ficam lá.
  • Não dá para voltar atrás. Amarrar o código ao SDK de um fabricante transforma "trocar de modelo" num projeto, e não numa configuração.

O custo de não resolver. O custo direto é engenharia repetida: prompt, retry, backoff e parsing reescritos por serviço. O custo indireto é maior e chega depois — é a auditoria perguntando quais dados pessoais foram enviados a terceiros e a resposta ser "depende do serviço". A política pública da Anthropic para a API é apagar entradas e saídas em até 30 dias do recebimento (Anthropic — how long do you store personal data), e os termos comerciais dizem que a Anthropic não treina modelos com conteúdo de cliente (Anthropic Commercial Terms, seção B). Isso é bom — mas só serve como resposta se você souber dizer o que mandou.


03

Proposta de valor

negócio
AntesDepois
Cada serviço integra o SDK do fabricante e guarda a chave deleUm endpoint autenticado pelo token do IAM; a chave fica só no AI Engine
Prompt e parser de JSON reescritos por timePOST /extract com a lista de campos; a resposta já vem tipada
Trocar de modelo é um projetoTrocar de modelo é um campo no corpo da requisição ou uma variável de ambiente
Dado sensível sai da rede sem decisão registradaO provedor é configuração explícita — ollama mantém tudo dentro da sua infraestrutura

Tarefa de negócio, não chamada crua. POST /sentiment devolve sentiment, confidence e score. POST /classify recebe as categorias e devolve a escolhida com a confiança. O prompt, a instrução de formato e o parsing estão do nosso lado.

OperaçãoO que você mandaO que volta pronto
generateUm promptTexto gerado + usage
summarizeUm textoResumo + usage
translateTexto e idioma de destinoTradução + usage
sentimentUm textosentiment, confidence, score
classifyTexto e suas categoriasCategoria escolhida + confiança
extractTexto e a lista de camposextracted, confidence por campo, missingFields
vision/analyzeImagem por URL ou base64Descrição estruturada
audio/transcribeÁudio por URL ou base64text, language, duration, segments

Provedor é configuração, não arquitetura. O mesmo corpo de requisição funciona com provider: "anthropic" ou provider: "ollama". Sem provedor informado, vale o padrão do ambiente.

Nada é guardado aqui. O AI Engine não tem banco. O texto entra, é processado e sai. Não há histórico de prompt para vazar nem para expurgar.

Uma superfície para auditar. Quando alguém pergunta "que dado de cliente sai da nossa rede", a resposta é um serviço, com um provedor configurado, e não uma investigação por repositório.


04

Casos de uso reais

negócio

Caso 1 — Triagem de mensagens de atendimento sem fila humana Cenário ilustrativo

Contexto

Financeira com operação de atendimento no WhatsApp, cerca de 8 mil mensagens novas por dia, três filas (cobrança, dúvida de contrato, reclamação).

A dor

A triagem era feita por palavra-chave. "Não consigo pagar" caía em cobrança; "não vou pagar porque me cobraram errado" também. A fila de reclamação recebia o que sobrava, e o prazo regulatório de resposta era descoberto tarde.

A solução com o BB

O consumidor de mensagens chama POST /ai-engine/api/v1/ai/classify com as categorias da operação e allowMultiple: false, e em seguida POST /ai-engine/api/v1/ai/sentiment para separar o que está irritado do que está só perdido. O resultado alimenta a regra de roteamento no Decision Platform.

O resultado

A triagem deixa de depender de lista de palavras e passa a depender de significado. Mensagem com sentimento negativo e categoria de reclamação sobe de prioridade automaticamente, em vez de esperar alguém ler.

flowchart LR
  MSG["Mensagem do WhatsApp"] --> CLS["POST /ai/classify<br/>categorias da operação"]
  CLS --> SENT["POST /ai/sentiment"]
  SENT --> DEC{"categoria + sentimento"}
  DEC -->|"reclamação e negativo"| P1["Fila de reclamação<br/>prioridade alta"]
  DEC -->|"cobrança"| P2["Fila de cobrança"]
  DEC -->|"dúvida de contrato"| P3["Fila de dúvida"]

Caso 2 — Campos estruturados a partir de texto que ninguém padronizou Cenário ilustrativo

Contexto

Correspondente bancário que recebe do vendedor uma observação em texto livre junto da proposta: renda informada, vínculo, tempo de empresa, tudo em uma frase.

A dor

O campo era text no banco e ninguém conseguia usar em regra de decisão. Extrair com expressão regular funcionava para uma redação e falhava para as outras onze.

A solução com o BB

POST /ai-engine/api/v1/ai/extract com a lista de campos (nome, tipo, descrição, obrigatório). A resposta traz extracted, confidence por campo e missingFields — o que não veio no texto é declarado, em vez de virar null silencioso.

O resultado

O texto livre vira entrada de motor de decisão. E missingFields dá ao operador a lista exata do que ainda precisa perguntar, em vez de reprovar a proposta por falta de informação.

flowchart LR
  TXT["Observação em texto livre<br/>escrita pelo vendedor"] --> EXT["POST /ai/extract<br/>fields: nome, vínculo, renda, tempo"]
  EXT --> OUT["extracted + confidence"]
  EXT --> MISS["missingFields"]
  OUT --> REGRA["Regra de decisão"]
  MISS --> OPER["Operador pergunta<br/>só o que faltou"]

Caso 3 — Áudio de cliente vira texto sem sair da rede Cenário ilustrativo

Contexto

Operação de cobrança que recebe áudios de 30 a 120 segundos pelo WhatsApp. Política interna proíbe enviar voz de cliente para serviço externo.

A dor

Sem transcrição, o áudio virava tarefa humana: alguém ouvia, resumia e digitava. O tempo de resposta era medido em horas.

A solução com o BB

POST /ai-engine/api/v1/ai/audio/transcribe aponta para uma instância do Whisper ASR rodando na própria infraestrutura, configurada em AI_ENGINE_WHISPER_URL. O áudio chega em base64 ou por URL — e, se for URL, passa por validação anti-SSRF antes de qualquer download. Nenhum byte de voz sai da rede.

O resultado

O áudio vira texto no mesmo minuto, e a política de não enviar voz para fora continua valendo, porque o modelo é seu. O Whisper é MIT, código e pesos (openai/whisper).

flowchart LR
  subgraph rede["Dentro da sua rede"]
    AUD["Áudio de 30 a 120s"] --> API["POST /ai/audio/transcribe"]
    API --> SSRF{"origem é URL?"}
    SSRF -->|sim| GUARD["isUrlSafe + secureFetch<br/>valida destino e fixa o IP"]
    SSRF -->|"base64"| WHI
    GUARD --> WHI["Whisper ASR<br/>AI_ENGINE_WHISPER_URL"]
    WHI --> TXT["Texto transcrito"]
  end
  rede -.->|"nenhum byte de voz atravessa"| FORA["Serviço externo"]

Caso 4 — O mercado converge para a camada de gateway Referência de mercado

Contexto

Praticamente todo fornecedor grande passou a oferecer uma camada única sobre vários modelos: o OpenRouter expõe mais de 500 modelos de mais de 80 provedores atrás de uma chave (openrouter.ai); a AWS lançou o Intelligent Prompt Routing no Bedrock, cobrado a US$ 1 por mil requisições (AWS Bedrock pricing); a Vercel publica que o AI Gateway não cobra markup sobre token (Vercel AI Gateway pricing).

A dor do mercado

Todos esses produtos resolvem o roteamento. Nenhum deles resolve o que vem antes e depois: o prompt da tarefa, o formato da resposta e a decisão de onde o dado pode trafegar.

Como a Catalisa endereça

O AI Engine assume a tarefa inteira, não só o transporte — e trata "rodar dentro da sua rede" como opção de primeira classe, não como plano enterprise. Em contrapartida, não temos catálogo de 500 modelos nem fallback automático entre provedores; leia a §15 antes de comparar linha a linha.

flowchart LR
  APP["Sua aplicação"] --> ANTES["O prompt da tarefa"]
  ANTES --> GW["Roteamento<br/>gateway de LLM"]
  GW --> DEPOIS["Formato da resposta<br/>e onde o dado pode trafegar"]
  DEPOIS --> USO["Dado estruturado"]

  GW:::gateway
  ANTES:::nosso
  DEPOIS:::nosso

  classDef gateway stroke-dasharray: 4 3
  classDef nosso stroke-width:3px

Leia o desenho assim. O trecho tracejado é o que OpenRouter, Portkey, LiteLLM e Bedrock resolvem. Os dois trechos em traço grosso — o prompt da tarefa antes, o formato e o destino do dado depois — continuam sendo do cliente em todos eles, e são justamente o que o AI Engine assume.

O resultado

Quem precisa de catálogo enorme escolhe um gateway. Quem precisa de tarefa pronta e controle de onde o dado roda escolhe o AI Engine.


05

Mercado e diferenciais

negócio

Panorama. A camada entre a aplicação e o modelo virou categoria em 2024–2025 e se dividiu em três formatos. Há o gateway hospedado (OpenRouter, Portkey, Vercel AI Gateway), que troca uma chave por muitas e cobra em taxa de crédito, log ou add-on. Há o proxy que você opera (LiteLLM), gratuito no núcleo e caro em operação. E há o gateway do provedor de nuvem (Amazon Bedrock), que resolve tudo desde que tudo esteja naquela nuvem.

Nenhum dos três sobe um nível: todos entregam acesso ao modelo, e a tarefa de negócio continua sendo escrita pelo cliente. O AI Engine nasce um nível acima, com um catálogo deliberadamente pequeno de modelos e um conjunto fechado de operações que já retornam dado estruturado.

CritérioCatalisa AI EngineOpenRouterLiteLLMPortkeyAmazon Bedrock
Modelo de preçoEm definição (§6)Sem markup no token; 5,5% na compra de créditoGrátis (MIT); enterprise sob consultaGrátis até 10k logs; US$ 49/mêsPor token; roteamento a US$ 1/mil req.
Nº de modelos6 aliases sobre 2 provedores de LLM500+100+Multi-provedor17+ famílias
Fallback automático entre provedoresNão (ver §15)SimSimSimFallback configurável no prompt routing
Rodar dentro da sua redeSim, via OllamaNãoSim (self-hosted)Sim (self-hosted)Não
Transcrição de áudio no mesmo serviçoSim (Whisper)NãoNãoNãoServiço separado
Tarefas de negócio prontasSim (8 operações)NãoNãoNãoNão
Observabilidade de promptNão (ver §15)SimSimSim, é o focoSim, via CloudWatch
Autorização por organizaçãoNativa, token do IAMPor chaveVirtual keysPor chaveIAM da AWS
Você opera a infraestruturaNãoNãoSimOpcionalNão

Nossos diferenciais

  1. O mesmo endpoint atende nuvem e rede interna. provider: "ollama" aponta para um modelo dentro da sua infraestrutura; provider: "anthropic" vai para a nuvem. A decisão de onde o dado trafega vira campo de requisição, não refatoração. É difícil de copiar para um gateway hospedado porque o produto dele é a nuvem dele.
  2. A operação é a tarefa, não o modelo. POST /classify recebe categorias e devolve categoria com confiança. O prompt, a instrução de formato e o parser são nossos e evoluem uma vez para todos os consumidores.
  3. A identidade já existe. Nenhuma chave de fabricante circula pelas suas aplicações: quem chama o AI Engine se autentica com o mesmo token do IAM que usa nos outros 31 building blocks, e a permissão exigida é AI_ENGINE_EXECUTE.

Quando escolher o concorrente. Se você precisa testar dezenas de modelos de fabricantes diferentes na semana que vem, o OpenRouter faz isso hoje e o AI Engine não faz — nosso catálogo é de dois provedores de LLM. Se o que você precisa é observabilidade profunda de prompt, com log por requisição, replay e guardrails configuráveis, o Portkey é um produto inteiro dedicado a isso e nós não temos nada equivalente (§15). Se a sua stack já vive na AWS e a exigência é faturamento unificado com o resto da conta, o Bedrock é a escolha óbvia. E se você quer o proxy sob seu controle total sem pagar licença, o LiteLLM é MIT e resolve. O AI Engine ganha quando o problema é tarefa de negócio sobre texto e áudio, dentro de uma plataforma que já tem identidade, permissão e os outros building blocks — e especialmente quando parte do processamento não pode sair da sua rede.


06

Modelo de cobrança e ROI

negócio

Unidade de cobrança. Precificação em definição. O AI Engine hoje é entregue como parte da plataforma e não tem medição própria — ele não persiste nada e não publica evento de uso (§15). Qualquer cobrança futura precisa primeiro de instrumentação.

O que dispara custo. Três drivers, em ordem de peso:

DriverPor que pesa
Tokens consumidos no provedor de LLMÉ o custo direto, pago ao fabricante. Cresce com o tamanho do texto de entrada e com o maxTokens pedido.
Minutos de áudio transcritosO Whisper roda em CPU ou GPU sua; o custo é de máquina, não de licença.
Chamadas por organizaçãoConcorrência e latência; a chamada ao provedor é síncrona e pode levar dezenas de segundos.

Comparação de custo — cenário: operação que classifica 200 mil mensagens curtas por mês e transcreve 3 mil minutos de áudio. Preços consultados em 2026-08-16.

Catalisa AI EngineOpenRouterPortkeyAmazon Bedrock
Custo da camadaEm definiçãoSem markup no token; 5,5% sobre o crédito comprado por cartãoUS$ 49/mês no Production, até 100 mil logs; US$ 9 por 100 mil adicionaisUS$ 1 por mil requisições, se usar o Intelligent Prompt Routing
Custo do modeloPreço do fabricante, ou zero com Ollama na sua máquinaPreço do fabricantePreço do fabricantePreço do fabricante, dentro da AWS
Custo da transcriçãoMáquina sua (Whisper MIT)Não ofereceNão ofereceServiço separado
Log de promptNão temosIncluído até o limite do planoCloudWatch, cobrado à parte

Estimativa para orientar conversa, não proposta comercial. As 200 mil mensagens do cenário estourariam a franquia de log do plano Production do Portkey e cairiam no overage. Confira as tabelas públicas dos fornecedores na data da sua análise — elas mudam.

ROI. O retorno não está na camada, que é barata em todos os fornecedores. Está em duas coisas. A primeira é a engenharia que não é gasta: prompt, retry, backoff e parser de JSON escritos uma vez em vez de uma vez por serviço. A segunda, maior, é a opção de rodar internamente: numa operação em que a política proíbe enviar dado de cliente para fora, o caminho alternativo é não usar IA, e o custo disso é o processo manual inteiro que continua existindo.


07

Arquitetura

As camadas e o caminho da requisição

flowchart TD
  HTTP["HTTP"] --> APP["Hono app — basePath '/ai-engine'"]

  subgraph rotas["routes/"]
    R1["/api/v1/ai<br/>completionsRouter · analysisRouter"]
    R2["/api/v1/ai/vision<br/>visionRouter"]
    R3["/api/v1/ai/audio<br/>transcriptionRouter"]
    R4["/api/v1/ai/models<br/>modelsRouter"]
    R5["/health<br/>versão e status"]
  end

  APP --> rotas
  rotas -->|"authMiddleware → requirePermission → requireOrganization<br/>Zod parse → ResultAsync"| svc

  subgraph svc["services/ — sem estado, sem banco"]
    S1["CompletionService<br/>generate · summarize · translate"]
    S2["AnalysisService<br/>sentiment · classify · extract"]
    S3["VisionService<br/>analyze — guarda anti-SSRF na URL"]
    S4["TranscriptionService<br/>transcribe — guarda anti-SSRF na URL"]
  end

  svc --> PF["ProviderFactory<br/>strategy single ou hybrid<br/>resolveModel: fast | smart | powerful"]

  PF --> CP["ClaudeProvider<br/>api.anthropic.com<br/>3 tentativas"]
  PF --> OP["OllamaProvider<br/>seu servidor<br/>sem retry"]
  PF --> WP["WhisperProvider<br/>seu servidor<br/>sem retry"]

  CP --> FORA["SAI DA REDE"]
  OP --> DENTRO["FICA NA REDE"]
  WP --> DENTRO

Como a estratégia hybrid escolhe o provedor

O roteamento é a decisão mais consultada deste building block e a mais fácil de descrever errado. O desenho abaixo é o que o código faz hoje, conferido em providers/provider.factory.ts e config/models.ts:

flowchart TD
  REQ["Requisição chega"] --> P{"campo provider<br/>informado no corpo?"}
  P -->|sim| USA["Usa exatamente esse provedor"]
  P -->|não| S{"strategy = hybrid<br/>e a operação passa texto?"}

  S -->|não| DEF["defaultProvider<br/>AI_ENGINE_DEFAULT_PROVIDER"]
  S -->|sim| T{"tamanho do texto"}

  T -->|"menor que 2000 caracteres"| OLL["Ollama<br/>alias fast"]
  T -->|"2000 caracteres ou mais"| CLA["Claude<br/>alias smart"]

  OLL --> EXEC["Executa a chamada"]
  CLA --> EXEC
  USA --> EXEC
  DEF --> EXEC

  EXEC --> RES{"o provedor respondeu?"}
  RES -->|sim| OK["Resposta com usage"]
  RES -->|não| ERR["Erro propaga — 500<br/>SEM queda para o outro provedor"]

Atenção — não há fallback entre provedores. Escolhido o provedor, é nele que a chamada acontece e é dele que o erro vem. O ClaudeProvider tenta até 3 vezes no mesmo provedor; Ollama e Whisper não repetem. Se o Ollama estiver fora do ar, a requisição falha — ela não cai para o Claude, nem o contrário. Isso está na §15 e não é um detalhe de implementação: é o que separa esta arquitetura da de um gateway como OpenRouter ou LiteLLM.

Duas ressalvas de leitura do diagrama, ambas verificadas no código:

SituaçãoO que acontece de fato
strategy = hybrid, texto curto, mas Ollama não configuradoCai para o Claude, se houver chave; senão, para o defaultProvider. É escolha na construção do provedor, não recuperação de falha em tempo de execução.
POST /vision/analyze com strategy = hybridNão passa texto para a decisão, então a bifurcação por tamanho nem é avaliada: usa sempre o defaultProvider.

Decisões não óbvias

  • Sem banco, de propósito. O AI Engine não tem modelo Prisma. Guardar prompt e resposta criaria um repositório de dado pessoal de altíssima sensibilidade, com obrigação de retenção e expurgo, para um serviço que é essencialmente uma função. O preço dessa escolha é não ter observabilidade nativa de prompt (§15) — é um trade-off consciente, não um esquecimento.
  • Aliases de modelo em vez de nome de modelo. O corpo aceita fast, smart ou powerful, e o resolveModel traduz para o identificador concreto do provedor escolhido. Assim o consumidor não carrega uma string de modelo que o fabricante vai aposentar. Nome concreto continua aceito, para quem precisa fixar.
  • Retry só no provedor Anthropic. O ClaudeProvider tenta até 3 vezes, respeita o cabeçalho Retry-After em 429 e aplica recuo exponencial nas demais falhas. Ollama e Whisper não têm retry — são serviços seus, e falha ali é falha de infraestrutura sua, que o cliente deve tratar.
  • A estratégia hybrid roteia por tamanho, não por custo nem por qualidade. Texto com menos de 2.000 caracteres vai para o Ollama; o resto vai para o Claude. É uma heurística simples, e chamá-la de "roteamento inteligente" seria exagero. Ela só age quando a requisição não informa provider e a operação passa texto — a análise de imagem, por exemplo, sempre usa o provedor padrão.
  • Guarda anti-SSRF nas URLs de mídia. imageUrl e audioUrl são controladas por quem chama. Antes de baixar qualquer coisa, o serviço valida o destino com isUrlSafe e busca com secureFetch, que resolve o DNS e fixa o IP validado contra rebinding. Sem isso, o servidor buscaria endereços internos e devolveria o conteúdo através do modelo.
  • isAvailable do Anthropic só verifica se há chave. Ele não faz chamada de teste à API. Por isso GET /models/providers responde available sempre que AI_ENGINE_ANTHROPIC_API_KEY estiver preenchida, mesmo com a chave revogada. O Ollama e o Whisper, por serem seus, são de fato consultados.

Monolito vs. standalone

Em monolito, os outros building blocks pegam CompletionService e companhia direto do container TypeDI, sem rede. Em standalone — o modo usado em produção — o AI Engine sobe como serviço próprio e é alcançado por HTTP. A ProviderFactory é construída no setupContainer a partir das variáveis de ambiente nos dois modos.


08

Conceitos e modelo de dados

Glossário

TermoSignifica
ProviderUm destino de execução. Hoje: anthropic (Claude API), ollama (LLM na sua rede) e o Whisper, usado só para transcrição.
Alias de modelofast, smart ou powerful. Um apelido estável que a ProviderFactory traduz para o identificador concreto do provedor escolhido.
Strategysingle (sempre o provedor padrão) ou hybrid (texto curto vai para o Ollama, texto longo vai para o Claude).
TokenUsageinputTokens e outputTokens devolvidos pelo provedor em toda resposta de LLM. É o que você usa para estimar custo.
OperaçãoCada uma das oito tarefas expostas. Todas exigem AI_ENGINE_EXECUTE e organização no token.

Modelo de dados. Nenhum. O AI Engine não tem modelo Prisma, não escreve no PostgreSQL e não usa Redis. Essa é a razão de entidades: 0 no frontmatter. Consequências práticas: não há histórico para consultar, não há reprocessamento de uma chamada anterior e não há dado a expurgar em pedido de eliminação — porque não há dado guardado.

Aliases para identificador concreto

Aliasprovider: anthropicprovider: ollama
fastclaude-haiku-4-5-20251001qwen2.5:3b
smart (padrão)claude-sonnet-4-20250514llama3.1:8b
powerfulclaude-opus-4-20250514qwen2.5:14b

Esses identificadores estão fixos em src/ai-engine/types/index.ts. Modelos de LLM são aposentados pelos fabricantes; confira a lista antes de assumir que o alias aponta para o modelo mais recente, e veja a §15.

Como uma requisição escolhe o modelo

Escolher o provedor (§7) e escolher o modelo são dois passos distintos, nesta ordem. O segundo passo só acontece depois que o primeiro definiu para onde a chamada vai — porque o mesmo alias aponta para identificadores diferentes em cada provedor.

flowchart TD
  BODY["Corpo da requisição"] --> ETAPA1["Etapa 1 — escolhe o PROVEDOR<br/>ver o fluxograma da §7"]
  ETAPA1 --> PROV["anthropic ou ollama"]
  PROV --> M{"campo model informado?"}

  M -->|sim| CONC["Usa como está<br/>alias ou identificador concreto"]
  M -->|não| ALIAS["Alias padrão da operação"]

  ALIAS --> A1{"qual operação?"}
  A1 -->|"summarize com texto curto"| FAST["fast"]
  A1 -->|"demais operações"| SMART["smart"]

  CONC --> RESOLVE["resolveModel(alias, provedor)"]
  FAST --> RESOLVE
  SMART --> RESOLVE
  RESOLVE --> FINAL["Identificador concreto do provedor escolhido"]

Atenção. O campo model da resposta sempre traz o identificador que o provedor de fato usou. É por ele que você confirma o que aconteceu, não pelo que você pediu.


09

Referência da API

Prefixo: /ai-engine. Em staging, a base é https://ai-engine.bb.stg.catalisa.app.

Atenção ao prefixo. O caminho completo repete o segmento: /ai-engine/api/v1/ai/.... O basePath do app é /ai-engine e os routers são montados sob /api/v1/ai. Não é engano de documentação — é o caminho real, e uma requisição para /ai-engine/api/v1/generate retorna 404.

Completions — /ai-engine/api/v1/ai

MétodoRotaDescriçãoPermissão
POST/ai-engine/api/v1/ai/generateGera texto a partir de um promptAI_ENGINE_EXECUTE
POST/ai-engine/api/v1/ai/summarizeResume um textoAI_ENGINE_EXECUTE
POST/ai-engine/api/v1/ai/translateTraduz um textoAI_ENGINE_EXECUTE

Análise — /ai-engine/api/v1/ai

MétodoRotaDescriçãoPermissão
POST/ai-engine/api/v1/ai/sentimentClassifica o sentimento do textoAI_ENGINE_EXECUTE
POST/ai-engine/api/v1/ai/classifyClassifica o texto em categorias que você defineAI_ENGINE_EXECUTE
POST/ai-engine/api/v1/ai/extractExtrai campos estruturados de texto livreAI_ENGINE_EXECUTE

Visão e áudio

MétodoRotaDescriçãoPermissão
POST/ai-engine/api/v1/ai/vision/analyzeAnalisa uma imagem por URL ou base64AI_ENGINE_EXECUTE
POST/ai-engine/api/v1/ai/audio/transcribeTranscreve áudio por URL ou base64AI_ENGINE_EXECUTE
MétodoRotaDescriçãoPermissão
GET/ai-engine/api/v1/ai/modelsLista os modelos disponíveis por provedorauthMiddlewaresem requirePermission e sem requireOrganization
GET/ai-engine/api/v1/ai/models/providersStatus de cada provedor configuradoauthMiddlewaresem requirePermission e sem requireOrganization

Saúde

MétodoRotaDescrição
GET/ai-engine/healthNome do serviço e versão. Não testa provedor.

Todas as oito rotas de execução aplicam, nesta ordem: authMiddlewarerequirePermission('AI_ENGINE_EXECUTE')requireOrganization. Token sem organizationId recebe 403 antes de qualquer chamada ao provedor.


POST /ai-engine/api/v1/ai/classify

Classifica um texto entre categorias que você define na própria requisição.

Request

json
{
  "text": "Fui cobrado duas vezes na mesma fatura e ninguém me atende desde segunda.",
  "categories": ["cobranca", "duvida_contrato", "reclamacao", "elogio"],
  "allowMultiple": false,
  "includeConfidence": true,
  "provider": "anthropic",
  "model": "smart"
}
{
  "text": "Fui cobrado duas vezes na mesma fatura e ninguém me atende desde segunda.",
  "categories": ["cobranca", "duvida_contrato", "reclamacao", "elogio"],
  "allowMultiple": false,
  "includeConfidence": true,
  "provider": "anthropic",
  "model": "smart"
}
CampoTipoObrigatórioDescrição
textstring (1 a 50.000)SimTexto a classificar
categoriesstring[] (2 a 50)SimCategorias candidatas
allowMultiplebooleanNãoPadrão false. Com true, a resposta traz categories[]
includeConfidencebooleanNãoPadrão true
provideranthropic | ollamaNãoOmitido, vale a estratégia do ambiente
modelstringNãoAlias (fast/smart/powerful) ou identificador concreto

Resposta 200

json
{
  "data": {
    "category": "reclamacao",
    "confidence": 0.92,
    "categories": [{ "name": "reclamacao", "confidence": 0.92 }],
    "reasoning": "O texto relata cobrança em duplicidade e falta de atendimento.",
    "model": "claude-sonnet-4-20250514",
    "usage": { "inputTokens": 128, "outputTokens": 64 }
  }
}
{
  "data": {
    "category": "reclamacao",
    "confidence": 0.92,
    "categories": [{ "name": "reclamacao", "confidence": 0.92 }],
    "reasoning": "O texto relata cobrança em duplicidade e falta de atendimento.",
    "model": "claude-sonnet-4-20250514",
    "usage": { "inputTokens": 128, "outputTokens": 64 }
  }
}

Erros

StatusQuando
400Corpo reprovado no schema Zod (menos de 2 categorias, texto vazio, texto acima do limite)
401Token ausente ou inválido
403Token sem AI_ENGINE_EXECUTE, ou sem organizationId
500Falha na chamada ao provedor após as tentativas
503Provedor não configurado (mensagem indica qual variável de ambiente falta)

POST /ai-engine/api/v1/ai/extract

Extrai campos estruturados de um texto sem formato.

Request

json
{
  "text": "Cliente João da Silva, autônomo há 4 anos, renda declarada de R$ 7.200 por mês.",
  "fields": [
    { "name": "nome", "type": "string", "required": true },
    { "name": "vinculo", "type": "string", "description": "CLT, autônomo ou aposentado" },
    { "name": "rendaMensal", "type": "number", "required": true },
    { "name": "tempoEmpresaAnos", "type": "number" }
  ],
  "language": "pt"
}
{
  "text": "Cliente João da Silva, autônomo há 4 anos, renda declarada de R$ 7.200 por mês.",
  "fields": [
    { "name": "nome", "type": "string", "required": true },
    { "name": "vinculo", "type": "string", "description": "CLT, autônomo ou aposentado" },
    { "name": "rendaMensal", "type": "number", "required": true },
    { "name": "tempoEmpresaAnos", "type": "number" }
  ],
  "language": "pt"
}
CampoTipoObrigatórioDescrição
textstring (1 a 100.000)SimTexto de origem
fieldsobject[] (1 a 50)SimCada item tem name, type (string, number, boolean, date, array, object), description opcional e required
languagestringNãoPadrão pt

Resposta 200

json
{
  "data": {
    "extracted": {
      "nome": "João da Silva",
      "vinculo": "autônomo",
      "rendaMensal": 7200,
      "tempoEmpresaAnos": 4
    },
    "confidence": { "nome": 0.98, "rendaMensal": 0.95 },
    "missingFields": [],
    "model": "claude-sonnet-4-20250514",
    "usage": { "inputTokens": 210, "outputTokens": 96 }
  }
}
{
  "data": {
    "extracted": {
      "nome": "João da Silva",
      "vinculo": "autônomo",
      "rendaMensal": 7200,
      "tempoEmpresaAnos": 4
    },
    "confidence": { "nome": 0.98, "rendaMensal": 0.95 },
    "missingFields": [],
    "model": "claude-sonnet-4-20250514",
    "usage": { "inputTokens": 210, "outputTokens": 96 }
  }
}

missingFields lista os campos marcados como required que o modelo não conseguiu encontrar. Trate essa lista, e não a ausência da chave em extracted.


POST /ai-engine/api/v1/ai/audio/transcribe

Transcreve áudio pelo Whisper ASR. Não aceita provider — a transcrição só existe pelo Whisper.

Request

json
{
  "audioSource": "base64",
  "base64": "UklGRiQAAABXQVZFZm10...",
  "language": "pt",
  "outputFormat": "json"
}
{
  "audioSource": "base64",
  "base64": "UklGRiQAAABXQVZFZm10...",
  "language": "pt",
  "outputFormat": "json"
}
CampoTipoObrigatórioDescrição
audioSourceurl | base64SimOrigem do áudio
audioUrlstring (URL)Se audioSource=urlPassa por validação anti-SSRF antes do download
base64stringSe audioSource=base64Conteúdo do arquivo
languagestring (até 10)NãoPadrão pt
outputFormattext | jsonNãojson traz segments com marcação de tempo

Resposta 200

json
{
  "data": {
    "text": "Boa tarde, estou ligando sobre a fatura.",
    "language": "pt",
    "duration": 4.2,
    "segments": [
      { "start": 0.0, "end": 4.2, "text": "Boa tarde, estou ligando sobre a fatura." }
    ]
  }
}
{
  "data": {
    "text": "Boa tarde, estou ligando sobre a fatura.",
    "language": "pt",
    "duration": 4.2,
    "segments": [
      { "start": 0.0, "end": 4.2, "text": "Boa tarde, estou ligando sobre a fatura." }
    ]
  }
}

Erros

StatusQuando
400Áudio ausente, URL reprovada pela guarda anti-SSRF, ou arquivo acima de 50 MB
503AI_ENGINE_WHISPER_URL não configurada, ou o serviço Whisper não respondeu

10

Início rápido

Do zero à primeira classificação, em staging.

1. Autenticar no IAM

bash
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@catalisa.app",
    "password": "root123456",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@catalisa.app",
    "password": "root123456",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)

2. Ver quais provedores estão de pé

bash
curl -s https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/models/providers \
  -H "Authorization: Bearer $TOKEN" | jq
curl -s https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/models/providers \
  -H "Authorization: Bearer $TOKEN" | jq
json
{ "data": [{ "name": "anthropic", "status": "available" }] }
{ "data": [{ "name": "anthropic", "status": "available" }] }

Um provedor só aparece na lista se estiver configurado. Se a lista vier vazia, nenhuma variável de provedor foi preenchida no ambiente.

3. Classificar um texto

bash
curl -s -X POST https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/classify \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "text": "Fui cobrado duas vezes na mesma fatura e ninguém me atende desde segunda.",
    "categories": ["cobranca", "duvida_contrato", "reclamacao", "elogio"]
  }' | jq '.data | {category, confidence, model, usage}'
curl -s -X POST https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/classify \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "text": "Fui cobrado duas vezes na mesma fatura e ninguém me atende desde segunda.",
    "categories": ["cobranca", "duvida_contrato", "reclamacao", "elogio"]
  }' | jq '.data | {category, confidence, model, usage}'
json
{
  "category": "reclamacao",
  "confidence": 0.92,
  "model": "claude-sonnet-4-20250514",
  "usage": { "inputTokens": 128, "outputTokens": 64 }
}
{
  "category": "reclamacao",
  "confidence": 0.92,
  "model": "claude-sonnet-4-20250514",
  "usage": { "inputTokens": 128, "outputTokens": 64 }
}

4. Confirmar que o tenant é obrigatório

bash
# Token sem organizationId
curl -s -o /dev/null -w "%{http_code}\n" \
  -X POST https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/classify \
  -H "Authorization: Bearer $TOKEN_SEM_ORG" -H "Content-Type: application/json" \
  -d '{"text":"teste","categories":["a","b"]}'
# Token sem organizationId
curl -s -o /dev/null -w "%{http_code}\n" \
  -X POST https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/classify \
  -H "Authorization: Bearer $TOKEN_SEM_ORG" -H "Content-Type: application/json" \
  -d '{"text":"teste","categories":["a","b"]}'

Retorna 403 sem chamar o provedor — o middleware barra antes.

Credenciais de staging, conforme AMBIENTES.md. Nunca use credencial de produção em documentação ou script de exemplo.

Os comandos acima não foram executados durante a redação deste documento; valide no seu ambiente antes de copiar para um runbook.


11

Receitas

Manter o processamento inteiramente dentro da sua rede

Objetivo: nenhum byte de conteúdo de cliente sai da infraestrutura.

flowchart LR
  subgraph sua["Sua infraestrutura"]
    AE["AI Engine"] --> OLL["Ollama<br/>texto"]
    AE --> WHI["Whisper ASR<br/>áudio"]
  end
  AE -.->|"chave ausente — provedor nem é construído"| ANT["api.anthropic.com"]

1. Configure só os provedores internos

bash
# No ambiente do AI Engine
AI_ENGINE_OLLAMA_URL=http://ollama.interno:11434
AI_ENGINE_WHISPER_URL=http://whisper.interno:9000
AI_ENGINE_DEFAULT_PROVIDER=ollama
AI_ENGINE_PROVIDER_STRATEGY=single
# e NÃO defina AI_ENGINE_ANTHROPIC_API_KEY
# No ambiente do AI Engine
AI_ENGINE_OLLAMA_URL=http://ollama.interno:11434
AI_ENGINE_WHISPER_URL=http://whisper.interno:9000
AI_ENGINE_DEFAULT_PROVIDER=ollama
AI_ENGINE_PROVIDER_STRATEGY=single
# e NÃO defina AI_ENGINE_ANTHROPIC_API_KEY

2. Confirme que o provedor externo não está configurado

bash
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jq
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jq

Resposta esperada — apenas ollama e whisper na lista, sem anthropic:

json
{
  "data": [
    { "name": "ollama", "status": "available" },
    { "name": "whisper", "status": "available" }
  ]
}
{
  "data": [
    { "name": "ollama", "status": "available" },
    { "name": "whisper", "status": "available" }
  ]
}

Armadilhas.

  • Com AI_ENGINE_ANTHROPIC_API_KEY definida, um cliente ainda pode pedir provider: "anthropic" explicitamente no corpo e escapar da política. Se a exigência é absoluta, não configure a chave.
  • strategy: hybrid sem AI_ENGINE_ANTHROPIC_API_KEY cai no provedor padrão. Deixe single para não depender dessa dedução.
  • Os aliases do Ollama (qwen2.5:3b, llama3.1:8b, qwen2.5:14b) precisam estar baixados no servidor. Alias que não existe vira erro do Ollama, não erro de validação.

Fixar o modelo em vez de usar alias

Objetivo: garantir reprodutibilidade de uma esteira regulada, onde trocar de modelo exige revalidação.

bash
curl -s -X POST "$BASE/ai-engine/api/v1/ai/extract" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "text": "...",
    "fields": [{"name":"cpf","type":"string","required":true}],
    "provider": "anthropic",
    "model": "claude-sonnet-4-20250514"
  }'
curl -s -X POST "$BASE/ai-engine/api/v1/ai/extract" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "text": "...",
    "fields": [{"name":"cpf","type":"string","required":true}],
    "provider": "anthropic",
    "model": "claude-sonnet-4-20250514"
  }'

Armadilhas. Nome concreto passa direto para o provedor, sem validação nossa: se o fabricante aposentar o modelo, você recebe o erro dele com 500. Monitore o campo model da resposta — ele é o que o provedor de fato usou.

Reduzir custo em texto curto sem perder qualidade em texto longo

Objetivo: pagar token de nuvem só quando o texto justifica, mantendo o restante dentro da sua rede.

1. Ligue a estratégia hybrid com os dois provedores construídos

bash
AI_ENGINE_PROVIDER_STRATEGY=hybrid
AI_ENGINE_DEFAULT_PROVIDER=anthropic
AI_ENGINE_OLLAMA_URL=http://ollama.interno:11434
AI_ENGINE_ANTHROPIC_API_KEY=<chave>
AI_ENGINE_PROVIDER_STRATEGY=hybrid
AI_ENGINE_DEFAULT_PROVIDER=anthropic
AI_ENGINE_OLLAMA_URL=http://ollama.interno:11434
AI_ENGINE_ANTHROPIC_API_KEY=<chave>

Com isso, requisição sem provider e com texto abaixo de 2.000 caracteres vai para o Ollama; acima disso, vai para o Claude.

2. Confirme o roteamento pelo campo model da resposta

bash
curl -s -X POST "$BASE/ai-engine/api/v1/ai/classify" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"Fui cobrado duas vezes.","categories":["cobranca","reclamacao"]}' \
  | jq '.data.model'
curl -s -X POST "$BASE/ai-engine/api/v1/ai/classify" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"Fui cobrado duas vezes.","categories":["cobranca","reclamacao"]}' \
  | jq '.data.model'

Resposta esperada para um texto curto — um identificador do Ollama, não do Claude:

json
"qwen2.5:3b"
"qwen2.5:3b"
Tamanho do textProvedor escolhidomodel esperado na resposta
Menos de 2.000 caracteresOllamaqwen2.5:3b (alias fast)
2.000 caracteres ou maisClaudeclaude-sonnet-4-20250514 (alias smart)

Armadilhas.

  • O corte é por tamanho de texto, não por dificuldade. Um texto curto e ambíguo vai para o modelo menor mesmo assim.
  • POST /vision/analyze não passa texto para a decisão e sempre usa o provedor padrão.
  • Se o Ollama estiver fora do ar, não há queda para o Claude — a requisição falha (§15).

Descobrir por que uma chamada voltou 503

bash
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jq
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jq

Ordem de diagnóstico:

  1. O provedor aparece na lista? Se não, a variável de ambiente dele não está preenchida.
  2. Aparece como unavailable? Para Ollama e Whisper isso significa que o serviço não respondeu — verifique a rede e a URL.
  3. Aparece como available mas a chamada falha? Para o anthropic, available só significa que existe uma chave — não que ela é válida. Uma chave revogada dá available aqui e 500 na chamada real.

Estimar custo antes de subir volume

Toda resposta de LLM traz usage.inputTokens e usage.outputTokens. Rode uma amostra representativa e some:

bash
for i in $(seq 1 20); do
  curl -s -X POST "$BASE/ai-engine/api/v1/ai/classify" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d @amostra-$i.json | jq '.data.usage'
done | jq -s 'reduce .[] as $u ({i:0,o:0}; {i: (.i + $u.inputTokens), o: (.o + $u.outputTokens)})'
for i in $(seq 1 20); do
  curl -s -X POST "$BASE/ai-engine/api/v1/ai/classify" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d @amostra-$i.json | jq '.data.usage'
done | jq -s 'reduce .[] as $u ({i:0,o:0}; {i: (.i + $u.inputTokens), o: (.o + $u.outputTokens)})'

Armadilhas. O AI Engine não acumula esse número por organização (§15). Se você precisa de controle de gasto por cliente, some no seu lado a partir do usage de cada resposta.


12

Integração com outros building blocks

Building blockComo se relacionaObrigatório
IAMEmite o token; o AI Engine exige AI_ENGINE_EXECUTE e organizationIdSim
Data ExtractionExtração de documento com OCR, fila e reprocessamento. Tem provedores próprios (ver ADR 0004) e não passa pelo AI EngineNão
Decision PlatformConsome o resultado da classificação como entrada de regraNão
WPP / WPP BusinessOrigem natural do texto e do áudio a analisarNão
RoomsTambém faz transcrição, mas ao vivo e por outro caminho (Deepgram no worker de agente)Não
File StorageGuarda a imagem ou o áudio; o AI Engine recebe a URL assinadaNão
flowchart TD
  WPP["WPP"] -->|"mensagem / áudio"| SEU["Seu serviço"]
  SEU -->|"POST /classify · /sentiment<br/>POST /audio/transcribe"| AI["AI Engine"]
  IAM["IAM"] -.->|"Bearer JWT"| AI
  AI -->|"categoria + confiança"| DP["Decision Platform<br/>regra de roteamento"]
  DP --> WH["Webhooks Engine<br/>avisa o sistema do cliente"]

A leitura comercial deste diagrama: a IA não é um produto solto no canto: ela entra no meio de uma esteira que já existe, autenticada pelo mesmo token, e o resultado vira entrada de decisão sem integração nova.


13

Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
AI_ENGINE_ANTHROPIC_API_KEYChave da API da Anthropic. Sem ela, o provedor anthropic não é registrado.Não*
AI_ENGINE_ANTHROPIC_BASE_URLURL alternativa da API (proxy corporativo, gateway).Nãohttps://api.anthropic.com
AI_ENGINE_OLLAMA_URLURL do seu servidor Ollama. Sem ela, o provedor ollama não é registrado.Não*
AI_ENGINE_WHISPER_URLURL do seu Whisper ASR. Sem ela, /audio/transcribe responde 503.Não
AI_ENGINE_PROVIDER_STRATEGYsingle ou hybridNãosingle
AI_ENGINE_DEFAULT_PROVIDERanthropic ou ollamaNãoanthropic
PORTPorta no modo standaloneNão3000 no main.ts; 3020 é a porta registrada em DEFAULT_MODULE_PORTS
MODULE_AI_ENGINE_URLUsada por outros building blocks para alcançar este em standaloneEm standalone''

* Nenhuma das duas é obrigatória isoladamente, mas pelo menos uma precisa existir. Sem provedor de LLM configurado, as seis operações de texto e a de imagem lançam erro em tempo de execução.

Dependências de infraestrutura

DependênciaPara quê
IAM alcançável (MODULE_IAM_URL)Verificação do token em standalone
API da Anthropic, ou Ollama, ou ambosExecução das operações de LLM
Whisper ASRExecução da transcrição
PostgreSQL / RedisNão usa nenhum dos dois

Limites

LimiteValor
text em sentiment e classify50.000 caracteres
text em summarize e extract100.000 caracteres
prompt em generate50.000 caracteres
maxTokens em generate1 a 4.096, padrão 500
temperature em generate0 a 2, padrão 0.7
categories em classify2 a 50
fields em extract1 a 50
Arquivo de áudio na transcrição50 MB
Timeout de chamada ao provedor120 segundos
Timeout de download de imagem e áudio por URL30 segundos
Tentativas na API da Anthropic3, com recuo exponencial

Catálogo de erros

StatusCódigoSignificaO que fazer
400VALIDATIONCorpo reprovado no Zod, ou URL de mídia reprovada pela guarda anti-SSRFConfira campos e limites contra §13
401UNAUTHORIZEDToken ausente, inválido ou expiradoRenove no IAM
403FORBIDDENFalta AI_ENGINE_EXECUTE ou falta organizationId no tokenAutentique informando a organização
500INTERNALFalha na chamada ao provedor após as tentativas, ou resposta que não pôde ser interpretadaVerifique a mensagem — ela repassa o erro do provedor
503SERVICE_UNAVAILABLEWhisper não configurado ou fora do arConfira AI_ENGINE_WHISPER_URL e a saúde do serviço

Observabilidade.

  • GET /ai-engine/health devolve nome do serviço e versão. Não testa provedor — para isso use GET /api/v1/ai/models/providers.
  • Na inicialização, a ProviderFactory registra em log a estratégia, o provedor padrão e quais provedores foram construídos. É a forma mais rápida de confirmar o que o ambiente enxerga.
  • Nenhum conteúdo de prompt vai para o log. Os erros registram só a mensagem de erro do provedor e o número da tentativa.
  • Não há métrica de uso nem evento publicado. O que existe é o usage de cada resposta, que quem chama precisa acumular (§15).

14

Segurança e compliance

O conteúdo sai da sua infraestrutura — e isso precisa de decisão explícita

O ponto que precisa de decisão explícita: o conteúdo sai da sua infraestrutura. Quando a requisição usa provider: "anthropic" — que é o padrão de fábrica — o texto, a imagem ou o prompt são enviados para a API da Anthropic, fora da sua rede e fora do país. Isso é tratamento de dado pessoal por operador terceiro e precisa estar previsto no seu inventário de tratamento e nos contratos com os titulares. Não é um detalhe de implementação; é uma decisão de LGPD.

O que é público sobre esse tratamento, consultado em 2026-08-16:

PerguntaResposta da AnthropicFonte
Por quanto tempo guardam entrada e saída da API?Exclusão automática em até 30 dias do recebimento ou geração, com exceções para violação de política de uso (até 2 anos), pontuações de trust and safety (até 7 anos) e obrigação legalprivacy.claude.com
Treinam modelos com o conteúdo enviado?"Anthropic may not train models on Customer Content from Services"Commercial Terms, seção B

O caminho para não enviar nada

O caminho para não enviar nada. Configure AI_ENGINE_OLLAMA_URL e AI_ENGINE_WHISPER_URL apontando para serviços na sua rede e não configure AI_ENGINE_ANTHROPIC_API_KEY. Sem a chave, o provedor externo não é sequer construído, e uma requisição com provider: "anthropic" falha em vez de vazar. Configurar a chave e apenas mudar o padrão não é suficiente: o cliente ainda pode pedir o provedor externo no corpo.

flowchart TD
  Q1{"AI_ENGINE_ANTHROPIC_API_KEY<br/>está definida?"}
  Q1 -->|não| SAFE["Provedor externo não é construído.<br/>provider: anthropic falha em vez de vazar.<br/>NADA sai da rede."]
  Q1 -->|sim| Q2{"o corpo pede<br/>provider: anthropic?"}
  Q2 -->|sim| OUT["O conteúdo vai para api.anthropic.com<br/>mesmo com o padrão em ollama"]
  Q2 -->|não| Q3{"qual o defaultProvider<br/>e a strategy?"}
  Q3 -->|"ollama · single"| SAFE2["Fica na rede nesta chamada,<br/>mas a porta continua aberta"]
  Q3 -->|"anthropic, ou hybrid com texto longo"| OUT

  SAFE:::ok
  SAFE2:::warn
  OUT:::risk
  classDef ok stroke-width:3px
  classDef warn stroke-dasharray: 4 3
  classDef risk stroke-width:3px

Atenção. Mudar AI_ENGINE_DEFAULT_PROVIDER para ollama não é um controle de LGPD — é um padrão. O único controle efetivo é a ausência da chave. Se a política da sua operação proíbe a saída do dado, a chave não pode existir no ambiente.

O que o AI Engine guarda

O que o AI Engine guarda. Nada. Sem banco, sem cache, sem fila. Não há histórico de prompt, não há resposta arquivada, não há dado a expurgar em pedido de eliminação — porque não há dado guardado. Em contrapartida, também não há registro de quem pediu o quê: se a sua obrigação inclui trilha de auditoria de uso de IA, ela precisa ser construída do lado de quem chama, com o Audit Trail.

Isolamento, SSRF, credenciais e logs

Isolamento entre tenants. As oito rotas de execução aplicam requireOrganization, que devolve 403 quando o token não traz organizationId. Como não há persistência, não existe consulta capaz de retornar dado de outra organização — o isolamento aqui é de acesso, não de armazenamento. As duas rotas de catálogo (/models e /models/providers) exigem só token válido e expõem apenas nomes de modelo e status de provedor, que não são dados de tenant.

Guarda anti-SSRF. imageUrl e audioUrl vêm de quem chama. Antes de qualquer download, isUrlSafe valida o destino e secureFetch resolve o DNS e fixa o IP validado, o que fecha a janela de DNS rebinding. Sem isso, o serviço poderia ser usado para buscar endereços internos — metadados de nuvem, serviços da rede overlay, localhost — e devolver o conteúdo através do modelo.

flowchart LR
  URL["imageUrl / audioUrl<br/>vindas de quem chama"] --> V{"isUrlSafe<br/>destino permitido?"}
  V -->|não| REJ["400 VALIDATION<br/>nenhum download acontece"]
  V -->|sim| SF["secureFetch<br/>resolve o DNS e fixa o IP validado"]
  SF --> DL["Download do conteúdo"]
  SF -.->|"fecha a janela de DNS rebinding"| REB["IP interno trocado<br/>depois da validação"]

Credenciais. A chave do fabricante fica só no ambiente do AI Engine, nunca nas aplicações que o consomem. Guarde com SOPS, conforme SECRETS-SOPS-REFERENCE.md. Rotacionar a chave é reiniciar o serviço com o novo valor; nenhum consumidor precisa mudar.

Logs. Nenhum trecho de prompt, resposta, imagem ou áudio é registrado. Os logs de erro carregam a mensagem do provedor, o número da tentativa e o tempo de recuo.

Limites como controle de custo e de abuso. Os tetos de tamanho de texto, de maxTokens e de arquivo de áudio (§13) também são a primeira barreira contra um consumidor que, por bug, envia um documento inteiro numa chamada de sentimento.


15

Limitações conhecidas

LimitaçãoImpactoSituação
Sem fallback entre provedoresSe o provedor escolhido falhar, a requisição falha. Não há queda automática do Claude para o Ollama nem o contrário. O ClaudeProvider tenta 3 vezes no mesmo provedor; Ollama e Whisper não repetem.Roadmap — não anuncie "alta disponibilidade multi-provedor"
Sem medição de uso por organizaçãoO usage volta em cada resposta, mas nada é acumulado nem publicado como evento. Não há como cobrar por consumo hoje.Roadmap — é o que bloqueia a precificação (§6)
Sem observabilidade de promptNão há log de requisição, replay, nem painel. Diagnosticar "por que essa classificação saiu errada" depende de reproduzir a chamada.Por design (sem banco) — reavaliar se a demanda crescer
Identificadores de modelo fixos no códigoOs aliases apontam para identificadores escritos em types/index.ts. Quando o fabricante aposenta um modelo, o alias quebra e a correção é deploy, não configuração.Conhecido — confira os identificadores da §8 antes de assumir que apontam para o modelo atual
GET /models e GET /models/providers sem permissão nem organizaçãoAs duas rotas exigem só token válido: não passam por requirePermission nem requireOrganization, ao contrário das outras oito. Expõem nomes de modelo e status de provedor.Divergência conhecida em relação ao restante do módulo
AI_ENGINE_READ e AI_ENGINE_ADMIN declaradas e não usadasAs duas permissões existem no vocabulário do IAM, mas nenhuma rota as exige. Conceder não muda nada.Especificado, não implementado
isAvailable do Anthropic não valida a chaveGET /models/providers responde available sempre que a variável estiver preenchida, mesmo com chave revogada. Só Ollama e Whisper são de fato consultados.Conhecido
Lista de modelos do Anthropic é estáticalistModels() devolve três identificadores fixos, sem consultar a API. Modelo novo do fabricante não aparece.Conhecido
Visão em Ollama não é declarada como suportadaO catálogo anuncia capabilities: ['text'] para modelos Ollama. Uma análise de imagem com provider: "ollama" depende do modelo carregado no seu servidor suportar imagem.Conhecido — teste antes de depender
Sem streamingTodas as respostas são completas. Não há server-sent events nem resposta parcial.Não implementado
Sem cacheDuas requisições idênticas custam duas chamadas ao provedor.Não implementado
Sem rate limit próprioO building block não limita chamadas por organização. Um consumidor com laço defeituoso consome a franquia do provedor.Roadmap
Transcrição só pelo WhisperPOST /audio/transcribe ignora o campo provider. Não há transcrição por provedor de nuvem neste building block.Por design
Sem OpenAI, Google ou BedrockO schema aceita apenas anthropic e ollama. Nenhum outro fabricante é alcançável por este building block.Por design — o catálogo é pequeno de propósito (§5)

16

Perguntas frequentes

Meus dados de cliente são usados para treinar modelo de IA?

Não, segundo os termos comerciais públicos da Anthropic, que dizem que a Anthropic não treina modelos com conteúdo de cliente (Commercial Terms, seção B). As entradas e saídas são apagadas em até 30 dias do recebimento, com exceções documentadas (privacy.claude.com). Ainda assim, o envio para fora existe e precisa estar no seu inventário de tratamento — leia a §14. Se a sua política proíbe o envio, use apenas Ollama e Whisper.

Dá para rodar tudo dentro da minha rede, sem internet?

Dá, para as operações de texto e para a transcrição: Ollama e Whisper rodam na sua infraestrutura. A receita está na §11. O ponto de atenção é qualidade: modelos que rodam em máquina comum são menores que os de nuvem, e a diferença aparece em texto ambíguo ou em extração com muitos campos. Meça no seu caso antes de decidir.

O que acontece se o provedor cair?

A requisição falha. Não há fallback automático entre provedores (§15). Na Anthropic há até 3 tentativas com recuo exponencial e respeito ao Retry-After em 429, o que cobre indisponibilidade curta. Para Ollama e Whisper não há repetição — trate no seu cliente.

Como eu sei quanto estou gastando?

Pelo campo usage de cada resposta, que traz inputTokens e outputTokens. O AI Engine não acumula esse número por organização hoje (§15), então quem precisa de controle de gasto soma no próprio lado. É a mesma razão pela qual a precificação ainda está em definição (§6).

Qual a diferença entre o AI Engine e o Data Extraction?

O AI Engine é síncrono, sem estado e trabalha com texto que você já tem: você manda uma string e recebe a resposta na mesma chamada. O Data Extraction é uma esteira: recebe um documento, guarda, processa em fila, permite reprocessar e mantém histórico — e tem provedores próprios, descritos na ADR 0004. Regra prática: se a origem é um arquivo que precisa ser arquivado e reprocessado, é Data Extraction; se é um trecho de texto dentro de um fluxo, é AI Engine.

Por que a rota tem ai duas vezes?

Porque o basePath do app é /ai-engine e os routers são montados sob /api/v1/ai. O caminho real é /ai-engine/api/v1/ai/generate. É feio e é assim; mudar quebraria integrações existentes. Copie os caminhos da §9 em vez de deduzir.

Posso escolher o modelo por requisição?

Pode, pelo campo model, com alias (fast, smart, powerful) ou com o identificador concreto. Prefira alias: identificador concreto passa direto para o provedor e quebra quando o fabricante aposenta o modelo. O campo model da resposta sempre diz o que foi de fato usado.

A transcrição funciona bem em português?

O Whisper é multilíngue e o parâmetro language tem pt como padrão neste building block. A qualidade depende do modelo Whisper carregado no seu servidor e da qualidade do áudio. Para transcrição ao vivo, dentro de uma sala de vídeo, o caminho é outro building block: o Rooms, que usa Deepgram em streaming.

Preciso de banco de dados para usar o AI Engine?

Não. Ele não tem modelo Prisma e não abre conexão com PostgreSQL nem com Redis. As únicas dependências são o IAM, para verificar o token, e pelo menos um provedor configurado.


Padrão: PADRAO-DOCUMENTACAO.md · Índice: INDEX.md