Um pipeline de notas fiscais pedia ao modelo seis coisas numa chamada. Extrair os itens, somar, aplicar o desconto, calcular o ICMS de cada linha, converter a data e validar o CNPJ do emitente.
Na maioria das notas, o total batia. Mas num lote, três notas em cada cem chegaram ao sistema contábil com o imposto errado na segunda casa decimal.
E não havia padrão. Às vezes era arredondamento.
Às vezes uma linha somada duas vezes.
E às vezes um dígito do CNPJ que o modelo resolveu corrigir, por conta própria.
Aritmética, imposto e dígito verificador não são interpretação. São regras. O modelo gera o próximo token por probabilidade. Uma função de soma, não. Nesta aula, você vai tirar esse trabalho do modelo, com segurança, e descobrir um bônus que talvez não esperava.
A ideia
A solução é tirar a operação determinística do prompt. O modelo extrai, ou decide, o que só ele consegue. Uma função comum faz a conta, a formatação ou a validação. E quando o modelo precisa do resultado no meio de uma conversa, essa função é oferecida a ele como ferramenta.
Antes e depois
Antes, entender e calcular acontecem na mesma chamada. A nota entra, números saem, e vão direto para o sistema.
Depois, o modelo lê, e o código calcula e confere. A nota chega, e o modelo só extrai os valores, exatamente como impressos.
Funções comuns cuidam das somas, do imposto, das datas e da checagem do CNPJ.
O código compara o total calculado com o total impresso.
E só uma nota coerente entra no sistema. Uma diferença vai para revisão.
Um caso real
Aqui está uma nota. O modelo devolve dois itens. Duas unidades a cento e cinquenta dólares, e uma a quatrocentos. Os dois com alíquota de 18%. Sem desconto. E um total impresso de setecentos dólares.
O código faz a conta. Duas vezes cento e cinquenta, mais quatrocentos, dá setecentos. Bate com o total impresso. Depois calcula o imposto com a regra de arredondamento que o time fiscal escolheu, e valida o CNPJ.
Agora vem o bônus. Digamos que o modelo tivesse lido cento e cinquenta como quinze. A soma não chegaria a setecentos, e a nota iria para revisão.
Antes, o modelo podia ajustar a soma em silêncio para fechar, e o erro de leitura simplesmente sumia. Agora a conta no código vira um fiscal da extração. O erro que passava calado agora levanta uma bandeira.
Um desafio
Uma pergunta para você. O time criou uma ferramenta chamada sum items. Ela manda os valores para um modelo pequeno e pede a soma. O eval de contas melhorou um pouco. A refatoração está pronta?
Não. A conta saiu de um prompt e entrou em outro. Continua sendo geração de token, continua errando, e agora o erro mora numa ferramenta com nome inocente.
Ferramenta, aqui, é função. Soma, uma função de arredondamento, um validador de CNPJ. O teste é este. Se a operação não tem teste unitário sem chamar a API de um modelo, ela ainda não saiu do modelo. E mais barato e ainda errado continua errado.
Passo a passo seguro
Agora, o passo a passo.
Passo zero, medir. Rode o eval de extração e separe os erros por tipo. Erro de leitura, quando um valor foi lido errado. E erro de conta, na soma, no imposto ou no formato. Se você não separar, vai achar que o modelo melhorou quando ele só acertou a soma por coincidência.
Liste as operações determinísticas do prompt. Todo verbo de cálculo, conversão ou validação. Somar, calcular, converter, validar o dígito verificador.
Escreva uma função para cada uma, com teste unitário, incluindo os casos de borda. Meio centavo, alíquota zero, feriado nacional, um CNPJ cujo dígito verificador é zero. E a regra do imposto vem de uma conversa com quem cuida do fiscal, não do palpite do modelo.
Reescreva o prompt pedindo só os dados brutos, como aparecem no documento. E tire os campos calculados, como o imposto e o total, do que o modelo preenche.
Ligue as funções logo depois da extração.
Ou, se o modelo precisa do resultado durante uma conversa, exponha como ferramenta. Um assistente de entregas que precisa do próximo dia útil no meio da conversa chama uma função que soma dias úteis. O modelo não soma dias. Ele pede.
Use as diferenças como alarme. Quando a conta do código discorda do documento, mande para revisão.
E meça de novo. Erro de conta deve cair a zero. Erro de leitura continua existindo, mas agora a conferência pega.
Riscos
Alguns riscos. Interpretação escondida dentro da conta. O total pode ou não incluir o frete. Acerte essa regra com quem conhece o negócio antes de codificar. Senão você troca o erro do modelo pelo seu. Determinístico, e em toda nota.
Os formatos brutos variam. Pedir a data como está escrita empurra o parse para o código. Teste o parser com formatos reais da pasta de notas.
Uma ferramenta que o modelo ignora. Se a função é ferramenta, o modelo pode pular e calcular sozinho. Diga na descrição quando usar, e confira no trace se a ferramenta foi chamada.
E mais revisões, no começo. A conferência vai pegar erros que antes passavam em silêncio. Esse é o objetivo. Mas avise o time de operações antes que ele ache que algo quebrou.
E se operações reclamar, não desligue o alarme. O volume normal de antes era erro invisível. Classifique as revisões. Separe as diferenças reais de uma regra de negócio mal codificada. E corrija a certa.
E tem um atalho tentador para resistir. A soma do código e o total impresso diferem em 2%, e alguém sugere deixar o modelo ajustar o total, porque ele entende desconto implícito. É o smell voltando. Um desconto de verdade vira um campo extraído, não um ajuste silencioso.
O que fica com o modelo
Então, o que fica com o modelo? Tudo o que não tem uma regra fechada que uma função aplique. Decidir se um parágrafo do contrato é cláusula de multa ou de confidencialidade é classificação. Nenhuma função calcula o significado de um parágrafo. Validar dígito verificador, ou transformar uma data escrita em data de calendário, é código.
Fechamento
Então, a ideia para levar. Separe leitura de cálculo. O modelo copia o que está na página, as funções fazem todo número novo, e a comparação entre os dois vira o seu melhor alarme de leitura errada. Uma refatoração que tira erro, e ainda acha os que você não via.
Até aqui, o erro vinha do modelo lendo errado. Na próxima aula, a gente olha o que acontece quando a própria chamada falha, e o código simplesmente tenta de novo: o retry cego. Até lá.
21Rl
RefatoraçãoSimplificando ferramentas
Trocar Chamada de LLM por Código
Replace LLM Call with Code
Trocar Chamada de LLM por Código (Replace LLM Call with Code) é uma refatoração de IA para sistemas com LLM, do grupo Simplificando ferramentas. Mover cálculo, formatação e validação para uma função que o modelo chama.
O modelo está calculando, formatando ou validando algo que tem regra exata, e erra de vez em quando sem padrão. É o smell LLM Fazendo Conta.
O pipeline de NF-e (nota fiscal eletrônica brasileira) pedia ao modelo: extraia os itens, some, aplique o desconto, calcule o ICMS de cada linha, converta a data e valide o CNPJ. Na maior parte das notas o total batia. Em um lote, três notas em cem chegaram ao ERP com ICMS errado na segunda casa. Não havia padrão no tipo de erro: ora o arredondamento, ora um item duplicado na soma, ora o CNPJ com dígito trocado que o modelo “corrigiu” sozinho.
Conta, imposto e dígito verificador não são interpretação. São regra. O modelo gera o próximo token por probabilidade; a função sum não.
Solução
Tire a operação determinística do prompt. O modelo extrai ou decide o que só ele consegue; uma função comum faz a conta, a formatação ou a validação, ou é oferecida ao modelo como ferramenta.
Antes e depois
-PROMPT = """Extraia os itens, some os valores, aplique o desconto,-calcule o ICMS de cada item, converta as datas para AAAA-MM-DD-e valide o CNPJ do emitente."""-result = llm_parse(PROMPT, nfe_text, schema=InvoiceWithTotals)+PROMPT = """Extraia os itens exatamente como aparecem na nota:+descrição, quantidade, valor unitário em centavos, alíquota de ICMS.+Extraia também o valor total impresso, o desconto e a data de emissão."""+raw = llm_parse(PROMPT, nfe_text, schema=RawInvoice)++items_total = sum(i.qty * i.unit_cents for i in raw.items)+expected = items_total - (raw.discount_cents or 0)+icms = [round_half_even(i.qty * i.unit_cents * i.icms_rate) for i in raw.items]+issue_date = parse_date_br(raw.issue_date_text)+cnpj_ok = validate_cnpj(raw.issuer_cnpj)++if expected != raw.printed_total_cents or not cnpj_ok:+ send_to_review(raw, reason="soma ou CNPJ não confere")
Antes: entender e calcular na mesma chamada.
flowchart LR
D(["1. NF-e"]):::io --> M(["2. Extrair e calcular"]):::llm
M --> E[("3. ERP")]:::store
Depois: o modelo lê, o código calcula e confere.
flowchart LR
D(["1. NF-e"]):::io --> M(["2. Extrair dados brutos"]):::llm
M --> C["3. Somar, calcular, validar"]:::tool
C --> V{"4. Confere?"}
V -->|sim| E[("5. ERP")]:::store
V -->|não| H((("Revisão"))):::human
A nota chega.
O modelo só extrai os valores como estão impressos.
Funções comuns fazem soma, imposto, data e validação de CNPJ.
O código compara o total calculado com o total impresso.
Só nota coerente entra no ERP. Divergência vai para revisão.
O código calcula 2 * 15000 + 40000 = 70000, confere com o impresso, aplica o arredondamento bancário no ICMS e valida o CNPJ. Se o modelo tivesse lido 1500 em vez de 15000, a soma não bateria com 70000 e a nota iria para revisão. Antes, o próprio modelo podia “ajustar” a soma para fechar, e o erro de leitura sumia.
Por que funciona
Um modelo de linguagem gera números como gera palavras: token por token, pela probabilidade. Na maioria das vezes isso produz a conta certa. Uma função produz a conta certa sempre, custa quase nada e pode ser testada com casos de borda (R$ 0,005, alíquota zero, CNPJ com dígito verificador 0).
A separação tem um segundo efeito, muitas vezes mais valioso: a conta em código vira um verificador da extração. Se o total calculado não bate com o impresso, algum valor foi lido errado. O erro que antes passava em silêncio agora levanta uma bandeira.
Quando a conta é necessária no meio de um raciocínio (o assistente precisa saber a data de entrega para responder), a função vira uma ferramenta: o modelo pede, o código calcula, o modelo usa o resultado. Isso é Uso de Ferramenta, não um segundo LLM escondido atrás do nome calculate.
Meça. Rode o eval de extração e separe os erros por tipo: leitura (valor lido errado) e conta (soma, imposto, formato). Se você não separa, vai achar que o modelo “melhorou” quando só acertou a soma por coincidência.
Liste as operações determinísticas no prompt: tudo que tem verbo de cálculo, conversão ou validação. “Some”, “calcule”, “converta”, “valide o dígito”.
Escreva a função de cada uma, com testes unitários, incluindo casos de borda (arredondamento, feriado nacional, CNPJ com zeros). A regra de ICMS sai da conversa com o fiscal, não do palpite do modelo.
Reescreva o prompt para pedir só os dados brutos, como aparecem no documento.
Ajuste o schema para os campos brutos. Tire icms_cents e total_cents calculados do que o modelo preenche.
Ligue as funções depois da extração, ou exponha como ferramenta se o modelo precisar do resultado durante a conversa.
Use a divergência como alarme: quando a conta em código não confere com o documento, mande para revisão.
Meça de novo. Erros de conta devem ir a zero; erros de leitura passam a ser detectados pela conferência.
Riscos
Interpretação escondida na conta. “Total” pode incluir ou não o frete. Decida a regra com quem entende do negócio antes de codificar. Senão você só troca o erro do modelo por um erro seu, determinístico e em todas as notas.
Formato bruto variado. Pedir o texto como aparece (“12/03/26”) empurra o parsing para o código. Teste o parser com formatos reais da pasta de notas.
Ferramenta não usada. Se a função vira ferramenta, o modelo pode ignorá-la e calcular sozinho. Diga na descrição quando usar, recuse resultado que o modelo “calculou” no texto, e confira no trace se a ferramenta foi chamada.
Mais revisões no começo. A conferência vai pegar erros que antes passavam. É o objetivo, mas avise o time de operação.
Erro comum: uma ferramenta que é outro prompt
Alguém envolve llm("some estes números: " + itens) e chama isso de ferramenta sum_items. A conta continua no modelo, só mudou o endereço. Ferramenta, aqui, é função: sum, round_half_even, validate_cnpj. Se a operação não tem teste unitário sem API, ainda não saiu do LLM.
Desafio
Quais partes deste prompt de um assistente de RH devem virar código?
Responda dúvidas sobre férias. O funcionário tem 30 dias por ano.Calcule o saldo descontando os dias já tirados. Se ele pedir paravender 10 dias, calcule o valor do abono: salário dividido por 30,vezes 10, mais um terço. Responda de forma simpática.
Ver solução
O saldo e o valor do abono são contas com regra exata e viram ferramentas. O modelo continua responsável por entender a pergunta e explicar o resultado.
tool vacation_balance(employee_id) -> { days_total, days_taken, days_left }tool vacation_bonus(employee_id, days_sold) -> { amount_cents, breakdown }Prompt: Responda dúvidas sobre férias de forma simpática. Para saldo,use vacation_balance. Para venda de dias, use vacation_bonus e expliqueo cálculo com o breakdown. Nunca calcule valores por conta própria.
Teste seu entendimento
Checagem da aula
Responda as 6 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Perguntas rápidas
O que é Trocar Chamada de LLM por Código?
Trocar Chamada de LLM por Código (Replace LLM Call with Code) é uma refatoração de IA para sistemas com LLM, do grupo Simplificando ferramentas. Mover cálculo, formatação e validação para uma função que o modelo chama.
Que problema Trocar Chamada de LLM por Código resolve?