Files
TextNLPClassifierApp/docs/structured_extraction/02_Arquitetura_Runtime_Consolidacao_Artigos.md

23 KiB

Documento de Arquitetura — Runtime de consolidação de artigos

Versão: 1.0
Status: proposta aceita para implementação
Data: 23 de agosto de 2026
Relacionado: PRD Runtime de consolidação e higienização de artigos

Regra mestre: atender integralmente aos requisitos com arquitetura production-ready e a menor quantidade necessária de código, dependências, abstrações e componentes.

1. Propósito

Definir a arquitetura mínima e production-ready do runtime que recebe um artigo extraído por três bibliotecas, um ECP obrigatório e produz manifesto JSON e, quando aplicável, Markdown editorial fundamentado.

Este documento não especifica self-healing. O runtime apenas preserva a telemetria necessária para um subprojeto futuro.

2. Direcionadores

  1. Atender 100% dos requisitos do PRD.
  2. Usar o menor número possível de componentes e dependências.
  3. Manter o fluxo explícito, testável e auditável.
  4. Não usar regex em processamento textual.
  5. Não usar palavras-chave manuais para decisões semânticas multilíngues.
  6. Não permitir geração livre do artigo pelo LLM.
  7. Executar LLM de higienização para todo artigo.
  8. Exigir ECP antes de Markdown.
  9. Usar somente modelos baratos no runtime.
  10. Permitir troca de provider e modelo por configuração certificada.
  11. Suportar 100 artigos por hora sem perda ou duplicação.

3. Limites do sistema

3.1 Entrada

  • um objeto de artigo já contendo selected_extractor;
  • um ECP Snapshot válido;
  • configuração versionada.

O artigo já deve ter sido considerado conteúdo textual elegível pelo processo anterior. O runtime não classifica predominância de vídeo ou galeria.

3.2 Saída

  • um resultado JSON estruturado por invocação;
  • um manifesto JSON persistido quando houver fingerprint estabelecido;
  • zero ou um arquivo Markdown;
  • estado persistido;
  • telemetria enviada ou preservada para reenvio.

3.3 Dependências externas

  • provider LLM primário barato;
  • provider LLM fallback barato;
  • classificador ECP existente;
  • Langfuse;
  • filesystem;
  • SQLite.

Promptfoo participa do CI e não do processo online.

4. Visão de contexto

flowchart LR
    O["Orquestrador"] --> R["Runtime CLI"]
    R --> L["Providers LLM baratos"]
    R --> E["Classificador ECP"]
    R --> S["Manifesto e Markdown"]
    R -. telemetria .-> F["Langfuse"]

5. Arquitetura lógica

flowchart TD
    A["Validação e fingerprint"] --> B["Parsing estrutural e candidatos"]
    B --> C["Higienização e reparos"]
    C --> D["Gate ECP"]
    D --> E{"ECP aprovado?"}
    E -- Não --> F["Manifesto rejeitado"]
    E -- Sim --> G["Enriquecimento e Markdown"]

6. Componentes

Componente Responsabilidade Não faz
CLI Carregar entradas, configuração e coordenar uma execução Processar lotes internamente
Contract Validator Validar artigo, ECP e configuração Inferir valores ausentes
Fingerprint Service Gerar identidade idempotente Deduzir identidade editorial
Structural Parser Parsear HTML, Markdown, JSON-LD, URLs e Unicode Interpretar intenção editorial
Candidate Builder Criar IDs, origem, posição e equivalências Escolher conteúdo final
Content Hygiene Selecionar blocos, metadados, links, imagens e reparos Sentimento, tags ou ECP
Repair Validator Validar e aplicar pequenos reparos Autorizar reescrita livre
Markdown Assembler Construir documento intermediário e final Criar conteúdo
ECP Adapter Invocar classificador e normalizar resultado Alterar artigo
Enrichment Gerar sentimento e tags Alterar o corpo
Model Gateway Executar primário, retry técnico e fallback Escolher modelo caro
State Store Idempotência, estado, saídas e telemetria pendente Armazenar segredos
Langfuse Adapter Instrumentar traces e generations Bloquear saída válida quando indisponível

7. Orquestração

7.1 Escolha

O runtime será uma máquina de estados explícita em Python. Não usará LangChain, LangGraph, agente, planner ou framework de workflow.

7.2 Justificativa

LangGraph foi avaliado como motor standalone de workflow com estado, e não apenas como framework para agentes. A existência de etapas determinísticas, chamadas de LLM, estados persistidos e arestas condicionais torna seu uso tecnicamente possível, mas não o torna necessário.

O runtime atual possui:

  • sequência predeterminada;
  • poucas bifurcações, todas conhecidas e controladas pela aplicação;
  • nenhuma escolha autônoma de ferramentas ou etapas pelo modelo;
  • nenhum ciclo semântico entre etapas;
  • nenhuma pausa aguardando intervenção humana ou evento externo;
  • uma unidade de artigo por execução curta;
  • SQLite necessário para idempotência, reconciliação, erros e escrita atômica, independentemente do mecanismo de orquestração;
  • retry técnico e fallback com políticas limitadas e conhecidas.

Nesse cenário, LangGraph não substituiria o gateway de modelos, os prompts, os schemas, o harness, as validações, o state store, a observabilidade ou os testes. Ele substituiria apenas uma pequena camada de transições já representável de forma direta em Python, adicionando dependência e semântica operacional próprias sem eliminar código relevante.

LangChain também não é necessário: as chamadas são delimitadas, não existem agentes ou tool calling, e o gateway agnóstico encapsula diretamente as diferenças entre providers.

Langfuse e Promptfoo são independentes dessa decisão. Langfuse permanece responsável pela observabilidade online, e Promptfoo pelos evals e gates fora do runtime.

7.3 Estados persistidos

Estado Significado
received Entradas carregadas
validated Artigo, ECP e configuração válidos
content_cleaned Conteúdo textual e reparos validados
ecp_approved ECP direto ou contextual
ecp_rejected ECP tangencial ou não relacionado
enriched Sentimento e tags válidos
completed_text Manifesto e Markdown gravados
failed Falha terminal com código

Cada transição deve registrar início, fim, duração e resultado. Reexecução parte do último estado seguro ou retorna a saída concluída quando o fingerprint e as versões forem idênticos.

7.4 Critérios de reavaliação

A decisão deve ser reavaliada somente se surgir requisito concreto que não seja atendido com simplicidade pela orquestração atual, como:

  • ciclos semânticos que retornem a etapas anteriores;
  • pausa e retomada aguardando aprovação humana ou evento externo;
  • roteamento dinâmico de etapas decidido por modelo;
  • coordenação de agentes ou subfluxos dinâmicos;
  • execução longa em que checkpoint por etapa reduza materialmente custo ou perda de trabalho;
  • capacidade do framework substituir persistência ou controle próprio relevante, em vez de duplicá-lo.

A adição de uma nova bifurcação predeterminada, isoladamente, não justifica introduzir um framework de workflow.

8. Organização de módulos

A implementação deve usar módulos coesos, sem criar camadas genéricas adicionais:

  • contratos;
  • parsing estrutural;
  • candidatos;
  • higienização;
  • reparos;
  • ECP;
  • enriquecimento;
  • renderização;
  • gateway LLM;
  • estado;
  • observabilidade;
  • CLI.

Essa lista representa responsabilidades, não exige um pacote ou classe por item. Responsabilidades pequenas podem compartilhar módulo quando isso reduzir código sem misturar regras.

9. Política de dependências

9.1 Preferência

  1. biblioteca padrão;
  2. dependência já existente no monorepo;
  3. biblioteca consolidada que elimine implementação própria relevante;
  4. código próprio somente quando a regra for específica do produto.

9.2 Aprovação

Toda nova dependência deve registrar:

  • requisito atendido;
  • alternativa da biblioteca padrão;
  • impacto de segurança e manutenção;
  • licença;
  • impacto de tamanho e inicialização.

9.3 Proibições

  • framework de agentes;
  • biblioteca apenas para uma função trivial;
  • segundo schema do ECP;
  • parser manual de HTML, Markdown ou URL;
  • regex para texto;
  • dependência antecipada de self-healing.

10. Contratos versionados

Devem possuir versão independente:

  • artigo de entrada;
  • ECP Snapshot referenciado;
  • configuração do runtime;
  • candidatos enviados ao LLM;
  • resposta de higienização;
  • operações de reparo;
  • resposta de enriquecimento;
  • manifesto de saída;
  • prompts.

Mudanças incompatíveis exigem nova versão e eval completo.

11. Validação inicial

Ordem obrigatória:

  1. parse JSON;
  2. validar schema do artigo;
  3. validar schema do ECP na fonte canônica;
  4. validar selected_extractor;
  5. validar presença mínima de URL, título e conteúdo textual;
  6. validar configuração certificada dos modelos;
  7. calcular fingerprint;
  8. consultar idempotência.

Nenhum provider, Langfuse remoto ou classificador é chamado antes das validações locais que podem encerrar a execução.

12. Fingerprint e idempotência

O fingerprint deve combinar, por serialização canônica e hash:

  • identidade do artigo;
  • hashes das três extrações;
  • selected_extractor;
  • identidade e versão do ECP;
  • versão dos prompts;
  • configuração funcional do runtime;
  • modelos configurados.

Dados operacionais que não alteram o resultado, como trace ID e timestamp, não entram no fingerprint.

SQLite mantém:

  • fingerprint;
  • estado atual;
  • timestamps;
  • caminhos de saída;
  • hashes dos arquivos;
  • versões funcionais;
  • erro terminal;
  • telemetria pendente.

Para concorrência compatível com o volume, usar transações curtas, modo WAL e timeout de bloqueio configurado. Não adicionar banco servidor sem evidência de necessidade.

13. Parsing estrutural sem regex

13.1 HTML

Usar parser DOM. Navegação, elementos e atributos são inspecionados pela árvore, nunca por manipulação de strings com regex.

13.2 Markdown

Usar parser CommonMark/AST para headings, parágrafos, links, imagens, listas, citações e ênfase.

13.3 JSON-LD e metadados

Usar parser JSON e navegação por objetos. Tipos estruturados são evidência, não decisão semântica completa.

13.4 URLs

Usar parser de URL. Validar esquema, host e componentes sem regex.

13.5 Texto

Usar normalização Unicode, segmentador e tokenizador multilíngue. Comparações de sequência, distância e similaridade devem operar sobre estruturas produzidas por essas bibliotecas.

13.6 Enforcement

O CI deve usar AST de Python para impedir importação ou chamada direta de engine de regex nos módulos do pipeline textual. Dependências transitivas não são inspecionadas internamente, mas nenhuma API de regex pode ser usada pelo código do projeto ou por assertions configuradas.

14. Modelo de candidatos

Cada candidato contém:

  • candidate_id estável na execução;
  • tipo;
  • extrator;
  • campo;
  • texto ou URL original;
  • representação estrutural;
  • índice de ordem;
  • parent ID, quando aplicável;
  • equivalências;
  • hash;
  • flags puramente estruturais.

IDs devem ser opacos ao LLM. A numeração não deve incorporar julgamento de qualidade.

14.1 Ordem

O selected_extractor fornece a espinha dorsal da ordem. Blocos equivalentes dos outros extratores oferecem representações alternativas e evidência de consenso.

Blocos exclusivos de outro extrator podem ser candidatos, mas só entram no resultado por seleção explícita do LLM e validação de grounding.

14.2 Equivalência

Equivalência pode usar:

  • igualdade após normalização Unicode e whitespace por biblioteca;
  • tokenização multilíngue;
  • similaridade de sequência;

Não usar regex nem dicionários de palavras.

15. Higienização extrativa

15.1 Interface

O LLM recebe candidatos e devolve decisões estruturadas. Ele não deve devolver o artigo completo.

Resposta mínima:

  • IDs de título, subtítulo e autor escolhidos;
  • IDs de blocos mantidos;
  • IDs de imagens comuns mantidas;
  • IDs de links mantidos;
  • lista de reparos;
  • motivo categórico para blocos removidos, quando exigido pelo harness.

15.2 Garantia de grounding

O harness:

  1. valida o schema;
  2. valida existência e tipo dos IDs;
  3. recupera conteúdo somente do mapa interno;
  4. aplica reparos válidos;
  5. monta o Markdown;
  6. confirma que URLs e imagens pertencem à entrada.

URL de origem e data de publicação são resolvidas deterministicamente antes da chamada. O LLM não as escolhe nem corrige.

O LLM nunca controla diretamente o renderer ou o filesystem.

15.3 Fallback

  • Falha técnica: retry limitado conforme política.
  • Schema ou grounding inválido: fallback barato.
  • Falha do fallback: usar seleção determinística conservadora apenas quando ela cumprir todos os requisitos de grounding e conteúdo mínimo.
  • Se não houver fallback seguro: HYGIENE_FAILED.

O fallback determinístico não usa regex nem tenta remover semanticamente publicidade. Ele preserva a base do selected_extractor, elimina apenas elementos estruturalmente inválidos e pode resultar em falha quando não houver garantia de qualidade.

16. Pequenos reparos

16.1 Representação

Cada operação contém:

  • ID alvo;
  • fragmento original exato;
  • substituição;
  • categoria fechada;
  • justificativa.

16.2 Validação

O harness deve:

  • confirmar que o fragmento original pertence ao candidato;
  • exigir ocorrência inequívoca ou referência estrutural suficiente;
  • comparar original e substituição com biblioteca Unicode/tokenização;
  • impedir alterações em entidades sensíveis como números, datas, placares, nomes e citações, salvo quando o defeito for exclusivamente de codificação comprovável;
  • registrar diff e decisão;
  • descartar apenas o reparo inválido e manter o original.

Os limites quantitativos de similaridade devem ser calibrados na golden set. Não podem ser inventados no código sem evidência de eval.

16.3 Responsabilidade do prompt

O prompt define expressamente que reparo não autoriza estilo, sinônimo, fluência, paráfrase ou correção factual.

17. Montagem do Markdown intermediário

O assembler:

  1. recupera metadados escolhidos;
  2. recupera blocos mantidos;
  3. aplica reparos validados;
  4. preserva a ordem canônica;
  5. materializa links escolhidos;
  6. insere imagens comuns em posições fundamentadas;
  7. remove duplicação estrutural exata de título/subtítulo;
  8. serializa Markdown sem HTML inline necessário.

Esse documento é a entrada do ECP.

18. Integração ECP

18.1 Adapter

O runtime usa o contrato público do classificador ECP existente. Não replica regras, embeddings ou schema.

18.2 Entrada

Entrada do ECP: Markdown intermediário higienizado.

18.3 Saída

O adapter exige categoria, is_inherent, confiança, rationale e evidências conforme contrato do classificador.

18.4 Gate

  • direto/contextual: prosseguir;
  • tangencial/não relacionado: manifesto rejeitado, sem Markdown;
  • falha: estado terminal, sem Markdown.

Qualquer tier LLM utilizado pelo classificador ECP durante esta execução deve estar configurado com modelo barato certificado. O adapter deve registrar essa generation e impedir configuração potente no runtime.

19. Enriquecimento

Uma chamada separada recebe somente:

  • título final;
  • subtítulo, se houver;
  • corpo final;
  • idioma;
  • identidade mínima do ECP;
  • schema.

Retorna sentimento relativo ao ECP, 3 a 8 tags e IDs de evidência. Não pode alterar o corpo.

Falha do primário chama fallback barato. Falha dos dois impede Markdown porque sentimento e tags são campos obrigatórios do contrato final.

20. Model Gateway

20.1 Interface mínima

O gateway aceita:

  • papel lógico;
  • mensagens/contexto;
  • schema estruturado;
  • timeout;
  • metadados de trace.

Retorna:

  • saída estruturada;
  • provider e modelo efetivos;
  • tokens, custo e latência;
  • status técnico;
  • tentativa e fallback.

20.2 Configuração

Cada papel mapeia para uma configuração certificada:

  • runtime_primary;
  • runtime_fallback.

Defaults inicialmente aprovados podem ser Groq com openai/gpt-oss-20b e DeepSeek com deepseek-v4-flash, sem acoplamento no domínio. A configuração efetiva somente entra em produção após Promptfoo.

20.3 Retry

Retry no mesmo provider é permitido apenas para:

  • timeout;
  • conexão interrompida;
  • 429;
  • 5xx;
  • resposta vazia por falha técnica.

Não repetir no mesmo modelo para buscar decisão semântica diferente.

21. Prompts

Prompts ficam em arquivos versionados no repositório. Cada um possui:

  • nome;
  • versão semântica;
  • hash;
  • schema associado;
  • casos Promptfoo correspondentes.

Prompts mínimos:

  • higienização e reparos;
  • sentimento e tags.

O mesmo arquivo é usado no runtime e no Promptfoo. Langfuse recebe a referência, mas não é a fonte primária nesta versão.

22. Renderer e arquivos

22.1 Nomes

Usar fingerprint no nome para evitar colisões e problemas de Unicode:

  • <fingerprint>.result.json;
  • <fingerprint>.md, quando aplicável.

22.2 Escrita atômica

  1. renderizar em arquivo temporário no mesmo filesystem;
  2. flush e fechamento;
  3. validar conteúdo e hash;
  4. renomear atomicamente para o destino;
  5. persistir estado concluído na mesma unidade lógica.

Se manifesto e Markdown forem necessários, nenhum deles pode ser apresentado como concluído enquanto o par não estiver consistente.

23. Observabilidade

23.1 Hierarquia

  • uma execução CLI: um run_id;
  • um artigo: uma trace estável;
  • cada etapa: span;
  • cada tentativa LLM: generation.

Nomes de spans não incluem modelo, provider, URL ou IDs dinâmicos.

23.2 Conteúdo

Registrar por padrão contexto normalizado e respostas estruturadas, nunca secrets, headers ou variáveis de ambiente.

HTML bruto e JSON integral não devem ser duplicados no Langfuse. Usar hashes e recortes necessários à depuração.

Deve existir configuração para não enviar conteúdo textual, preservando métricas e hashes.

23.3 Degradação

Se Langfuse falhar:

  • continuar processamento;
  • persistir evento mínimo pendente no SQLite;
  • registrar log local estruturado;
  • tentar flush no encerramento;
  • permitir reenvio posterior pela mesma ferramenta operacional, sem serviço adicional.

24. Logging

Logs estruturados em JSON devem conter:

  • timestamp;
  • nível;
  • run_id;
  • fingerprint;
  • estado;
  • evento;
  • código de erro;
  • duração;
  • provider/modelo/prompt quando aplicável;
  • sem conteúdo sensível por padrão.

Exceções devem manter stack trace no log técnico, sem expor secrets.

25. Segurança e privacidade

  • secrets somente por mecanismo de configuração seguro do ambiente;
  • nenhuma chave em CLI, arquivo de saída ou log;
  • permissões mínimas para diretórios;
  • URLs de entrada tratadas como dados, não executadas;
  • HTML e texto são conteúdo não confiável;
  • instruções contidas no artigo não podem alterar o prompt;
  • structured output e seleção por IDs limitam prompt injection;
  • dependências fixadas e verificadas pelo processo do repositório.

26. Capacidade e concorrência

O runtime processa uma unidade por execução. O paralelismo é responsabilidade do orquestrador externo.

O componente deve permitir execuções concorrentes sobre o mesmo state store sem:

  • duplicidade;
  • corrupção;
  • perda de estado;
  • sobrescrita parcial.

O teste de staging deve sustentar 100 artigos por hora no perfil real de chamadas. Custo e latência são medidos; limites numéricos são aprovados antes do go-live com base nessa execução.

27. Falhas e recuperação

Falha Tratamento
Artigo ou ECP inválido Encerrar antes de LLM
selected_extractor inválido Encerrar sem recalcular
Provider primário indisponível Retry técnico e fallback barato
Resposta sem grounding Rejeitar e usar fallback barato
Reparo inválido Preservar original e registrar
ECP indisponível ou inválido Encerrar sem Markdown
Enriquecimento indisponível Encerrar sem Markdown
Escrita interrompida Limpar temporário seguro e retomar idempotentemente
Langfuse indisponível Persistir telemetria pendente e continuar

28. Deploy e configuração

O runtime deve ser empacotável de forma reproduzível e executar como processo CLI efêmero.

Configurações por ambiente:

  • caminhos de entrada, saída e estado;
  • endpoints e credenciais dos providers;
  • primário e fallback;
  • timeouts e retries técnicos;
  • versões de prompt;
  • Langfuse;
  • política de conteúdo em traces;
  • limites de concorrência do state store.

Configurações funcionais versionadas entram no fingerprint. Secrets e parâmetros puramente operacionais não entram.

29. CI/CD

Ordem mínima:

  1. validação de formatos;
  2. lint e análise estática;
  3. verificação AST da proibição de regex;
  4. testes unitários;
  5. testes de contrato;
  6. testes de integração com providers simulados;
  7. Promptfoo reduzido;
  8. build do pacote;
  9. golden set completo antes da promoção;
  10. staging com teste de 100 artigos/hora;
  11. aprovação dos limites de custo e latência;
  12. promoção controlada.

30. Decisões de simplicidade

  • SQLite em vez de banco servidor enquanto atender concorrência e recuperação.
  • filesystem em vez de object storage dentro do runtime.
  • CLI em vez de API.
  • orquestração direta em vez de framework de workflow sem requisito comprovado.
  • dois adapters de providers em vez de roteador inteligente.
  • prompts no repositório em vez de plataforma remota como fonte primária.
  • telemetria pendente na mesma persistência em vez de fila adicional.

Cada escolha deve ser revisitada somente diante de requisito ou evidência operacional nova.

31. Preparação mínima para self-healing futuro

O runtime registra:

  • prompt, modelo, provider e versões;
  • falhas categorizadas;
  • respostas estruturadas;
  • scores;
  • custos e latências;
  • trace ID;
  • hashes e evidências.

Não cria:

  • serviço de análise;
  • fila de candidatos;
  • otimizador;
  • judge;
  • prompt candidato;
  • promoção;
  • canário;
  • alerta de self-healing.

32. Riscos arquiteturais

Risco Mitigação mínima
LLM remover conteúdo válido Golden set por blocos e fallback
LLM reescrever ao reparar Operações de reparo, diff, prompt e eval específicos
Multilinguismo NLP apropriado e eval estratificado por idioma
Regex ou keyword rule introduzida ADR, revisão e teste AST
Provider indisponível Retry técnico e fallback barato
Mudança de modelo alterar comportamento Promptfoo e configuração certificada
Langfuse indisponível Persistência de telemetria pendente
SQLite tornar-se gargalo Medição; migrar apenas com evidência

33. Critérios arquiteturais de aceite

  1. Fluxo predeterminado implementado como máquina de estados explícita, com bifurcações controladas.
  2. Nenhuma dependência de LangChain, LangGraph ou agente.
  3. Nenhum uso de regex no pipeline textual ou assertions.
  4. Nenhum dicionário manual decide semântica multilíngue.
  5. LLM retorna decisões e IDs, não o artigo completo.
  6. Reparos são operações auditáveis e reversíveis.
  7. ECP bloqueia Markdown quando não inerente.
  8. Runtime conhece somente modelos baratos configurados por papel.
  9. Escrita e retomada são idempotentes.
  10. Langfuse pode falhar sem perder resultado ou telemetria mínima.
  11. Promptfoo não é dependência online.
  12. Teste de 100 artigos/hora passa sem perda ou duplicação.
  13. Limites de custo e latência são definidos antes do go-live a partir da baseline.
  14. Nenhum componente de self-healing é implantado.