Análise profunda de cada token enviado ao Gemini na conversa da Luana. O que é necessário, o que é desperdício, e como cortar 78% sem perder qualidade.
Mapa de calor real: cada célula é uma chamada de IA num processing específico. Cor mais intensa = mais tokens consumidos naquela chamada.
O routerValidator tem intensidade idêntica em todas as 11 colunas (5.2K fixo). Isso significa que o system prompt é estático — o node não adapta nada ao contexto. Está enviando 17,228 caracteres de regras genéricas para responder {"isValid": true}.
responseGenerator + responseValidator + conversationRouter + routerValidator = 333,772 tokens. Os outros 3 nodes juntos somam 35,244. A otimização precisa focar nos 4 gigantes.
Decomposição seção-por-seção dos 4 system prompts principais. O tamanho da barra mostra o peso relativo de cada seção.
O maior system prompt. Gera a resposta que o cliente recebe.
O segundo mais pesado. Responde apenas {"isValid": true/false, "reason": "..."}.
5,500 chars de exemplos para uma validação boolean. É como entregar um livro de direito para um juiz que precisa decidir "sim ou não" sobre uma questão simples. O excesso de exemplos não reduz alucinação — aumenta, porque o modelo pode se confundir entre cenários irrelevantes. 3-5 exemplos relevantes ao stage atual > 30 exemplos genéricos.
O bloco agent_instructions (3,200 chars = ~800 tokens) aparece inteiro em 4 dos 7 nodes que fazem chamadas de IA. Em 12 processings, isso são ~35,200 tokens de texto repetido.
| Node | Tem agent_instructions? | Precisa? | Motivo | Tokens Desperdiçados |
|---|---|---|---|---|
| conversationRouter | Sim (no state input) | Parcialmente | Precisa de clinic.city para acceptsInPersonVisit. Não precisa de tone, emoji_rules, variedade | ~400 × 11 = 4,400 |
| routerValidator | Sim (system prompt) | Não | Valida transição de stage. Não gera texto. Não precisa de persona, tone, forbidden_words | ~800 × 11 = 8,800 |
| responseGenerator | Sim (system prompt) | Sim | Gera resposta usando persona, tone, forbidden_words, objections. Essencial. | 0 |
| responseValidator | Sim (system prompt) | Parcialmente | Precisa de forbidden_words e clinic para checar fatos. Não precisa de tone, variedade, conexao_humana | ~400 × 11 = 4,400 |
| Total desperdiçado | ~17,600 tokens | |||
| Seção | ~chars | Router | RouterVal | ResponseGen | ResponseVal |
|---|---|---|---|---|---|
| persona | 180 | ✗ | ✗ | ✓ | ✓ |
| identity | 420 | ✗ | ✗ | ✓ | ✓ |
| clinic | 380 | city only | ✗ | ✓ | ✓ |
| tone | 420 | ✗ | ✗ | ✓ | ✗ |
| uso_do_nome | 520 | ✗ | ✗ | ✓ | ✓ |
| variedade | 480 | ✗ | ✗ | ✓ | ✗ |
| acolhimento_sem_culpa | 350 | ✗ | ✗ | ✓ | ✓ |
| emoji_rules | 80 | ✗ | ✗ | ✓ | ✗ |
| forbidden_words | 200 | ✗ | ✗ | ✓ | ✓ |
| abbreviations | 160 | ✗ | ✗ | ✓ | ✗ |
| objections | 1,200 | ✗ | ✗ | ✓ | ✓ |
Na conversa da Luana, ela nunca mencionou preço. As 1,200 chars de regras de objeções foram enviadas 44x (4 nodes × 11 processings) = 48,400 chars = ~12,000 tokens de regras sobre um assunto que nunca surgiu. Isso é mais tokens que TUDO que o behaviorAnalyzer consumiu na conversa inteira.
Marcar como "essencial" não significa "intocável". Aqui está o que pode ser enxugado com cuidado, usando exemplos reais dos prompts enviados na conversa da Luana.
2,200 chars. Dois blocos de exemplos proibidos que se sobrepõem. Merge economiza ~400 chars sem perder cobertura.
Destacado: "equipe inventada" e "valores inventados" aparecem nos dois blocos. São ~250 chars duplicados.
O Gemini 3 Flash generaliza bem a partir de poucos exemplos. 3 gatilhos diversos cobrem o padrão.
Modelos Flash são treinados para generalizar a partir de few-shot. Se o modelo vê 3 exemplos diversos (evento, família, nome), ele infere que "Carnaval", "praia" e "aniversário" seguem o mesmo padrão. 8 exemplos com a mesma estrutura não adicionam informação — adicionam tokens.
Se implementar null-stripping (remover campos null das variáveis), a presença do dado É a regra. Não precisa explicitar cada campo.
Com null-stripping, <client> só mostra campos com valor. Se name aparece, o modelo sabe que foi coletado. Se não aparece, o modelo sabe que precisa perguntar.
Sem null-stripping, o modelo vê "name": "Luana", "age": null e a regra genérica pode não ser suficiente — o modelo pode interpretar null como "dado existe mas é nulo" em vez de "não coletado". Com null-stripping, {"name": "Luana"} é inequívoco: age não existe = não foi coletado. A regra genérica funciona.
Seções pequenas com redundância que somadas fazem diferença.
Todos seguem "acolhe + redireciona". 2 exemplos cobrem o padrão.
A 3a linha ("Foque APENAS") já está implícita na instrução do stage.
| Seção | Otimização | Chars Salvos | Risco | Depende de |
|---|---|---|---|---|
| factual_accuracy | Merge dos 2 blocos de exemplos duplicados | ~400 | Baixo | Nada |
| conexao_humana (gatilhos) | 8 gatilhos → 3 + regra geral | ~250 | Médio | Monitorar qualidade |
| redundancy | 6 regras específicas → 1 regra genérica | ~240 | Médio | Null-stripping implementado |
| off_topic | 4 exemplos → 2 | ~140 | Baixo | Nada |
| contact_info | 3 linhas → 1 | ~95 | Zero | Nada |
| client_resistance | Remover 3 exemplos de detecção (redundantes com router) | ~150 | Baixo | Nada |
| Total por processing | ~1,275 chars | ~320 tokens × 12 processings = ~3,840 tokens/conversa | ||
Com 200 conversas/dia × 30 dias = 23M tokens/mês economizados só nessas micro-otimizações. E o ganho real não é custo — é foco. Cada frase removida é uma frase que o Gemini 3 Flash não precisa processar para decidir se é relevante. Menos ruído no attention window = respostas mais precisas.
A ideia central: em vez de um system prompt monolítico, montar o prompt dinamicamente com apenas as seções relevantes ao stage atual. Menos contexto = menos confusão = menos alucinação.
System prompt montado por "slots" ligados/desligados conforme o stage, o estado das variáveis e flags do conversation state.
-24% tokens neste processing
Em vez de 30 exemplos + agentInstructions completo, enviar apenas: regra do currentStage, regra do nextStage, 2-3 exemplos do cenário.
Task + 13 stage validations + rules (3 caminhos completos + avanço acelerado + estados finais + anti-alucinação preferência) + agent_instructions completo + 30 exemplos + output format
~5,200 tokens input → 47 tokens output
Ratio 110:1 — enviou 5K tokens para receber "isValid: true"
Task + validação de PROBLEM_INVESTIGATION + validação de PAIN_DISCOVERY + regra do Caminho B (sem A e C) + 3 exemplos relevantes (PROBLEM_INVESTIGATION→PAIN_DISCOVERY) + output format
~1,200 tokens input → 47 tokens output
Ratio 25:1 — 4.4x mais eficiente
O spam detector roda 11x nesta conversa. Todas com score 0. Após 3 mensagens limpas consecutivas, skip.
| # | Score | Resultado | Tokens | Necessário? |
|---|---|---|---|---|
| 1 | 0.0 | CONTINUE | 1,280 | Sim |
| 2 | 0.0 | CONTINUE | 1,393 | Sim |
| 3 | 0.0 | CONTINUE | 1,505 | Sim |
| 4 | 0.0 | CONTINUE | 1,576 | Não (3 limpas) |
| 5 | 0.0 | CONTINUE | 1,624 | Não |
| 6 | 0.0 | CONTINUE | 1,716 | Não |
| 7-11 | 0.0 | CONTINUE | 9,032 | Não |
| Tokens desperdiçados | 13,948 | |||
Os system prompts são idênticos entre processings. O Gemini 3 Flash Preview suporta context caching: o system prompt é enviado 1x e reutilizado nas chamadas seguintes com 75% de desconto nos tokens cacheados.
System prompts = ~65% dos input tokens.
Com cache a 75% de desconto:
E isso sem mudar UMA linha de prompt. Apenas configuração da API.
Se você cachear o system prompt que inclui agent_instructions, o cache é por organização (cada org tem persona diferente). Isso limita o hit rate. Alternativa: separar o prompt em 2 partes — (1) engine rules (compartilhável entre orgs, cacheável globalmente) e (2) agent_instructions (por org, cacheável por org). O engine rules é ~70% do system prompt e seria cacheado com hit rate máximo.
O cachedContent do Gemini exige um mínimo de tokens para ser cacheável (varia por modelo). Os system prompts individuais (~5-7K tokens cada) podem ficar abaixo desse mínimo. Duas soluções práticas:
Contraintuitivo mas verdadeiro: prompts maiores NÃO são mais seguros. O Gemini Flash performa melhor com instruções concisas e focadas. Aqui estão técnicas específicas.
30+ exemplos no routerValidator diluem a atenção do modelo. 3-5 exemplos cirúrgicos no contexto do stage atual são mais eficazes.
Processing #5 (LOCATION_VALIDATION → PROBLEM_INVESTIGATION): o router recebeu exemplos de PAIN_DISCOVERY, SOLUTION, SCHEDULE_PREFERENCE, todos irrelevantes. O routerValidator precisou filtrar mentalmente 27 exemplos para encontrar os 3 relevantes. Funcionou — mas cada exemplo irrelevante é uma chance do modelo "ativar" um padrão errado.
Regras que previnem alucinação (factual_accuracy, forbidden_words) SEMPRE. Regras que lidam com cenários específicos (objections, scheduling) sob GATILHO.
| Seção do Prompt | Tipo | Gatilho para Inclusão | Impacto se ausente no momento errado |
|---|---|---|---|
| factual_accuracy | Sempre | — | Alucinação de fatos sobre clínica |
| forbidden_words | Sempre | — | Usa palavras proibidas |
| client_data_accuracy | Sempre | — | Troca dados do cliente |
| redundancy | Sempre | — | Pergunta dado já coletado |
| uso_do_nome | Sempre | — | Usa nome do cliente em excesso |
| objections | Gatilho | lastMessage menciona preço/valor/caro | Responde preço sem seguir protocolo |
| scheduling_accuracy | Gatilho | stage = SCHEDULING | Inventa horários |
| social_proof_rules | Gatilho | stage = SOCIAL_VALIDATION | Inventa URLs de imagem |
| client_resistance | Sempre | — | Cliente pode pedir humano em qualquer stage. Distinção "me passa + OBJETO vs PESSOA" é crítica. |
| returning_client | Gatilho | isCarryoverRestart = true | Repete perguntas de cliente que voltou |
| humor detail | Gatilho | Mensagem contém "rs", "kk", "haha", emoji de riso | Não ri junto / repete apelido |
| service_scope | Gatilho | Mensagem pergunta sobre serviço/tratamento específico | Confirma serviço que clínica não tem |
Quem detecta o gatilho? O conversationRouter já analisa a mensagem do cliente e extrai variáveis. Ele pode adicionar flags ao state: { triggers: ["PRICE_QUESTION", "HUMOR"] }. O responseGenerator lê as flags e monta o prompt com as seções correspondentes. Custo incremental: ~0 (o router já faz essa análise).
Enviar 28 campos com 26 nulls é ruído visual para o modelo. Omitir campos null foca o modelo nos dados que existem.
Quando o modelo vê "preferredWeekday": null no contexto, o conceito de "preferredWeekday" é ativado na atenção. No processing #1 (NAME_COLLECTION), o modelo não deveria nem pensar em dia da semana. Omitir o campo remove o conceito do attention window. Isso é especialmente relevante para o bug conhecido de alucinação de preferredPeriod/preferredWeekday a partir de runtime_variables.
Prompts têm duas camadas: regras do engine (genéricas, compartilháveis) e regras da org (agent_instructions, config). Hoje estão misturadas no mesmo system prompt.
Quando regras do engine e da org estão no mesmo nível, o modelo não sabe qual tem prioridade. Exemplo:
Com a separação, o Gemini 3 Flash entende: "engine_rules = obedecer sempre", "org_context = fonte que posso citar". Quando o modelo vai gerar "200.000 atendimentos", a tag priority="ABSOLUTE" lembra que precisa citar LITERALMENTE. "180.000+" ≠ "200.000".
Cada item é independente. Podem ser implementados em paralelo ou sequencialmente. Ordenados por tokens salvos / esforço.
| # | Ação | Tokens Salvos | Esforço | Risco | Impacto em Alucinação |
|---|---|---|---|---|---|
| 1 | Gemini context caching Configurar cachedContent na API |
~180,000 | 1-2 dias | Zero | Neutro (não muda prompts) |
| 2 | Remover agent_instructions do routerValidator Deletar seção do prompt builder |
~8,800 | 30 min | Zero | Positivo (menos ruído) |
| 3 | routerValidator dinâmico por stage Example routing table + filtro de caminhos |
~44,000 | 2-3 dias | Médio | Muito positivo |
| 4 | responseGenerator stage-conditional Slot-based prompt assembly |
~18,000 | 2-3 dias | Médio | Positivo |
| 5 | Skip behaviorAnalyzer após 3 limpas Guard no node com re-ativação periódica |
~13,000 | 1 hora | Baixo | Neutro |
| 6 | Null-stripping nas variáveis Filter nulls/defaults na serialização |
~8,000 | 1 hora | Zero | Positivo (reduz priming de campos) |
| 7 | Skip knowledgeStrategy em stages de coleta Lista de stages que não precisam de RAG |
~3,500 | 30 min | Baixo | Neutro |
| 8 | Condensar agent_instructions no responseValidator Enviar apenas forbidden_words, clinic, identity, objections |
~5,000 | 1-2 horas | Baixo | Positivo |
| 9 | Trigger flags no router para responseGenerator Router emite { triggers: ["PRICE", "HUMOR"] } |
~12,000 | 1-2 dias | Médio | Positivo (objections só quando relevante) |
| Total Combinado | ~292,300 | 78% de redução — de 369K para ~77K tokens | |||
De 369,016 tokens para ~77,000 tokens por conversa de 12 processings.
A conversa da Luana custaria 78% menos. Com 100 conversas/dia, são ~29M tokens/dia economizados. E o mais importante: prompts menores e mais focados significam menos alucinação, não mais. O modelo recebe instruções cirúrgicas em vez de um manual de 400 linhas.
Comece pelos itens #1 e #2 — zero risco, ROI imediato, implementação em horas.