Como integrar IA em uma aplicação Full Stack sem criar um chatbot improvisado
Veja como estruturar uma funcionalidade com IA considerando backend, frontend, fila, worker, validação, segurança, custos e experiência do usuário.
Chamar uma API de IA é simples. O desafio real começa quando a funcionalidade precisa lidar com custo, latência, erro, validação, segurança, persistência e experiência do usuário dentro de uma aplicação Full Stack.
Contexto
Muita integração com IA começa do jeito mais direto possível: uma chamada para uma API de modelo, um prompt, uma resposta e uma tela exibindo o resultado. Para um protótipo, isso pode ser suficiente.
Mas quando a ideia precisa virar parte de uma aplicação real, a conversa muda. Uma feature com IA pode demorar, falhar, custar mais do que o esperado, retornar algo fora do formato, lidar com dados sensíveis e precisar de revisão humana.
O problema da chamada direta
O problema de colocar a chamada ao modelo direto no controller ou no componente é que responsabilidades começam a se misturar. O frontend sabe demais sobre o provedor, o backend monta prompt no lugar errado, a resposta é exibida sem validação e trocar de modelo vira uma mudança espalhada.
Funciona no começo. Depois cobra juros.
Uma separação mais saudável
Eu gosto de pensar nesse fluxo em camadas: frontend, API, serviço de domínio, camada de IA, provedor de modelo e registros de logs, métricas, custos e avaliações.
- O frontend mostra status, erro, resultado e revisão.
- A API valida entrada e registra a solicitação.
- O serviço de domínio entende a regra do produto.
- A camada de IA monta contexto, seleciona modelo, chama provedor e valida resposta.
- Logs e métricas registram modelo, custo, latência, status e falhas.
Quando usar fila e worker
Se a tarefa é lenta, cara, instável ou precisa de reprocessamento, uma fila começa a fazer sentido. O usuário inicia a solicitação, a API registra status pendente, um worker processa a chamada de IA, a resposta é validada e o frontend acompanha o resultado.
- Solicitação registrada com status.
- Job enviado para fila.
- Worker chama a camada de IA.
- Resposta validada antes de salvar.
- Frontend acompanha status, erro e resultado.
1type AIJobStatus =2 | 'pending'3 | 'processing'4 | 'completed'5 | 'failed'6 | 'cancelled';78type AIJob = {9 id: string;10 userId: string;11 feature: 'report_summary';12 status: AIJobStatus;13 provider?: 'openai' | 'anthropic' | 'google';14 model?: string;15 promptVersion: string;16 inputTokens?: number;17 outputTokens?: number;18 costEstimateCents?: number;19 latencyMs?: number;20 errorCode?: string;21 createdAt: string;22 updatedAt: string;23};O que vale registrar
Para uma funcionalidade com IA, eu registraria pelo menos o tipo da tarefa, versão do prompt, provedor, modelo, tokens quando disponíveis, custo estimado, tempo de processamento, status, erro, resposta validada e avaliação humana ou automática quando existir.
Isso ajuda a entender a feature depois que ela sai da demo. Sem esse registro, fica difícil saber se o problema é prompt, modelo, dado de entrada, latência, custo ou experiência de uso.
Validar a resposta antes de salvar
Se a funcionalidade espera JSON, eu prefiro tratar a resposta do modelo como dado externo não confiável. Ela precisa passar por schema, assim como qualquer entrada importante do sistema.
1import { z } from 'zod';23export const ReportSummarySchema = z.object({4 summary: z.string().min(80).max(1200),5 riskLevel: z.enum(['low', 'medium', 'high']),6 mainPoints: z.array(z.string().min(8)).min(3).max(7),7 confidence: z.number().min(0).max(1),8});910const parsed = ReportSummarySchema.safeParse(modelOutput);1112if (!parsed.success) {13 throw new InvalidAIResponseError(parsed.error);14}Timeout, retry e fallback
Outro ponto que separa demo de sistema real é decidir como a aplicação reage quando o provedor demora, recusa, fica indisponível ou devolve um formato inválido.
1for (const attempt of [1, 2, 3]) {2 try {3 const output = await ai.generate(request, {4 timeoutMs: 20_000,5 idempotencyKey: job.id,6 });78 return validateAndPersist(output);9 } catch (error) {10 if (!isRetryable(error) || attempt === 3) break;11 await wait(exponentialBackoff(attempt));12 }13}1415const fallbackOutput = await ai.generate(request, {16 provider: 'secondary',17 timeoutMs: 20_000,18});1920return validateAndPersist(fallbackOutput);UX também faz parte da arquitetura
Quando uma funcionalidade com IA demora ou falha, a interface precisa ajudar. Status claro, tentativa novamente, histórico, edição do resultado e indicação de revisão humana podem ser tão importantes quanto a chamada ao modelo.
Conclusão
Integrar IA em uma aplicação Full Stack não é encaixar um chatbot em algum canto da interface. É desenhar um fluxo que conversa com frontend, backend, dados, erros, custos, validação e experiência do usuário.
