dimesum

Início / Blog / Engenharia

Engenharia

Apagamos nosso analisador de despesas em linguagem natural

· 9 min de leitura ·

Nosso analisador por regras durou uma semana, e quatro frases comuns bastaram para aposentá-lo em favor de uma única regra de recusa.

Apagamos um analisador que tínhamos acabado de escrever. A primeira tentativa da Dimesum de interpretar despesas em linguagem natural foi um motor de regras, e quatro frases comuns o aposentaram: dinner 12-08 400 foi lido como R$12, ravioli 600 colocou um membro chamado Ravindra na divisão, kirana 500 cobrou um membro chamado Kiran, e refund -483.50 virou uma cobrança de R$483,50.

Entender uma frase é tarefa do modelo e de mais ninguém. O que substituiu o motor de regras é um único nível chamado amount_only: um único número, retornado apenas quando o texto contém exatamente um candidato a valor sem ambiguidade, e nunca uma afirmação sobre pessoas. Dois candidatos significam nenhum valor. Uma regra substituiu uma pilha crescente de proteções.

Quatro frases encerraram o analisador por regras

O analisador apagado cobria todo o conjunto de entidades que nosso documento de design de IA especifica: correspondência de membros e apelidos, um léxico de exclusão, inferência de pagador, um léxico de categorias, e confianças ajustadas por campo. Cada uma das quatro falhas abaixo tinha uma correção óbvia. Cada correção era uma nova proteção com seu próprio ponto cego.

As quatro falhas que aposentaram o analisador por regras da Dimesum, registradas 2026-08-20 em docs/tech/10-ai-design.md.
O que o usuário digitouO que o analisador fezPor que aconteceu
dinner 12-08 400Leu o valor como R$12Uma pessoa vê uma data e um total. Um regex vê três números e pega o primeiro.
ravioli 600Colocou um membro chamado Ravindra na divisãoA correspondência aproximada de apelidos pontuou um prato contra uma pessoa.
kirana 500Cobrou um membro chamado KiranO mesmo comparador, desta vez com o nome de uma loja.
refund -483.50Registrou uma cobrança de R$483,50Os dígitos sobreviveram e o sinal não, então o dinheiro apontou para o lado oposto.

Duas das quatro são o mesmo defeito com roupas diferentes. A correspondência aproximada não distingue um prato de uma pessoa nem uma loja de uma pessoa, porque no nível dos caracteres ravioli e Ravindra realmente se parecem. Exija uma correspondência de prefixo mais longa e você quebra ravi, que é o caso para o qual o comparador existe.

Cada proteção que você adiciona revela duas novas formulações

Um analisador por regras falha de um jeito específico: responde com confiança e de forma errada. Um campo em branco custa ao usuário um toque. Uma divisão errada custa a confiança no livro-razão, e num aplicativo de controle de despesas compartilhadas o livro-razão é o produto. As quatro linhas acima não são quase-acertos, são erros de dinheiro.

A esteira sem fim é o verdadeiro argumento, não um defeito isolado. Adicione uma proteção de data e surge o caso do número de pedido. Adicione uma proteção de número de pedido e surgem números soltos, depois quantidades, depois números de mesa. A lista de proteções cresce e nunca converge, porque a linguagem natural não tem um conjunto finito de formulações a enumerar.

Antes e depois: uma pilha crescente de proteções, substituída por uma única regra de recusa ANTES: ANALISADOR POR REGRAS (APAGADO) dinner 12-08 400 regex de valor proteção de data proteção de número de pedido comparador de apelidos léxico de exclusão lê R$12 e registra Cada correção revelava duas novas formulações. DEPOIS: amount_only dinner 12-08 400 exatamente um candidato de dinheiro sem ambiguidade? não, três números valor fica em branco sim, um número retornado em centavos O nível não diz nada sobre pessoas, então não pode colocar dinheiro na pessoa errada.
O analisador apagado respondia a toda frase. O nível que o substituiu responde um número ou nada.
A decisão, datada

A entrada em linguagem natural é um recurso de IA, e nada no repositório da Dimesum se equipara a ela. A decisão foi tomada em 2026-08-20, registrada junto com as quatro falhas, para que ninguém reconstrua as proteções por acidente.

O que foi lançado é um número e uma recusa

amount_only é o nível degradado do nosso documento de design de IA implementado ao pé da letra. Esse nível diz "o formulário simples com pré-preenchimento de valor por regex no cliente", então o nível retorna um valor e nada mais: sem descrição, sem categoria, sem pagadores, sem participantes, sem exclusões. Não custa nada e não chama ninguém, e por isso os testes e o CI rodam sobre ele.

Ambiguidade é uma recusa, não um critério de desempate

A regra inteira vive em uma função, extract_amount_minor. Um número volta apenas quando o texto contém exatamente um candidato para ele, então order 90210 dinner 400 e flat 402 rent 15000 retornam em branco em vez de escolher um vencedor. Um número com marca de moeda conta como sem ambiguidade mesmo ao lado de números soltos, e por isso split 3 ways R$1.200 ainda lê R$1.200.

Um número negado não é valor nenhum, em vez de seu valor absoluto. -500, minus 200 e o (500) do contador voltam todos vazios, porque o campo é uma cobrança e manter os dígitos descartando o sinal aponta o dinheiro para o lado oposto ao do texto. Os parênteses contam apenas quando se fecham sobre o próprio número, então (500 each) continua sendo um aparte entre parênteses.

Como o amount_only decide entre um número e nenhuma resposta reúne todo número que poderia ser dinheiro nenhum encontrado, ou algum deles com sinal negativo? não exatamente um número com marca de moeda, como R$1.200 ou 500/-? não nenhuma marca, e exatamente um número solto? sim amount_minor: unidades menores inteiras, via o expoente ISO 4217 sim não nenhum valor o campo fica em branco sim
Três perguntas, duas delas terminam em branco. Escolher entre dois valores plausíveis produziu o jantar de R$12.

A moeda decide a aritmética

As unidades menores são a única representação que o dinheiro assume na Dimesum, então o nível converte com uma tabela de expoentes ISO 4217 em vez de uma multiplicação fixa por 100. O iene japonês não tem sub-unidade nenhuma, e ×100 infla um recibo de ¥1,200 em cem vezes. Um número mais fino do que a menor unidade da moeda é recusado em vez de arredondado, porque arredondar um valor que alguém digitou é inventar um.

As palavras numéricas indianas fazem parte de escrever um número, não de entender uma frase. 1.2k, 2 lakh e 500/- todas se resolvem, como 1,200. Qualquer coisa acima do valor máximo do livro-razão volta em branco, então um número digitado errado deixa um campo em branco em vez de estourar um inteiro adiante.

O que cada nível de análise pode afirmar, em 29 de agosto de 2026.
CampoAnalisador por regras (apagado)amount_only (ativo)Nível de modelo (conectado, sem chave)
ValorAdivinhado a partir de vários númerosUm número sem ambiguidade, senão em brancoLido no contexto
Descrição, categoriaCorrespondência por léxicoSempre nuloExtraído da frase
Participantes, exclusõesCorrespondência aproximada de nomesSempre vazioResolvidos para ids reais de membros
PagadorInferido pela formulaçãoSempre vazioNomeado, com um valor que pode ser nulo
Confiança geralAjustada por campoFixa em 0.3Por análise
Prompt reportadoNenhum existiaNulo, nenhum prompt foi lidoId e versão do prompt

O nível ativo nunca passa do limite útil de 0.6

Nosso contrato de análise abandona uma análise abaixo de 0.6 de confiança geral e leva o usuário ao formulário simples. amount_only reporta 0.3 em toda resposta, e a constante é estrutural, não ajustada. Um valor sozinho não é uma análise, então o nível fica na metade do limite, seja lá o que tiver encontrado. Uma constante para toda resposta é o que o mantém ali: uma pontuação caso a caso é uma pontuação que alguém acaba empurrando para cima.

O nível também não reporta prompt nenhum. Tanto prompt_id quanto prompt_version voltam nulos, porque o nível não leu nenhum prompt. Nomear um atribuiria todo resultado de avaliação a uma versão de prompt que o nível nunca viu, e o arcabouço de avaliação é o único instrumento autorizado a promover um nível a sugestão de um toque, com 95% de precisão em valor e participantes juntos.

Dois outros níveis estão declarados e nenhum está ativo. O nível cheap-fast tem um adaptador Groq para openai/gpt-oss-120b e nenhuma chave; o nível intermediário não tem adaptador. Selecionar qualquer um deles falha na inicialização em vez de no primeiro pedido do usuário, porque um LLM cobra por chamada e uma dependência cobrável precisa falhar de forma fechada.

Nada é registrado automaticamente, então um branco custa um toque

Um campo em branco custa tão pouco só porque nenhuma captura na Dimesum pode escrever dinheiro. Uma captura cria uma sugestão, uma pessoa a confirma, e a confirmação é o que cria a despesa. A decisão D5 do Brief define a regra, e o .go-arch-lint.yml a aplica: o contexto de ingestão fica proibido de qualquer dependência de despesa ou livro-razão, então uma captura não pode registrar um lançamento nem por engano. O CI reprova a importação, o que verificamos adicionando uma.

A captura mantém o texto bruto faça o que fizer o analisador, e indica quais campos ficaram sem resolver. O cliente destaca esses brancos em vez de mostrar um rascunho inventado, que é a diferença entre um analisador que não diz nada e um que adivinha. A confirmação deriva seu id de despesa do id da sugestão, então um toque duplo se repete em vez de cobrar duas vezes.

A regra que vale copiar

Conte suas proteções, não seus erros. Uma lista de proteções que cresce toda semana está lhe dizendo que o trabalho é compreensão, e compreensão pertence a um modelo. Nosso próximo passo é rodar o conjunto de referência em contracts/parse_expense/eval/ contra um nível de modelo real, porque nada aqui vira uma sugestão de um toque sem esse veredito.

Perguntas frequentes

Por que a Dimesum apagou o analisador de despesas por regras?

A Dimesum o apagou porque quatro frases comuns geraram erros de dinheiro e cada proteção adicionada revelava mais duas formulações. dinner 12-08 400 foi lido como R$12, ravioli 600 adicionou um membro chamado Ravindra, kirana 500 cobrou um membro chamado Kiran, e refund -483.50 virou uma cobrança de R$483,50. Entender uma frase é tarefa do modelo.

O que o nível amount_only realmente retorna?

O nível amount_only retorna um número e nada mais. Ele responde com um valor apenas quando o texto contém exatamente um candidato a dinheiro sem ambiguidade, e nunca nomeia um participante, um pagador, uma descrição ou uma categoria. Dois candidatos significam nenhum valor. Todo o resto do contrato de análise espera por um nível de modelo.

Por que o amount_only informa confiança 0.3 em vez de uma pontuação real?

O 0.3 é estrutural, não ajustado. Nosso contrato de análise abandona uma análise abaixo de 0.6 e leva o usuário ao formulário simples, e um valor sozinho não é uma análise, então o nível fica na metade desse limite, seja lá o que tiver encontrado. Uma constante fixa para toda resposta impede que uma pontuação caso a caso seja empurrada para cima depois.

Uma captura em linguagem natural pode escrever no livro-razão sem um humano?

Não. Uma captura cria uma sugestão e uma pessoa a confirma, conforme a decisão D5 do Brief. A regra é aplicada no .go-arch-lint.yml, que nega ao contexto de ingestão qualquer dependência de despesa ou livro-razão, então o CI reprova a importação se uma captura tentar alcançar um lançamento. Confirmar é o que cria a despesa.

O que acontece com a análise de despesas em linguagem natural quando nenhum nível de modelo está disponível?

A Dimesum degrada para amount_only e mostra campos em branco. O nível cheap-fast está conectado ao openai/gpt-oss-120b da Groq e é lançado sem chave, e o nível intermediário não tem adaptador, então selecionar qualquer um falha na inicialização em vez de no primeiro pedido do usuário. A captura mantém o texto bruto de qualquer forma.