Construindo um pipeline RAG híbrido com Reciprocal Rank Fusion
Como a similaridade vetorial e a busca full-text do PostgreSQL rodam em paralelo e se fundem via Reciprocal Rank Fusion em respostas com citações e pontuações de relevância.
Por que uma única estratégia de recuperação nunca basta
O retrieval-augmented generation vale o que vale o que o retriever entrega ao modelo. Pergunte a uma base de conhecimento, e a resposta é tão boa quanto os chunks que a precedem - e perguntas enterprise vêm em duas formas que nenhuma estratégia de busca única lida bem.
Perguntas semânticas - "como a cobrança lida com a prorata?", "o que decidimos sobre vendor lock-in?" - dizem respeito ao significado. A busca vetorial brilha aqui: ela incorpora a consulta e os chunks de documentos no mesmo espaço e recupera os vizinhos mais próximos, então um chunk escrito com palavras completamente diferentes ainda pode surgir. Mas a busca puramente vetorial não é confiável para a outra forma de pergunta: identificadores exatos. Códigos de produto, strings de erro, números de regulamento, nomes de pessoas - os embeddings os borram, e o vizinho mais próximo de um código de erro obscuro costuma ser outro código, não relacionado.
Perguntas por palavras-chave são a imagem espelhada. A busca full-text do PostgreSQL acerta tokens exatos e termos raros, mas perde paráfrases por completo: pergunte por "ampliação de equipe" e ela não encontrará o documento que diz "contratação de fornecedores".
Documentos corporativos reais atraem os dois tipos de pergunta, muitas vezes na mesma sessão. O Simmetric Chat, portanto, se recusa a escolher. Cada recuperação executa duas pernas em paralelo - similaridade vetorial e busca full-text do PostgreSQL - e funde os resultados com Reciprocal Rank Fusion (RRF) antes que qualquer coisa chegue ao modelo. Este artigo percorre esse pipeline: as duas pernas, a matemática da fusão, o caminho de ingestão que as alimenta e a camada de citações que torna as respostas auditáveis.
Duas pernas, uma consulta
Quando um chat precisa de conhecimento, o agente ReAct invoca sua skill rag_search, e a busca híbrida inicia duas recuperações independentes ao mesmo tempo:
- A perna vetorial sai do servidor e chama o collector, o serviço que possui o armazenamento vetorial. O collector incorpora a consulta com o mesmo modelo de embedding usado na ingestão e retorna os chunks mais próximos da coleção do workspace. O armazenamento vetorial em si é plugável - LanceDB por padrão (baseado em arquivos, local, amigável a air-gap), ou Qdrant, pgvector ou Chroma para implantações que preferem um índice no lado do servidor.
- A perna full-text nunca sai do servidor. Ela consulta o PostgreSQL diretamente, comparando a consulta contra uma coluna vetorial full-text concatenada na tabela de chunks, apoiada por um índice GIN e classificada por relevância lexical. A tokenização é sensível ao idioma em sete línguas, então documentos italianos e alemães podem ser pesquisados com suas próprias regras de palavras em vez de aproximações em inglês.
As coleções são por workspace, que é o que mantém o conhecimento isolado: um chat no workspace A recupera dos chunks do workspace A e nada mais. Rodar as duas pernas em cada consulta custa um round trip extra e compra uma cobertura que nenhuma perna tem sozinha.
O modo de falha também é deliberadamente redundante. Um guard compara o modelo de embedding configurado hoje com o que foi usado para incorporar os documentos do workspace. Em caso de incompatibilidade - após uma mudança de modelo, por exemplo - a perna vetorial é totalmente pulada e a recuperação degrada para apenas full-text, em vez de servir pontuações de similaridade de espaços de embedding incomparáveis. O chat continua funcionando, e o operador obtém um caminho de reindexação explícito no painel de administração em vez de uma regressão de qualidade silenciosa.
Reciprocal Rank Fusion, concretamente
As duas pernas retornam listas classificadas medidas em escalas incomensuráveis: similaridade de cosseno de um lado, relevância lexical do outro. Fazer a média das pontuações brutas significaria calibrar essas escalas entre si - frágil, e diferente para cada modelo de embedding e cada corpus. O Simmetric Chat funde ranks em vez de pontuações, usando Reciprocal Rank Fusion:
fused(chunk) = sum over legs of 1 / (k + rank), with k = 60Cada chunk acumula uma contribuição por perna em que aparece, então um chunk classificado primeiro nas duas pernas pontua aproximadamente o dobro de um chunk classificado primeiro em apenas uma. A constante k amortiza a influência dos ranks mais altos - com k = 60, a diferença entre o rank um e o cinco em uma única perna é um delta pequeno, e é o consenso entre pernas que realmente move um chunk para cima na lista mesclada.
Dois detalhes de engenharia tornam a fusão de nível de produção em vez de livro-texto:
- Um conjunto de fontes por chunk registra se ele foi encontrado pela perna vetorial, pela full-text ou por ambas. Um chunk recuperado por ambas as pernas é a corroboração mais forte que o pipeline pode produzir, e a formatação a jusante preserva esse sinal em vez de achatá-lo.
- Um desempate determinístico - pontuação fundida decrescente, depois identificador do documento, depois posição do chunk - garante que a mesma consulta retorne a mesma ordem em cada réplica. Quando alguém pergunta "por que citou este parágrafo?", a resposta tem que ser reproduzível; a fusão não determinística tornaria a pergunta sem resposta.
De documentos a chunks pesquisáveis
A fusão é apenas metade do pipeline; a outra metade acontece na ingestão, quando um documento enviado se torna chunks que ambas as pernas podem pesquisar. O collector analisa arquivos PDF, Word, Excel, PowerPoint, Markdown, CSV e transcrições do YouTube, divide o texto com um divisor recursivo por caracteres (1.000 caracteres por chunk, 200 caracteres de sobreposição para que frases que cruzam um limite não se percam), incorpora cada chunk e escreve os vetores na coleção do workspace.
Crucialmente, o texto do chunk também aterrissa no PostgreSQL, onde o servidor mantém o vetor de busca full-text que a perna FTS consulta. O collector nunca toca no banco de dados relacional - uma fronteira de serviço rígida que mantém o pipeline de parse-e-embed substituível e deixa o índice de busca nas mãos do servidor que o consulta.
A versão 0.22 tornou a recuperação precisa em uma segunda dimensão: filtragem por metadados. O agente agora pode escopar uma chamada rag_search a tipos de documento (PDF, Markdown, TXT, CSV, DOCX, XLSX) e janelas de data de ingestão, o que corrige o clássico "documentos de 2023 respondendo perguntas de 2025". Os filtros atravessam as duas pernas - como pré-filtros reais sobre metadados de ingestão carimbados nos armazenamentos vetoriais, e como predicados SQL parametrizados no lado full-text. Um backstop de metadados no lado do servidor então reverifica cada resultado fundido contra a tabela de documentos autoritativa, então a correção do filtro não depende de cada provedor vetorial implementar predicados perfeitamente. Vetores legados ingeridos antes do carimbo de metadados simplesmente caem fora enquanto um filtro está ativo, e recuperam a filtrabilidade por meio de um re-embed acionado pelo admin.
Respostas que mostram seu trabalho
A qualidade da recuperação é apenas metade da história; a outra metade é a honestidade sobre o que fundamenta a resposta. As citações fluem pelo mesmo canal de streaming que os tokens: os selos de citação são renderizados inline na resposta, cada um carregando o documento de origem, o chunk e uma pontuação de relevância, e cada um vinculando à fonte para que o leitor possa verificar a afirmação em um clique.
A disciplina de frescor também vive aqui. Após deduplicar os resultados fundidos, um filtro de ancoragem limita citações por documento às duas melhores por pontuação de relevância e descarta citações de chunks cujo texto não compartilha sobreposição lexical significativa com a resposta final. O portão é deliberadamente conservador - páginas wiki, resultados de arquivo e respostas muito curtas são sempre mantidos - mas elimina o modo de falha onde uma resposta diz "nenhuma informação adicional" enquanto exibe "Fontes (5)" tiradas de um único documento marginalmente relacionado. O aviso de "nenhuma evidência de apoio" é julgado contra o mesmo conjunto filtrado, então o aviso e a lista de fontes sempre descrevem a mesma realidade.
Escolhendo e ajustando a stack
O pipeline é deliberadamente dirigido por configuração, e os padrões são os que priorizam privacidade:
- Armazenamento vetorial: LanceDB por padrão - baseado em arquivos, armazenado localmente, sem serviço externo necessário, totalmente compatível com air-gap. Qdrant, pgvector e Chroma estão a uma mudança de ambiente quando uma implantação o supera; o provedor é selecionado no boot.
- Embeddings: transformers locais (384 dimensões, totalmente offline) por padrão, ou embeddings da OpenAI quando uma implantação aceita a chamada de saída. Trocar o modelo de embedding exige um re-índice, dirigido pelo painel de administração; o guard de incompatibilidade mantém o sistema seguro no interim.
- Re-ranking: opcional. Um reranker pode sobre-recuperar e reordenar os resultados fundidos quando a qualidade da resposta importa mais do que a latência.
- Constante de fusão: k = 60 é entregue como padrão; é uma constante única no serviço de busca híbrida se suas distribuições de classificação pedirem amortização diferente.
O passo a passo completo do fluxo de dados - a fronteira servidor-collector, a disposição de armazenamento atrás de cada provedor vetorial e os eventos de citação em streaming - está documentado no guia de arquitetura.


