feat(runtime): implement single-article consolidation runtime and modularize codebase
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
# Architecture Decision Records — Runtime de consolidação de artigos
|
||||
|
||||
**Versão do conjunto:** 1.0
|
||||
**Data:** 23 de agosto de 2026
|
||||
**Escopo:** somente runtime
|
||||
|
||||
**Regra mestre:** cada decisão deve atender a um requisito ou risco real de produção com a menor complexidade necessária.
|
||||
|
||||
## Convenções
|
||||
|
||||
Cada ADR registra uma decisão arquitetural individual. Todas apresentam o estado aprovado da arquitetura para implementação.
|
||||
|
||||
## ADR-001 — Separar runtime e self-healing
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O runtime precisa consolidar artigos em produção. O self-healing de prompts adiciona análise de falhas, modelos potentes, LLM-as-a-judge, promoção e rollback de prompts. Implementar ambos simultaneamente mistura objetivos e aumenta risco e código antes da validação do fluxo principal.
|
||||
|
||||
### Decisão
|
||||
|
||||
Runtime e self-healing serão projetos documentais e técnicos separados.
|
||||
|
||||
O runtime apenas registra a telemetria necessária ao futuro projeto. Não implementa otimizador, judge, prompt candidato, promoção, canário, rollback automático de prompt ou alerta de self-healing.
|
||||
|
||||
### Consequências
|
||||
|
||||
- foco integral no processamento principal;
|
||||
- menor superfície operacional;
|
||||
- self-healing só começa após estabilização do runtime;
|
||||
- os documentos do runtime apenas referenciam a capacidade futura.
|
||||
|
||||
### Alternativa rejeitada
|
||||
|
||||
Construir o ciclo completo de self-healing junto com o runtime.
|
||||
|
||||
## ADR-002 — Iniciar o runtime após a seleção do extrator
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
Um processo anterior já executa Trafilatura, Newspaper4k e Readability e determina `selected_extractor`.
|
||||
|
||||
### Decisão
|
||||
|
||||
Cada execução recebe um único objeto de artigo já contendo `selected_extractor`. O runtime valida o campo e a disponibilidade do extrator, mas não calcula, recalcula ou corrige a seleção.
|
||||
|
||||
O wrapper de lote com `articles` não pertence ao contrato da execução.
|
||||
|
||||
### Consequências
|
||||
|
||||
- fronteira clara entre seleção e consolidação;
|
||||
- runtime menor;
|
||||
- entrada inválida falha cedo;
|
||||
- nenhuma divergência silenciosa com a decisão anterior.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- recalcular sempre a seleção;
|
||||
- aceitar lote e selecionar internamente;
|
||||
- substituir silenciosamente extrator inválido.
|
||||
|
||||
## ADR-003 — Usar orquestração explícita em Python, sem LangChain ou LangGraph
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O fluxo combina etapas determinísticas, chamadas de LLM, estados persistidos e bifurcações entre validação, higienização, ECP, enriquecimento, fallback e saída.
|
||||
|
||||
LangGraph pode operar como motor standalone de workflows com estado e combinar passos determinísticos e probabilísticos. Portanto, sua rejeição não pode ser fundamentada apenas no fato de o runtime não ser um agente ou de o fluxo ser linear.
|
||||
|
||||
No runtime atual, porém:
|
||||
|
||||
- a sequência é predeterminada;
|
||||
- as bifurcações são poucas, conhecidas e controladas pela aplicação;
|
||||
- o modelo não escolhe livremente ferramentas nem a próxima etapa;
|
||||
- não existem ciclos semânticos;
|
||||
- não existe human-in-the-loop;
|
||||
- não há pausa aguardando evento externo;
|
||||
- cada execução processa um artigo e tem curta duração;
|
||||
- SQLite continua necessário para idempotência, reconciliação, erros e escrita atômica;
|
||||
- LangGraph não substituiria gateway, prompts, schemas, harness, validações, persistência, observabilidade ou testes.
|
||||
|
||||
LangChain também não elimina implementação relevante, pois as chamadas aos modelos são delimitadas e o gateway agnóstico já encapsula providers, modelos, parâmetros, retry técnico e fallback.
|
||||
|
||||
### Decisão
|
||||
|
||||
Implementar orquestração direta em Python com máquina de estados persistida em SQLite. Não usar LangChain, LangGraph, agentes ou planner no runtime.
|
||||
|
||||
Langfuse permanece como observabilidade online e Promptfoo como ferramenta de eval e gate fora do runtime. Ambos são independentes de LangChain e LangGraph.
|
||||
|
||||
### Consequências
|
||||
|
||||
- menor código indireto e menos dependências;
|
||||
- estados, erros e retomadas explícitos;
|
||||
- testes por etapa simples;
|
||||
- uma única implementação de estado e retomada;
|
||||
- novas bifurcações predeterminadas permanecem na orquestração direta;
|
||||
- introdução de framework exige requisito concreto e redução comprovada de complexidade própria.
|
||||
|
||||
### Critérios de reavaliação
|
||||
|
||||
Reavaliar LangGraph somente se o runtime passar a exigir uma ou mais capacidades que alterem materialmente o fluxo atual:
|
||||
|
||||
- ciclos semânticos entre etapas;
|
||||
- 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;
|
||||
- substituição de persistência ou orquestração própria relevante pelo framework, sem duplicação de responsabilidades.
|
||||
|
||||
A existência isolada de estados, chamadas de LLM ou arestas condicionais não é critério suficiente.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- **LangChain para encapsular chamadas simples:** rejeitado porque o gateway já fornece a abstração necessária e não há agente ou tool calling.
|
||||
- **LangGraph standalone como motor do workflow atual:** rejeitado porque adicionaria dependência e semântica operacional sem eliminar implementação relevante.
|
||||
- **Agente autônomo para escolher etapas:** rejeitado porque retiraria previsibilidade de um processo com sequência conhecida.
|
||||
|
||||
## ADR-004 — Gateway agnóstico com apenas modelos baratos no runtime
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
Providers e modelos podem mudar por custo, qualidade, disponibilidade ou descontinuação. Modelos potentes devem ser reservados ao self-healing futuro.
|
||||
|
||||
### Decisão
|
||||
|
||||
O domínio depende de dois papéis configuráveis:
|
||||
|
||||
- `runtime_primary`;
|
||||
- `runtime_fallback`.
|
||||
|
||||
O gateway traduz esses papéis para provider, modelo, parâmetros, timeout e prompt certificado. Ambos devem ser baratos. Nenhum caminho do runtime chama modelo potente.
|
||||
|
||||
### Consequências
|
||||
|
||||
- troca de modelo sem alterar regras de domínio;
|
||||
- toda combinação exige Promptfoo antes de promoção;
|
||||
- falha dos modelos baratos termina com fallback determinístico seguro ou falha controlada;
|
||||
- não existe escalada cara por artigo.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- SDK de provider espalhado pelo domínio;
|
||||
- roteador inteligente com vários modelos;
|
||||
- escalada automática para Sol, Terra ou equivalente em artigos difíceis.
|
||||
|
||||
## ADR-005 — Proibir regex e palavras-chave manuais em decisões textuais
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O corpus é multilíngue. Regex e listas de palavras produzem falsos positivos e negativos grosseiros em classificação e higienização textual.
|
||||
|
||||
### Decisão
|
||||
|
||||
Nenhum módulo do pipeline textual ou assertion de conteúdo pode usar regex. Nenhuma decisão semântica pode depender de dicionário manual de palavras por idioma.
|
||||
|
||||
Usar parsers estruturais, bibliotecas Unicode, tokenizadores, segmentadores, NLP e LLM quando houver interpretação semântica.
|
||||
|
||||
O CI verifica o código Python por AST para impedir uso direto de engine de regex nos módulos relevantes.
|
||||
|
||||
### Consequências
|
||||
|
||||
- menor fragilidade multilíngue;
|
||||
- decisões estruturais separadas das semânticas;
|
||||
- qualquer exceção futura exige nova ADR explícita;
|
||||
- bibliotecas transitivas podem internamente usar seus próprios algoritmos, mas o projeto não chama APIs de regex para texto.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- regex por domínio;
|
||||
- dicionários traduzidos;
|
||||
- regras como título contém determinada palavra;
|
||||
- comprimento textual como classificador final.
|
||||
|
||||
## ADR-006 — LLM seleciona evidências e propõe reparos, não regenera o artigo
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O LLM precisa remover ruído e organizar o conteúdo, mas não pode reescrever, resumir ou inventar. Pequenos defeitos textuais precisam ser corrigíveis.
|
||||
|
||||
### Decisão
|
||||
|
||||
O LLM de higienização retorna:
|
||||
|
||||
- IDs de metadados e blocos;
|
||||
- IDs de links e imagens;
|
||||
- operações explícitas de reparo.
|
||||
|
||||
Ele não retorna livremente o artigo completo.
|
||||
|
||||
Cada reparo referencia fragmento original, substituição, categoria e justificativa. O harness valida e aplica. Reparo inválido é descartado e o original permanece.
|
||||
|
||||
### Consequências
|
||||
|
||||
- grounding verificável;
|
||||
- reparos auditáveis e reversíveis;
|
||||
- renderer totalmente controlado pela aplicação;
|
||||
- prompt, eval e harness precisam distinguir reparo de reescrita.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- pedir ao LLM um Markdown final livre;
|
||||
- proibir qualquer correção;
|
||||
- aceitar texto corrigido sem diff e origem.
|
||||
|
||||
## ADR-007 — Tornar o ECP obrigatório antes de toda saída editorial
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
Markdown só deve existir para conteúdo inerente à entidade definida pelo ECP.
|
||||
|
||||
### Decisão
|
||||
|
||||
Toda execução exige ECP válido.
|
||||
|
||||
- O ECP recebe o Markdown intermediário higienizado.
|
||||
- `DIRECT_INHERENT` e `CONTEXTUAL_INHERENT` prosseguem.
|
||||
- `TANGENTIAL` e `NOT_RELATED` não geram Markdown.
|
||||
|
||||
O runtime usa o schema e o classificador ECP existentes, sem duplicá-los.
|
||||
|
||||
### Consequências
|
||||
|
||||
- nenhuma saída escapa do gate de relevância;
|
||||
- ECP inválido encerra antes do LLM;
|
||||
- o pipeline depende explicitamente do contrato versionado do ECP.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- permitir execução sem ECP;
|
||||
- aplicar ECP depois da geração do arquivo final.
|
||||
|
||||
## ADR-008 — Langfuse no runtime e Promptfoo no CI
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O runtime requer observabilidade de produção e evals de promoção, mas não deve acoplar execução online a ferramentas de teste.
|
||||
|
||||
### Decisão
|
||||
|
||||
- Langfuse recebe traces, spans, generations, scores, custos e versões.
|
||||
- Promptfoo executa evals, comparação de modelos baratos e gates no CI/staging.
|
||||
- Prompts versionados no repositório são a fonte primária.
|
||||
- Falha do Langfuse não bloqueia resultado; telemetria mínima fica pendente.
|
||||
- Promptfoo não é chamado pelo runtime.
|
||||
|
||||
### Consequências
|
||||
|
||||
- responsabilidades claras;
|
||||
- runtime não depende da disponibilidade do sistema de eval;
|
||||
- prompts usados no CI e produção são idênticos;
|
||||
- dados suficientes ficam disponíveis ao futuro self-healing.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- Promptfoo online por artigo;
|
||||
- Langfuse como única fonte de prompt nesta versão;
|
||||
- bloquear processamento quando observabilidade estiver indisponível.
|
||||
|
||||
## ADR-009 — Persistir estado em SQLite e saídas no filesystem
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
O runtime é uma CLI, processa um artigo por execução e precisa de idempotência, retomada, escrita atômica e telemetria pendente. O volume é de até 100 artigos por hora.
|
||||
|
||||
### Decisão
|
||||
|
||||
Usar:
|
||||
|
||||
- SQLite para estado, fingerprints, versões, erros e telemetria pendente;
|
||||
- WAL e transações curtas para concorrência;
|
||||
- filesystem para manifesto e Markdown;
|
||||
- nomes baseados em fingerprint;
|
||||
- arquivos temporários e rename atômico.
|
||||
|
||||
Não introduzir Postgres, fila ou object storage no runtime enquanto staging não demonstrar necessidade.
|
||||
|
||||
### Consequências
|
||||
|
||||
- deployment simples;
|
||||
- menor custo operacional;
|
||||
- concorrência e disco precisam ser monitorados;
|
||||
- migração futura depende de evidência de gargalo ou requisito novo.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- persistência apenas em memória;
|
||||
- arquivos sem índice idempotente;
|
||||
- Postgres antecipado;
|
||||
- fila dedicada para telemetria.
|
||||
|
||||
## ADR-010 — Produzir resultado estruturado sempre e Markdown condicionalmente
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
Rejeição ECP e falhas controladas precisam de resultado legível por máquina mesmo quando não existe Markdown.
|
||||
|
||||
### Decisão
|
||||
|
||||
Toda invocação devolve resultado JSON estruturado. Quando a entrada for parseável e houver fingerprint estabelecido, o resultado também é persistido como manifesto. Markdown é produzido somente quando existe texto editorial aprovado pelo ECP e enriquecimento válido.
|
||||
|
||||
O manifesto informa estado, ECP, versões, trace e erro.
|
||||
|
||||
### Consequências
|
||||
|
||||
- saída não ambígua;
|
||||
- auditoria possível sem Markdown;
|
||||
- entrada que nem possa ser parseada retorna erro estruturado e log, sem exigir arquivo persistente.
|
||||
|
||||
### Alternativas rejeitadas
|
||||
|
||||
- não produzir saída para descartes esperados.
|
||||
|
||||
## ADR-011 — Definir SLOs de custo e latência a partir de staging
|
||||
|
||||
**Status:** Accepted
|
||||
|
||||
### Contexto
|
||||
|
||||
Não existe baseline real de tokens, custo e duração para a cadeia final. Definir números agora seria arbitrário.
|
||||
|
||||
### Decisão
|
||||
|
||||
- instrumentar custo e latência desde o primeiro teste;
|
||||
- executar staging com o corpus representativo e 100 artigos por hora;
|
||||
- calcular p50, p95 e p99 por etapa e total;
|
||||
- medir custo por artigo recebido e por Markdown aprovado;
|
||||
- aprovar limites operacionais antes do go-live;
|
||||
- impedir produção enquanto esses limites não estiverem registrados.
|
||||
|
||||
### Consequências
|
||||
|
||||
- SLOs baseados em evidência;
|
||||
- documentação não contém números inventados;
|
||||
- staging possui gate operacional obrigatório.
|
||||
|
||||
### Alternativa rejeitada
|
||||
|
||||
Escolher limites de custo e latência sem dados do pipeline implementado.
|
||||
Reference in New Issue
Block a user