Trabalho realizado / DefendaMe

Engenharia / DefendaMe

DefendaMe: documentos, IA e operação

Backend e infraestrutura de IA para um produto jurídico: OCR local, recuperação de relatórios estruturados, investigação de falhas e decisões de busca e operação.

Vinícius Menezes

Backend e infraestrutura de IA · Atuação desde 2025

Leitura completa: ~11 min · estimativa

O trabalho em resumo

O desafio era processar documentos e evidências digitais sob restrições de privacidade, capacidade da máquina do usuário e confiabilidade das respostas dos modelos.

Onde atuei

Trabalho no backend e na infraestrutura de IA. Orientei a adoção do Sentry, com participação do Ryan, e contribuí para o reprocessamento de páginas, a recuperação de respostas inválidas, a evolução dos logs e a integração do Doppler ao deploy.

Pessoas e relacionamentosDados sintéticos
Grafo de pessoas conectadas e painel com informações da pessoa selecionada em um caso sintético no DefendaMe.

O produto reúne pessoas, relações e informações associadas em uma interface navegável. A captura usa um caso sintético e mostra o trabalho da equipe no produto.

Ver captura completa (abre em nova aba)
Ir para uma seção

01 / OCR

Manter as imagens locais sem exigir outra instalação

Problema
As imagens precisavam permanecer no dispositivo, sem exigir outra instalação do advogado.
Decisão
Executar OCR no navegador e administrar concorrência, memória e gravação incremental.
Consequência
A equipe implementou controles locais e acompanhamento da indexação por página, com novas tentativas.
Limite
Salvar resultados não garantia retomar todo o processamento. Desktop continua uma direção a validar.

Onde cada parte do documento é processada

Dispositivo do usuário

PDF.jsPreparar páginas
Tesseract.jsReconhecer texto
IndexedDBSalvar resultados

As imagens permanecem no dispositivo neste fluxo.

Texto extraído

Nuvem

Busca e análise
PDF.js prepara páginas, Tesseract.js reconhece texto e IndexedDB salva resultados intermediários no dispositivo. Somente o texto extraído segue para busca e análise na nuvem neste fluxo. Salvar uma página não garante retomar todo o processamento.

PDF.jsPreparação das páginas

Tesseract.jsReconhecimento no navegador

IndexedDBResultados intermediários

Ler o episódio completo

No fluxo local de PDFs, as imagens das páginas precisavam permanecer na máquina do usuário porque podiam conter material sensível. O texto extraído podia seguir para busca e análise na nuvem. Essa distinção orientava o desenho: processar localmente as imagens não significava manter todo o tratamento do documento fora da nuvem.

A escolha inicial foi executar o OCR no navegador. Nosso usuário era um advogado não técnico; queríamos evitar a instalação e a operação de outro programa para usar um sistema web. Usamos PDF.js para preparar as páginas e Tesseract.js para o reconhecimento do texto.

O custo dessa escolha apareceu na execução. Na nossa experiência, o processamento consumia bastante memória e, em alguns casos, encerrava o navegador. Clientes com máquinas mais fracas tinham ainda mais dificuldade. Evitar a instalação não eliminava a dependência do dispositivo.

A equipe passou a ajustar a concorrência à capacidade da máquina, processar páginas em blocos, liberar recursos de renderização e recriar workers entre blocos. Esses workers executavam no navegador; não eram os workers das filas do backend. Também implementamos a gravação incremental dos resultados em IndexedDB, o armazenamento local do navegador.

Salvar resultados intermediários preservava parte do trabalho, mas não garantia retomada automática de qualquer interrupção. Era preciso considerar também a recuperação do conteúdo original e os caminhos de reprocessamento ainda incompletos.

Depois da extração, havia outra fronteira: indexar o texto. Implementamos acompanhamento por página, novas tentativas após falhas e identificadores estáveis no Elasticsearch. Repetir a indexação da mesma página não deveria criar um segundo documento. Essa proteção se limitava à indexação; não tornava todos os efeitos do pipeline idempotentes. Minha contribuição nessa evolução incluiu o reprocessamento das páginas que falharam.

O navegador resolveu o atrito inicial de instalação, mas manteve restrições de memória e execução que precisávamos administrar. Considero uma versão desktop como direção de evolução para ter mais controle do processamento local. É uma alternativa a validar, inclusive na instalação e manutenção, não uma entrega deste relato.

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

02 / Relatórios

O custo de obter uma resposta utilizável

Problema
JSONs extensos precisavam respeitar o esquema e manter qualidade jurídica.
Decisão
Validar e recuperar respostas, considerando o custo de obter um relatório utilizável.
Consequência
A recuperação evoluiu de correções e novas validações para o tratamento dos campos problemáticos.
Limite
A troca de modelo não eliminou falhas. Não há benchmark reproduzível de custo ou acerto jurídico.

Gerar uma resposta era só parte do trabalho

Resposta do modelo
Interpretar JSON
Validar esquema

falha estrutural

Corrigir camposvalidar novamente

estrutura aceita

Estrutura utilizável

Qualidade jurídica é uma exigência distinta.

A resposta do modelo passa por leitura do JSON e validação do esquema. Falhas estruturais levam à correção e a uma nova validação. Uma estrutura aceita pode seguir para o sistema, mas isso não confirma a qualidade jurídica do conteúdo.
Exemplo didático · dados sintéticos

Por que validar campos faz diferença

Este exemplo simplificado ilustra uma falha de tipo em um JSON parseável. Não é o esquema proprietário, uma resposta real do modelo ou evidência de recuperação em produção.

Antes da correção

{
  "event": "Synthetic event A",
  "sourceIds": "document-a"
}

Após corrigir o campo

{
  "event": "Synthetic event A",
  "sourceIds": [
    "document-a"
  ]
}
O campo sourceIds deve ser uma lista. Corrigir esse tipo permite validar a estrutura do exemplo; não verifica a veracidade do evento. Baixar exemplo completo ↓

ZodValidação do esquema

GPT-5Geração no fluxo descrito

JSONEstrutura consumida pelo sistema

Ler o episódio completo

Os relatórios jurídicos precisavam seguir um esquema validado com Zod, muitas vezes com JSONs extensos. Havia três exigências diferentes: o JSON precisava ser parseável, os campos precisavam respeitar o esquema e o conteúdo precisava ter qualidade jurídica. Passar na validação estrutural não demonstrava que a análise estava correta.

Avaliávamos modelos considerando essas exigências nas cargas do produto. Quando a estrutura falhava, corrigir ou repetir a geração acrescentava tempo e custo. Por isso, o preço por chamada era insuficiente: o que importava era o custo de chegar a um relatório utilizável, incluindo as tentativas descartadas e as correções.

Nesse processo, passamos do Gemini 2.5 Pro para GPT-5. A troca não eliminou as falhas. Trabalhei na recuperação de respostas inválidas, com sanitização, correção e novos ciclos de validação. A implementação evoluiu depois para corrigir apenas os campos problemáticos, em vez de refazer indiscriminadamente toda a resposta.

Também houve ajustes de compatibilidade entre os esquemas e a API de Structured Outputs. Parte da dificuldade estava na integração, não apenas no modelo. Essas mudanças sustentam o relato de uma escolha acompanhada de trabalho de recuperação; não uma conclusão sobre toda a família Gemini ou uma garantia de acerto jurídico dos modelos adotados.

O trade-off era manter validação e recuperação no sistema, mesmo depois de trocar o modelo. Não temos uma comparação reproduzível que permita quantificar quanto essa escolha reduziu falhas ou custo total. A decisão foi avaliar a resposta que conseguíamos aproveitar, e não somente o preço anunciado por token.

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

Eventos organizados no tempoDados sintéticos
Linha do tempo de um caso sintético, com eventos, datas, pessoas envolvidas e referência ao documento associado.

A cronologia apresenta eventos com datas, pessoas e referência ao documento associado. É um exemplo de como informações estruturadas aparecem na interface, com dados sintéticos.

Ver captura completa (abre em nova aba)

03 / Observabilidade

Entender o que acontecia na mão dos usuários

Problema
Era difícil entender o que acontecia durante o uso do produto.
Decisão
Combinar captura de erros e eventos das etapas com logs estruturados e correlação.
Consequência
Foram implementados mecanismos para relacionar eventos e investigar execuções nos pontos instrumentados.
Limite
A instrumentação não cobria todos os caminhos nem comprova redução no tempo de diagnóstico.

SentryCaptura e contexto de erros

JSONLogs estruturados

CorrelaçãoRelacionar eventos

Ler o episódio completo

Orientei a adoção do Sentry para entendermos melhor as falhas durante o uso do produto, com participação do Ryan na implementação. Combinamos a captura de erros no navegador com eventos de etapas como upload, OCR e análise, nos pontos instrumentados do cliente e do servidor. O contexto incluía a operação em andamento e eventos de início, conclusão ou falha.

Depois, trabalhei na evolução dos logs estruturados em JSON e da correlação entre execuções. Conforme o ponto instrumentado, os registros incluíam execução, serviço, etapa, duração e tentativa. Um identificador de correlação permitia relacionar eventos que, antes, dependeriam da leitura de mensagens isoladas.

Isso acrescentou mecanismos para investigar o processamento, mas não significava rastreamento completo de todos os caminhos nem demonstrava uma redução medida no tempo de diagnóstico. O resultado que posso descrever aqui é a instrumentação implementada e sua evolução.

O contexto útil para investigar também precisava ter limites. Aplicamos mascaramento e filtros para restringir o envio de conteúdo sensível. Mais telemetria exigia mais cuidado com a coleta; esses mecanismos não eram garantia de anonimização integral.

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

04 / Busca

Transferir a geração dos embeddings

Problema
A aplicação executava embeddings e alimentava duas camadas de busca.
Decisão
Transferir a geração dos embeddings ao fluxo de ingestão associado ao Elasticsearch.
Consequência
A execução do modelo saiu do servidor da aplicação e a busca textual e vetorial foi reunida.
Limite
A inferência continuou consumindo recursos e a dependência da infraestrutura de busca aumentou.

A mesma responsabilidade, em outro lugar

Antes

Texto do documento
Aplicação
Elasticsearch · textual
Embeddings na aplicação
PostgreSQL · vetorial

Depois

Texto do documento
Aplicação
Ingestão e embeddings
Elasticsearch · textual + vetorial

O destaque acompanha a responsabilidade transferida. O custo computacional da inferência continua existindo.

Antes, a aplicação enviava texto ao Elasticsearch e executava embeddings para o PostgreSQL. Depois, enviava texto à infraestrutura de busca, cujo fluxo de ingestão assumia os embeddings e reunia busca textual e vetorial. Representação conceitual, sem detalhar a inferência ou a topologia interna.

ElasticsearchBusca textual e vetorial

PostgreSQLDestino vetorial anterior

Ler o episódio completo

Usávamos Elasticsearch para busca textual e PostgreSQL para busca vetorial. A aplicação também executava o modelo de embeddings sobre os textos, dividindo processamento e memória com as funcionalidades do produto.

A aplicação alimentava duas camadas de busca e executava o modelo de embeddings.

Como o Elasticsearch já fazia parte da infraestrutura, transferimos a responsabilidade pela geração dos embeddings para o fluxo de ingestão associado à busca. A aplicação passou a enviar o texto, e reunimos a busca textual e a vetorial no Elasticsearch.

O custo computacional da inferência continuava existindo. A mudança retirava a execução do modelo do servidor da aplicação e concentrava a indexação na infraestrutura de busca. Isso também aumentava nossa dependência dessa infraestrutura.

Os diagramas representam essa transferência de responsabilidade. Não detalham o mecanismo de inferência nem a topologia interna.

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

05 / Operação

Mais controle também significava mais responsabilidade

Problema
Cargas longas, custos de plataforma e divergências de configuração criavam atritos.
Decisão
A equipe migrou para uma Droplet e adotou o Doppler para centralizar a configuração.
Consequência
Ganhamos controle e assumimos diretamente deploy, proxy, containers e recuperação de processos.
Limite
Eu defendia permanecer na Vercel. Hoje preferiria separar a camada web dos serviços persistentes; essa alternativa não foi medida.

VercelOperação gerenciada anterior

DigitalOceanInfraestrutura sob operação da equipe

DopplerConfiguração no deploy

Três desenhos para a responsabilidade operacional
AlternativaO que ofereceResponsabilidade e limite
Manter tudo na Vercel · posição defendida à épocaPreservar a operação gerenciada da plataforma.As cargas longas e o custo por assento continuavam sendo os atritos relatados.
Migrar tudo para a Droplet · decisão da equipeControle sobre processos e dependências.A equipe passou a operar deploy, proxy, containers, memória e recuperação.
Web na Vercel e serviços fora · avaliação retrospectivaPreservar a camada web gerenciada e mover as cargas que precisavam de outro modelo.É a alternativa que eu preferiria hoje; custo e disponibilidade não foram comparados em condições equivalentes.
Ler o episódio completo

Executar embeddings, indexação e análises podia levar muitos minutos. Acomodar essas cargas no modelo de execução da Vercel estava ficando inconveniente. A equipe decidiu migrar para uma Droplet na DigitalOcean; a camada web foi junto porque, na nossa avaliação, o custo fixo da máquina era mais conveniente que o custo por assento da plataforma.

Eu estava entre os que mais defendiam permanecer na Vercel, pelo trabalho de operação gerenciada que ela assumia. Chegamos a um acordo pela migração, ainda com minhas ressalvas. A decisão e o trabalho de lidar com as consequências foram coletivos.

Ganhamos controle sobre processos e dependências, mas passamos a cuidar diretamente de deploy, proxy, comunicação entre containers, memória e recuperação de processos. Tivemos problemas nessas camadas e precisamos dedicar tempo a investigá-los. Para uma equipe pequena, esse trabalho disputava atenção com as funcionalidades do produto.

Retrospectivamente, considero que mover a aplicação inteira foi um erro. Eu teria preferido manter a camada web na Vercel e mover apenas os serviços que precisavam de outro modelo de execução. É uma avaliação minha hoje, não uma alternativa cujo custo ou disponibilidade tenhamos medido em condições equivalentes.

A configuração trouxe outro problema concreto. A aplicação acumulou centenas de variáveis de ambiente, entre configurações e segredos, e divergências entre ambientes estavam entre os bugs que encontrávamos com frequência. Avaliamos alternativas, especialmente o HashiCorp Vault, e escolhemos o Doppler pela simplicidade relativa para o nosso contexto. Participei diretamente da integração ao deploy, usando a CLI para fornecer as variáveis ao processo.

Centralizar a configuração não garantia que ela chegasse a cada serviço. Uma correção posterior tratou justamente de uma variável disponível no Doppler que não era repassada ao container pelo Compose. A integração mudou a gestão da configuração; continuávamos responsáveis por conferir sua propagação.

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

06 / Resultado

O que esse recorte permite afirmar

Implementamos controles para o OCR local, acompanhamento e retentativas na indexação por página, recuperação de respostas estruturadas e instrumentação para investigar falhas. Essas entregas tinham limites diferentes: salvar uma página não assegurava retomar todo o processamento; validar um JSON não assegurava qualidade jurídica; capturar eventos não assegurava cobertura de todas as falhas.

A parte mais importante dessas decisões foi delimitar a responsabilidade de cada etapa. O navegador evitava outra instalação, mas dependia da máquina do advogado. O modelo precisava produzir uma estrutura utilizável, e a aplicação ainda precisava validá-la. A infraestrutura mais aberta nos dava controle, mas também exigia operação. Foi nessas fronteiras que boa parte do trabalho de engenharia aconteceu.

07 / Autoria

Autoria, evidência e limites

O DefendaMe é construído por uma equipe. Uso “nós” para as decisões e implementações coletivas e delimito minha contribuição nos episódios. Ryan participou da implementação do Sentry; as otimizações de OCR e as mudanças de infraestrutura também tiveram trabalho de outras pessoas.

Este texto reúne meu relato e descrições de código, documentos e histórico examinados durante a coleta editorial. As referências públicas abaixo explicam mecanismos; não comprovam os resultados do DefendaMe. Não publico código proprietário, dados de clientes ou métricas sem medição rastreável.

Referências técnicas

Documentação pública explica mecanismos; não comprova a implementação ou os resultados do produto.

Seu produto enfrenta um problema parecido?

Projetos de engenharia

Se o seu produto enfrenta dificuldades com documentos, IA, integrações ou operação, conte o que acontece hoje.

Contar meu problema →

Oportunidades profissionais

Para conversar sobre minha atuação em backend, produto e infraestrutura de IA.

Conversar sobre uma oportunidade →Ver perfil profissional ↗