Arquitetura¶
O alberto-research separa deliberadamente o trabalho determinístico, feito em
Python, do trabalho de julgamento, delegado a LLMs. Essa divisão é a decisão de
arquitetura mais importante do projeto: ela mantém o comportamento auditável,
barato e reproduzível, e concentra o não determinismo em pontos com contrato
explícito.
Mapa de módulos¶
| Módulo | Responsabilidade |
|---|---|
alberto_research.cli |
Ponto de entrada do script alberto-research. Define os subcomandos e traduz argumentos em chamadas de biblioteca. |
alberto_research.workflow |
Orquestra o fluxo: descoberta, deduplicação, filtros, resolução de texto completo, triagem, leitura, busca de citações e síntese. |
alberto_research.config |
Carrega e valida o YAML do projeto, incluindo os campos obrigatórios e os intervalos numéricos. |
alberto_research.fulltext |
Cadeia de resolvedores de texto completo, cache local de PDFs e limites de download. |
alberto_research.providers |
Clientes dos provedores de descoberta e dos resolvedores externos (Crossref, Semantic Scholar e resolvedores de PDF). |
alberto_research.db |
Conexão SQLite, descoberta das migrações dentro do pacote, aplicação de migrações e repositórios de acesso a dados. |
alberto_research.digest |
Monta o corpo do digest e grava o Markdown local. |
alberto_research.delivery |
Entrega do digest pelos canais configurados, incluindo SMTP. |
alberto_research.zotero |
Integração opcional com a API do Zotero. |
alberto_research.notion |
Integração opcional com a API do Notion, criação do banco e backfill. |
alberto_research.reader |
Constrói os prompts de leitura, normaliza a resposta do LLM e produz o template de saída estruturada. |
alberto_research.schemas |
Contratos JSON Schema, incluindo a validação da saída de leitura e as chaves proibidas. |
alberto_research.openclaw |
Resolve o binário OpenClaw, monta a linha de comando e invoca o agente, interpretando a resposta JSON. |
Módulos auxiliares completam o quadro: dedupe (deduplicação de registros),
feedback (registro do retorno do leitor), models e enums (tipos de domínio),
logging (configuração de log a partir de LOG_LEVEL) e os módulos de
integração com bibliotecas sombra, que ficam isolados do caminho padrão.
Divisão entre determinístico e LLM¶
O critério é simples: se a resposta correta é única e verificável, fica em Python; se exige interpretação, vai para o LLM.
| Determinístico (Python) | Delegação a LLM |
|---|---|
| Chamadas aos provedores de descoberta | Julgamento de relevância na triagem |
| Normalização e deduplicação de DOI | Extração do argumento central e da metodologia |
| Aplicação de filtros, termos e recortes de data | Comparação entre artigos e detecção de discordâncias |
| Resolução de texto completo e cache de PDFs | Decisões editoriais do digest |
| Migrações, repositórios e estado de execução | Recomendação de leitura humana |
| Validação por JSON Schema antes de persistir | — |
| Estatísticas, entrega e plumbing | — |
A consequência prática: os pontos de contato com o LLM são poucos e cada um tem
um contrato de entrada e saída. A triagem produz uma pontuação; a leitura produz
um objeto JSON que precisa satisfazer READER_OUTPUT_SCHEMA — com campos como
access_level, central_argument, methodology, major_findings,
relevance_to_project, disagreements e confidence — antes de chegar ao banco.
Cadeia de resolvedores de texto completo¶
A resolução percorre os resolvedores na ordem declarada em
fulltext.resolver_order:
flowchart LR
Z[zotero] --> U[unpaywall]
U --> O[openalex]
O --> C[core]
C --> D[doaj]
D --> E[europepmc]
E --> P[provider_url]
Regras da cadeia:
- A ordem declarada tem prioridade. Nomes desconhecidos ou resolvedores indisponíveis são ignorados.
- Resolvedores que não aparecem em
resolver_ordersão anexados ao final, na ordem interna do pacote — por isso a lista configurada não precisa ser exaustiva. zoterovem primeiro por ser o mais barato: se o PDF já está na sua biblioteca, nenhuma requisição externa é necessária.unpaywalldepende deunpaywall_email; sem endereço de contato, o resolvedor não é usado.coredepende decore_api_key; sem chave, é ignorado.provider_urlusa links de PDF oferecidos pelos próprios metadados.- Os resolvedores de bibliotecas sombra ficam fora dessa cadeia padrão e desligados; quando habilitados, desativam a verificação de certificado TLS.
O primeiro resolvedor que devolve um documento utilizável vence, e o resultado é
gravado no cache local (fulltext.cache_dir), respeitando
fulltext.max_fulltext_bytes e fulltext.download_timeout.
Fronteira de confiança¶
Tudo o que vem de fora é tratado como hostil: títulos, abstracts, metadados, páginas HTML e, principalmente, o texto extraído dos PDFs. Um artigo pode conter instruções maliciosas destinadas a sequestrar o comportamento do modelo — a chamada injeção de prompt.
O desenho da fronteira:
flowchart LR
X[Conteúdo externo hostil] --> R[reader: sandbox de leitura]
R --> V[schemas: validação JSON Schema]
V --> S[(SQLite: fonte de verdade)]
S --> W[workflow e digest]
- O conteúdo externo entra somente pelo módulo
reader, que monta o prompt explicitamente instruindo o modelo a devolver apenas o JSON do schema, sem executar nem repetir instruções encontradas no documento. - A saída do modelo é validada contra o JSON Schema, e chaves proibidas
(
tool_calls,commands,shell,email,secrets,credentials,delete_files) são rejeitadas. - Só depois da validação os dados são persistidos no SQLite.
- O agente de leitura roda isolado, sem acesso a finanças, ao sistema de arquivos amplo, a e-mail, a ferramentas destrutivas, a cookies de navegador ou a segredos não relacionados.
Detalhes do modelo de ameaças e do tratamento de injeção de prompt estão em Segurança.
Fonte de verdade e integrações¶
O SQLite é a fonte de verdade operacional. Zotero e Notion são integrações
opcionais e unidirecionais a partir do estado local: o fluxo nunca depende delas
para funcionar, e uma indisponibilidade dessas APIs não corrompe o histórico. As
migrações SQL viajam dentro do pacote, então atualizar o código e aplicar
db migrate mantém o banco alinhado.
Para a referência gerada a partir do código, veja Referência da API.