04-04-02 — HyDE, parent-document retriever, self-query
TL;DR
Três técnicas que melhoram retrieval sem mudar o vector store: HyDE usa o LLM para gerar um documento hipotético antes de buscar (melhora o embedding da query); parent-document retriever indexa chunks pequenos mas retorna o documento pai completo (precisão na busca + contexto na resposta); self-query faz o LLM traduzir a pergunta em filtros estruturados + busca semântica (queries com critérios explícitos).
O problema: queries são curtas, documentos são longos
Há um mismatch natural no RAG: a query do usuário é curta ("quais são as penalidades por atraso?") enquanto os documentos indexados são parágrafos detalhados. Embeddings de textos curtos ficam num espaço diferente dos embeddings de textos longos — isso reduz a similaridade mesmo quando o conteúdo é relevante.
As três técnicas abaixo atacam esse problema de ângulos diferentes.
1. HyDE — Hypothetical Document Embeddings
Ideia: em vez de buscar pelo embedding da query curta, peça ao LLM para gerar um documento hipotético que responderia à query. Esse documento hipotético tem o mesmo formato e densidade dos documentos indexados — logo, seu embedding fica mais próximo dos chunks relevantes.
from openai import AzureOpenAI
client = AzureOpenAI(azure_endpoint="...", api_key="...", api_version="2024-02-01")
def hyde_retrieve(query: str, vector_store, k: int = 5) -> list:
# 1. Gerar documento hipotético
hyde_prompt = f"""Escreva um trecho de documento técnico-jurídico que responderia
diretamente à seguinte pergunta. Seja específico e use linguagem formal.
Pergunta: {query}
Trecho (2-3 parágrafos):"""
response = client.chat.completions.create(
model="gpt-4o-mini", # modelo barato para geração hipotética
messages=[{"role": "user", "content": hyde_prompt}],
max_tokens=300
)
hypothetical_doc = response.choices[0].message.content
# 2. Embeddar o documento hipotético (não a query original)
hyp_embedding = embed(hypothetical_doc)
# 3. Buscar por similaridade com o documento hipotético
return vector_store.similarity_search(hyp_embedding, k=k)
HyDE funciona melhor em domínios especializados onde a query do usuário é informal mas os documentos são formais (ex: jurídico, médico, técnico). Se o usuário pergunta em linguagem coloquial e os documentos usam jargão técnico, HyDE pode aumentar o retrieval recall em 10-20%. Contra-indicado quando o LLM não conhece bem o domínio — pode gerar hipóteses que atrapalham.
2. Parent-Document Retriever
Ideia: indexe chunks pequenos (melhor para encontrar a informação exata) mas retorne o documento pai maior (melhor para o LLM entender o contexto). Dois índices: um de chunks pequenos para busca, um de chunks grandes (pais) para leitura.
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
# Splitter para chunks PEQUENOS (busca)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
# Splitter para chunks GRANDES (contexto entregue ao LLM)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
# Store para chunks pais
docstore = InMemoryStore()
# Vector store para chunks filhos
vectorstore = Chroma(embedding_function=embeddings)
retriever = ParentDocumentRetriever(
vectorstore=vectorstore, # busca nos pequenos
docstore=docstore, # retorna os grandes
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
# Indexar documentos
retriever.add_documents(documents)
# Na hora da query: busca no índice de chunks pequenos, retorna pais grandes
relevant_parents = retriever.invoke("penalidades por atraso na entrega")
É como usar o índice remissivo de um livro (chunks pequenos com referência precisa) para depois ler o capítulo completo (documento pai). Você encontra a agulha com precisão, mas lê o contexto da agulha no palheiro.
3. Self-Query Retriever
Ideia: o LLM analisa a pergunta e gera automaticamente tanto a query semântica quanto filtros estruturados de metadados. Isso elimina recuperação irrelevante quando a pergunta tem critérios explícitos.
Exemplo: "contratos da Vale assinados em 2023 sobre penalidades" → o LLM gera:
- Query semântica: "penalidades contratuais"
- Filtros:
{"client": "Vale", "year": 2023, "doc_type": "contrato"}
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
from langchain_openai import AzureChatOpenAI
# Descrever os metadados disponíveis no índice
metadata_field_info = [
AttributeInfo(name="client", description="Nome do cliente (ex: Vale, TIM, Michelin)", type="string"),
AttributeInfo(name="year", description="Ano de assinatura do contrato", type="integer"),
AttributeInfo(name="doc_type", description="Tipo do documento: contrato, aditivo, proposta", type="string"),
AttributeInfo(name="value", description="Valor total do contrato em reais", type="float"),
]
document_content_description = "Contratos e documentos jurídicos da empresa"
llm = AzureChatOpenAI(azure_deployment="gpt-4o", ...)
retriever = SelfQueryRetriever.from_llm(
llm=llm,
vectorstore=vectorstore,
document_contents=document_content_description,
metadata_field_info=metadata_field_info,
verbose=True # mostra os filtros gerados — útil para debug
)
# O LLM vai gerar query + filtros automaticamente
results = retriever.invoke("contratos da Vale acima de 1 milhão assinados em 2023")
Self-query só funciona se você tiver metadados estruturados no seu vector store. Se os documentos foram indexados sem metadados (ou com metadados inconsistentes), a técnica não ajuda. Invista em metadados na fase de indexação — é a fundação de muitas otimizações avançadas.
Quando usar qual técnica
| Técnica | Problema que resolve | Complexidade | Overhead de latência |
|---|---|---|---|
| HyDE | Queries informais vs documentos formais | Baixa | +1 chamada LLM |
| Parent-doc | Chunk pequeno perde contexto | Média | Mínimo |
| Self-query | Queries com critérios explícitos | Média | +1 chamada LLM |
Como isso se conecta
- 04-04-03 — Contextual retrieval — outra técnica que melhora a qualidade dos chunks
- 04-03-01 — Chunking — parent-doc é uma extensão natural de chunking hierárquico
- 04-05-01 — Métricas de RAG — como medir o ganho de cada técnica
Fontes
- Gao, L. et al. (2022). Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE). arxiv.org/abs/2212.10496
- LangChain. Parent Document Retriever. python.langchain.com
- LangChain. Self Query Retriever. python.langchain.com
- LlamaIndex. Advanced Retrieval Strategies. docs.llamaindex.ai