Usando o Postgres como mecanismo de busca
(anyblockers.com)- É possível construir dentro do Postgres um mecanismo de busca híbrido com busca semântica, full-text e fuzzy
- A busca é uma parte importante de muitos apps, mas não é fácil implementá-la corretamente. Especialmente em pipelines de RAG, a qualidade da busca pode determinar o sucesso ou fracasso de todo o processo
- A busca semântica está em alta, mas a busca tradicional baseada em termos ainda é a espinha dorsal da busca
- Técnicas semânticas podem melhorar os resultados, mas funcionam melhor sobre uma base sólida de busca textual
Implementando um mecanismo de busca com Postgres
- Combina três técnicas:
- busca full-text com
tsvector - busca semântica com
pgvector - correspondência fuzzy com
pg_trgm
- busca full-text com
- Essa abordagem pode não ser a melhor absoluta em todos os cenários, mas é uma excelente alternativa a construir um serviço de busca separado
- Um ponto de partida robusto que pode ser implementado e escalado dentro do banco de dados Postgres já existente
- Por que usar Postgres para tudo: Apenas use Postgres para tudo, PostgreSQL é suficiente, Apenas use Postgres
Implementando FTS e busca semântica
- O Supabase tem uma excelente documentação sobre implementação de busca híbrida, então ela será usada como ponto de partida
- Seguindo o guia, a FTS é implementada com índices GIN, e a busca semântica com
pgvector(também chamada de bi-encoder dense retrieval) - Pela experiência pessoal, escolher embeddings de 1536 dimensões pode gerar resultados muito melhores
- As funções do Supabase são substituídas por CTEs e consultas, e
$é adicionado antes dos parâmetros - Aqui, os resultados são mesclados usando RRF (Reciprocal Ranked Fusion)
- Esse método garante que itens bem posicionados em várias listas recebam alta colocação na lista final
- Também evita que itens que estão muito bem em uma lista, mas mal em outras, acabem recebendo uma posição alta na lista final
- Calcular a pontuação colocando a posição no denominador pode penalizar registros com ranking mais baixo
- Pontos importantes
$rrf_k: para evitar que a pontuação do item na primeira posição fique extrema demais (já que se divide pela posição), costuma-se adicionar uma constante k ao denominador para suavizar a pontuação$ _weight: é possível atribuir pesos a cada método. Isso é muito útil ao ajustar os resultados
Implementando busca fuzzy
- Os métodos anteriores já resolvem bastante coisa, mas podem surgir problemas imediatos quando há erros de digitação em entidades nomeadas
- A busca semântica capta similaridade e elimina parte desses problemas, mas ainda tem dificuldade com nomes, siglas e outros textos que não são semanticamente parecidos
- Para mitigar isso, introduz-se a extensão
pg_trgmpara permitir busca fuzzy- Ela funciona com trigramas. Trigramas são úteis para busca fuzzy porque decompõem palavras em sequências de 3 caracteres
- Isso permite corresponder palavras semelhantes mesmo quando há erros de digitação ou pequenas variações
- Por exemplo, "hello" e "helo" compartilham muitos trigramas, então podem ser correspondidos mais facilmente na busca fuzzy
- Cria-se um novo índice para a coluna desejada e depois ele é adicionado à consulta geral de busca
- A extensão
pg_trgmexpõe o operador%, que filtra textos cuja similaridade é maior quepg_trgm.similarity_threshold(o padrão é 0.3) - Há também vários outros operadores úteis
Ajustando a busca full-text
- Ajustando os pesos do
tsvector: documentos reais incluem não só o título, mas também o conteúdo - Mesmo com várias colunas, mantém-se apenas uma coluna de embedding
- Pessoalmente, foi observado que manter
titleebodyno mesmo embedding não traz grande diferença de desempenho em relação a manter vários embeddings - No fim, o
titledeve ser uma representação resumida do corpo. Vale a pena experimentar isso conforme a necessidade - Espera-se que o título seja curto e rico em palavras-chave, enquanto o corpo tende a ser mais longo e conter mais detalhes
- Portanto, é preciso ajustar como as colunas da busca full-text recebem pesos entre si
- É possível atribuir prioridade conforme a posição da palavra no documento ou sua importância
- A-weight: mais importante (ex.: título, cabeçalhos). Padrão
1.0 - B-weight: importante (ex.: começo do documento, resumo). Padrão
0.4 - C-weight: importância padrão (ex.: texto do corpo). Padrão
0.2 - D-weight: menos importante (ex.: notas de rodapé, comentários). Padrão
0.1
- A-weight: mais importante (ex.: título, cabeçalhos). Padrão
- A relevância pode ser refinada ajustando os pesos de acordo com a estrutura do documento e os requisitos da aplicação
- Por que dar mais peso ao título
- Porque o título normalmente expressa de forma concisa o tema principal do documento
- Os usuários tendem a olhar primeiro os títulos ao pesquisar, então a correspondência de palavras-chave no título geralmente se relaciona mais com a intenção do usuário do que a correspondência no texto do corpo
Ajuste por comprimento
- Ao ler a documentação de
ts_rank_cd, percebe-se que há parâmetros de normalização-
Ambas as funções de ranking usam uma opção inteira
normalizationpara especificar se e como o comprimento do documento deve afetar a classificação. Como a opção inteira controla vários comportamentos, ela é uma máscara de bits: use|para especificar um ou mais comportamentos (ex.:2|4).
-
- Com essas várias opções, é possível:
- ajustar o viés de comprimento do documento
- equilibrar a relevância em diferentes conjuntos de documentos
- ajustar os resultados de ranking para uma representação consistente
- Definir
0(sem normalização) para o título e1(comprimento logarítmico do documento) para o corpo produziu bons resultados - Novamente, vale experimentar várias opções para encontrar a que melhor se adapta ao seu caso de uso
Re-ranking com cross-encoder
- Muitos sistemas de busca são compostos por duas etapas
- Ou seja, usa-se um bi-encoder para recuperar os N resultados iniciais e depois um cross-encoder para comparar esses resultados com a consulta de busca e reordená-los
- bi-encoder: por ser rápido, é bom para recuperar um grande número de documentos
- cross-encoder
- É mais lento, mas oferece melhor desempenho, sendo ideal para reordenar os resultados recuperados
- Processa a consulta e o documento em conjunto, permitindo uma compreensão mais sutil da relação entre ambos
- Isso oferece melhor precisão de ranking à custa de tempo de computação e escalabilidade
- Existem várias ferramentas para fazer isso
- Uma das melhores é o Rerank da Cohere
- Outra abordagem é construir a sua própria usando o GPT da OpenAI
- Cross-encoders podem melhorar a precisão dos resultados de busca por entenderem melhor a relação entre a consulta e o documento
- No entanto, têm alto custo computacional, o que impõe limitações de escalabilidade
- Por isso, uma abordagem em duas etapas é eficaz: usar um bi-encoder para a busca inicial e aplicar o cross-encoder apenas a um pequeno conjunto de documentos recuperados
Quando procurar uma solução alternativa
- O PostgreSQL é uma boa escolha para muitos cenários de busca, mas não está livre de limitações
- A ausência de algoritmos avançados como BM25 pode ser sentida ao lidar com documentos de comprimentos muito variados
- A busca full-text do PostgreSQL depende de TF-IDF, então pode ter dificuldades com documentos muito longos e termos raros em coleções muito grandes
- Antes de buscar uma solução alternativa, é essencial medir. Talvez não valha a pena
Conclusão
- Este texto cobre muita coisa, desde busca full-text básica até técnicas avançadas como fuzzy matching, busca semântica e boosting de resultados
- Aproveitando os recursos poderosos do Postgres, é possível criar um mecanismo de busca forte e flexível adaptado a requisitos específicos
- Postgres talvez não seja a primeira ferramenta que vem à mente para busca, mas ele pode levar isso muito longe
- Chaves para uma ótima experiência de busca
- iteração contínua e ajuste fino
- não tenha receio de usar as técnicas de depuração discutidas para entender o desempenho da busca e ajustar pesos e parâmetros com base no feedback e no comportamento dos usuários
- Embora o PostgreSQL possa não ter alguns recursos avançados de busca, na maioria dos casos ele é poderoso o suficiente para construir um mecanismo de busca eficaz
- Antes de procurar soluções alternativas, vale a pena explorar ao máximo os recursos do Postgres e medir o desempenho; se ainda assim não for suficiente, então pode-se considerar outra solução
2 comentários
Fico curioso para saber se a busca em coreano também funciona bem.
O tema do Weekly de hoje também era Postgres, e claro, lá vem Postgres de novo. Dá para ver que, proporcionalmente à popularidade, saem muitos textos sobre ele haha.
Sobre BM25, veja abaixo.
pg_bm25 - Extensão de busca full-text para Postgres que oferece qualidade no nível do Elastic
ParadeDB - PostgreSQL for Search