Há uns meses tivemos de meter as mãos num sistema que convertia texto livre — notas de comerciais, transcrições de chamadas, emails reencaminhados — em registos estruturados de CRM: clientes, oportunidades, próximos passos, resumos acumulativos. A lógica de negócio estava bem pensada e o prompt tinha sido escrito por alguém que sabia muito do domínio comercial.
O problema era outro: demorava entre 46 e 68 segundos por entrada.
Cinquenta e oito segundos em média. Mais de um minuto de spinner sempre que um comercial gravava uma nota a partir do telemóvel. Hoje esse mesmo processo responde em 8-12 segundos: até oito vezes mais rápido, com a mesma funcionalidade, o mesmo modelo base e sem eliminar um único campo do resultado.
Este artigo conta como baixámos esse processo para menos de 20 segundos sem retirar uma única funcionalidade. Não há truque de prompt nenhum. A mudança foi arquitetural, e a conclusão — que já intuíamos, mas que aqui ficou medida — é que um prompt gigante não é uma arquitetura, é um adiamento da arquitetura.
O projeto é de um cliente, portanto tudo o que se segue está anonimizado: não há nomes, nem prompts literais, nem dados de negócio. O que é real é a estrutura do problema, as medições e as decisões.
Passo 1: tudo numa só chamada
O ponto de partida era o clássico, e não o digo com desprezo: é assim que começam 90% das funcionalidades de IA que são validadas primeiro num chat. Uma única chamada ao modelo, com um system prompt de vários milhares de palavras, que fazia tudo isto ao mesmo tempo:
- Detetar que eventos comerciais existiam no texto
- Classificá-los por tipo
- Interpretar o subtexto e decidir qual era o movimento mais importante
- Construir um JSON por evento com mais de trinta campos
- Gerar narrativa: fotografias de estado, insights táticos, briefings, guiões de chamada
- Consolidar os resumos acumulativos de cada cliente e de cada oportunidade, respeitando o histórico
Funcionava. Num chat, com um exemplo bonito, funcionava bastante bem. Mas em produção, com temperatura 0.7 e dez pedidos seguidos, os tempos eram estes:
| Métrica | Tempo |
|---|---|
| Mínimo | 45,6 s |
| Máximo | 67,9 s |
| Média | 58,3 s |
| P95 | 67,9 s |
Baixar a temperatura para 0.2 — a suspeita habitual — não mudou nada: média de 59,1 s. Aí já sabíamos que o problema não era de configuração.
O ponto de partida: uma chamada, cinco responsabilidades, um output enorme e 58,3 segundos de espera.
Porque é que uma só chamada era o problema
Há três razões, e convém separá-las porque se resolvem de forma diferente.
A latência é marcada pelo output, não pelo input. Um LLM gera tokens um a um. Podes dar-lhe 8.000 tokens de contexto e ele processa-os depressa, mas cada token que escreve custa tempo. Um prompt que pede um JSON de trinta campos mais cinco textos narrativos por cada evento detetado não demora muito porque pensa muito: demora muito porque escreve imenso. E se o texto de entrada contém três eventos comerciais, o output triplica.
A atenção dilui-se. Quando metes regras de formato, definições de campos, instruções de tom, critérios de priorização e lógica condicional no mesmo prompt, todas competem pelo mesmo orçamento de atenção. Uma regra de naming pode roubar capacidade de raciocínio à deteção de eventos. É o mesmo efeito de pedir a uma pessoa que, numa só passagem, leia um texto, o classifique, preencha um formulário de trinta campos, escreva três parágrafos de análise estratégica e atualize um histórico. Consegue fazê-lo. Fá-lo-á pior do que se lhe pedires por partes.
O raciocínio é opaco. Se o sistema deteta mal um evento, o erro arrasta-se para a estruturação, para a narrativa e para a consolidação. Não há forma de saber onde falhou nem de repetir só essa parte. E também não podes otimizar nada em separado: se a narrativa demora muito, não a podes retirar sem partir o resto do output.
Esse terceiro ponto é o que sai mais caro a médio prazo, e o menos visível numa demo.
Passo 2: dividir o prompt para ver, não para correr
A primeira mudança não a fizemos para otimizar. Fizemo-la para perceber.
Separámos a chamada única em fases com entradas e saídas explícitas, medimos cada uma em separado e deixámos o resto igual. Nem paralelização, nem modelos diferentes, nem cortes de prompt. Só dividir e cronometrar.
O resultado foi uma melhoria modesta — de 58 para 28,4 segundos, basicamente porque cada chamada gerava menos texto de uma vez —, mas não era isso o importante. O importante é que passámos de uma caixa negra a um sistema observável. Pela primeira vez podíamos responder a “onde é que se vão os segundos?”.
O mesmo trabalho, três fases mensuráveis. O ganho de tempo foi modesto; o de visibilidade, total.
E a resposta foi clara: o passo de estruturação continuava a ser um mini-cérebro completo. Detetava, estruturava, priorizava, escrevia narrativa e consolidava entidades. Tínhamos dividido o monólito em pedaços, mas um dos pedaços continuava a ser um monólito.
Este passo intermédio parece dispensável quando o contas depois. Não é. Se tivéssemos saltado diretamente para o desenho final, teríamos otimizado às cegas e, provavelmente, o passo errado.
Passo 3: especializar, paralelizar e tirar do caminho crítico
Com as medições à frente, o redesenho fez-se sozinho. Três decisões:
- Uma responsabilidade por chamada. O passo grande foi dividido outra vez, separando o operacional (dados que vão para a base de dados) do narrativo (textos que uma pessoa lê).
- O que não depende de ninguém, em paralelo. Vários passos só precisavam da saída da deteção inicial, não da dos outros. Não havia razão para os executar em série.
- O que não bloqueia, para segundo plano. A narrativa é o mais lento de gerar e o menos urgente: ninguém lê um briefing de reunião no mesmo segundo em que grava uma nota.
O pipeline ficou assim:
| Passo | O que faz | Modelo | Tempo | Bloqueia o utilizador? |
|---|---|---|---|---|
| 1. Deteção | Identifica e classifica os eventos do texto | Leve | 3-5 s | Sim |
| 2. Estruturação | Converte cada evento num registo operacional | Principal | 5-10 s | Sim |
| 3. Consolidação | Atualiza os resumos acumulativos de cada entidade | Principal | 5-10 s | Sim, em paralelo com o 2 |
| 4. Ações automáticas | Deteta alterações explícitas de dados aplicáveis sem critério | Leve | 2-4 s | Sim, em paralelo com 2 e 3 |
| 5. Enriquecimento | Gera narrativa, insights e conteúdo de agenda | Principal | 10-15 s | Não |
Os passos 2, 3 e 4 arrancam ao mesmo tempo assim que o 1 termina, porque os três consomem a mesma coisa: a lista de eventos detetados. O passo 5 é lançado depois de responder ao utilizador e atualiza os registos quando termina.
O desenho final: um passo leve que abre o grafo, três passos em paralelo e toda a narrativa fora do caminho crítico.
Os números
| Antes | Pipeline sequencial | Pipeline paralelo | |
|---|---|---|---|
| Tempo total | 58,3 s | 28,4 s | 14,2 s |
| Tempo percebido | 58,3 s | 28,4 s | 8-12 s |
Numa frase: o processo ficou mais de 4 vezes mais rápido de ponta a ponta e até 8 vezes mais rápido naquilo que o utilizador percebe. A espera passou de 58,3 segundos — mais de um minuto à frente de um spinner — para 8-12 segundos. Menos 83%, com a mesma funcionalidade e com o mesmo modelo base.
E nem sequer é a comparação mais dura. O pior caso medido no sistema original foi de 67,9 segundos: mais de um minuto para gravar uma nota. Hoje o troço mais lento de todo o processo — a narrativa, 10-15 segundos — já não está no caminho crítico. O utilizador nunca espera por ele.
O que significam 48 segundos a menos
As percentagens esquecem-se; o tempo acumulado não. Com 100 entradas por dia, um volume modesto para uma equipa comercial média:
| Antes | Agora | |
|---|---|---|
| Espera acumulada por dia | 1 h 37 min | 17 min |
| Por mês (20 dias úteis) | 32 horas | 6 horas |
São 26 horas por mês — mais de três jornadas completas — que uma equipa deixou de passar a olhar para um ecrã que não responde.
Mas a mudança importante não é aritmética, é de comportamento. Acima dos 40 segundos, as pessoas lançam o pedido e vão-se embora: abrem outro separador, entram no carro, esquecem-se. Abaixo dos 20, ficam a olhar para o resultado — e se algo correu mal, corrigem-no a quente, enquanto ainda se lembram da conversa que acabaram de ter. Essa diferença não aparece em nenhum gráfico de latência e é a que decide se a funcionalidade é usada ou abandonada.
E há uma parte que também não aparece na tabela: agora sabemos que passo falha quando algo falha, podemos repetir só esse, podemos mudar o modelo de um passo sem tocar nos outros e podemos acrescentar um passo novo sem reescrever um prompt de 4.000 palavras.
Como ficaram os prompts na prática
Nada disto se sustenta só com arquitetura de caixas e setas. Há técnicas concretas de prompting por trás de cada passo, e são a parte que normalmente não se conta. Não podemos mostrar o prompt real do cliente — é propriedade dele —, mas sim o padrão, que é reutilizável em qualquer projeto com esta forma.
Antes: um system prompt com cinco trabalhos dentro
O prompt original tinha a forma clássica do “faz tudo”: uma lista longa de instruções, todas ao mesmo nível, a competir pela mesma atenção e com o JSON de saída descrito em prosa no final.
És um assistente que processa notas comerciais.
1. Deteta todos os eventos comerciais do texto (reunião, oferta, objeção, fecho...)
2. Para cada evento, classifica-o por tipo e por urgência
3. Constrói um JSON com: cliente, oportunidade, tipo, data, valor, probabilidade,
próximos passos, contactos mencionados... (30+ campos)
4. Escreve uma narrativa de estado, um briefing de reunião e um guião de chamada
5. Atualiza o resumo acumulado do cliente e da oportunidade, fundindo
com o histórico sem perder informação prévia
Responde sempre em JSON com este formato: {...}
Genericamente correto e muito difícil de manter. Pedir “responde em JSON” em texto livre — em vez de forçar a saída por schema — provoca falhas de parsing com alguma regularidade, e qualquer ajuste de tom no ponto 4 arrisca partir o ponto 3, porque os dois vivem no mesmo bloco de texto.
Depois: um prompt, um trabalho, uma técnica diferente em cada um
Passo 1 — Deteção. Prompt curto, modelo leve, temperatura 0, e saída forçada por tool calling em vez de pedir JSON em prosa:
const result = await client.messages.create({
model: "modelo-leve",
temperature: 0,
system: "Deteta os eventos comerciais do texto. Mais nada.",
tools: [{
name: "eventos_detetados",
input_schema: {
type: "object",
properties: {
eventos: {
type: "array",
items: {
type: "object",
properties: {
tipo: { enum: ["reuniao", "oferta", "objecao", "fecho"] },
fragmento: { type: "string" }
},
required: ["tipo", "fragmento"]
}
}
},
required: ["eventos"]
}
}],
tool_choice: { type: "tool", name: "eventos_detetados" }
});
Forçar a saída por schema em vez de pedir “responde em JSON” elimina pela raiz uma categoria inteira de falhas: JSON mal fechado, comentários colados, campos inventados que não estavam no contrato. O passo 3 do prompt original — o que mais falhas silenciosas dava — desapareceu quase por completo só com esta mudança.
Passo 2 — Estruturação. Mesmo padrão, schema maior, mas um único trabalho: converter um evento já detetado num registo. Não decide o que é relevante nem escreve uma única linha de narrativa; isso já o resolveu o passo 1 e resolvê-lo-á o passo 5.
Passo 5 — Enriquecimento. Aqui muda quase tudo o resto: temperatura alta (0.7-0.8), o modelo principal, e um system prompt que já não compete por espaço com regras de formato:
És [a voz do assistente]. A tua única tarefa é escrever, a partir deste
registo já estruturado e validado, uma narrativa breve para o comercial:
o que aconteceu, o que está em jogo no próximo passo, como abrir a próxima chamada.
Tom: direto, próximo, sem enchimento corporativo.
Não inventes dados que não estejam no registo.
Esse prompt não tem regras de formato de JSON, nem definições de campo, nem lógica de classificação. Só tom e uma tarefa. É a diferença entre pedir a alguém que redija bem e pedir-lhe que redija bem enquanto preenche um formulário de trinta campos ao mesmo tempo.
Três técnicas que mudaram mais as coisas do que o redesenho em si
- Tool calling / structured outputs em cada passo operacional, nunca “responde em JSON” em texto livre. É a mudança com melhor ratio esforço-benefício de todo o projeto: converte uma falha de parsing imprevisível num erro de validação de schema, que se pode capturar e repetir.
- Prompt caching do contexto partilhado. Os passos 2, 3 e 4 reutilizam boa parte do mesmo contexto — o evento detetado, as regras de negócio do domínio. Fazer cache desse bloco evita pagá-lo por inteiro como tokens novos em cada uma das três chamadas paralelas.
- Um dataset de avaliação por passo, não um global. Antes de tocar num prompt em produção, passamo-lo por 20-30 casos de exemplo com saída esperada para essa fase concreta. Sem isto, cada ajuste de prompt é uma experiência sem controlo: podes melhorar a deteção e não perceber que partiste a consolidação até um cliente notar.
Nada disto é exótico nem requer uma biblioteca nova. É prompting básico — temperatura por tarefa, schema em vez de prosa, contexto em cache, avaliação por fase — aplicado de forma disciplinada, passo a passo, em vez de amontoado num único bloco de texto.
As regras que tirámos daqui
Se tens uma funcionalidade de IA que faz demasiado numa só chamada, esta é a ordem por que a atacaríamos hoje:
1. Separa os dados da narrativa. É a divisão mais rentável de todas e quase sempre existe. Os campos operacionais são curtos, determinísticos e validáveis contra um esquema. Os textos narrativos são longos, subjetivos e beneficiam de um prompt com personalidade. Misturá-los obriga o modelo a mudar de registo a meio da geração e encarece cada regeneração: se só queres retocar o tom de um briefing, não devias ter de voltar a extrair os dados.
2. Manda para segundo plano tudo o que o utilizador não leia agora. O critério não é a importância, é o momento de consumo. Um resumo que será lido amanhã antes de uma reunião pode demorar 20 segundos a gerar. Um dado que aparece no ecrã ao gravar, não.
3. Paraleliza por dependências reais, não por ordem lógica. É fácil encadear os passos pela ordem em que os pensaste. Desenha o que cada passo precisa de verdade: no nosso caso, três dos cinco dependiam apenas do primeiro.
4. Usa o modelo barato onde não há critério. Classificar, detetar e extrair alterações explícitas é trabalho mecânico. Não precisa do modelo mais caro: precisa de instruções claras e temperatura baixa. Reserva o modelo bom para onde há interpretação.
5. Começa por medir, não por otimizar. Dividir para observar é um passo com valor próprio, mesmo que não melhore os tempos. Sem medições por fase, otimizar é adivinhar.
O que esta arquitetura te cobra
Quem te vender pipelines de agentes sem contar esta parte está a vender-te metade.
Mais pontos de falha. Cinco chamadas são cinco coisas que podem dar timeout, devolver um JSON partido ou ficar a meio. Precisas de repetições por passo, validação de esquema em cada saída e uma política clara sobre o que fazer quando falha um passo de background (repetir?, deixar vazio?, avisar?). Esse código não existia quando tudo era uma chamada.
Tokens de entrada repetidos. Cada passo precisa da sua parte de contexto, e há contexto que viaja em várias chamadas. O custo de input sobe. No nosso caso foi largamente compensado pela descida de tokens de saída — que são os caros — e pela cache de prompt, mas é uma conta que há que fazer, não assumir.
Coerência de voz. Ao separar a narrativa num passo próprio, esse passo deixa de ver o prompt completo com toda a personalidade do assistente. Se não lhe passares contexto de tom suficiente, o resultado sai correto mas insosso. Custou-nos duas iterações ajustá-lo.
Orquestração a sério. Alguém tem de gerir o paralelismo, os timeouts, a ordem de escrita na base de dados e o que acontece se o passo de background terminar depois de o utilizador ter editado o registo à mão. É trabalho de engenharia normal, mas é trabalho.
Nenhuma destas contrapartidas nos fez duvidar da decisão. Todas nos fizeram dedicar dias que não teríamos dedicado a manter o monólito.
A mudança mais importante não foi técnica
Há um benefício que não aparece em nenhuma métrica e que, com o tempo, se revelou o mais valioso.
Num projeto assim há dois tipos de conhecimento distintos. Há o de quem define o que é um evento comercial relevante e como se estrutura, que é conhecimento de domínio e de dados. E há o de quem define como fala o assistente, que é conhecimento de produto, de tom e de marca. Com um prompt monolítico, essas duas pessoas editam o mesmo bloco de texto de 4.000 palavras e pisam-se constantemente. Qualquer ajuste de tom obriga a revalidar a extração de dados, e qualquer ajuste de extração pode destruir a voz.
Ao separar os passos, cada um passa a ter dono. A pessoa que define a voz itera sobre o prompt de enriquecimento sem tocar em nada do operacional. A equipa técnica ajusta a extração sem reescrever a personalidade. Pode versionar-se, comparar-se e reverter-se em separado.
Dito de outra forma: deixámos de tentar que o modelo fizesse tudo e começámos a desenhar como queríamos que pensasse. E o efeito secundário foi que também ficou claro quem decide o quê.
Como replicar isto no teu projeto
Se estás no ponto de partida — uma funcionalidade de IA que vai bem na demo e assim-assim em produção — a ordem que seguiríamos é:
- Instrumenta antes de tocar em nada. Mede latência, tokens de entrada e de saída por pedido. Se não consegues dizer quantos tokens gera o teu pior caso, ainda não sabes qual é o teu problema.
- Lista as responsabilidades do prompt atual. Se ao enumerá-las te saírem mais de três verbos distintos (detetar, classificar, redigir, consolidar…), tens um pipeline escondido dentro de um prompt.
- Divide e mede, sem otimizar. Aceita que a primeira versão dividida melhore pouco. O objetivo é ver.
- Desenha o grafo de dependências reais e paraleliza o que não dependa entre si.
- Tira do caminho crítico tudo o que é narrativo e devolve-o quando estiver pronto.
- Fixa uma avaliação por passo antes de continuar a otimizar: um conjunto de entradas de exemplo com saídas esperadas, para cada fase. Sem isto, cada melhoria de velocidade é uma aposta sobre a qualidade.
O ponto 6 é o que mais gente salta e o que sai mais caro. Uma arquitetura modular sem avaliação por módulo é uma arquitetura modular que não sabes se funciona.
Nada disto exigiu um modelo melhor, nem mais orçamento de tokens, nem uma biblioteca de agentes. Exigiu decidir o que o modelo tinha de pensar em cada momento — e essa mudança, medida, valeu 48 segundos por entrada.
Se estás a construir funcionalidades de IA sobre LLMs e te deparas com tempos de resposta que não encaixam, ou com um prompt que já ninguém da equipa se atreve a tocar, falamos. É um padrão que vimos vezes suficientes para o reconhecer depressa.
E se te interessa a parte económica desta decisão, em quanto custa realmente executar LLMs em produção detalhamos os números de tokens, infraestrutura e otimização com dados atuais.