Prompt Efficiency Audit — Production Data

369K tokens
para 12 respostas.

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.

369KInput Tokens
10KOutput Tokens
36:1Ratio In/Out
0.2%Cache Hit
gemini-3-flash-previewModelo
01 — Radiografia do Consumo

Onde Cada Token Foi Gasto

Mapa de calor real: cada célula é uma chamada de IA num processing específico. Cor mais intensa = mais tokens consumidos naquela chamada.

#1#2#3#4#5#6#7#8#9#10#11#12
responseGenerator
8.3K
7.7K
7.7K
8.2K
8.0K
8.9K
9.5K
10.2K
8.6K
8.9K
9.3K
9.3K
responseValidator
7.9K
7.4K
7.3K
7.4K
7.4K
7.7K
9.1K
8.0K
8.1K
8.4K
8.4K
conversationRouter
7.0K
7.1K
7.2K
7.3K
8.9K
7.4K
7.5K
7.9K
8.0K
8.2K
8.2K
routerValidator
5.2K
5.2K
5.2K
5.2K
5.2K
5.2K
5.3K
5.3K
5.3K
5.3K
5.3K
behaviorAnalyzer
1.3K
1.4K
1.5K
1.6K
1.6K
1.7K
1.8K
1.8K
1.8K
1.8K
1.8K
toolDecider
1.7K
2.0K
2.4K
2.5K
knowledgeStrategy
713
709
702
706
702
706
745
710
715
706
724
703
menos
mais tokens
Padrão visual: faixas horizontais de cor constante

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}.

Distribuição por Node

responseGenerator
104,571 — 28.3%
28.3%
responseValidator
87,128 — 23.6%
23.6%
conversationRouter
84,623 — 22.9%
22.9%
routerValidator
57,450 — 15.6%
15.6%
behaviorAnalyzer
18,126
4.9%
toolDecider
8,577
2.3%
knowledgeStrategy
8,541
2.3%
90.4% do custo está em 4 nodes

responseGenerator + responseValidator + conversationRouter + routerValidator = 333,772 tokens. Os outros 3 nodes juntos somam 35,244. A otimização precisa focar nos 4 gigantes.

02 — Anatomia dos Prompts

O Que Cada Prompt Contém

Decomposição seção-por-seção dos 4 system prompts principais. O tamanho da barra mostra o peso relativo de cada seção.

responseGenerator — 26,073 chars (~6,500 tokens)

O maior system prompt. Gera a resposta que o cliente recebe.

agent_instructionsDUPLICADO~3,200 chars
Persona completa (Bia, Magrass SJC), identity, clinic, tone, uso_do_nome, variedade, acolhimento_sem_culpa, emoji_rules, forbidden_words, abbreviations, objections. Cópia idêntica do que já está no routerValidator e responseValidator.
conexao_humanaCONDICIONAL~2,800 chars
Regras detalhadas de humor, risada, classificação de "rs/kk/haha", exemplos de piada vs suavização, regra de apelidos, variação de risada. Luana nunca fez piada em 12 mensagens. As regras de humor (~1,500 chars) nunca foram ativadas. Poderiam ser enviadas apenas quando behaviorAnalyzer detecta humor na mensagem.
factual_accuracyESSENCIAL~2,200 chars
Regras contra alucinação factual. Checklist obrigatório, escopo de tratamento no KB, informação parcial. Essencial em todos os stages. Mas os 6 exemplos proibidos (800 chars) poderiam ser 3.
client_resistanceESSENCIAL~1,200 chars
Ensina o responseGenerator como responder quando o cliente pede humano ou recusa dar dados. Precisa estar em todos os stages porque o cliente pode pedir humano a qualquer momento. A distinção "me passa o pix" (factual) vs "me passa pra outra pessoa" (TRANSFER) é crítica.

Otimização possível: Os 3 exemplos de TRANSFER ("Prefiro falar com uma pessoa", "Quero falar com o gerente", "Me passa pra outra pessoa") são redundantes com o router — quem decide a transição para TRANSFER é o conversationRouter, não o responseGenerator. O gerador só precisa saber o tom. Mas a distinção "me passa + OBJETO vs PESSOA" (~350 chars) é insubstituível e deve permanecer.
scheduling_accuracyCONDICIONAL~700 chars
Regras sobre horários de agendamento. Só relevante no stage SCHEDULING. Mas foi enviado em todos os 12 processings. Luana só chegou no SCHEDULING no processing #11-12.
social_proof_rulesCONDICIONAL~600 chars
Regras de prova social (URLs, captions, metadados inventados). Só relevante em SOCIAL_VALIDATION. Luana passou por esse stage 1x (processings #8-9). Nos outros 10 processings, essas 600 chars foram desperdício.
returning_client_rulesCONDICIONAL~500 chars
Regras para cliente que volta (reconfirmar dados, não repetir perguntas). Luana é cliente nova. Enviado 12x, necessário 0x.
format + role + stage + outrosESSENCIAL~4,500 chars
role, format, rich_text, tool_usage, off_topic, service_scope, client_data_accuracy, redundancy, contact_info, objection_priority, coerencia, contextual_awareness, runtime_variables, stage instruction, transitions, validation. Necessário sempre.

routerValidator — 17,228 chars (~5,200 tokens)

O segundo mais pesado. Responde apenas {"isValid": true/false, "reason": "..."}.

examples (30+ cenários)OVER-ENGINEERED~5,500 chars
30+ exemplos cobrindo todos os caminhos (A, B, C), todos os stages, multi-stage jump, reclassification, pain depth com/sem targetBodyRegion. No processing #1 (NAME_COLLECTION → NAME_COLLECTION), apenas 2-3 desses exemplos são relevantes. Os outros 27+ são ruído que o modelo precisa filtrar mentalmente. Enviar exemplos irrelevantes ao stage atual aumenta confusão e probabilidade de o modelo "lembrar" de um exemplo errado.
agent_instructions (COMPLETO)DESNECESSARIO~3,200 chars
Persona, identity, clinic, tone, uso_do_nome, variedade, acolhimento_sem_culpa, emoji_rules, forbidden_words, abbreviations, objections. O routerValidator NÃO gera texto. Ele só valida se a transição de stage é válida. Não precisa saber o tom, as regras de emoji, as objeções ou o uso do nome. No máximo precisa da clinic.city para validar acceptsInPersonVisit.
rules (funil + caminhos + preferência)ESSENCIAL~3,500 chars
Caminhos A/B/C, ordem do funil, avanço acelerado, estados finais, anti-alucinação de preferredPeriod. Necessário, mas poderia ser filtrado por caminho atual. Se o lead está no Caminho B, as regras do Caminho C são ruído.
task + stage_validations + outputESSENCIAL~2,000 chars
Lista das 13 validações de stage + formato de output. Poderia enviar apenas currentStage e nextStage.
Achado-chave: 64% do routerValidator são exemplos que o modelo precisa ignorar

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.

03 — Mapa de Duplicação

O Mesmo Texto, 4 Vezes por Processing

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.

NodeTem agent_instructions?Precisa?MotivoTokens 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ções dentro de agent_instructions: análise de necessidade por node

Seção~charsRouterRouterValResponseGenResponseVal
persona180
identity420
clinic380city only
tone420
uso_do_nome520
variedade480
acolhimento_sem_culpa350
emoji_rules80
forbidden_words200
abbreviations160
objections1,200
Insight: "objections" tem 1,200 chars e só é relevante quando o cliente fala de preço

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.

03.5 — Gordura Dentro dos Essenciais

Mesmo os Blocos Verdes
Têm Espaço para Cortar

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.

1

factual_accuracy — exemplos duplicados

2,200 chars. Dois blocos de exemplos proibidos que se sobrepõem. Merge economiza ~400 chars sem perder cobertura.

O problema: dois blocos dizem a mesma coisa

Bloco 1 — "PROIBIDO ELABORAR"

NÃO descreva composição de protocolos (ex: "drenagem tecnológica", "quebra de células de gordura") NÃO descreva exames (ex: "exame de alta tecnologia que analisa hidratação e gordura visceral") NÃO descreva equipe (ex: "nutricionistas e profissionais habilitados semana a semana") NÃO invente valores (ex: "R$ 89,00", "12x", "desconto de 45%")

Bloco 2 — "PROIBIDO" (600 chars abaixo)

"Estamos com uma promoção de Black Friday!" (promoção inventada) "Aceitamos PIX com 10% de desconto" (condição inventada) "Temos 5 nutricionistas na equipe" (equipe inventada) "Temos vaga amanhã às 14h" (disponibilidade inventada) "Aos sábados atendemos até 18h" (horário diferente) "Sempre temos condições especiais pra você!" (evasão)

Destacado: "equipe inventada" e "valores inventados" aparecem nos dois blocos. São ~250 chars duplicados.

Proposta: merge em bloco único

<!-- ANTES: 900 chars em 2 blocos separados --> <!-- DEPOIS: 550 chars em 1 bloco único --> PROIBIDO inventar informações que NÃO constam em <knowledge_base> ou <agent_instructions>: - Protocolos/procedimentos ("drenagem tecnológica") - Exames ("exame de alta tecnologia") - Equipe ("5 nutricionistas") - Valores/condições ("R$ 89,00", "12x", "PIX com 10%") - Promoções ("Black Friday!") - Disponibilidade ("vaga amanhã 14h") - Horários diferentes de <clinic> - Evasão ("sempre temos condições") Em vez disso: "Na consulta a especialista explica todo o protocolo!"
Pros
  • ~350 chars a menos por call × 12 processings = ~4,200 chars economizados
  • Um único bloco é mais fácil de scanear para o Gemini 3 Flash
  • Zero perda de cobertura — todos os tipos de alucinação continuam listados
Contras
  • Se algum exemplo específico foi adicionado por causa de uma alucinação real do Gemini, tirá-lo pode fazê-la voltar
  • Mitigação: rodar eval suite antes e depois para verificar regressão
2

conexao_humana — 8 gatilhos para 3

O Gemini 3 Flash generaliza bem a partir de poucos exemplos. 3 gatilhos diversos cobrem o padrão.

Hoje — 8 gatilhos (~500 chars)

- Mesmo nome → "Que prazer, xará!" - Carnaval → "Carnaval é tudo de bom!" - Praia/verão → "Praia é vida!" - Viagem → "Que delícia de plano!" - Casamento/formatura → "Que momento especial!" - Aniversário → "Que presente melhor..." - Fim de semana → "Merecido!" - Filhos/família → "Família é tudo!"

Proposto — 3 gatilhos + regra geral (~250 chars)

Conecte emocionalmente quando o CLIENTE compartilhar algo sobre sua vida. Exemplos: - Evento (casamento, viagem, formatura) → "Que momento especial!" - Família/filhos → "Família é tudo!" - Mesmo nome da atendente → "Que prazer, xará!" Use sensibilidade para identificar QUALQUER oportunidade de conexão.
Por que funciona com o Gemini 3 Flash Preview

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.

3

redundancy — 6 regras específicas para 1 genérica

Se implementar null-stripping (remover campos null das variáveis), a presença do dado É a regra. Não precisa explicitar cada campo.

Hoje — 6 regras (~300 chars)

- name preenchido → NÃO pergunte o nome - age preenchido → NÃO pergunte a idade - city preenchido → NÃO pergunte a cidade - weightTargetInKg preenchido → NÃO pergunte peso - complaints preenchido → NÃO pergunte queixas - preferredWeekday e preferredPeriod preenchidos → NÃO pergunte dia/horário

Proposto — 1 regra (~60 chars)

NUNCA pergunte dado que já está em <client>.

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.

Dependência: null-stripping precisa estar implementado primeiro

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.

4

off_topic + contact_info — cortes cirúrgicos

Seções pequenas com redundância que somadas fazem diferença.

off_topic: 4 exemplos → 2

<!-- ANTES: 4 exemplos (280 chars) --> "Nossa! Mas me conta, o que te trouxe aqui hoje?" "Legal isso! E sobre os tratamentos, ficou alguma dúvida?" "Ah sim! Agora, você está buscando qual objetivo?" "Entendi! E pra você, qual seria a prioridade agora?" <!-- DEPOIS: 2 exemplos (140 chars) --> "Legal isso! E sobre os tratamentos, ficou alguma dúvida?" "Entendi! E pra você, qual seria a prioridade agora?"

Todos seguem "acolhe + redireciona". 2 exemplos cobrem o padrão.

contact_info: 3 linhas → 1

<!-- ANTES (150 chars) --> PROIBIDO perguntar dados de contato (telefone, email, WhatsApp). A conversa já é via WhatsApp - já temos o número do cliente. Foque APENAS nas informações definidas em <stages> para cada estágio. <!-- DEPOIS (55 chars) --> PROIBIDO perguntar telefone/email — já temos via WhatsApp.

A 3a linha ("Foque APENAS") já está implícita na instrução do stage.

Resumo: economia dentro dos essenciais

SeçãoOtimizaçãoChars SalvosRiscoDepende 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
Perspectiva: 3,840 tokens parece pouco, mas...

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.

04 — Prompt Assembly por Stage

Enviar Apenas o Que o Stage Precisa

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.

A

Slot-Based Prompt Assembly

System prompt montado por "slots" ligados/desligados conforme o stage, o estado das variáveis e flags do conversation state.

Como funciona

// Pseudo-código do assembly const sections = { // ─── SEMPRE (anti-alucinação + comportamento base) ─── base: ALWAYS, factual_accuracy: ALWAYS, agent_instructions: ALWAYS, client_resistance: ALWAYS, // cliente pode pedir humano em qualquer stage coerencia: ALWAYS, stage_instruction: ALWAYS, // ─── CONDICIONAL (só quando o cenário existe) ─── scheduling: stage === 'SCHEDULING', social_proof: stage === 'SOCIAL_VALIDATION', returning_client: isCarryoverRestart === true, humor_detail: lastMessageHasHumor === true, objections: lastMessageMentionsPrice === true, service_scope: lastMessageAsksAboutService === true, }

Exemplo concreto: Processing #3 (NAME_COLLECTION → AGE_COLLECTION)

Hoje — 26,073 chars

  • base + role + format (~1,500)
  • client_resistance (~1,200)
  • off_topic + service_scope (~1,400)
  • factual_accuracy (~2,200) ← exemplos duplicados
  • scheduling_accuracy (~700) ← desnecessário
  • client_data_accuracy + redundancy (~800)
  • conexao_humana COMPLETA (~2,800) ← humor desnecessário
  • agent_instructions COMPLETO (~3,200) ← objections sem gatilho
  • social_proof_rules (~600) ← desnecessário
  • returning_client (~500) ← desnecessário
  • stage instruction + outros (~4,500)

Proposto — ~19,700 chars

  • base + role + format (~1,500)
  • client_resistance (~1,200)
  • off_topic (~500)
  • factual_accuracy ENXUTO (~1,800)
  • client_data_accuracy + redundancy (~800)
  • conexao_humana SEM humor (~1,300)
  • agent_instructions SEM objections (~2,000)
  • stage instruction + outros (~4,500)

-24% tokens neste processing

Pros e Contras

Pros
  • Menos tokens = menos custo + resposta mais rápida
  • Menos contexto irrelevante = MENOS alucinação (modelo foca no que importa)
  • Adaptativo: se cliente faz piada, humor rules são incluídas no próximo processing
  • Fácil de implementar: dicionário de seções + condições booleanas
  • Backward-compatible: nenhuma mudança na lógica do engine, só no assembly
Contras
  • Mais complexidade no prompt builder (mas é complexidade de engenharia, não de IA)
  • Risco de edge case: cliente muda de assunto abruptamente e a seção relevante não estava incluída
  • Mitigação: para seções críticas (factual_accuracy, forbidden_words), sempre incluir
  • Debugging mais difícil (prompt muda entre processings) — mitigação: logging de quais sections foram incluídas
  • Precisa de testes de regressão para cada combinação de stages × sections
B

routerValidator Cirúrgico

Em vez de 30 exemplos + agentInstructions completo, enviar apenas: regra do currentStage, regra do nextStage, 2-3 exemplos do cenário.

Exemplo: Processing #6 (PROBLEM_INVESTIGATION → PAIN_DISCOVERY)

Hoje — 17,228 chars

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"

Proposto — ~4,200 chars

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

Pros e Contras

Pros
  • Redução de 77% nos tokens do routerValidator (5,200 → 1,200)
  • Menos exemplos irrelevantes = menos confusão = validação mais precisa
  • Elimina completamente agent_instructions deste node
  • Acumulado: 44,000 tokens salvos nesta conversa
  • Implementação: criar mapa stage→examples, filtrar por currentStage+nextStage
Contras
  • Multi-stage jump (avanço acelerado) precisa de validações de stages intermediários
  • Mitigação: detectar jump no router output e incluir stages intermediários dinamicamente
  • Curadoria inicial: organizar os 30+ exemplos por stage (esforço único)
  • Risco de perder cobertura de edge cases raros — mitigação: manter exemplos gerais em fallback
C

Eliminar behaviorAnalyzer Após 3 Limpas

O spam detector roda 11x nesta conversa. Todas com score 0. Após 3 mensagens limpas consecutivas, skip.

Dados desta conversa

#ScoreResultadoTokensNecessário?
10.0CONTINUE1,280Sim
20.0CONTINUE1,393Sim
30.0CONTINUE1,505Sim
40.0CONTINUE1,576Não (3 limpas)
50.0CONTINUE1,624Não
60.0CONTINUE1,716Não
7-110.0CONTINUE9,032Não
Tokens desperdiçados13,948

Pros e Contras

Pros
  • Zero risco: se cliente começar a agir estranho após 3 limpas, o conversationRouter detecta (já tem lógica de TRANSFER)
  • Pode re-ativar por trigger: se router.extractedVariables contém dados suspeitos, liga de volta
  • Implementação: 5 linhas — if (sequence > 3 && abuseScore === 0) skip
  • Savings: ~13K tokens/conversa × centenas de conversas/dia
Contras
  • Edge case: cliente educado nas primeiras 3 msgs, depois começa spam
  • Mitigação: re-ativar a cada N mensagens (ex: a cada 5) como "check periódico"
  • Alternativa: rodar behaviorAnalyzer localmente com regex/heurística em vez de LLM
05 — Gemini Context Caching

O Maior ROI com Menor Esforço

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 Prompt Fixo por Node

conversationRouter22,418 chars (fixo)
routerValidator17,228 chars (fixo)
responseGenerator~26,073 chars (fixo)
responseValidator24,154 chars (fixo)

Impacto do Cache

System prompts = ~65% dos input tokens.

Com cache a 75% de desconto:

369,016 tokens total × 65% system portion = 239,860 × 75% discount = 179,895 tokens economizados Custo efetivo: ~189,121 tokens Redução: 48.8%

E isso sem mudar UMA linha de prompt. Apenas configuração da API.

Detalhe que ninguém pensa: cache invalidation por org

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.

Detalhe avançado: Gemini 3 Flash Preview e context caching

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:

// Approach 1: cachear system + agent_instructions como bloco único const cachedContent = await cacheManager.create({ model: 'gemini-3-flash-preview', contents: [{ role: 'user', parts: [{ text: systemPrompt // engine rules + agentInstructions // org config + stageDefinitions // funil completo }] }], ttl: '300s' // 5 min = duração de 1 conversa }); // Approach 2: usar system_instruction (automático no Gemini) const result = await model.generateContent({ systemInstruction: engineRules, // cacheado automaticamente contents: [{ parts: [{ text: userPrompt // variável a cada call }]}] });
06 — Redução de Alucinações

Menos Prompt = Menos Alucinação

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.

1

Exemplos Relevantes > Exemplos Abundantes

30+ exemplos no routerValidator diluem a atenção do modelo. 3-5 exemplos cirúrgicos no contexto do stage atual são mais eficazes.

Evidência nesta conversa

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.

Técnica: Example Routing Table

// Mapear exemplos por stage atual const VALIDATOR_EXAMPLES: Record< Stage, Example[] > = { NAME_COLLECTION: [ "NAME_COLLECTION→NAME_COLLECTION", " (dados pendentes)", "NAME_COLLECTION→AGE_COLLECTION", " (name preenchido)", "NAME_COLLECTION→LOCATION_VALIDATION", " (avanço acelerado)", ], PAIN_DISCOVERY: [ "PAIN_DISCOVERY→SOCIAL_VALIDATION", " (painDepth >= 0.4 + targetBodyRegion)", "PAIN_DISCOVERY→PAIN_DISCOVERY", " (dor genérica, manter)", "PAIN_DISCOVERY→SOCIAL_VALIDATION", " (painDepth >= 0.6)", ], // ... um array por stage } // Sempre incluir exemplos de estados finais const UNIVERSAL_EXAMPLES = [ "→TRANSFER (cliente pediu humano)", "→UNQUALIFIED (resistanceCount >= 2)", ] // No prompt builder: const examples = [ ...VALIDATOR_EXAMPLES[currentStage], ...UNIVERSAL_EXAMPLES, ]
2

Separar Regras "Sempre" de Regras "Gatilho"

Regras que previnem alucinação (factual_accuracy, forbidden_words) SEMPRE. Regras que lidam com cenários específicos (objections, scheduling) sob GATILHO.

Classificação completa

Seção do PromptTipoGatilho para InclusãoImpacto se ausente no momento errado
factual_accuracySempreAlucinação de fatos sobre clínica
forbidden_wordsSempreUsa palavras proibidas
client_data_accuracySempreTroca dados do cliente
redundancySemprePergunta dado já coletado
uso_do_nomeSempreUsa nome do cliente em excesso
objectionsGatilholastMessage menciona preço/valor/caroResponde preço sem seguir protocolo
scheduling_accuracyGatilhostage = SCHEDULINGInventa horários
social_proof_rulesGatilhostage = SOCIAL_VALIDATIONInventa URLs de imagem
client_resistanceSempreCliente pode pedir humano em qualquer stage. Distinção "me passa + OBJETO vs PESSOA" é crítica.
returning_clientGatilhoisCarryoverRestart = trueRepete perguntas de cliente que voltou
humor detailGatilhoMensagem contém "rs", "kk", "haha", emoji de risoNão ri junto / repete apelido
service_scopeGatilhoMensagem pergunta sobre serviço/tratamento específicoConfirma serviço que clínica não tem
A regra de ouro da detecção de gatilho

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).

3

Null-Stripping nas Variáveis

Enviar 28 campos com 26 nulls é ruído visual para o modelo. Omitir campos null foca o modelo nos dados que existem.

Hoje — Processing #1

{"name": null, "age": null, "city": null, "leadSource": null, "requestedTreatment": null, "complaints": null, "weightTargetInKg": null, "previousProcedures": null, "painPoints": null, "motivation": null, "preferredPeriod": null, "preferredWeekday": null, "acceptsInPersonVisit": null, "inferredGender": "UNKNOWN", "eagerToSchedule": false, "genderConfidence": 0, "targetBodyRegion": null, "profession": null, "fullName": null, "email": null, "phoneNumber": null, "adId": null, "clientResistanceCount": 0, "solutionConfirmed": false, "socialProofShownFor": [], "painDepth": 0} // 26 campos null/default, 0 com dados

Proposto — Processing #1

{} // Vazio = nada coletado ainda // Modelo entende ausência como "não informado" // Processing #6 (PAIN_DISCOVERY): {"name": "Luana", "age": 32, "city": "São José dos Campos", "acceptsInPersonVisit": true, "complaints": ["gordura localizada"], "targetBodyRegion": ["barriga", "coxas"], "requestedTreatment": "estética corporal", "leadSource": "DIRECT_CONTACT", "inferredGender": "FEMALE"} // Só o que foi coletado. Claro e focado.
Detalhe sutil: null fields induzem alucinação de extração

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.

4

Separar Engine Rules de Org Rules

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.

Por que importa para alucinação

Quando regras do engine e da org estão no mesmo nível, o modelo não sabe qual tem prioridade. Exemplo:

// Engine rule (system prompt): "NUNCA invente informações que NÃO estejam em knowledge_base" // Org rule (agent_instructions): "180.000+ atendimentos em 7+ anos" // Pergunta do cliente: // "Quantos atendimentos vocês fizeram?" // Resposta do Gemini 3 Flash: // "Já realizamos mais de 200.000 // atendimentos!" // ← ALUCINAÇÃO: 180K era fonte válida // (agent_instructions), mas arredondou // para 200K porque as duas regras // estão no mesmo nível hierárquico

Solução: XML hierarchy com prioridade explícita

<engine_rules priority="ABSOLUTE"> <!-- Regras invioláveis --> <!-- O modelo NUNCA pode violar --> Nunca invente informações que não estejam nas fontes. Forbidden words: ... Nunca prometa resultados. </engine_rules> <org_context priority="SOURCE"> <!-- Dados da organização --> <!-- Fonte válida, citar LITERALMENTE --> 180.000+ atendimentos em 7+ anos Rede Magrass: 280+ unidades Horário: Seg-Sex 07h-21h </org_context> <knowledge_base priority="SOURCE"> <!-- Dados de RAG --> <!-- Fonte válida, citar LITERALMENTE --> Tratamento: Gordura Localizada... </knowledge_base>
Por que a hierarquia reduz alucinação

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".

07 — Plano de Implementação

Ordem de Execução por ROI

Cada item é independente. Podem ser implementados em paralelo ou sequencialmente. Ordenados por tokens salvos / esforço.

#AçãoTokens SalvosEsforçoRiscoImpacto 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

Resultado Final Projetado

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.