Pular para o conteúdo principal
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

augmenter := memory.NewHyDEAugmenter(cfg, llmCallback, logger)
expanded := augmenter.Augment(ctx, query, originalHints)
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

Adiciona busca por cosine similarity sobre embeddings de facts.

Arquitetura

┌──────────────────┐
│ Query do usuário │
└────────┬─────────┘


┌─────────────────────────┐
│ EmbeddingProvider.Embed │  (Voyage AI / OpenAI / Null)
└────────┬────────────────┘

         ▼  vector float32[1024] ou [1536]
┌─────────────────────────┐
│ VectorIndex.SimilarFacts│  (cosine pure-Go)
└────────┬────────────────┘

         ▼  top-K fact IDs
┌─────────────────────────┐
│ FactIndex.GetByID       │
│ ExtractKeywords(content)│
└────────┬────────────────┘


    Hints expandidas via Phase 3a E 3b

Providers suportados

export CHATCLI_QUALITY_HYDE_ENABLED=true
export CHATCLI_QUALITY_HYDE_USE_VECTORS=true
export CHATCLI_EMBED_PROVIDER=voyage
export VOYAGE_API_KEY=pa-...
# Opcional: escolher modelo (default voyage-3, 1024-dim)
export CHATCLI_EMBED_MODEL=voyage-3
Por quê Voyage: é o provider recomendado pela Anthropic para workflows Claude, tem alta qualidade/$ em retrieval, e o modelo default (voyage-3, 1024-dim) é o sweet spot geral.

Vector store pure-Go

Sem CGO, sem SQLite-vec, sem dependências externas.float32[] + cosseno + persistência JSON em ~/.chatcli/memory/vector_index.json.
// cli/workspace/memory/vector_store.go
type VectorEntry struct {
    FactID    string    `json:"fact_id"`
    Vector    []float32 `json:"vector"`
    Dimension int       `json:"dim"`
    Provider  string    `json:"provider"`
}
Para N < 1000 facts (o caso típico do chatcli), a busca linear em memória completa em microssegundos. Sem necessidade de índice HNSW ou IVFFlat.

Dimension lock

Trocar de provider (Voyage 1024 → OpenAI 1536) não é automático: o store rejeita com erro explicativo. Razão: cosine entre vetores de dimensões diferentes é inválido matematicamente.
# Para migrar, limpe o arquivo
rm ~/.chatcli/memory/vector_index.json
# Altere o provider e reinicie — backfill lazy repopula

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-25 facts visíveis:
// cli/workspace/memory/store.go:120
go func(items map[string]string) { //#nosec G118 -- detached on purpose
    if err := m.vectors.BackfillFacts(context.Background(), items); err != nil {
        m.logger.Warn("vector backfill failed", zap.Error(err))
    }
}(items)
O backfill é bounded: no máximo 25 facts por invocação de retrieve. Em uma sessão normal, a maioria do index fica embeddado nas primeiras dezenas de interações.

Configuração completa

Env varDefaultO que faz
CHATCLI_QUALITY_HYDE_ENABLEDfalseMaster switch (phase 3a)
CHATCLI_QUALITY_HYDE_USE_VECTORSfalseLiga phase 3b (requer provider)
CHATCLI_QUALITY_HYDE_PROVIDERProvider name para display
CHATCLI_QUALITY_HYDE_NUM_KEYWORDS5Cap de keywords da hipótese em phase 3a
CHATCLI_EMBED_PROVIDERvoyage|openai|null
CHATCLI_EMBED_MODELprovider defaultEx: voyage-3, text-embedding-3-small
CHATCLI_EMBED_DIMENSIONSprovider defaultSó para OpenAI

/config quality expõe o estado

── RAG + HyDE (#4)
  CHATCLI_QUALITY_HYDE_ENABLED    : enabled
  CHATCLI_QUALITY_HYDE_USE_VECTORS: enabled
  CHATCLI_QUALITY_HYDE_PROVIDER   : voyage
  CHATCLI_EMBED_PROVIDER          : voyage
  CHATCLI_EMBED_MODEL             : voyage-3
  CHATCLI_QUALITY_HYDE_NUM_KEYWORDS: 5
  Provedor de vetores            : voyage:voyage-3
  Entradas vetoriais             : 127

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, considere self-host de um embedding model (futuro: roadmap de 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.