04-04-02 — HyDE, parent-document retriever, self-query

⏱ 15 minFontes validadas em: 2026-04-29

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.

flowchart LR Q["Query: 'penalidades por atraso'"] --> LLM_H[LLM\ngera documento hipotético] LLM_H --> HD["'Em caso de atraso na entrega,\nincide multa de 0,5% ao dia\nsobre o valor do contrato...'"] HD --> EMB[Embedding do\ndocumento hipotético] EMB --> VS[(Vector Store)] VS --> CHUNKS[Chunks reais 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)
💡 Quando HyDE ajuda

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")
🔗 Analogia

É 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 requer metadados ricos

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écnicaProblema que resolveComplexidadeOverhead de latência
HyDEQueries informais vs documentos formaisBaixa+1 chamada LLM
Parent-docChunk pequeno perde contextoMédiaMínimo
Self-queryQueries com critérios explícitosMédia+1 chamada LLM

Como isso se conecta

Fontes

  1. Gao, L. et al. (2022). Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE). arxiv.org/abs/2212.10496
  2. LangChain. Parent Document Retriever. python.langchain.com
  3. LangChain. Self Query Retriever. python.langchain.com
  4. LlamaIndex. Advanced Retrieval Strategies. docs.llamaindex.ai