Pular para o conteúdo
cd /blog

Busca com Fusão Multimodal: Escolhendo o Retriever Certo Para Cada Consulta

[Busca] [Arquitetura]

> Uma consulta como 'onde parseConfig é definido' quer um tipo de busca diferente de 'como funciona a autenticação'. O Maguyva classifica a intenção, pondera quatro modalidades de retrieval de acordo, e funde os resultados com Reciprocal Rank Fusion ponderada.

Nota sobre o projeto histórico: este artigo de abril de 2026 descreve o projeto de roteamento daquela época, não o comportamento padrão atual. A correção de setembro abaixo trata apenas de uma afirmação sobre medição. Para conhecer o roteamento atual, as predefinições e os controles explícitos de modalidade, veja Como funciona.

Uma consulta de busca não é uma coisa só.

“onde parseConfig é definido” quer um símbolo exato — um local preciso, rápido. “como funciona a autenticação” quer significado — o conjunto espalhado de código relacionado que explica um conceito. “o que quebra se eu mudar esta função” quer o grafo de dependências. “encontre a string ECONNREFUSED” quer uma correspondência literal, nada sofisticado.

O grep é excelente para correspondências literais e útil para algumas caças a referências, mas não é um grafo de dependências e não entende significado. Embeddings cobrem o lado semântico, mas são a ferramenta errada para strings exatas e análise de impacto. A maioria das ferramentas de busca de código escolhe um único motor e faz toda consulta conviver com essa escolha. O Maguyva não escolhe. Ele descobre que tipo de pergunta você fez, e então combina quatro retrievers na proporção que essa pergunta merece.

Quatro modalidades

Internamente, existem quatro formas independentes de encontrar código:

  • semantic — busca vetorial sobre embeddings binários da Voyage; encontra código por significado.
  • text — correspondência de trigramas; encontra literais, strings de erro, identificadores exatos.
  • structural — queries de AST; encontra definições, assinaturas e construções da linguagem.
  • graph — o grafo de dependências; encontra chamadores, chamados e raio de impacto.

Cada um é forte em uma classe diferente de pergunta. O truque é decidir o quanto confiar em cada um para a consulta que está na sua frente.

Classificação de intenção

Antes de qualquer retrieval rodar, um classificador leve classifica a consulta em uma de seis intenções, com uma pontuação de confiança. É deliberadamente barato — heurísticas ordenadas, a primeira correspondência vence — porque roda no caminho crítico e adiciona apenas um ou dois milissegundos:

  • começa com def , class , func , import … → find_definition (confiança 0,95)
  • “who calls”, “usages of”, “references to” (quem chama, usos de, referências a) → find_references (0,90)
  • “impact”, “blast radius”, “what depends on” (impacto, raio de impacto, o que depende de) → impact_analysis (0,90)
  • uma "string" entre aspas ou um token de erro como traceback → exact_match (0,85–0,90)
  • um identificador CamelCase ou snake_case → find_definition (0,60–0,80)
  • “how”, “why”, “explain”, “architecture” (como, por que, explique, arquitetura) → understand_code (0,75)
  • nada corresponde → understand_code, baixa confiança (0,40)

Cada intenção carrega um perfil de peso entre as quatro modalidades. Estes são os números reais:

Intenção semantic text structural (AST) graph
find_definition 0,2 0,1 0,6 0,1
find_references 0,1 0,2 0,2 0,5
understand_code 0,5 0,2 0,2 0,1
find_similar 0,4 0,3 0,2 0,1
impact_analysis 0,1 0,1 0,1 0,7
exact_match 0,0 0,9 0,1 0,0

Então “onde parseConfig é definido” se apoia fortemente em AST (0,6). “como funciona a autenticação” se apoia em vetores semânticos (0,5). “o que depende disso” é quase todo graph (0,7). “encontre ECONNREFUSED” é quase todo trigrama (0,9), com o modelo de embedding completamente desligado — porque similaridade semântica é exatamente a ferramenta errada para uma string exata.

O caminho rápido, e o caminho fundido

Quando o classificador está confiante — pontuação ≥ 0,85 — e a consulta é normal, o Maguyva pula a fusão por completo e roteia direto para a única modalidade dominante. “onde X é definido” não precisa de quatro retrievers; precisa do índice de AST, agora. Esse caminho direto é reportado de volta como fusion_strategy: "direct".

Tudo que é ambíguo passa pela fusão. As quatro (ou três, no preset padrão) modalidades rodam em paralelo, cada uma retornando sua própria lista ranqueada, e nós as combinamos.

Reciprocal Rank Fusion Ponderada

Fundir retrievers heterogêneos é mais difícil do que parece: uma similaridade de cosseno de 0,82, uma pontuação de trigrama de 137 e uma centralidade de grafo de 0,004 não estão na mesma escala, então você não pode simplesmente somá-las. O Reciprocal Rank Fusion contorna o problema descartando as pontuações brutas e mantendo apenas o rank que cada motor atribuiu. A contribuição de um resultado vinda de uma modalidade é:

contribution = weight × 1 / (k + rank + 1)

onde rank é a posição dele na lista daquela modalidade e k é uma constante de suavização. As contribuições são somadas entre modalidades para qualquer resultado que mais de um motor encontrou — a concordância entre retrievers naturalmente sobe ao topo. Usamos k = 40 no preset padrão e 60 em thorough (quick roda apenas com semantic, então a fusão nunca entra em ação ali). O trabalho original de RRF chegou a k = 60 para retrieval de propósito geral; nosso padrão enfatiza um pouco mais as primeiras posições, dando mais peso aos resultados que aparecem bem classificados em várias modalidades — e não recomendamos ajustá-lo manualmente.

Além disso, os resultados carregam um boost de importância de grafo. Um hub — uma função em que todo o codebase se apoia — deve superar em ranking um nó-folha pouco conhecido mesmo com a mesma relevância textual, então multiplicamos cada contribuição por:

boost = min(1 + 0.3 × ln(1 + centrality), 1.5)

A centralidade vem das métricas pré-computadas de PageRank/grau do pipeline, e o boost é limitado a 1,5× para que uma função popular não consiga sepultar completamente uma obscura mais relevante. Por fim, rebaixamos resultados de caminhos de dependências externas, artefatos de build e conteúdo arquivado, e removemos duplicatas mantendo o melhor chunk por arquivo.

O que ainda é imperfeito

O classificador de intenção é uma pilha de regexes, não um modelo aprendido. Ele trata os formatos comuns de consulta com heurísticas, e uma consulta ambígua recorre a understand_code e a uma combinação com maior peso semântico. Uma versão anterior deste artigo descreveu como alcançada uma redução da taxa de consultas sem resultados de aproximadamente 15% para menos de 5%. Na verdade, a proposta de dezembro de 2025 apresentava uma estimativa inicial e uma meta; não foi fornecido um conjunto de consultas nem um registro de medição que sustentasse essa redução. Retiramos essa afirmação de melhoria alcançada; veja as evidências de origem e a correção (em inglês). Não substituímos o classificador por um modelo treinado porque a versão de baixo custo é rápida e boa o suficiente para a função de roteamento pretendida, e porque um classificador errado, mas confiante, é pior do que uma alternativa honesta. Os pesos em si são valores a priori escolhidos manualmente, não aprendidos a partir de dados de cliques que não coletamos.

Faça a Pergunta, Não a Ferramenta

Um agente não deveria precisar saber se deve recorrer ao grep, a embeddings ou ao call graph — ele deveria fazer a sua pergunta em termos simples e obter a resposta certa. A fusão multimodal é o que permite que find_symbol, busca semântica e análise de dependências fiquem por trás de uma única interface de consulta: o sistema lê o formato da pergunta e silenciosamente monta o retriever certo para ela. O modelo que pontua os embeddings importa, mas também importa saber quando não usá-los. Escolher a ferramenta certa para cada consulta é um tipo de qualidade em si, e é algo que preferimos assumir a empurrar para quem chama.

// you bring the question. it brings the tools.

Leituras relacionadas

Mais do log de build do Maguyva