Apresentação
Design do RAG — studyAI
Especificação do sistema RAG: chunking, indexação, retrieval e uso por agente.
Índice
- Visão geral
- Chunking
- Indexação
- Retrieval
- Avaliação do RAG (groundedness)
- Melhorias avançadas
- Uso por agente
- Diagrama do pipeline
- Decisões e trade-offs
Visão geral
O RAG é o coração do studyAI. Todos os ficheiros carregados e documentos gerados são indexados para:
- Content Planner: Encontrar conteúdo relevante para planejar a estrutura
- Content Writer: Fundamentar a escrita em chunks recuperados
- RAG QA Agent: Responder perguntas com base no material
- Question Evaluator: (Opcional) Material de referência para comparação
- Question Generator: (Opcional) Contexto para gerar perguntas
Scope: Um índice por projeto. Cada projeto tem os seus chunks isolados.
Faseamento (MVP): Sprint 2 pode começar com dense-only para validar o pipeline. BM25 + RRF + cross-encoder adicionados assim que a base estiver estável (Sprint 2 fim ou Sprint 3).
Chunking
Estratégia
| Tipo de fonte | Estratégia | Tamanho | Overlap |
|---|---|---|---|
| Por parágrafo, agrupar até max | 500–1200 chars | 50–100 chars | |
| Markdown | Por header (##, ###) | 500–1200 chars | 50 chars |
| TXT | Por parágrafo | 500–1200 chars | 50 chars |
Regras
- Respeitar limites semânticos: Não cortar a meio de uma frase ou lista.
- Headers como separadores: Em MD, cada
##ou###pode iniciar novo chunk. - Overlap: Últimos N chars do chunk anterior no início do próximo (evitar perda de contexto).
- Metadata por chunk:
source,section,page(se PDF),char_start,char_end.
Exemplo (Markdown)
Input:
## Introdução
A colonoscopia é um exame...
## Técnicas
A polipectomia consiste em...
Output chunks:
- chunk_1: "## Introdução\nA colonoscopia é um exame..." (metadata: section=Introdução)
- chunk_2: "## Técnicas\nA polipectomia consiste em..." (metadata: section=Técnicas)
Indexação
Pipeline
Ficheiro/Documento
→ Parse (extrair texto)
→ Chunk (estratégia por tipo)
→ Embed (modelo de embeddings)
→ Store: Vector DB (dense) + índice lexical (BM25)
Índices necessários
| Índice | Uso | Implementação |
|---|---|---|
| Dense | Similarity search | pgvector, coluna embedding |
| Lexical (BM25) | Busca por palavras | PostgreSQL tsvector + GIN, ou Elasticsearch/Meilisearch |
Para PostgreSQL: coluna content_tsv tsvector com GIN(content_tsv). Atualizar com to_tsvector('portuguese', content).
Modelo de embeddings
- Modelo:
text-embedding-3-small(OpenAI) — 1536 dimensões - Alternativa:
text-embedding-3-largepara mais qualidade (3072 dims)
Metadata por chunk
| Campo | Tipo | Uso |
|---|---|---|
| project_id | UUID | Filtro obrigatório (isolamento) |
| source_type | string | file | document |
| source_id | UUID | file_id ou document_id |
| section | string | Secção do documento |
| subsection | string | Subsecção (opcional) |
| page | int | Página (PDF) |
| char_start | int | Posição inicial no texto |
| char_end | int | Posição final |
| content_tsv | tsvector | Índice lexical (BM25) — to_tsvector('portuguese', content) |
| created_at | timestamp | Ordem, debugging |
Quando indexar
- Upload de ficheiro: Após parse (job assíncrono)
- Documento gerado: Após Content Writer terminar
- Edição de documento: Re-indexar (ou indexar diff)
- Apagar ficheiro/documento: Remover chunks do índice
Retrieval
Estratégia: pipeline híbrido em três estágios
Com base em benchmarks e práticas de produção (2024–2025), a abordagem escolhida é o pipeline híbrido em três estágios: hybrid search → RRF → reranking com cross-encoder.
Passo 1 — Hybrid Search (gerar candidatos)
Executam-se duas buscas em paralelo com a mesma query:
| Tipo | Como funciona | Pontos fortes | Limitações |
|---|---|---|---|
| Dense (vetorial) | Query → embedding → similarity search nos chunks. Top-100. | Paráfrases, sinónimos, linguagem natural | Falha em exact match (IDs, códigos como ORA-00942, nomes técnicos) |
| BM25 (lexical) | TF-IDF: frequência das palavras da query nos docs. Top-100. | Exact match, IDs, códigos, negações ("não suportado") | Falha em sinónimos ou linguagem diferente do documento |
Resultado: Duas listas (ex: 100 dense + 100 BM25). Com fusão, ~120–150 candidatos únicos. Cobre semântico e lexical.
Implementação:
- Dense: pgvector, cosine similarity ou inner product. Filtro
project_id = X. - BM25: PostgreSQL full-text (tsvector/tsquery, ts_rank) ou Elasticsearch/Meilisearch para BM25 nativo.
Passo 2 — RRF (Reciprocal Rank Fusion)
As listas têm scores em escalas diferentes. RRF combina pelos ranks, não pelos scores:
- Cada documento recebe
score = 1/(k + posição)em cada lista. Ex: k=60, posição 1 → 1/61; posição 10 → 1/70. - Os scores das duas listas são somados. Um doc no top-5 de ambas fica com score alto.
- Ordenar por score total e deduplicar.
Resultado: Uma única lista ordenada, sem normalização de scores. Simples e robusto.
Parâmetro k: típico k=60. Ajustar se necessário.
Passo 3 — Reranking com cross-encoder
~120 candidatos, mas o LLM só precisa de 5–10. O cross-encoder avalia cada par (query, chunk) com um único forward pass — mais preciso que o bi-encoder da busca inicial:
- Input:
[query] [SEP] [chunk]concatenados. - Output: score de relevância (0–1).
- Ordenar por score e selecionar top-5 ou top-10.
Resultado: Chunks de alta precisão para o LLM. Elimina falsos positivos que passaram por BM25 ou dense.
Modelos sugeridos: BAAI/bge-reranker-v2-m3, cross-encoder/ms-marco-MiniLM-L-6-v2, ou equivalentes em português.
Parâmetros por agente
| Agente | Candidatos (RRF) | Final (após rerank) | Filtros adicionais |
|---|---|---|---|
| Content Planner | ~120–150 | 15–20 | — |
| Content Writer | ~120–150 | 8–10 | section (se planeado) |
| RAG QA Agent | ~120–150 | 8–10 | — |
| Question Evaluator | ~120–150 | 5 | document_id (opcional) |
| Question Generator | ~120–150 | 10 | document_id |
Filtros
- Obrigatório:
project_id = Xem todas as pesquisas (multi-tenant). - Opcionais:
source_type,section,document_id.
Avaliação do RAG
Métricas de avaliação
Para cada resposta RAG (chat, geração), avaliar e guardar:
| Métrica | Descrição | Onde guardar |
|---|---|---|
| grounded | Resposta fundamentada nos chunks? (sim/não) | rag_evaluations |
| groundedness_score | 0–1, score contínuo | rag_evaluations |
| answer_relevance | 0–1, relevância da resposta à pergunta | rag_evaluations |
| retrieval_recall | 0–1, chunks recuperados cobrem a pergunta? | rag_evaluations |
| hallucination_risk | 0–1, probabilidade de alucinação | rag_evaluations |
Implementação
- LangSmith evals: Dataset de perguntas → run eval → groundedness score
- LLM-as-judge: Prompt que compara resposta com chunks, devolve score
- Guardar: Tabela
rag_evaluations(ver DATA_MODEL.md)
Integração
- Amostragem: 10–20% das respostas de chat
- Ou: todas em staging, amostra em produção
- Alertas se hallucination_risk > 0.7
Melhorias avançadas
Query rewriting
Antes do retrieval, reescrever a query para melhor recall:
- "O que é X?" → "definição de X", "o que significa X"
- LLM ou regras simples
- Útil para perguntas vagas
Multi-query retrieval
- Gerar 2–3 variações da query
- Retrieval para cada uma
- Unir resultados (deduplicar, RRF)
- Melhora recall em perguntas ambíguas
Context compression
- Chunks recuperados podem ser longos
- Resumir ou extrair apenas frases relevantes antes de passar ao LLM
- Reduz tokens, mantém relevância
Embedding version
- Guardar
embedding_model_versionem metadata dos chunks - Permite re-indexar com novo modelo sem conflitos
- Útil para A/B de modelos
Uso por agente
Content Planner
Query: Tópico pedido (ex: "técnicas de colonoscopia")
Retrieval: Top-15 a 20 chunks relevantes para ter visão ampla
Uso: Analisar cobertura, propor estrutura, identificar lacunas
Content Writer
Query: Título da secção + contexto (ex: "Polipectomia - técnicas de remoção de pólipos")
Retrieval: Top-8 a 10 chunks, preferencialmente da secção planeada
Uso: Escrever Markdown fundamentado nos chunks. Instrução: "Não inventes; cita apenas o que está nos chunks."
RAG QA Agent
Query: Pergunta do utilizador (ex: "O que é uma colonoscopia?")
Retrieval: Top-8 a 10 chunks mais relevantes
Uso: Construir prompt com chunks como contexto. Resposta + citações (source, excerpt).
Question Evaluator
Query: Pergunta + resposta do user (opcional: para retrieval semântico)
Retrieval: Opcional. Se usar, top-5 chunks do documento da pergunta.
Uso: Comparar user_answer com solution e material. LLM faz a avaliação.
Question Generator
Query: Título do documento ou secção
Retrieval: Opcional. Pode usar o documento completo como contexto (já em memória).
Uso: Gerar perguntas variadas (recall, application, comparison).
Diagrama do pipeline
Indexação
A carregar diagrama…
Retrieval (3 estágios)
A carregar diagrama…
Decisões e trade-offs
| Decisão | Escolha | Trade-off |
|---|---|---|
| Retrieval | Hybrid (dense + BM25) + RRF + cross-encoder | Melhor recall e precisão; mais latência e custo que dense-only. |
| Chunk size | 500–1200 chars | Menor: mais preciso, mais chunks. Maior: mais contexto, menos granular. |
| Overlap | 50–100 chars | Reduz perda de contexto; aumenta redundância. |
| Candidatos | 100 dense + 100 BM25 → ~120 únicos | Cobre semântico e lexical. |
| Final top-k | 5–20 (por agente) | Mais chunks: mais contexto, mais tokens. |
| Cross-encoder | BGE-reranker ou similar | +50–100ms por query; ganho significativo em precisão. |
| Embedding model | text-embedding-3-small | Equilíbrio custo/qualidade. |
| Índice por projeto | Sim | Isolamento; project_id obrigatório. |
| Re-indexar ao editar | Sim | Consistência; custo de re-embed e re-index lexical. |
Zona de prática
Sem perguntas. Clica em Editar para adicionar.