Orquestrando IA em produção com Firebase Genkit
Aquele tutorial de IA que parece trivial esconde um emaranhado de responsabilidades que só emerge quando o código encontra o tráfego real.

Integrar IA parece simples: chamar uma API de LLM e ler a resposta. Essa simplicidade é uma ilusão. Em produção, aquela única seta vira um emaranhado de responsabilidades — e é exatamente esse problema que o Firebase Genkit se propõe a resolver, atuando como uma camada de orquestração que desacopla a lógica de negócio da infraestrutura de IA.
A tese central é direta: um framework de orquestração transforma chamadas frágeis de LLM em arquitetura previsível, tipada e observável. O ganho não está em prompts mágicos, mas em contratos rígidos, abstrações maduras e isolamento de infraestrutura.
Parte 1 — Por que chamadas isoladas falham
O fluxo linear que imaginamos — App → LLM → Resposta — se desdobra, na prática, em um conjunto de responsabilidades que ninguém pediu mas todas aparecem em produção: gerência de contexto, retry com backoff, parsing de JSON quebrado, troca de chaves, chunking de documentos e integração com vector store. Quatro problemas estruturais emergem desse acoplamento:
Falta de tipagem — sem contratos rígidos, a resposta do modelo é um JSON imprevisível que quebra silenciosamente o consumidor.
Prompts hardcoded — a intenção do LLM mora cravada no código de aplicação, impossível de versionar ou testar isoladamente.
Vendor lock-in — a lógica de negócio fica amarrada a um modelo proprietário específico.
Zero observabilidade — sem visibilidade de latência e custo, cada chamada é uma caixa-preta financeira.
A resposta profissional é decompor a integração antes que ela vire dívida técnica: separar cada preocupação em um módulo com seu próprio contrato. É o que o Genkit organiza em três camadas.
Parte 2 — O modelo operacional em três camadas
O Genkit se organiza em três camadas sobrepostas. A de baixo é agnóstica de provedor; a do meio concentra as abstrações reutilizáveis; a de cima entrega a experiência de desenvolvimento.
A ideia-chave dessa arquitetura é escreva a lógica uma vez; troque a infraestrutura com uma linha de código. O runtime de primeira classe é JS/TS, mas o framework também tem SDKs em Go, Python e Dart.
Flows tipados e contratos rígidos
Um Flow é uma operação observável com contratos de entrada e saída definidos por schemas Zod. O segredo está no campo output: { schema }: o Genkit injeta as regras do schema na instrução ao modelo (constrained generation) e valida a resposta de volta.
import { genkit, z } from 'genkit';import { googleAI } from '@genkit-ai/googleai';const ai = genkit({ plugins: [googleAI()] });// contrato de saída: nada de JSON soltoconst Relatorio = z.object({ titulo: z.string(), risco: z.enum(['baixo', 'medio', 'alto']), pontos: z.array(z.string()),});export const gerarRelatorio = ai.defineFlow( { name: 'gerarRelatorio', inputSchema: z.object({ texto: z.string() }), outputSchema: Relatorio, }, async ({ texto }) => { const { output } = await ai.generate({ model: 'googleai/gemini-1.5-flash', prompt: `Analise e resuma: ${texto}`, output: { schema: Relatorio }, }); return output!; });O enum no campo risco não é decoração: ele elimina toda uma classe de bugs onde o modelo inventaria "risco moderado-alto" e quebraria o switch do consumidor. Trate o schema de saída do LLM com o mesmo rigor de um contrato de API REST.
Dotprompt: prompts como código
Para acabar com prompts hardcoded, o Genkit usa arquivos .prompt versionáveis e testáveis. Um frontmatter YAML declara configuração (modelo, temperatura, schemas, tools) e um template Handlebars define as mensagens, com lógica condicional embutida.
---model: googleai/gemini-1.5-flashconfig: temperature: 0.4input: schema: UserSchemaoutput: schema: ReportSchematools: [buscarClima]---{{role "system"}}Você é um assistente técnico especializado.{{role "user"}}Analise os dados de {{usuario.nome}} e a imagem:{{media url=imagemPerfil}}{{#if isPremium}}Forneça resposta detalhada.{{/if}}Ajustar um prompt vira um diff de texto, revisável em pull request, sem recompilar a aplicação.
Parte 3 — RAG, reranking e tools
Pergunte a um LLM sobre um termo interno da sua empresa e ele alucina, porque o dado não existe no treinamento dele. Injetar o documento inteiro resolve a alucinação mas consome ~4.000 tokens por pergunta. A resposta arquitetural é o RAG (Retrieval-Augmented Generation): recuperar apenas os trechos relevantes. O pipeline nativo tem duas vias que compartilham o mesmo vector store.
O ganho concreto: o consumo cai de ~4.000 para ~830 tokens por consulta, mantendo a exatidão. Paga-se o custo de vetorizar uma vez, na ingestão, e colhem-se respostas baratas e precisas em toda consulta.
Reranking: two-stage retrieval
Um retriever vetorial é rápido mas barulhento. O padrão two-stage retrieval adiciona um reranker (cross-encoder) que reordena os candidatos pela relevância exata e estreita a saída para o topo — só o que tem maior valor estatístico ocupa a janela de contexto. O primeiro estágio prioriza recall; o segundo, precisão.
Tools: de LLMs passivos a agentes
As Tools permitem que o modelo peça dados externos. Você envia o catálogo de tools; quando o modelo precisa de algo que não tem, ele pausa a geração, o Genkit intercepta e executa a tool localmente, devolve o resultado e o modelo sintetiza a resposta. Uma tool é só uma função tipada com Zod que o modelo aprende a invocar.
export const buscarClima = ai.defineTool( { name: 'buscarClima', description: 'Retorna o clima atual de uma cidade', inputSchema: z.object({ cidade: z.string() }), outputSchema: z.object({ tempC: z.number() }), }, async ({ cidade }) => { // código real: chama uma API de clima const r = await fetch(`/api/clima?c=${cidade}`); return { tempC: (await r.json()).temp }; });Parte 4 — Produção: multimodalidade, segurança e ciclo
Texto bruto, mídia via Handlebars, URLs HTTPS e PDFs em Base64 entram por abstrações uniformes: o framework trata conversão e encoding e monta um array multimodal homogêneo. Antes do deploy, o comando genkit start sobe uma Dev UI local com trace inspector (cascata e tempo de cada etapa), model runner e contagem de tokens.
Na borda, expor endpoints generativos sem proteção é risco financeiro severo. Chaves vão para o Secret Manager (fora do código), a autenticação é nativa via Cloud Functions, e o App Check bloqueia requisições fraudulentas no perímetro (DeviceCheck / Play Integrity), garantindo que só instâncias genuínas do app invoquem cobranças.
O isolamento estrutural também define os alvos de deploy por linguagem — e aqui está a ponte direta para o frontend: Next.js e Angular entram via Firebase App Hosting.
Linguagem | Modelo (ex.) | Vector store | Alvo de deploy |
|---|---|---|---|
JS / TS | Gemini | Cloud Firestore | Firebase App Hosting (Next.js / Angular) |
Go | Claude | Pinecone | Cloud Run (binário) |
Python | GPT-4o | pgvector | Cloud Functions |
Dart | Ollama (local) | Redis | Telemetria local |
Tudo se amarra num ciclo de quatro fases em torno do Genkit:
Conclusão: orquestração no lugar de prompts mágicos
O arco deste material descreve uma maturação que outras disciplinas da computação já viveram: a integração com IA está saindo da fase artesanal — em que o resultado dependia de acertar o prompt — e entrando na fase de engenharia, em que o resultado depende da qualidade do sistema.
Cada abstração apresentada — Flows tipados, Dotprompt, RAG, reranking, Tools, segurança de borda — é uma resposta de engenharia a uma falha específica e reprodutível das chamadas isoladas de LLM. A confiabilidade em produção já não é um golpe de sorte; é um processo orquestrado e observável.


