Download docs/METRICS.md from Madras1/AetherMap: direct link, hf CLI and curl.
- Browser
- Download file 30.1 kB
-
https://huggingface.co/spaces/Madras1/AetherMap/resolve/main/docs/METRICS.md
- Command line
-
hf download hf://spaces/Madras1/AetherMap/docs/METRICS.md
-
curl -L -o METRICS.md https://huggingface.co/spaces/Madras1/AetherMap/resolve/main/docs/METRICS.md
AetherMap - Métricas, Arquitetura e Avaliação do RAG
Atualizado em: 2026-08-22
Snapshot principal dos benchmarks: 2026-05-11
Este documento é a fonte principal de métricas do AetherMap. Ele descreve a stack, a arquitetura do RAG, os modos de ablação, os benchmarks executados contra o Space e a leitura crítica dos resultados. Os números abaixo são um snapshot histórico, não medições contínuas da versão atualmente publicada.
1. O que é o AetherMap
O AetherMap é uma aplicação de cartografia semântica + RAG híbrido. Ele recebe um corpus em TXT/CSV, transforma os textos em embeddings, cria um mapa 3D navegável, agrupa documentos por similaridade, detecta duplicatas e responde perguntas com citações para os documentos recuperados.
O projeto tem duas frentes principais:
| Frente | Entrega |
|---|---|
| Mapa semântico | Visualização 3D, clusters, top termos, riqueza lexical, entropia e duplicatas |
| RAG híbrido | Busca semântica + busca lexical + reranking + resposta LLM com citações |
- Backend principal:
app.py - Frontend:
frontend/ - Experimentos e benchmarks:
metrics/
2. Stack de tecnologia
Backend e API
| Tecnologia | Uso no projeto |
|---|---|
| Python | Linguagem principal do backend e dos scripts de avaliação |
| FastAPI | API HTTP para upload, processamento, busca RAG, busca web e grafo de entidades |
| Uvicorn | Servidor ASGI para execução da API |
| python-multipart | Upload de arquivos via formulário |
| OpenAI SDK | Cliente OpenAI-compatible para chamar OpenRouter |
| Tavily | Busca web opcional em /search_web/ |
Machine Learning, NLP e busca
| Tecnologia | Uso no projeto |
|---|---|
| SentenceTransformers | Runtime para carregar o modelo de embeddings e gerar vetores de corpus/query |
Madras1/minilm-gooaq-mnr-v5 |
Embedding fine-tunado por Gabriel Yogi no GooAQ, usado como recuperador semântico customizado do AetherMap |
| CrossEncoder | Reranking dos documentos candidatos |
mmarco-mMiniLMv2-L12-H384-v1 |
Reranker multilíngue treinado para recuperação |
| FAISS | Índice vetorial para busca semântica por produto interno |
| BM25 customizado | Busca lexical sem dependência externa de rank_bm25 |
| RRF | Fusão de rankings FAISS e BM25 |
| UMAP | Projeção 3D semântica mais rica |
| PCA | Projeção 3D rápida em fast_mode |
| HDBSCAN | Clustering por densidade, sem número fixo de clusters |
| scikit-learn | TF-IDF, CountVectorizer, StandardScaler, PCA |
NLTK + stopwords.txt |
Stopwords PT/EN e stopwords customizadas |
| spaCy | NER para grafo de entidades em PT/EN |
| langdetect | Detecção de idioma para escolher modelo spaCy |
Observabilidade e avaliação
| Tecnologia | Uso no projeto |
|---|---|
| Prometheus FastAPI Instrumentator | Métricas HTTP automáticas |
| prometheus-client | Histogramas de latência do LLM, busca total e etapas internas por modo de ablação |
| Hugging Face Datasets | Carregamento de SQuAD-PT nos benchmarks |
| requests | Execução dos benchmarks contra o Space |
| Mistral API | LLM-as-judge externo para avaliar respostas geradas |
| JSON/Markdown | Persistência dos resultados brutos e relatórios |
Frontend
| Tecnologia | Uso no projeto |
|---|---|
| HTML/CSS/JavaScript | Interface do usuário |
| Plotly/Three.js | Visualização 3D e exploração semântica |
| Chart.js | Gráficos auxiliares de métricas |
3. Arquitetura do RAG
O RAG do AetherMap é um pipeline em camadas:
Camada 1 - Ingestão
TXT/CSV
-> extração dos textos
-> seleção de coluna textual
Camada 2 - Representação
textos
-> embeddings com modelo fine-tuned para RAG
-> normalização dos vetores
-> índices FAISS e BM25
Camada 3 - Exploração semântica
embeddings
-> UMAP ou PCA para 3D
-> HDBSCAN para clusters
-> TF-IDF por corpus e por cluster
-> duplicatas exatas e semânticas
Camada 4 - Retrieval
query
-> embedding da query
-> busca FAISS
-> busca BM25
-> fusão RRF
Camada 5 - Reranking
candidatos recuperados
-> CrossEncoder(query, documento)
-> top documentos finais
Camada 6 - Geração
contexto com [ID: n]
-> prompt com regra de citação, honestidade e idioma
-> LLM via OpenRouter
-> resposta final citada
Por que o RAG é híbrido
| Recuperador | Força | Fraqueza |
|---|---|---|
| FAISS/embeddings | Sinônimos, paráfrases e similaridade semântica | Pode errar nomes próprios, códigos e termos exatos |
| BM25 | Termos raros, nomes, siglas e correspondência literal | Pode falhar com sinônimos e perguntas parafraseadas |
| RRF | Combina rankings sem depender de escala comum de score | Depende da qualidade dos candidatos iniciais |
| CrossEncoder | Reordena olhando o par query-documento | Custa mais latência que retrieval puro |
A arquitetura favorece recall amplo primeiro, precisão no topo depois. FAISS e BM25 trazem candidatos por sinais diferentes; RRF junta os rankings; o CrossEncoder decide a ordem final dos melhores documentos.
Query expansion
O modo full ativa a expansão de query via LLM antes da recuperação. A ideia é
aumentar o recall, mas ela tem custo: aumenta a latência e pode introduzir ruído.
No snapshot principal, full empatou com hybrid_rerank em qualidade e foi mais
lento; portanto, a recomendação operacional é usar hybrid_rerank.
4. Endpoints principais
| Endpoint | Papel |
|---|---|
GET / |
Verificação de integridade simples |
POST /csv_columns/ |
Lista colunas de um CSV antes do processamento |
POST /process/ |
Processa corpus, gera embeddings, clusters, índices e métricas |
POST /search/ |
Executa RAG com ablation_mode configurável |
POST /describe_clusters/ |
Usa LLM para descrever clusters |
POST /search_web/ |
Busca web via Tavily e cria corpus a partir dos resultados |
POST /entity_graph/ |
Extrai entidades e cria grafo |
POST /analyze_graph/ |
Análise LLM do grafo de entidades |
GET /metrics |
Métricas Prometheus |
5. Modos de ablação
| Modo | Componentes ativos | Uso recomendado |
|---|---|---|
faiss_only |
FAISS | Baseline semântico |
bm25_only |
BM25 | Baseline lexical |
hybrid |
FAISS + BM25 + RRF | Modo rápido com recuperação híbrida |
hybrid_rerank |
FAISS + BM25 + RRF + CrossEncoder | Melhor compromisso atual entre qualidade e latência |
full |
Hybrid rerank + query expansion | Teto experimental de qualidade, mais lento |
Configuração atual de busca:
| Parâmetro | Normal | turbo_mode=true |
|---|---|---|
top_k_retrieval |
até 100 candidatos |
até 30 candidatos |
final_top_k |
10 documentos |
5 documentos |
RRF k |
60 |
60 |
| Query expansion | apenas em full |
desativada |
6. Métricas usadas
As métricas foram separadas em retrieval, geração, citação, abstenção e latência. Isso evita conclusões erradas: um sistema pode recuperar o documento certo e gerar mal, ou recuperar mal e ainda parecer plausível.
| Área | Métricas |
|---|---|
| Retrieval | Hit@1, Hit@3, Hit@5, Hit@10, MRR |
| Geração literal | Generation success, Strict answer contains gold |
| Geração semântica | Semantic answer quality, Correctness pass rate |
| Citação | Citation valid, Citation supports gold doc |
| Abstenção | Refusal accuracy |
| LLM-as-judge | correctness, faithfulness, citation_quality, refusal_quality, verdict_pass_rate |
| Latência | média, mediana, p95, llm_api_latency_seconds |
| Corpus | documentos, clusters, ruído, riqueza lexical, entropia, duplicatas |
Definições importantes:
| Métrica | Definição |
|---|---|
Hit@k |
Documento correto apareceu entre os k primeiros resultados |
MRR |
Média de 1/rank do primeiro documento correto |
Generation success |
A resposta foi gerada, sem cair no fallback |
Strict answer contains gold |
A resposta contém literalmente a resposta esperada após normalização |
Semantic answer quality |
Correctness avg / 5.0 no juiz Mistral; aproxima qualidade semântica da resposta |
Correctness pass rate |
Proporção de respostas com correctness >= 4 no juiz Mistral |
Citation valid |
Toda citação [ID: n] aponta para um resultado existente |
Citation supports gold doc |
A citação aponta para o documento correto |
Refusal accuracy |
O sistema recusa quando o tópico correto não foi fornecido ao corpus |
7. Observabilidade implementada
O backend expõe /metrics via Prometheus FastAPI Instrumentator:
curl https://madras1-aethermap.hf.space/metrics
Além das métricas HTTP automáticas, o backend agora expõe métricas customizadas
para latência do LLM e para as etapas internas do /search/.
| Métrica | Tipo | Mede |
|---|---|---|
llm_api_latency_seconds |
Histograma | Tempo da chamada externa ao LLM |
aethermap_search_total_latency_seconds |
Histograma | Latência total do endpoint /search/, por ablation_mode |
aethermap_search_stage_latency_seconds |
Histograma | Latência por etapa interna, com os rótulos stage e ablation_mode |
Etapas instrumentadas em aethermap_search_stage_latency_seconds:
| Stage | O que mede |
|---|---|
model_load |
Carregamento de modelos sob demanda |
query_expansion |
Expansão de query do modo full |
query_embedding |
Embedding da pergunta |
faiss_search |
Busca vetorial no índice FAISS |
bm25_search |
Busca lexical BM25 |
rank_fusion |
Fusão RRF |
candidate_prep |
Preparação dos documentos candidatos |
rerank |
CrossEncoder reranking |
prompt_assembly |
Montagem do contexto e prompt |
llm_call |
Chamada externa ao LLM |
O endpoint /search/ também devolve timings_ms na resposta JSON. Isso foi
usado para medir o gargalo real do Space sem instalar FAISS localmente.
Buckets do histograma legado do LLM:
0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0 segundos
8. Benchmark principal: SQuAD-PT estrito
Este é o benchmark principal para portfólio porque está em português, usa um dataset real e mede o caminho completo da API do Space: processamento, recuperação e resposta LLM citada.
| Item | Valor |
|---|---|
| Dataset | nunorc/squad_v1_pt |
| Split | validation |
| API medida | https://madras1-aethermap.hf.space |
| Embedding no Space | Madras1/minilm-gooaq-mnr-v5 |
| Contextos enviados | 120 |
| Perguntas respondíveis | 40 |
| Perguntas controladas sem resposta | 10 |
| Total por modo | 50 queries |
| Total de buscas | 250 |
| Modos testados | faiss_only, bm25_only, hybrid, hybrid_rerank, full |
| Script | metrics/run_aethermap_squad_llm_metrics.py |
| Resultado bruto | metrics/results/squad_llm_metrics_squadpt-50q-strict-specific-fullanswer-minilm-gooaq-mnr-v5_20260511T025206Z.json |
Como o SQuAD-PT não possui exemplos nativos sem resposta, essas perguntas foram criadas de forma controlada. Na versão final do benchmark, os controles sem resposta usam três filtros:
| Filtro | Motivo |
|---|---|
source_title ausente do corpus |
Evita vazamento por outros trechos do mesmo artigo |
| título específico, não tópico genérico | Evita controles amplos como Construction, que podem ser parcialmente respondidos por documentos relacionados |
| pergunta com âncora nominal | Prefere perguntas com entidades como BSkyB, Sky, Virgin ou Microsoft |
Na rodada final, os controles sem resposta vieram de Sky_(United_Kingdom),
título ausente do corpus enviado.
Resultados automáticos
| Modo | Hit@1 | Hit@3 | MRR | Generation success | Strict answer contains gold | Citation valid | Citation supports gold doc | Refusal accuracy | Mean latency | p95 latency |
|---|---|---|---|---|---|---|---|---|---|---|
faiss_only |
0.57 | 0.80 | 0.69 | 1.00 | 0.60 | 0.88 | 0.88 | 1.00 | 9.31s | 24.18s |
bm25_only |
0.80 | 0.82 | 0.83 | 1.00 | 0.72 | 0.93 | 0.93 | 1.00 | 10.30s | 26.94s |
hybrid |
0.72 | 0.90 | 0.82 | 1.00 | 0.70 | 0.88 | 0.88 | 1.00 | 8.11s | 18.45s |
hybrid_rerank |
0.97 | 1.00 | 0.99 | 1.00 | 0.68 | 0.97 | 0.97 | 1.00 | 22.34s | 36.26s |
full |
0.97 | 1.00 | 0.99 | 1.00 | 0.70 | 1.00 | 1.00 | 1.00 | 24.82s | 35.61s |
Qualidade semântica da resposta
O Strict answer contains gold é uma métrica propositalmente dura: ela procura
a string do gabarito dentro da resposta. Isso é bom para detectar falhas óbvias,
mas subestima respostas corretas que parafraseiam, traduzem, flexionam ou
respondem com uma formulação equivalente. Por isso a métrica de portfólio para
qualidade de resposta deve ser a qualidade semântica julgada pelo Mistral, junto
com fidelidade e citação.
Artefato:
metrics/results/answer_quality_summary_squadpt_50q_strict_specific_20260511T191047Z.md
| Modo | Strict answer contains gold | Semantic answer quality | Mistral correctness avg | Correctness pass rate | Faithfulness avg | Verdict pass rate |
|---|---|---|---|---|---|---|
faiss_only |
0.60 | 0.92 | 4.60 | 0.88 | 4.82 | 0.90 |
bm25_only |
0.72 | 0.97 | 4.86 | 0.97 | 4.92 | 0.98 |
hybrid |
0.70 | 0.95 | 4.76 | 0.93 | 4.80 | 0.94 |
hybrid_rerank |
0.68 | 0.98 | 4.88 | 0.97 | 4.98 | 0.98 |
full |
0.70 | 0.98 | 4.92 | 0.97 | 5.00 | 0.96 |
Leitura: o número literal 0.68 do hybrid_rerank não significa que o sistema
só acerta 68% das respostas. Ele significa que 68% das respostas continham
literalmente a string esperada do SQuAD-PT. Quando o julgamento considera
equivalência semântica, o mesmo modo chega a Semantic answer quality = 0.98,
Faithfulness avg = 4.98 e Verdict pass rate = 0.98.
Leitura técnica
O melhor modo medido é hybrid_rerank.
Ele empatou com full em retrieval (Hit@1 = 0.97, Hit@3 = 1.00,
MRR = 0.99, refusal 1.00), mas teve menor latência média
(22.34s contra 24.82s) e melhor pass rate no juiz Mistral. O full
continua útil como teto experimental, mas não é o modo recomendado para
portfólio porque a query expansion não trouxe ganho de retrieval nessa rodada.
O hybrid foi o modo rápido mais interessante: Hit@3 = 0.90,
MRR = 0.82, Refusal accuracy = 1.00 e p95 = 18.45s. Ele é bom para uma demonstração
rápida, mas o modo de portfólio deve ser hybrid_rerank, porque ele estabiliza
muito melhor o top 1.
Strict answer contains gold ficou abaixo das demais métricas porque é uma
métrica literal. Ela penaliza respostas semanticamente corretas que parafraseiam
o gabarito, mudam flexão, usam tradução alternativa ou incluem uma forma
equivalente da entidade. Por isso ela deve ser lida junto com citação e
LLM-as-judge, não isoladamente.
9. Avaliação LLM-as-judge com Mistral
Foi rodada uma avaliação LLM-as-judge usando a API da Mistral sobre a rodada
SQuAD-PT estrita. O juiz avaliou todos os cinco modos de ablação, com 50
respostas por modo (40 respondíveis e 10 controladas sem resposta),
totalizando 250 julgamentos.
Arquivos:
| Artefato | Caminho |
|---|---|
| Script | metrics/run_mistral_llm_judge.py |
| Resultado JSON | metrics/results/mistral_llm_judge_squadpt_50q_strict_specific_fullanswer_all_modes_20260511T025836Z.json |
| Relatório Markdown | metrics/results/mistral_llm_judge_squadpt_50q_strict_specific_fullanswer_all_modes_20260511T025836Z.md |
Correção metodológica importante: a versão anterior do juiz lia
summary_preview, truncado em 700 caracteres. Isso foi corrigido. O benchmark
agora salva generated_answer com a resposta completa, e o juiz usa
answer_text_source = generated_answer. Respostas muito longas ainda podem ser
recortadas apenas pelo limite de prompt do juiz (max_answer_chars = 6000), o
que não ocorreu de forma relevante nesta rodada.
| Modo | Julgados | Correctness avg | Faithfulness avg | Citation avg | Refusal avg | Verdict pass rate |
|---|---|---|---|---|---|---|
faiss_only |
50 | 4.60 | 4.82 | 4.64 | 5.00 | 0.90 |
bm25_only |
50 | 4.86 | 4.92 | 4.70 | 5.00 | 0.98 |
hybrid |
50 | 4.76 | 4.80 | 4.56 | 5.00 | 0.94 |
hybrid_rerank |
50 | 4.88 | 4.98 | 4.92 | 5.00 | 0.98 |
full |
50 | 4.92 | 5.00 | 4.94 | 5.00 | 0.96 |
Leitura: o juiz confirma que hybrid_rerank é o melhor modo de apresentação.
Ele empatou com o melhor pass rate (0.98), teve Faithfulness avg = 4.98,
Citation avg = 4.92 e manteve Refusal avg = 5.00. O full teve médias
ligeiramente maiores em correctness/faithfulness, mas menor pass rate (0.96)
e maior latência média.
Os casos reprovados restantes foram todos em perguntas respondíveis; nenhum modo falhou nos controles sem resposta:
| Modo | Casos reprovados | Padrão observado |
|---|---|---|
faiss_only |
5 | Recuperação/citação mais fraca e algumas recusas indevidas |
bm25_only |
1 | Recusa indevida em uma pergunta sobre Victoria/Koori |
hybrid |
3 | Recusas indevidas e uma citação parcial |
hybrid_rerank |
1 | Resposta parcial sobre oxigênio livre |
full |
2 | Respostas semanticamente válidas, mas parciais frente ao gabarito literal |
10. Investigação da precisão de recusa
A rodada ampliada anterior tinha Refusal accuracy = 0.83. A investigação
mostrou que isso era um artefato do benchmark, não uma conclusão limpa sobre o
sistema.
O problema era este: os exemplos controlados sem resposta vinham do título
Super_Bowl_50. O benchmark removia o contexto exato da pergunta, mas ainda
permitia que outros trechos do mesmo artigo entrassem no corpus. Assim, a
pergunta era marcada como "sem resposta", mas o retriever encontrava documentos
semanticamente próximos sobre Broncos, Panthers, MVP e evento do Super Bowl.
Caso que falhou na rodada antiga:
Qual time da NFL representou o AFC no Super Bowl 50?
O sistema recuperava trechos sobre Denver Broncos e Super Bowl 50. O texto citado não dizia literalmente "representou a AFC", mas o LLM completava a lacuna por inferência/conhecimento externo. Havia dois problemas:
| Problema | Efeito |
|---|---|
| Controle sem resposta por contexto, não por título | O corpus ainda tinha documentos do mesmo tema |
| Prompt permissivo a inferência indireta | O LLM respondia a partir de pista parcial |
Correções aplicadas:
| Arquivo | Correção |
|---|---|
metrics/run_aethermap_squad_llm_metrics.py |
Controles sem resposta agora exigem que o source_title da pergunta esteja ausente do corpus enviado |
app.py |
Prompt do RAG foi endurecido contra conhecimento externo, inferência indireta e citação apenas relacionada |
Depois disso, uma rodada com 200 contextos revelou um segundo problema: ao
aumentar demais o corpus, sobravam controles sem resposta vindos de
Construction, que é um tópico genérico. Mesmo removendo o título Construction,
outros documentos relacionados a prédios, reformas e infraestrutura podiam
sustentar respostas parciais. Isso não era um bom controle de abstenção.
A versão final do script filtra esses casos: controles sem resposta precisam
vir de títulos específicos e perguntas com âncora nominal. Na rodada final, os
controles vieram de Sky_(United_Kingdom), ausente do corpus. Resultado:
Refusal accuracy = 1.00 em todos os modos e Refusal avg = 5.00 no juiz
Mistral em todos os modos.
Observação: essas medições foram feitas contra o Space em 2026-05-11. O app.py
local também foi endurecido para proibir conhecimento externo, inferência indireta
e vazamento de raciocínio interno em respostas finais. Em 2026-08-22, a configuração
local do build foi corrigida para fixar versões compatíveis do PyTorch e permitir
que o instalador resolva dependências no PyPI. Até a nova versão ser publicada e
os testes serem executados novamente, os números desta seção devem ser tratados
como um snapshot histórico, não como validação do commit atual.
11. Análise arquitetural: escala, RAM e Space
O AetherMap foi desenhado como um sistema de processamento de corpus inteiro antes da busca. Isso muda o comportamento do RAG: em vez de calcular tudo a cada pergunta, ele faz uma fase pesada de ingestão e depois consulta estruturas já materializadas.
Fluxo real:
/process/
CSV/TXT completo
-> extração da coluna textual
-> embeddings em batch
-> normalização vetorial
-> índice FAISS
-> índice BM25
-> mapa 3D
-> clusters HDBSCAN
-> TF-IDF global/por cluster
-> detecção de duplicatas
-> cache por job_id
/search/
query
-> consulta o job_id já indexado
-> recupera candidatos
-> funde rankings
-> rerankeia
-> envia apenas top documentos ao LLM
Por isso a arquitetura aguenta melhor datasets reais do que uma demo que faz retrieval improvisado a cada pergunta:
| Mecanismo | Por que ajuda |
|---|---|
Indexação uma vez por job_id |
O custo pesado fica no /process/; várias perguntas reaproveitam embeddings, FAISS, BM25 e DataFrame |
| Embeddings em batch | Vetoriza o corpus de forma mais eficiente do que documento a documento |
| FAISS com vetores normalizados | Busca semântica sobre índice pronto por produto interno/cosseno |
| Troca para HNSW em corpus grande | Acima de FAISS_HNSW_MIN_SIZE, usa IndexHNSWFlat, reduzindo custo de busca em bases maiores |
| Reranker sobre os principais candidatos | O CrossEncoder não vê o corpus inteiro; ele reordena até 100 candidatos recuperados |
Trade-offs atuais da demo no Hugging Face Space:
| Trade-off | Leitura técnica |
|---|---|
| Cache em RAM | Decisão adequada para demo em Space: evita depender de armazenamento persistente |
| Sem persistência de jobs/índices | Escolha pragmática, porque o armazenamento local do Space não deve ser tratado como banco durável |
| BM25 custom atual | Percorre documentos para pontuar; em corpus muito grande pode virar gargalo por query |
plot_data retorna todos os textos |
Bom para demo visual imediata, mas respostas de /process/ podem ficar pesadas com datasets inteiros |
UMAP fora do fast_mode |
Pode ficar caro em datasets grandes; fast_mode com PCA é o caminho prático |
Query expansion do full |
Aumenta latência e, nas medições, não melhorou qualidade sobre hybrid_rerank |
Perfil de latência do /search/ no Space
Para investigar a latência sem instalar FAISS localmente, foi criado um
profiler que usa a própria API hospedada no Hugging Face Space. O script envia
o corpus pelo /process/, executa buscas no /search/ e coleta o campo
timings_ms devolvido pelo backend instrumentado.
Artefatos:
| Artefato | Caminho |
|---|---|
| Script | metrics/profile_space_search_timings.py |
| Resultado JSON | metrics/results/space_search_timing_profile_squadpt_5q_20260511T204758Z.json |
| Relatório Markdown | metrics/results/space_search_timing_profile_squadpt_5q_20260511T204758Z.md |
Estas medições incluem efeitos reais do Space: CPU compartilhada, poucos workers/threads, fila, rede e chamada externa ao LLM. Elas descrevem a latência da demo pública, não uma implantação de produção otimizada.
Latência ponta a ponta do endpoint:
| Modo | Queries | Mean | Median | p95 |
|---|---|---|---|---|
faiss_only |
5 | 10.62s | 10.97s | 16.45s |
bm25_only |
5 | 5.19s | 3.00s | 11.17s |
hybrid |
5 | 4.82s | 3.66s | 8.11s |
hybrid_rerank |
5 | 16.47s | 15.24s | 22.48s |
full |
5 | 18.94s | 18.47s | 25.73s |
Média das etapas internas mais relevantes:
| Modo | Query embedding | FAISS | BM25 | RRF | Rerank | Query expansion | LLM call | Total backend |
|---|---|---|---|---|---|---|---|---|
faiss_only |
12.29ms | 0.28ms | 0.00ms | 0.02ms | 0.10ms | 0.00ms | 10223.82ms | 10240.65ms |
bm25_only |
15.63ms | 0.00ms | 2.90ms | 0.02ms | 0.10ms | 0.00ms | 4999.80ms | 5022.49ms |
hybrid |
13.95ms | 0.30ms | 2.69ms | 0.17ms | 0.10ms | 0.00ms | 4639.20ms | 4660.91ms |
hybrid_rerank |
13.56ms | 0.29ms | 2.41ms | 0.15ms | 8018.13ms | 0.00ms | 8076.30ms | 16115.11ms |
full |
24.20ms | 0.29ms | 2.81ms | 0.14ms | 7937.09ms | 3252.05ms | 7552.86ms | 18773.65ms |
Leitura técnica: no corpus medido (120 documentos), FAISS e BM25 são baratos.
FAISS fica abaixo de 0.4ms; BM25 fica em torno de 2.4ms a 2.9ms; RRF fica
abaixo de 0.2ms. Portanto, a latência alta do portfólio não vem do retrieval
base. O custo vem principalmente de três pontos:
| Gargalo | Evidência |
|---|---|
| Chamada externa ao LLM | De 4.6s a 10.2s de média nos modos sem rerank/full |
| CrossEncoder reranking | Aproximadamente 8.0s em hybrid_rerank e full |
| Query expansion | Aproximadamente 3.25s adicionais no full |
O BM25 customizado merece ser substituído em um corpus grande, mas ele não é o gargalo desta
demonstração. A decisão correta é manter a medição por etapa e revisitar o BM25 quando
o corpus passar para milhares/dezenas de milhares de documentos ou quando o
perfil mostrar bm25_search como componente dominante.
Portanto, o AetherMap já é mais do que um protótipo simples: ele tem arquitetura de
RAG de corpus real, com ingestão, indexação, cache por job, recuperação híbrida,
reranking e resposta citada. No formato atual, ele está corretamente otimizado
para uma demo interativa no Space. Para produção fora do Space, os próximos
passos seriam persistir índices/jobs em armazenamento durável, paginar plot_data,
otimizar reranking/LLM e trocar o BM25 manual por uma estrutura lexical mais
eficiente quando a escala justificar.
12. Ablação do embedding: customizado com GooAQ vs. all-MiniLM-L6-v2
Foi rodada uma comparação A/B com o mesmo dataset, mesma seed, mesmas queries e
mesmos modos. O baseline foi sentence-transformers/all-MiniLM-L6-v2; a rodada
customizada usa o embedding fine-tunado no GooAQ.
Arquivo de comparação:
metrics/results/embedding_ablation_custom_vs_minilm.md
Resumo dos deltas do embedding customizado contra o baseline:
| Modo | Delta Hit@1 | Delta Hit@3 | Delta MRR | Delta citation support | Delta p95 latency |
|---|---|---|---|---|---|
faiss_only |
+0.125 | +0.125 | +0.135 | +0.000 | +6.097s |
bm25_only |
+0.000 | +0.000 | +0.000 | +0.000 | +8.452s |
hybrid |
+0.125 | +0.000 | +0.083 | +0.000 | +4.684s |
hybrid_rerank |
+0.000 | +0.000 | +0.000 | +0.000 | +3.887s |
full |
+0.000 | +0.000 | +0.000 | +0.000 | +6.908s |
Leitura: nesse benchmark controlado pequeno, o embedding customizado melhorou
os modos em que o recuperador semântico aparece com menos compensação do
reranker, especialmente faiss_only e hybrid. Nos modos com CrossEncoder
(hybrid_rerank e full), a qualidade empatou porque o reranker corrigiu o
topo do ranking nos dois casos.
O ganho não deve ser generalizado para qualquer dataset. Em uma rodada pequena
separada sobre SQuAD em inglês, o baseline foi melhor no faiss_only, enquanto
os modos com reranker empataram em recuperação. A conclusão sustentada é que o
projeto implementa um embedding próprio e uma medição A/B capaz de revelar onde
ele ajuda ou regride. Para afirmar superioridade por domínio, a comparação deve
ser repetida de forma pareada em SQuAD-PT com 50 a 100 perguntas por modo.
13. Nota sobre benchmark controlado antigo
Existe um benchmark sintético pequeno em metrics/run_aethermap_portfolio_metrics.py.
Ele não é usado como evidência principal neste documento, porque a rodada antiga
misturava um dataset toy com comportamento de geração/fallback do full que não
representa o benchmark real em SQuAD-PT. Para apresentação pública, a evidência
principal deve ser a rodada SQuAD-PT estrita com 50 queries por modo e
LLM-as-judge sobre resposta completa.
O benchmark controlado continua útil como teste de sanidade do harness, mas deve ser executado novamente depois que a correção do build e o prompt endurecido forem publicados no Space.
14. Como reproduzir
Benchmark principal em português, modo estrito:
python metrics/run_aethermap_squad_llm_metrics.py --dataset-id nunorc/squad_v1_pt --split validation --n-contexts 120 --n-answerable 40 --n-unanswerable 10 --modes faiss_only bm25_only hybrid hybrid_rerank full --run-label squadpt-50q-strict-specific-fullanswer --embedding-label minilm-gooaq-mnr-v5 --timeout 300 --pause 0.15
LLM-as-judge com Mistral:
python metrics/run_mistral_llm_judge.py --input-json metrics/results/squad_llm_metrics_squadpt-50q-strict-specific-fullanswer-minilm-gooaq-mnr-v5_20260511T025206Z.json --modes faiss_only bm25_only hybrid hybrid_rerank full --model mistral-small-latest --max-answer-chars 6000 --output-prefix metrics/results/mistral_llm_judge_squadpt_50q_strict_specific_fullanswer_all_modes
Resumo de qualidade semântica da resposta:
python metrics/summarize_answer_quality.py --benchmark-json metrics/results/squad_llm_metrics_squadpt-50q-strict-specific-fullanswer-minilm-gooaq-mnr-v5_20260511T025206Z.json --judge-json metrics/results/mistral_llm_judge_squadpt_50q_strict_specific_fullanswer_all_modes_20260511T025836Z.json --output-prefix metrics/results/answer_quality_summary_squadpt_50q_strict_specific
Perfil de latência por etapa no Space, sem instalar FAISS local:
python metrics/profile_space_search_timings.py --api-url https://madras1-aethermap.hf.space --contexts-csv metrics/results/squad_contexts_latest.csv --queries-json metrics/results/squad_queries_latest.json --modes faiss_only bm25_only hybrid hybrid_rerank full --max-queries 5 --timeout 300 --pause 0.15 --output-prefix metrics/results/space_search_timing_profile_squadpt_5q
Benchmark controlado:
python metrics/run_aethermap_portfolio_metrics.py
Ablação do embedding:
python metrics/compare_embedding_runs.py --baseline-json metrics/results/<baseline>.json --custom-json metrics/results/<custom>.json