AI Engine
ProduçãoTarefas de IA sobre texto, imagem e áudio numa API só, na nuvem ou na sua rede
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.
- 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
- 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
- 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
11 endpoints em 5 recursos.
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.
| Atributo | Valor |
|---|---|
| Identificador | ai-engine |
| Categoria | Inteligência |
| Escopo | Tenant (exige organizationId no token nas oito rotas de execução) |
| Porta (standalone) | 3020 |
| Path alias | @ai-engine |
| Prefixo HTTP | /ai-engine |
| Status | Produção desde 2026-02 |
| Depende de | IAM (token). Nenhum banco, nenhum Redis. |
O problema
negócioO 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
fetchdireto, 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.
Proposta de valor
negócio| Antes | Depois |
|---|---|
| Cada serviço integra o SDK do fabricante e guarda a chave dele | Um endpoint autenticado pelo token do IAM; a chave fica só no AI Engine |
| Prompt e parser de JSON reescritos por time | POST /extract com a lista de campos; a resposta já vem tipada |
| Trocar de modelo é um projeto | Trocar de modelo é um campo no corpo da requisição ou uma variável de ambiente |
| Dado sensível sai da rede sem decisão registrada | O 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ção | O que você manda | O que volta pronto |
|---|---|---|
generate | Um prompt | Texto gerado + usage |
summarize | Um texto | Resumo + usage |
translate | Texto e idioma de destino | Tradução + usage |
sentiment | Um texto | sentiment, confidence, score |
classify | Texto e suas categorias | Categoria escolhida + confiança |
extract | Texto e a lista de campos | extracted, confidence por campo, missingFields |
vision/analyze | Imagem por URL ou base64 | Descrição estruturada |
audio/transcribe | Áudio por URL ou base64 | text, 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.
Casos de uso reais
negócioCaso 1 — Triagem de mensagens de atendimento sem fila humana Cenário ilustrativo
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 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.
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.
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
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.
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.
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 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
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.
Sem transcrição, o áudio virava tarefa humana: alguém ouvia, resumia e digitava. O tempo de resposta era medido em horas.
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 á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
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).
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.
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.
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.
Mercado e diferenciais
negócioPanorama. 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ério | Catalisa AI Engine | OpenRouter | LiteLLM | Portkey | Amazon Bedrock |
|---|---|---|---|---|---|
| Modelo de preço | Em definição (§6) | Sem markup no token; 5,5% na compra de crédito | Grátis (MIT); enterprise sob consulta | Grátis até 10k logs; US$ 49/mês | Por token; roteamento a US$ 1/mil req. |
| Nº de modelos | 6 aliases sobre 2 provedores de LLM | 500+ | 100+ | Multi-provedor | 17+ famílias |
| Fallback automático entre provedores | Não (ver §15) | Sim | Sim | Sim | Fallback configurável no prompt routing |
| Rodar dentro da sua rede | Sim, via Ollama | Não | Sim (self-hosted) | Sim (self-hosted) | Não |
| Transcrição de áudio no mesmo serviço | Sim (Whisper) | Não | Não | Não | Serviço separado |
| Tarefas de negócio prontas | Sim (8 operações) | Não | Não | Não | Não |
| Observabilidade de prompt | Não (ver §15) | Sim | Sim | Sim, é o foco | Sim, via CloudWatch |
| Autorização por organização | Nativa, token do IAM | Por chave | Virtual keys | Por chave | IAM da AWS |
| Você opera a infraestrutura | Não | Não | Sim | Opcional | Não |
Nossos diferenciais
- 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. - A operação é a tarefa, não o modelo.
POST /classifyrecebe 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. - 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.
Modelo de cobrança e ROI
negócioUnidade 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:
| Driver | Por 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 transcritos | O Whisper roda em CPU ou GPU sua; o custo é de máquina, não de licença. |
| Chamadas por organização | Concorrê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 Engine | OpenRouter | Portkey | Amazon Bedrock | |
|---|---|---|---|---|
| Custo da camada | Em definição | Sem markup no token; 5,5% sobre o crédito comprado por cartão | US$ 49/mês no Production, até 100 mil logs; US$ 9 por 100 mil adicionais | US$ 1 por mil requisições, se usar o Intelligent Prompt Routing |
| Custo do modelo | Preço do fabricante, ou zero com Ollama na sua máquina | Preço do fabricante | Preço do fabricante | Preço do fabricante, dentro da AWS |
| Custo da transcrição | Máquina sua (Whisper MIT) | Não oferece | Não oferece | Serviço separado |
| Log de prompt | Não temos | — | Incluído até o limite do plano | CloudWatch, 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.
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 --> DENTROComo 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ção | O que acontece de fato |
|---|---|
strategy = hybrid, texto curto, mas Ollama não configurado | Cai 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 = hybrid | Nã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,smartoupowerful, e oresolveModeltraduz 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
ClaudeProvidertenta até 3 vezes, respeita o cabeçalhoRetry-Afterem429e 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
hybridroteia 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 informaprovidere 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.
imageUrleaudioUrlsão controladas por quem chama. Antes de baixar qualquer coisa, o serviço valida o destino comisUrlSafee busca comsecureFetch, 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. isAvailabledo Anthropic só verifica se há chave. Ele não faz chamada de teste à API. Por issoGET /models/providersrespondeavailablesempre queAI_ENGINE_ANTHROPIC_API_KEYestiver 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.
Conceitos e modelo de dados
Glossário
| Termo | Significa |
|---|---|
| Provider | Um destino de execução. Hoje: anthropic (Claude API), ollama (LLM na sua rede) e o Whisper, usado só para transcrição. |
| Alias de modelo | fast, smart ou powerful. Um apelido estável que a ProviderFactory traduz para o identificador concreto do provedor escolhido. |
| Strategy | single (sempre o provedor padrão) ou hybrid (texto curto vai para o Ollama, texto longo vai para o Claude). |
| TokenUsage | inputTokens e outputTokens devolvidos pelo provedor em toda resposta de LLM. É o que você usa para estimar custo. |
| Operação | Cada 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
| Alias | provider: anthropic | provider: ollama |
|---|---|---|
fast | claude-haiku-4-5-20251001 | qwen2.5:3b |
smart (padrão) | claude-sonnet-4-20250514 | llama3.1:8b |
powerful | claude-opus-4-20250514 | qwen2.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.
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étodo | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /ai-engine/api/v1/ai/generate | Gera texto a partir de um prompt | AI_ENGINE_EXECUTE |
POST | /ai-engine/api/v1/ai/summarize | Resume um texto | AI_ENGINE_EXECUTE |
POST | /ai-engine/api/v1/ai/translate | Traduz um texto | AI_ENGINE_EXECUTE |
Análise — /ai-engine/api/v1/ai
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /ai-engine/api/v1/ai/sentiment | Classifica o sentimento do texto | AI_ENGINE_EXECUTE |
POST | /ai-engine/api/v1/ai/classify | Classifica o texto em categorias que você define | AI_ENGINE_EXECUTE |
POST | /ai-engine/api/v1/ai/extract | Extrai campos estruturados de texto livre | AI_ENGINE_EXECUTE |
Visão e áudio
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /ai-engine/api/v1/ai/vision/analyze | Analisa uma imagem por URL ou base64 | AI_ENGINE_EXECUTE |
POST | /ai-engine/api/v1/ai/audio/transcribe | Transcreve áudio por URL ou base64 | AI_ENGINE_EXECUTE |
Catálogo
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /ai-engine/api/v1/ai/models | Lista os modelos disponíveis por provedor | Só authMiddleware — sem requirePermission e sem requireOrganization |
GET | /ai-engine/api/v1/ai/models/providers | Status de cada provedor configurado | Só authMiddleware — sem requirePermission e sem requireOrganization |
Saúde
| Método | Rota | Descrição |
|---|---|---|
GET | /ai-engine/health | Nome do serviço e versão. Não testa provedor. |
Todas as oito rotas de execução aplicam, nesta ordem: authMiddleware → requirePermission('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
{
"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"
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
text | string (1 a 50.000) | Sim | Texto a classificar |
categories | string[] (2 a 50) | Sim | Categorias candidatas |
allowMultiple | boolean | Não | Padrão false. Com true, a resposta traz categories[] |
includeConfidence | boolean | Não | Padrão true |
provider | anthropic | ollama | Não | Omitido, vale a estratégia do ambiente |
model | string | Não | Alias (fast/smart/powerful) ou identificador concreto |
Resposta 200
{
"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
| Status | Quando |
|---|---|
400 | Corpo reprovado no schema Zod (menos de 2 categorias, texto vazio, texto acima do limite) |
401 | Token ausente ou inválido |
403 | Token sem AI_ENGINE_EXECUTE, ou sem organizationId |
500 | Falha na chamada ao provedor após as tentativas |
503 | Provedor 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
{
"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"
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
text | string (1 a 100.000) | Sim | Texto de origem |
fields | object[] (1 a 50) | Sim | Cada item tem name, type (string, number, boolean, date, array, object), description opcional e required |
language | string | Não | Padrão pt |
Resposta 200
{
"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
{
"audioSource": "base64",
"base64": "UklGRiQAAABXQVZFZm10...",
"language": "pt",
"outputFormat": "json"
}{
"audioSource": "base64",
"base64": "UklGRiQAAABXQVZFZm10...",
"language": "pt",
"outputFormat": "json"
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
audioSource | url | base64 | Sim | Origem do áudio |
audioUrl | string (URL) | Se audioSource=url | Passa por validação anti-SSRF antes do download |
base64 | string | Se audioSource=base64 | Conteúdo do arquivo |
language | string (até 10) | Não | Padrão pt |
outputFormat | text | json | Não | json traz segments com marcação de tempo |
Resposta 200
{
"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
| Status | Quando |
|---|---|
400 | Áudio ausente, URL reprovada pela guarda anti-SSRF, ou arquivo acima de 50 MB |
503 | AI_ENGINE_WHISPER_URL não configurada, ou o serviço Whisper não respondeu |
Início rápido
Do zero à primeira classificação, em staging.
1. Autenticar no IAM
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é
curl -s https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/models/providers \
-H "Authorization: Bearer $TOKEN" | jqcurl -s https://ai-engine.bb.stg.catalisa.app/ai-engine/api/v1/ai/models/providers \
-H "Authorization: Bearer $TOKEN" | jq{ "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
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}'{
"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
# 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.
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
# 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_KEY2. Confirme que o provedor externo não está configurado
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jqcurl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jqResposta esperada — apenas ollama e whisper na lista, sem anthropic:
{
"data": [
{ "name": "ollama", "status": "available" },
{ "name": "whisper", "status": "available" }
]
}{
"data": [
{ "name": "ollama", "status": "available" },
{ "name": "whisper", "status": "available" }
]
}Armadilhas.
- Com
AI_ENGINE_ANTHROPIC_API_KEYdefinida, um cliente ainda pode pedirprovider: "anthropic"explicitamente no corpo e escapar da política. Se a exigência é absoluta, não configure a chave. strategy: hybridsemAI_ENGINE_ANTHROPIC_API_KEYcai no provedor padrão. Deixesinglepara 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.
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
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
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:
"qwen2.5:3b""qwen2.5:3b"Tamanho do text | Provedor escolhido | model esperado na resposta |
|---|---|---|
| Menos de 2.000 caracteres | Ollama | qwen2.5:3b (alias fast) |
| 2.000 caracteres ou mais | Claude | claude-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/analyzenã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
curl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jqcurl -s "$BASE/ai-engine/api/v1/ai/models/providers" -H "Authorization: Bearer $TOKEN" | jqOrdem de diagnóstico:
- O provedor aparece na lista? Se não, a variável de ambiente dele não está preenchida.
- Aparece como
unavailable? Para Ollama e Whisper isso significa que o serviço não respondeu — verifique a rede e a URL. - Aparece como
availablemas a chamada falha? Para oanthropic,availablesó significa que existe uma chave — não que ela é válida. Uma chave revogada dáavailableaqui e500na chamada real.
Estimar custo antes de subir volume
Toda resposta de LLM traz usage.inputTokens e usage.outputTokens. Rode uma amostra representativa e some:
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.
Integração com outros building blocks
| Building block | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Emite o token; o AI Engine exige AI_ENGINE_EXECUTE e organizationId | Sim |
| Data Extraction | Extração de documento com OCR, fila e reprocessamento. Tem provedores próprios (ver ADR 0004) e não passa pelo AI Engine | Não |
| Decision Platform | Consome o resultado da classificação como entrada de regra | Não |
| WPP / WPP Business | Origem natural do texto e do áudio a analisar | Não |
| Rooms | Também faz transcrição, mas ao vivo e por outro caminho (Deepgram no worker de agente) | Não |
| File Storage | Guarda a imagem ou o áudio; o AI Engine recebe a URL assinada | Nã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.
Configuração e operação
Variáveis de ambiente
| Variável | Descrição | Obrigatória | Padrão |
|---|---|---|---|
AI_ENGINE_ANTHROPIC_API_KEY | Chave da API da Anthropic. Sem ela, o provedor anthropic não é registrado. | Não* | — |
AI_ENGINE_ANTHROPIC_BASE_URL | URL alternativa da API (proxy corporativo, gateway). | Não | https://api.anthropic.com |
AI_ENGINE_OLLAMA_URL | URL do seu servidor Ollama. Sem ela, o provedor ollama não é registrado. | Não* | — |
AI_ENGINE_WHISPER_URL | URL do seu Whisper ASR. Sem ela, /audio/transcribe responde 503. | Não | — |
AI_ENGINE_PROVIDER_STRATEGY | single ou hybrid | Não | single |
AI_ENGINE_DEFAULT_PROVIDER | anthropic ou ollama | Não | anthropic |
PORT | Porta no modo standalone | Não | 3000 no main.ts; 3020 é a porta registrada em DEFAULT_MODULE_PORTS |
MODULE_AI_ENGINE_URL | Usada por outros building blocks para alcançar este em standalone | Em 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ência | Para quê |
|---|---|
IAM alcançável (MODULE_IAM_URL) | Verificação do token em standalone |
| API da Anthropic, ou Ollama, ou ambos | Execução das operações de LLM |
| Whisper ASR | Execução da transcrição |
| PostgreSQL / Redis | Não usa nenhum dos dois |
Limites
| Limite | Valor |
|---|---|
text em sentiment e classify | 50.000 caracteres |
text em summarize e extract | 100.000 caracteres |
prompt em generate | 50.000 caracteres |
maxTokens em generate | 1 a 4.096, padrão 500 |
temperature em generate | 0 a 2, padrão 0.7 |
categories em classify | 2 a 50 |
fields em extract | 1 a 50 |
| Arquivo de áudio na transcrição | 50 MB |
| Timeout de chamada ao provedor | 120 segundos |
| Timeout de download de imagem e áudio por URL | 30 segundos |
| Tentativas na API da Anthropic | 3, com recuo exponencial |
Catálogo de erros
| Status | Código | Significa | O que fazer |
|---|---|---|---|
400 | VALIDATION | Corpo reprovado no Zod, ou URL de mídia reprovada pela guarda anti-SSRF | Confira campos e limites contra §13 |
401 | UNAUTHORIZED | Token ausente, inválido ou expirado | Renove no IAM |
403 | FORBIDDEN | Falta AI_ENGINE_EXECUTE ou falta organizationId no token | Autentique informando a organização |
500 | INTERNAL | Falha na chamada ao provedor após as tentativas, ou resposta que não pôde ser interpretada | Verifique a mensagem — ela repassa o erro do provedor |
503 | SERVICE_UNAVAILABLE | Whisper não configurado ou fora do ar | Confira AI_ENGINE_WHISPER_URL e a saúde do serviço |
Observabilidade.
GET /ai-engine/healthdevolve nome do serviço e versão. Não testa provedor — para isso useGET /api/v1/ai/models/providers.- Na inicialização, a
ProviderFactoryregistra 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
usagede cada resposta, que quem chama precisa acumular (§15).
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:
| Pergunta | Resposta da Anthropic | Fonte |
|---|---|---|
| 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 legal | privacy.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:3pxAtençã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.
Limitações conhecidas
| Limitação | Impacto | Situação |
|---|---|---|
| Sem fallback entre provedores | Se 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ção | O 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 prompt | Nã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ódigo | Os 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ção | As 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 usadas | As 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 chave | GET /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ática | listModels() 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 suportada | O 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 streaming | Todas as respostas são completas. Não há server-sent events nem resposta parcial. | Não implementado |
| Sem cache | Duas requisições idênticas custam duas chamadas ao provedor. | Não implementado |
| Sem rate limit próprio | O 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 Whisper | POST /audio/transcribe ignora o campo provider. Não há transcrição por provedor de nuvem neste building block. | Por design |
| Sem OpenAI, Google ou Bedrock | O 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) |
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