|
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
30.1 kB
| # 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: | |
| ```text | |
| 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: | |
| ```bash | |
| 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: | |
| ```text | |
| 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: | |
| ```text | |
| 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: | |
| ```text | |
| 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: | |
| ```text | |
| /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: | |
| ```text | |
| 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: | |
| ```bash | |
| 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: | |
| ```bash | |
| 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: | |
| ```bash | |
| 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: | |
| ```bash | |
| 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: | |
| ```bash | |
| python metrics/run_aethermap_portfolio_metrics.py | |
| ``` | |
| Ablação do embedding: | |
| ```bash | |
| python metrics/compare_embedding_runs.py --baseline-json metrics/results/<baseline>.json --custom-json metrics/results/<custom>.json | |
| ``` | |