Skip to content

Commit 720463c

Browse files
author
MarceloClaro
committed
feat(rag): implementa proposta pos-Recaman e metrica de diversidade (R457)
1 parent 6c297df commit 720463c

6 files changed

Lines changed: 749 additions & 2 deletions

File tree

VALIDATION_R457.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Recibo local de validação — SPEC-935-R457
2+
3+
## Escopo
4+
5+
Implementação da **proposta pós-Recamán** documentada no manual técnico RAG
6+
(SPEC-935-R456): um conjunto de artefatos que diversificam de forma
7+
**determinística** e **estruturada** o ranking pós-ranqueado da arquitetura RAG,
8+
fechando a lacuna de métrica de diversidade do painel `EnhancedRAG.metrics()`.
9+
10+
A implementação é **aditiva, de baixo acoplamento e não-invasiva**: cria o módulo
11+
`rag/recaman.py` e acrescenta um campo novo (`diversity`) ao dicionário de
12+
métricas, sem alterar o contrato dos campos existentes nem o comportamento dos
13+
ranqueadores atuais.
14+
15+
## Artefatos implementados (GREEN)
16+
17+
| Artefato | Descrição |
18+
|---|---|
19+
| `rag/recaman.py` | Novo módulo 100% stdlib, determinístico. |
20+
| `recaman_sequence(n)` | Gera os `n` primeiros termos de A005132 (oráculo OEIS). |
21+
| `ArtifactType` | Enum de classificação semântica (paper/regulation/judicial/clinical/generic). |
22+
| `AnchorResolver` | Deduplica por âncora canônica (identidade de fonte/âmago). |
23+
| `RecamanDiversifier` | Diversifica ranking via offsets `(1 + a_m) mod N` (Tabela 1 do manual). |
24+
| `CanonicalContextPacker` | Posiciona âncoras distintas de forma determinística (mitiga "lost in the middle"). |
25+
| `diversity(items)` | Métrica Div(S) ∈ [0,1] sobre âncoras canônicas (Eq. 6.1 do manual). |
26+
| `EnhancedRAG.metrics()` | Integração ADITIVA: novo campo `diversity`, sem remover/renomear os 4 existentes. |
27+
28+
## Evidência observada
29+
30+
```text
31+
25 passed in 0.34s # tests/test_r457_recaman_diversifier.py (25 contratos GREEN)
32+
48 passed in 0.81s # R457 + R456 + R455
33+
47 passed in 6.49s # regressão R436 (enhanced_search_rag) + R99 (rag_evolved)
34+
```
35+
36+
## Fidelidade matemática ao manual
37+
38+
- **Oráculo OEIS A005132**: `[0,1,3,6,2,7,13,...]` — confirmado por teste.
39+
- **Tabela 1 (N=8)**: índices `(1 + a_m) mod 8 = [1,2,4,7,3,0]` — confirmado por teste.
40+
- **Seção 5.1**: relevância primária (top-1) nunca perdida — confirmado por teste
41+
para vários `k`.
42+
- **Seção 6.1**: Div(S) ≈ 0 para itens idênticos; ≈ 1 para itens disjuntos —
43+
confirmado por teste.
44+
- **Custo O(N)/O(M)**: implementação iterativa com conjunto visitado; sem
45+
recursão, termina sempre — confirmado por teste para N até 512.
46+
47+
## Distinção escopo (anti-overclaim)
48+
49+
- A implementação **torna a capacidade e a métrica disponíveis**, mas **NÃO
50+
promove o diversificador ao pipeline padrão**: a flag `_DIVERSIFIER_ENABLED`
51+
permanece `False` e o fluxo principal fica configurável/desligado.
52+
- **NÃO** alega ganhos empíricos de qualidade (isso exige experimento de coorte
53+
futuro, fora desta spec).
54+
- **NÃO** altera ranqueamento primário (BM25/denso) nem roteamento adaptativo.
55+
56+
## Comandos executados
57+
58+
```bash
59+
cd /home/marceloclaro/opencode-ecosystem-core
60+
python3 -m pytest tests/test_r457_recaman_diversifier.py -q
61+
python3 -m pytest tests/test_r457_recaman_diversifier.py tests/test_r456_manual_tecnico_rag.py tests/test_r455_readme_historico_operacional.py -q
62+
python3 -m pytest tests/test_r436_enhanced_search_rag.py tests/test_r99_rag_evolved.py -q
63+
python3 -m marceloclaro.cli doctor
64+
```
65+
66+
## Limites conhecidos
67+
68+
- O `diversity` é computado sobre âncoras canônicas (source/doc_id/title), que é
69+
uma aproximação determinística de "âmago" sem embeddings — suficiente para a
70+
métrica, que pode ser refinada em ciclo futuro.
71+
- Para `N` pequenos, offsets de Recamán repetem posições; o `diversify` pula
72+
posições já usadas e completa por relevância, garantindo unicidade.
73+
- A sequência de Recamán legitimanmente revisita valores (A005132 não é injetora
74+
para `n` grande) — os testes refletem a definição real, não uma premissa falsa
75+
de termos todos distintos.

evolution/cycles.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5042,6 +5042,25 @@
50425042
"Tabelas em ABNT dentro de ambientes com recuo (quote) devem usar linewidth e colunas raggedright (L{}) para nao estourar as margens; e o comando marginpar colide com as margens ABNT estreitas, devendo ser desativado em favor de marcadores inline."
50435043
],
50445044
"timestamp": 1788096506.2147543
5045+
},
5046+
{
5047+
"round_id": "R457",
5048+
"objective": "Implementacao da proposta pos-Recaman (SPEC-935-R456): diversificador estruturado deterministico + metrica de diversidade.",
5049+
"changes": [
5050+
"rag/recaman.py (novo, 100% stdlib): recaman_sequence (oraculo A005132), ArtifactType, AnchorResolver, RecamanDiversifier (offsets (1+a_m) mod N, Tabela 1), CanonicalContextPacker e metrica diversity() (Eq. 6.1 Div(S)).",
5051+
"rag/enhanced_search_rag.py: integracao ADITIVA de EnhancedRAG.metrics() com novo campo 'diversity' em [0,1], sem quebrar os 4 campos historicos; flag _DIVERSIFIER_ENABLED=False (pipeline padrao nao aciona).",
5052+
"specs/SPEC-935-R457-implementacao-proposta-recaman.md criada (status green com 13 criterios executaveis).",
5053+
"tests/test_r457_recaman_diversifier.py: 25 contratos GREEN (oraculo, tabela N=8, determinismo, relevancia primaria, orcamento, DEDUP por ancora, faixa/identicos/disjuntos Div, packer canonico, integracao aditiva).",
5054+
"VALIDATION_R457.md criado com escopo e limites anti-overclaim."
5055+
],
5056+
"score": 9.0,
5057+
"lessons": [
5058+
"A sequencia de Recaman (A005132) NAO e injetora para n grande (revisita valores); testes que exigem 'todos termos distintos' sao incorretos e devem ser corrigidos para refletir a definicao real.",
5059+
"Implementacao 'da proposta do manual' deve preservar o espirito anti-overclaim: expor a capacidade e a metrica, deixar o pipeline padrao desligado (flag) e nao alegar ganho empirico sem experimento de coorte.",
5060+
"Integracao de metrica nova em API existente deve ser aditiva (append de chave) e tolerante (try/except), para nao quebrar contrato nem falhar se o modulo opcional estiver ausente.",
5061+
"Os offsets da Tabela 1 sao (1 + a_m) mod N, nao a_m mod N: a coluna 0 (relevancia max) e coberta no percurso e o topo nunca e perdido."
5062+
],
5063+
"timestamp": 1788099545.03523
50455064
}
50465065
]
50475066
}

rag/enhanced_search_rag.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
CitationGraph = object # type: ignore
3232
RAGEvolved = object # type: ignore
3333

34+
# ── Diversificador proposto (SPEC-935-R457) ────────────────────────────────
35+
_DIVERSIFIER_ENABLED = False # API exposta; pipeline padrão não a aciona ainda.
36+
try:
37+
from rag.recaman import RecamanDiversifier as _RecamanDiversifier
38+
from rag.recaman import diversity as _diversity
39+
except Exception:
40+
_RecamanDiversifier = None # type: ignore
41+
_diversity = None # type: ignore
42+
3443
try:
3544
from research.searchers import PaperRecord, MultiSearcher
3645
except Exception:
@@ -542,9 +551,18 @@ def answer_grounded(self, query: str, top_k: int = 3) -> Dict[str, Any]:
542551
}
543552

544553
def metrics(self, evidence: List[Any]) -> Dict[str, float]:
545-
"""Métricas de qualidade do retrieval."""
554+
"""Métricas de qualidade do retrieval (inclui diversidade — SPEC-935-R457).
555+
556+
Retorna os 4 campos históricos (groundedness, citation_coverage,
557+
temporal_spread, avg_year) mais o novo campo ``diversity`` em [0,1]
558+
(Div(S) sobre âncoras canônicas), de forma ADITIVA e sem quebrar
559+
contrato existente.
560+
"""
546561
if not evidence:
547-
return {"groundedness": 0.0, "citation_coverage": 0.0, "temporal_spread": 0.0, "avg_year": 0.0}
562+
return {
563+
"groundedness": 0.0, "citation_coverage": 0.0,
564+
"temporal_spread": 0.0, "avg_year": 0.0, "diversity": 0.0,
565+
}
548566
# groundedness = média dos final_score
549567
scores = [float(getattr(ev, "final_score", 0) or (ev.get("final_score", 0) if isinstance(ev, dict) else 0)) for ev in evidence]
550568
groundedness = sum(scores) / len(scores) if scores else 0.0
@@ -556,11 +574,16 @@ def metrics(self, evidence: List[Any]) -> Dict[str, float]:
556574
years = [y for y in years if isinstance(y, int)]
557575
temporal_spread = float(max(years) - min(years)) if len(years) >= 2 else 0.0
558576
avg_year = float(sum(years) / len(years)) if years else 0.0
577+
# diversity (SPEC-935-R457) — Div(S) sobre âncoras canônicas.
578+
div_value = 0.0
579+
if _diversity is not None:
580+
div_value = float(_diversity(evidence))
559581
return {
560582
"groundedness": round(groundedness, 4),
561583
"citation_coverage": round(citation_coverage, 4),
562584
"temporal_spread": temporal_spread,
563585
"avg_year": round(avg_year, 1),
586+
"diversity": round(div_value, 4),
564587
}
565588

566589
def status(self) -> Dict[str, Any]:

0 commit comments

Comments
 (0)