Skip to main content
HyDE (Hypothetical Document Embeddings) é a técnica clássica que gera uma resposta hipotética para a pergunta do usuário e usa essa resposta como signal adicional de retrieval. No ChatCLI, HyDE opera em duas fases complementares: 3a expande keywords via hipótese LLM, 3b adiciona busca vetorial por cosseno.
HyDE é opt-in (CHATCLI_QUALITY_HYDE_ENABLED=true) para manter o steady-state sem custo adicional. Phase 3a custa +1 LLM call cheap; Phase 3b requer configurar um embedding provider.

O problema que HyDE resolve

O retrieval de memory.Fact pré-pipeline era keyword-only: o scorer bate tokens extraídos de mensagens recentes contra tags e content dos facts armazenados. Funciona bem quando o vocabulário bate exatamente — falha quando o usuário usa sinônimos ou faz perguntas abstratas. Exemplo do gap:
Usuário: como fazer X em Go?
Keywords extraídas: [fazer, go]
Fact armazenado: "use goroutines for concurrency in X pipelines"
Match: ❌ — “fazer” e “go” não aparecem literalmente no fact.

Phase 3a — Hypothesis-based keyword expansion

1

Usuário digita query

A query entra em cli_llm.go ou agent_mode.go.
2

HyDEAugmenter.Augment

3

LLM gera hipótese curta

Prompt: “Write a 2-4 sentence plausible answer that uses the technical nouns that would appear in any matching note. Bilingual if the query mixes languages.”
4

ExtractKeywords da hipótese

O mesmo extractor já usado no chat mode (stop words en+pt, min 3 chars).
5

Merge unique + lower-case

Keywords originais + top-N da hipótese, cap configurável via CHATCLI_QUALITY_HYDE_NUM_KEYWORDS (default 5).
6

FactIndex.Search usa o set expandido

Scorer keyword-based existente opera sobre hints mais rico → recall muito maior.
Phase 3a funciona sem configurar embedding provider. É o default recomendado se o custo de +1 LLM call leve é aceitável.

Phase 3b — Vector embeddings + ranking fundido

Adiciona busca por cosine similarity sobre embeddings de facts — e, desde a refatoração de ranking, funde o score de cosseno com os sinais lexical e temporal num único ranking.
O que mudou (PR #1027): antes, os hits vetoriais eram dissolvidos de volta em keywords e o score de cosseno era descartado — pagava-se uma chamada de embedding e jogava-se fora justamente o sinal semântico. Agora o cosseno flui direto para o ranker fundido SearchBlended, então um fato achado só por paráfrase (zero overlap lexical) consegue rankear.

Arquitetura

Ranking fundido (SearchBlended)

Três sinais independentes e complementares, cada um normalizado min-max sobre o conjunto de candidatos antes da soma ponderada: Pesos default (DefaultRankWeights): semantic 0.55 · lexical 0.30 · temporal 0.15 — semantic-first, porque a chamada de embedding já foi paga. A fusão é aditiva (não multiplicativa) de propósito: um fato com cosseno alto e zero keyword ainda rankeia — um produto o zeraria. A normalização min-max é o que torna os pesos provider-agnostic: cosseno Voyage 1024-d e OpenAI 1536-d caem ambos em [0,1] após normalizar.
Floor de cosseno (MinCosineScore, default 0.25): embeddings sobre texto normalizado são quase sempre fracamente positivos, então o antigo corte em > 0 admitia ruído quase-ortogonal. O floor mantém só fatos genuinamente relacionados no top-K.

Providers suportados

Por quê Voyage: é o provider recomendado pela Anthropic para workflows Claude, tem alta qualidade/$ em retrieval, e o modelo default (voyage-4, 1024-dim) é o tier general-purpose da família voyage-4 — mesmo preço do voyage-3, estritamente melhor. voyage-4-large (flagship de qualidade), voyage-4-lite (mais barato) e voyage-code-4 (retrieval de código) compartilham o mesmo espaço de embedding.

Vector store pure-Go — primitivo genérico vindex

Sem CGO, sem SQLite-vec, sem dependências externas.float32[] + cosseno + persistência JSON em ~/.chatcli/memory/vector_index.json.
O índice cosseno vive num pacote genérico e reutilizável, llm/embedding/vindex, extraído quando um segundo consumidor (o retrieval semântico de /context) apareceu. O memory é só um adapter fino sobre ele — sem máquina vetorial duplicada por pacote:
A seleção top-K usa um min-heap O(n log k) (não full-sort O(n log n)), então o custo escala com k, não com o tamanho do corpus — o teto de escala deixa de ser “centenas de facts”. Para o caso típico do chatcli a busca linear completa em microssegundos; sem HNSW ou IVFFlat.

Auto-migração de provider/dimensão

Trocar de provider ou dimensão (Voyage 1024 → OpenAI 1536, ou voyage→cohere no mesmo 1024) não exige mais rm manual. No load, o índice detecta o mismatch — de dimensão (cosseno entre arities diferentes é indefinido) ou de provider (dois espaços de embedding distintos não são comparáveis, mesmo na mesma dimensão) — e auto-limpa o cache, removendo o arquivo para o backfill repopular:
O caso provider→provider no mesmo dim (ex.: voyage 1024 → cohere 1024) antes era silenciosamente servido como lixo — o cosseno entre espaços diferentes não tem sentido. Agora é detectado e re-embeddado.

Lazy backfill

Ao retrieve uma fact, se ela não tem vetor (fact pré-existe à ativação de embeddings), o index spawna goroutine detached para embedar as top-500 facts visíveis:
O backfill é bounded e configurável: no máximo Config.BackfillBatchMax facts por retrieve (default 500). Com Voyage (~0.05/Mtokens)issocustacercadeUS0.05/M tokens) isso custa cerca de US 0,001 num cold cache cheio. Em uma sessão normal, a maioria do index fica embeddado já na primeira interação.

O tool @embed para o agent

O backend de embeddings também é exposto diretamente ao modelo como o tool @embed (modos agent/coder), do mesmo jeito que o @image expõe geração de imagem:
Usa a mesma configuração CHATCLI_EMBED_PROVIDER descrita acima. Para RAG persistente sobre documentos, o modelo é direcionado ao @context (construir/atachar base de conhecimento) + @knowledge (consultar) — o @embed cobre as operações semânticas ad-hoc que não precisam de índice.

Avaliação — provando o retrieval (não supondo)

Antes não havia como medir se o retrieval era bom. Agora há um harness de avaliação dependency-free em cli/workspace/memory/eval — métricas padrão de IR macro-averaged: O A/B versionado (em ranking_test.go, com um provider de embedding determinístico) compara keyword-only vs. ranking fundido sobre queries que usam sinônimos ausentes do texto dos facts:
O harness roda igual em CI (provider determinístico, reproduzível) ou contra um backend real — ele nunca importa um provider, então fica neutro entre os 14 suportados. É o que transforma “parece bom” em “é bom, medido”, e guarda contra regressões.

Tunables de ranking (sem novos env vars)

Os parâmetros do ranking fundido vivem como campos de Config com defaults fortes — não foram criados novos env vars (e os CHATCLI_MEMORY_* que já existiam, antes ignorados no caminho estruturado, agora são aplicados via ConfigFromEnv + clamp):

Configuração completa


/config quality expõe o estado


Integração com Reflexion

HyDE amplifica o valor de Reflexion: as lições persistidas pela #3 são recuperadas com muito mais recall quando a próxima tarefa não usa exatamente as mesmas keywords. Workflow:
1

Turn 1: refactor auth.go falha (timeout)

Reflexion persiste lesson: "use Edit tool for large files", tags [go, refactor, edit-tool].
2

Turn 5 (dias depois): 'me ajuda a dividir pkg/engine'

Query não contém refactor ou edit. Keyword-only perderia a lesson.
3

HyDE 3a gera hipótese

"To split a Go package, identify logical groupings and use refactor patterns with Edit tool for surgical changes..."
Keywords extraídas: [split, package, refactor, edit, patterns, …]
4

Match!

Lesson aparece no system prompt. Coder escolhe Edit ao invés de write de primeira.

Caveats e tuning

Token cost de phase 3a: ~200 tokens por turn de retrieval. Em workflows com muitos turns de leitura, o custo acumula. Use CHATCLI_QUALITY_HYDE_NUM_KEYWORDS=3 para budget mais apertado.
Privacy: a query do usuário é enviada ao provider de embedding. Para workloads sensíveis em ambientes corporativos, Bedrock é o caminho preferido — fica dentro do perímetro AWS (CloudTrail, VPC endpoint, IAM). Para workloads locais sem rede, considere self-host (roadmap: provider Ollama-embedding).
Fallback gracioso: se o LLM fail ou o provider embedding retornar erro, o retrieval cai para keyword-only silenciosamente. Nenhum turn é abortado por falha de HyDE.

Leia também

#3 Reflexion

As lições que HyDE recupera com mais recall.

Bootstrap Memory

A camada embaixo: como memory.Fact é populada e mantida.

Persistent Context

/context attach para contextos explícitos de arquivos.

Configuração completa

Todos os env e slashes.