Pipeline ETL de conhecimento com validação estatística via scipy.stats. Ensine documentos para uma IA local e prove matematicamente que o conhecimento foi absorvido.
Arquitetura validada por pesquisa: A abordagem de validação estatística para RAG é respaldada por 4 papers recentes — VERA (KDD 2024), QuCo-RAG (ACL Findings 2026), IndiaFinBench (2026) e IEEE Signal Processing (2026). Veja Validação Científica.
Empresas querem "ensinar" documentos para uma IA, mas o fine-tuning tradicional é caro, lento e causa overfitting. O RAG resolve isso mantendo o modelo intacto — mas como saber se a base de conhecimento está realmente boa?
| Abordagem | Custo | Tempo | Overfitting | Prova de qualidade |
|---|---|---|---|---|
| Fine-Tuning (OpenAI) | $50-500 | 2-24h | Alto | Nenhuma |
| LoRA (GPU A100) | $2-10/h | 1-8h | Médio | Nenhuma |
| RAG simples | $0 | Minutos | Zero | Nenhuma |
| Local Knowledge Trainer | $0 | Minutos | Zero | p-value + IC 95% + Cohen's d |
O Local Knowledge Trainer não apenas faz RAG — ele prova estatisticamente que cada documento ingerido melhorou (ou piorou) a base de conhecimento, usando scipy.stats.
Documentos → Parser → Knowledge Graph → Embeddings → Validação Estatística → Vector DB → RAG Engine → LLM
(PDF,MD, (split (spaCy NER (sentence- (scipy.stats: Shapiro, (FAISS (BM25 +
TXT,PY, por + networkx) transf.) KS, T-test, ANOVA, HNSW / Denso +
SQL,Java) heading/ Bootstrap, Qui²) Chroma) RRF)
parág.)
Cada documento ingerido passa por:
- Parse — extração estruturada por tipo de arquivo
- Chunking — segmentação com overlap configurável
- Embedding — vetorização com
sentence-transformers - Validação pré-ingestão — KS test para detectar viés de distribuição
- Armazenamento — FAISS HNSW ou ChromaDB
- Validação pós-ingestão — t-test A/B (novo doc melhorou a base?)
- Knowledge Graph — entidades e relações via spaCy NER
| Formato | Estratégia | O que extrai |
|---|---|---|
pdfplumber página a página |
Texto + número de página | |
| Markdown / RST | Segmenta por headings (#) |
Texto por seção com nome da seção |
| Python | ast.parse() |
Funções, classes e docstrings |
| Java | Regex de classe/método | Declarações de classe e método |
| SQL | Split por ; |
Statements DDL/DML com nome da tabela |
| TXT | Split por parágrafo (\n\n) |
Blocos de texto |
IDs de chunk são hashes SHA-256 (16 chars) de (source, text[:100]) — determinísticos e reproduzíveis.
- Entidades: Extração via
spaCyNER — PT-BR (pt_core_news_sm) e EN (en_core_web_sm) - Relacionamentos: Co-ocorrência entre entidades no mesmo chunk (
co_occurs) - Grafo:
networkx.DiGraphcom atributoslabel,frequency,weight,source_doc - Exportação: RDF/XML, GraphML, GML, GEXF
Requer pip install -e ".[nlp]" para ativar (sem spaCy, o Knowledge Graph é silenciosamente ignorado).
| Teste | Função scipy | O que valida | Quando roda |
|---|---|---|---|
| Shapiro-Wilk | scipy.stats.shapiro() |
Normalidade dos scores de embedding | validate_knowledge_base() |
| Kolmogorov-Smirnov | scipy.stats.ks_2samp() |
Viés de distribuição entre documentos | ingest(hypothesis_test="ks") |
| T-test Independente | scipy.stats.ttest_ind() |
A/B test: novo documento melhorou a base? | ingest(hypothesis_test="ttest") |
| ANOVA one-way | scipy.stats.f_oneway() |
Conflito entre tópicos | test_topic_conflict() |
| Qui-Quadrado | scipy.stats.chi2_contingency() |
Impacto do chunk size na recuperação | optimize_chunking() |
| Bootstrap CI | Resampling empírico (VERA, 10K) | Intervalo de confiança robusto (não assume normalidade) | bootstrap_confidence_interval() |
| Bootstrap Permutation | Resampling 10K (IndiaFinBench) | p-value robusto para A/B testing | bootstrap_ab_test() |
| Cohen's d | Cálculo próprio | Tamanho do efeito (não apenas significância) | ingest() + query() |
| Bonferroni | Correção manual | Controle de falsos positivos em múltiplos testes | validator.bonferroni_correct() |
| IQR Outlier Removal | Quartis + 1.5×IQR | Remove outliers antes dos testes | validate_knowledge_base() |
Pipeline de recuperação em 3 etapas:
Query
│
├─► 1. Dense (FAISS HNSW cosine) ─────┐
│ ├─► 3. RRF Fusion ─► Top-K chunks ─► LLM
└─► 2. Sparse (BM25 lexical) ─────┘
(rank_bm25)
- Dense: cosine similarity com FAISS HNSW (k=20 candidatos)
- Sparse: BM25 Okapi com tokenização por whitespace
- Fusão RRF:
score = Σ 1/(k + rank)com k=60, combina listas sem normalização de escala - Top-K final: configurável via
query(k=N), padrãok=5
Dois backends suportados, com fallback automático para numpy se a dependência não estiver instalada:
| Backend | Instalação | Características |
|---|---|---|
| FAISS HNSW (padrão) | incluído no base | Rápido, in-memory, salvo via persist_dir |
| ChromaDB | pip install chromadb |
Persistência nativa, filtros por metadata |
| numpy (fallback) | sempre disponível | Busca linear por cosine similarity |
- Registra co-ocorrência de entidades no Knowledge Graph
- Detecta relações conflitantes entre documentos (
conflicting_relation) - Identifica gaps de conhecimento — entidades sem co-ocorrência prévia (
zero_cooccurrence) - Score de contradição:
contradictions / total_pairs— flag automático se > 0.3
| Requisito | Mínimo | Recomendado |
|---|---|---|
| Python | 3.10+ | 3.11+ |
| RAM | 4 GB | 16 GB |
| Disco | 500 MB | 10 GB (para modelos de embedding) |
| GPU | Não requer | NVIDIA (para FAISS GPU) |
# Clonar
git clone https://github.com/IA-PlayGround/local-knowledge-trainer.git
cd local-knowledge-trainer
# Instalação base (FAISS + BM25 + embeddings + PDF)
pip install -e .
# Com NLP para Knowledge Graph (spaCy PT-BR + EN)
pip install -e ".[nlp]"
python -m spacy download pt_core_news_sm
python -m spacy download en_core_web_sm
# Com suporte GPU (FAISS GPU)
pip install -e ".[gpu]"
# Desenvolvimento (pytest)
pip install -e ".[dev]"KnowledgeTrainer(
embedding_model = "all-MiniLM-L6-v2", # modelo sentence-transformers
vector_store = "faiss", # "faiss" | "chromadb"
language = "pt", # "pt" | "en" (para Knowledge Graph)
device = "cpu", # "cpu" | "cuda"
chunk_size = 512, # palavras por chunk
chunk_overlap = 64, # palavras de overlap entre chunks
persist_dir = "", # "" = in-memory | "/path/to/dir" = salva no disco
)Sobre persist_dir: quando especificado, o vector store é salvo em persist_dir/vector_store/. Na próxima inicialização com o mesmo caminho, os dados são recarregados. Útil para bases grandes que não devem ser re-indexadas a cada execução.
from local_knowledge_trainer import KnowledgeTrainer
# Inicializa
trainer = KnowledgeTrainer(
embedding_model="all-MiniLM-L6-v2",
language="pt",
persist_dir="/tmp/minha_base", # persiste no disco
)
# Ingere documentos com validação estatística
resultado = trainer.ingest(
"docs/",
hypothesis_test="all", # "ttest" | "ks" | "all"
chunk_size=512,
chunk_overlap=64,
)
print(f"Chunks ingeridos: {resultado.chunks}")
print(f"Aprovados (p<0.05): {resultado.approved_docs}")
print(f"Rejeitados: {resultado.rejected_docs}")
print(f"Relatório estatístico: {resultado.statistical_report}")O KnowledgeTrainer aceita qualquer função com assinatura (str) -> str como LLM. Sem LLM configurado, a query retorna o contexto recuperado como texto bruto.
# Exemplo com OpenAI
from openai import OpenAI
client = OpenAI()
def meu_llm(prompt: str) -> str:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
trainer.set_llm(meu_llm)
# Exemplo com Ollama (local)
import requests
def ollama_llm(prompt: str) -> str:
r = requests.post("http://localhost:11434/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False})
return r.json()["response"]
trainer.set_llm(ollama_llm)resposta = trainer.query(
"Explique o módulo de autenticação",
return_confidence=True,
k=5, # número de chunks recuperados
)
print(resposta.text)
print(f"Confiança: {resposta.confidence:.1%}") # 1 - p_value
print(f"IC 95%: [{resposta.ci_lower:.3f}, {resposta.ci_upper:.3f}]")
print(f"p-value: {resposta.p_value:.4f}") # probabilidade de ser aleatório
print(f"Cohen's d: {resposta.cohens_d:.2f}") # tamanho do efeito vs baseline
print(f"Chunks usados: {len(resposta.source_chunks)}")
for chunk in resposta.source_chunks:
print(f" [{chunk['score']:.3f}] {chunk['source']}: {chunk['text'][:80]}...")Sem LLM configurado, resposta.text contém o contexto recuperado formatado:
[Sem LLM configurado] Contexto:
<texto dos top-K chunks>
validacao = trainer.validate_knowledge_base()
print(f"Normalidade (Shapiro): p={validacao.shapiro_p:.4f}")
print(f"Distribuição normal: {validacao.distribution_normal}")
print(f"Homogeneidade (KS): p={validacao.ks_p:.4f}")
print(f"Outliers removidos: {validacao.outlier_count}")
for w in validacao.warnings:
print(f" AVISO: {w}")# Testa estatisticamente qual chunk_size é melhor via Qui-Quadrado
otimizacao = trainer.optimize_chunking(
strategies=[256, 512, 1024],
metric="hit_rate",
)
print(f"Melhor tamanho: {otimizacao.best_size}")
print(f"χ² = {otimizacao.chi2:.2f}, p = {otimizacao.p_value:.4f}")
print(f"Significativo: {otimizacao.p_value < 0.05}")
print(f"Resultados por estratégia: {otimizacao.strategy_results}")# 1. Registra pares de entidades do Knowledge Graph
pares = trainer.register_entity_pairs()
print(f"{pares} pares de entidades registrados")
# 2. Detecta contradições em novo documento antes de ingerir
contradicoes = trainer.detect_contradictions(
"Python foi criado por Brendan Eich em 1995."
)
if contradicoes["flagged"]:
print(f"ALERTA: {contradicoes['contradiction_count']} contradições detectadas")
print(f"Score: {contradicoes['contradiction_score']:.2f} (threshold: 0.30)")
for c in contradicoes["contradictions"]:
print(f" {c['entity1']} → {c['entity2']}: {c['type']}")# Intervalo de confiança via bootstrap (não assume normalidade)
ci = trainer.bootstrap_confidence_interval(confidence=0.95)
print(f"Bootstrap CI: [{ci['ci_lower']:.3f}, {ci['ci_upper']:.3f}]")
print(f"Método: {ci['method']}") # "bootstrap_percentile"
print(f"N resamples: {ci['n_bootstrap']}")
# Score composto multi-métrica
qualidade = trainer.compute_quality_score("Explique a arquitetura do sistema")
print(f"Score composto: {qualidade['composite_score']:.2f}")
print(f"Tier: {qualidade['quality_tier']}") # excellent | good | fair | poor
print(f"Componentes: {qualidade['components']}")
print(f"IC do score: [{qualidade['ci_lower']:.3f}, {qualidade['ci_upper']:.3f}]")
# Bootstrap A/B test (mais robusto que t-test para dados não-normais)
import numpy as np
scores_antes = np.array([0.5, 0.48, 0.52, 0.51])
scores_depois = np.array([0.72, 0.69, 0.74, 0.71])
ab = trainer.bootstrap_ab_test(scores_antes, scores_depois)
print(f"Bootstrap p-value: {ab['bootstrap_pvalue']:.4f}")
print(f"Significativo: {ab['significant']}")
# Export do Knowledge Graph
trainer.export_graph("grafo.graphml", format="graphml") # ou "rdf", "gml", "gexf"print(trainer.stats)
# {
# "chunks": 1247,
# "entities": 83,
# "relations": 412,
# "embedding_model": "all-MiniLM-L6-v2",
# "dim": 384
# }| Método | Parâmetros | Retorno | Descrição |
|---|---|---|---|
ingest(path, hypothesis_test, chunk_size, chunk_overlap) |
path: str, test: "ttest"|"ks"|"all" | IngestResult |
Ingere arquivo ou diretório com validação estatística |
query(question, return_confidence, k) |
question: str, k: int=5 | QueryResponse |
Busca híbrida + resposta LLM opcional |
optimize_chunking(strategies, metric) |
strategies: list[int] | ChunkOptimizationResult |
Qui-Quadrado sobre hit/miss por chunk size |
validate_knowledge_base() |
— | ValidationReport |
Shapiro + KS + IQR sobre a base atual |
bootstrap_confidence_interval(confidence) |
confidence: float=0.95 | dict | Bootstrap CI empírico (VERA) |
compute_quality_score(query) |
query: str | dict | Score composto multi-métrica com CI |
bootstrap_ab_test(scores_before, scores_after) |
arrays numpy | dict | Bootstrap permutation test (IndiaFinBench) |
register_entity_pairs() |
— | int | Registra pares do KG para contradição |
detect_contradictions(text) |
text: str | dict | Detecta contradições vs KG existente |
export_graph(path, format) |
format: "graphml"|"rdf"|"gml"|"gexf" | None | Exporta Knowledge Graph |
set_llm(llm_fn) |
fn: (str) -> str | None | Configura função LLM para geração |
stats |
— | dict | chunks, entities, relations, dim, model |
| Tipo | Campos principais |
|---|---|
IngestResult |
total_docs, chunks, approved_docs, rejected_docs, statistical_report, errors |
QueryResponse |
text, source_chunks, confidence, p_value, ci_lower, ci_upper, cohens_d, chunk_scores |
ChunkOptimizationResult |
best_size, chi2, p_value, strategy_results |
ValidationReport |
shapiro_stat, shapiro_p, ks_stat, ks_p, outlier_count, total_chunks, distribution_normal, warnings |
Chunk |
id (SHA-256 16c), text, metadata, source, page, section |
local-knowledge-trainer/
├── .gitignore
├── README.md
├── pyproject.toml
├── src/local_knowledge_trainer/
│ ├── __init__.py # exporta KnowledgeTrainer
│ ├── core/
│ │ ├── trainer.py # KnowledgeTrainer — orquestrador principal
│ │ └── types.py # Dataclasses: Chunk, IngestResult, QueryResponse, ...
│ ├── parser/
│ │ └── parser.py # DocumentParser — PDF, MD, TXT, PY, Java, SQL
│ ├── knowledge/
│ │ └── graph.py # KnowledgeGraphBuilder — spaCy NER + networkx
│ ├── rag/
│ │ ├── vector_store.py # VectorStore — FAISS HNSW / ChromaDB / numpy fallback
│ │ └── engine.py # RAGEngine — BM25 + Dense + RRF
│ ├── validation/
│ │ └── hypothesis.py # HypothesisValidator — scipy.stats + Bootstrap
│ └── api/ # placeholder para API REST futura
└── tests/
├── test_parser.py # 7 testes
├── test_validation.py # 11 testes
├── test_trainer.py # 8 testes
└── test_research_features.py # 8 testes (Bootstrap, Contradição, VERA)
# Instalar dependências de dev
pip install -e ".[dev]"
# Todos os testes
pytest tests/ -v
# Por módulo
pytest tests/test_validation.py -v # testes scipy.stats
pytest tests/test_research_features.py -v # Bootstrap, Contradição, VERA
pytest tests/test_parser.py -v # parser multi-formato
pytest tests/test_trainer.py -v # pipeline completoSaída esperada:
tests/test_parser.py 7 passed
tests/test_validation.py 11 passed
tests/test_trainer.py 8 passed
tests/test_research_features.py 8 passed
============================== 34 passed ==============================
A camada de validação estatística é respaldada por 4 papers recentes:
| Paper | Veículo | Contribuição | Implementação |
|---|---|---|---|
| VERA (arXiv:2409.03759) | KDD 2024 | Bootstrap CI empírico + score multi-métrica (relevância, fidelidade, cobertura) | bootstrap_confidence_interval() + compute_multi_metric_score() |
| QuCo-RAG (arXiv:2512.19134) | ACL Findings 2026 | Co-ocorrência de entidades para detectar contradições e long-tail knowledge gaps | detect_contradiction() + register_entity_cooccurrence() |
| IndiaFinBench (arXiv:2604.19298) | 2026 | Bootstrap permutation 10K para significância robusta em dados não-normais | bootstrap_pvalue() |
| AI Teaching Assistant (arXiv:2604.04670) | IEEE SP 2026 | ANOVA + Bonferroni para validação de impacto de ferramentas RAG em domínios educacionais | test_topic_conflict() + bonferroni_correct() |
┌──────────────────────────────────────────────────────────────┐
│ VALIDAÇÃO ESTATÍSTICA DO LOCAL KNOWLEDGE TRAINER │
│ │
│ Bootstrap CI ──────────────► VERA (KDD 2024) │
│ Multi-Metric Score ────────► VERA (KDD 2024) │
│ Bootstrap Permutation ─────► IndiaFinBench (2026) │
│ Contradiction Detection ───► QuCo-RAG (ACL 2026) │
│ Entity Co-occurrence ──────► QuCo-RAG (ACL 2026) │
│ ANOVA + Bonferroni ────────► IEEE SP (2026) │
│ Shapiro-Wilk + KS ─────────► Validação clássica + VERA │
└──────────────────────────────────────────────────────────────┘
Dado um conjunto de n chunks recuperados com similaridades s₁, s₂, ..., sₙ:
μ = (1/n) Σ sᵢ # média amostral
σ = √(Σ(sᵢ - μ)²/(n-1)) # desvio padrão amostral
SE = σ / √n # erro padrão
z = (μ - μ_global) / (σ_global / √n)
p = 2 × (1 - Φ(|z|)) # onde Φ é a CDF da normal padrão
confiança = 1 - p
Para i = 1 até B (padrão: 10.000):
amostra* ← reamostragem com reposição de tamanho n
média*[i] ← média(amostra*)
CI inferior = percentil(α/2, médias*)
CI superior = percentil(1-α/2, médias*)
d = (μ₁ - μ₂) / s_pooled
s_pooled = √(((n₁-1)s₁² + (n₂-1)s₂²) / (n₁+n₂-2))
Interpretação: |d| < 0.2 desprezível | 0.2–0.5 pequeno | 0.5–0.8 médio | > 0.8 grande
RRF(chunk) = Σ_{lista ∈ {dense, sparse}} 1 / (k + rank(chunk, lista))
k = 60 (suavização para penalizar menos os itens mais baixos)
| Funcionalidade | Local Knowledge Trainer | LangChain | LlamaIndex | Haystack |
|---|---|---|---|---|
| Parser multi-formato | ✅ PDF, MD, PY, Java, SQL | ✅ | ✅ | ✅ |
| Knowledge Graph | ✅ spaCy NER + export RDF/GraphML | ❌ | Parcial | ❌ |
| Validação estatística | ✅ 9 testes scipy | ❌ | ❌ | ❌ |
| Bootstrap CI | ✅ VERA (10K amostras) | ❌ | ❌ | ❌ |
| Detecção de contradições | ✅ QuCo-RAG | ❌ | ❌ | ❌ |
| Confiança na resposta | ✅ IC 95% + p-value + Cohen's d | ❌ | ❌ | ❌ |
| Otimização de chunking | ✅ χ² estatístico | ❌ | ❌ | ❌ |
| Funciona 100% offline | ✅ | ✅ | ✅ | ✅ |
| LLM plugável | ✅ qualquer (str) -> str |
✅ | ✅ | ✅ |
| Fase | Status | Funcionalidades |
|---|---|---|
| Fase 1 — MVP | ✅ Entregue | Parser PDF/TXT, embeddings, FAISS, RAG simples, T-test + Shapiro-Wilk |
| Fase 2 — Validação | ✅ Atual | Knowledge Graph, RAG híbrido (BM25+Dense+RRF), KS + ANOVA + Chi² + Bonferroni + Bootstrap CI + Contradição |
| Fase 2.5 — Robustez | 🔲 Q3 2026 | Cross-Encoder real, Optuna para hyperparameter tuning, cache de validação, dashboard Streamlit |
| Fase 3 — Maturidade | 🔲 Q4 2026 | CUDA para estatística (CuPy), multi-idioma unificado no KG, exportação ONNX, benchmark suite |
Licença MIT.
@software{local_knowledge_trainer_2026,
title = {Local Knowledge Trainer: Plataforma RAG com Validação Estatística},
year = {2026},
url = {https://github.com/IA-PlayGround/local-knowledge-trainer}
}