Filtro
Com o Qdrant, você pode definir condições para pesquisar ou recuperar pontos, o que significa que você pode filtrar por atributo além de pesquisas de similaridade para vetores, semelhante à definição de condições SQL where
. Por exemplo, você pode definir condições para a carga útil e o id
do ponto.
É importante definir condições adicionais quando nem todos os recursos de um objeto podem ser expressos em um embedding. Por exemplo, vários requisitos comerciais, como disponibilidade de inventário, localização do usuário ou faixa de preço esperada.
Condições do Filtro
O Qdrant permite combinar condições em cláusulas. As cláusulas são diferentes operações lógicas, como OR
, AND
e NOT
. As cláusulas podem ser aninhadas recursivamente umas dentro das outras, para que você possa recriar qualquer expressão booleana.
Vamos dar uma olhada nas cláusulas implementadas no Qdrant.
Suponha que tenhamos um conjunto de pontos com cargas úteis:
[
{ "id": 1, "city": "Londres", "color": "verde" },
{ "id": 2, "city": "Londres", "color": "vermelho" },
{ "id": 3, "city": "Londres", "color": "azul" },
{ "id": 4, "city": "Berlim", "color": "vermelho" },
{ "id": 5, "city": "Moscou", "color": "verde" },
{ "id": 6, "city": "Moscou", "color": "azul" }
]
Deve
Exemplo:
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"deve": [
{ "chave": "city", "match": { "valor": "Londres" } },
{ "chave": "color", "match": { "valor": "vermelho" } }
]
}
...
}
O ponto filtrado será:
[{ "id": 2, "city": "Londres", "color": "vermelho" }]
Quando se usa deve
, a cláusula é verdadeira
apenas se cada condição listada em deve
for satisfeita. Nesse sentido, deve
é equivalente ao operador E
.
Deveria
Deveria
é semelhante ao operador OU
em SQL.
Exemplo:
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"deveria": [
{ "chave": "city", "match": { "valor": "Londres" } },
{ "chave": "color", "match": { "valor": "vermelho" } }
]
}
...
}
Os pontos filtrados serão:
[
{ "id": 1, "city": "Londres", "color": "verde" },
{ "id": 2, "city": "Londres", "color": "vermelho" },
{ "id": 3, "city": "Londres", "color": "azul" },
{ "id": 4, "city": "Berlim", "color": "vermelho" }
]
Ao usar deveria
, a cláusula é verdadeira
contanto que pelo menos uma condição listada em deveria
seja satisfeita. Nesse sentido, deveria
é equivalente ao operador OU
.
não_deve
Exemplo:
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"não_deve": [
{ "chave": "city", "match": { "valor": "Londres" } },
{ "chave": "color", "match": { "valor": "vermelho" } }
]
}
...
}
Os pontos filtrados serão:
[
{ "id": 5, "city": "Moscou", "color": "verde" },
{ "id": 6, "city": "Moscou", "color": "azul" }
]
Ao usar não_deve
, a subcláusula é verdadeira
apenas se nenhuma das condições listadas em não_deve
for satisfeita. Nesse sentido, não_deve
é equivalente à expressão (NÃO A) E (NÃO B) E (NÃO C)
.
Combinação de Condições
É possível usar múltiplas condições simultaneamente:
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"deve": [
{ "chave": "cidade", "correspondência": { "valor": "Londres" } }
],
"não_deve": [
{ "chave": "cor", "correspondência": { "valor": "vermelho" } }
]
}
...
}
Os pontos filtrados serão:
[
{ "id": 1, "cidade": "Londres", "cor": "verde" },
{ "id": 3, "cidade": "Londres", "cor": "azul" }
]
Neste caso, as condições são combinadas usando AND
.
Além disso, as condições podem ser aninhadas de forma recursiva. Por exemplo:
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"não_deve": [
{
"deve": [
{ "chave": "cidade", "correspondência": { "valor": "Londres" } },
{ "chave": "cor", "correspondência": { "valor": "vermelho" } }
]
}
]
}
...
}
Os pontos filtrados serão:
[
{ "id": 1, "cidade": "Londres", "cor": "verde" },
{ "id": 3, "cidade": "Londres", "cor": "azul" },
{ "id": 4, "cidade": "Berlim", "cor": "vermelho" },
{ "id": 5, "cidade": "Moscou", "cor": "verde" },
{ "id": 6, "cidade": "Moscou", "cor": "azul" }
]
Filtro de Condições
No payload, diferentes tipos de valores correspondem a diferentes tipos de consultas que podem ser aplicadas a eles. Vamos dar uma olhada nas variantes de condições existentes e nos tipos de dados a que elas se aplicam.
Correspondência
{
"chave": "cor",
"correspondência": {
"valor": "vermelho"
}
}
Para outros tipos, as condições de correspondência são exatamente iguais, apenas com tipos diferentes usados:
{
"chave": "contagem",
"correspondência": {
"valor": 0
}
}
A condição mais simples verifica se o valor armazenado é igual ao valor fornecido. Se vários valores estiverem armazenados, pelo menos um deles deve satisfazer a condição. Isso pode ser aplicado a cargas úteis de palavras-chave, inteiros e booleanos.
Qualquer Correspondência
Disponível desde a v1.1.0
Se desejar verificar se o valor armazenado é um dos vários valores, pode usar a condição de qualquer correspondência. A correspondência de qualquer trata o valor fornecido como uma operação lógica OR. Também pode ser descrita como o operador IN
.
Você pode aplicá-lo a cargas úteis de palavras-chave e inteiros.
Exemplo:
{
"chave": "cor",
"correspondência": {
"qualquer": ["preto", "amarelo"]
}
}
Neste exemplo, se o valor armazenado for preto
ou amarelo
, então a condição será satisfeita.
Se o valor armazenado for uma lista, deve ter pelo menos um valor que corresponda a qualquer um dos valores fornecidos. Por exemplo, se o valor armazenado for ["preto", "verde"]
, então a condição será satisfeita porque "preto"
está em ["preto", "amarelo"]
.
Exclusão de Correspondência
Disponível desde a v1.2.0
Se desejar verificar se o valor armazenado não é nenhum dos vários valores, pode usar a condição de exclusão de correspondência. A exclusão de correspondência trata o valor fornecido como uma operação lógica NOR. Também pode ser descrita como o operador NOT IN
.
Você pode aplicá-lo a cargas úteis de palavras-chave e inteiros.
Exemplo:
{
"chave": "cor",
"correspondência": {
"exceto": ["preto", "amarelo"]
}
}
Neste exemplo, se o valor armazenado não for nem preto
nem amarelo
, então a condição será satisfeita.
Se o valor armazenado for uma lista, deve ter pelo menos um valor que não corresponda a nenhum dos valores fornecidos. Por exemplo, se o valor armazenado for ["preto", "verde"]
, então a condição será satisfeita porque "verde"
não corresponde a "preto"
ou "amarelo"
.
Chaves Aninhadas
Disponível a partir da v1.1.0 em diante
Como a carga útil é um objeto JSON arbitrário, talvez você precise filtrar campos aninhados.
Para conveniência, usamos uma sintaxe similar ao projeto Jq.
Suponha que tenhamos um conjunto de pontos com a seguinte carga útil:
[
{
"id": 1,
"country": {
"name": "Alemanha",
"cities": [
{
"name": "Berlim",
"population": 3.7,
"sightseeing": ["Portão de Brandemburgo", "Reichstag"]
},
{
"name": "Munique",
"population": 1.5,
"sightseeing": ["Marienplatz", "Parque Olímpico"]
}
]
}
},
{
"id": 2,
"country": {
"name": "Japão",
"cities": [
{
"name": "Tóquio",
"population": 9.3,
"sightseeing": ["Torre de Tóquio", "Tokyo Skytree"]
},
{
"name": "Osaka",
"population": 2.7,
"sightseeing": ["Castelo de Osaka", "Universal Studios Japan"]
}
]
}
}
]
Você pode usar a notação de ponto para pesquisar por campos aninhados.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.name",
"match": {
"value": "Alemanha"
}
}
]
}
}
Também é possível usar a sintaxe [ ]
para pesquisar o array projetando valores internos.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].population",
"range": {
"gte": 9.0
}
}
]
}
}
Esta consulta apenas retorna o ponto com id 2, pois apenas o Japão tem uma cidade com uma população maior que 9.0.
Campos aninhados também podem ser um array.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"should": [
{
"key": "country.cities[].sightseeing",
"match": {
"value": "Castelo de Osaka"
}
}
]
}
}
Esta consulta apenas retorna o ponto com id 2, pois apenas o Japão possui uma cidade com um local turístico que inclui "Castelo de Osaka".
Filtro de Objeto Aninhado
Disponível desde a versão 1.2.0
Por padrão, as condições levam em consideração a carga completa de um ponto.
Por exemplo, considerando os dois pontos na carga abaixo:
[
{
"id": 1,
"dinosaur": "t-rex",
"diet": [
{ "food": "leaves", "likes": false},
{ "food": "meat", "likes": true}
]
},
{
"id": 2,
"dinosaur": "diplodocus",
"diet": [
{ "food": "leaves", "likes": true},
{ "food": "meat", "likes": false}
]
}
]
A consulta a seguir buscaria esses dois pontos:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{
"key": "diet[].food",
"match": {
"value": "meat"
}
},
{
"key": "diet[].likes",
"match": {
"value": true
}
}
]
}
}
A razão pela qual os dois pontos acima são correspondidos é porque ambos satisfazem essas duas condições:
- "t-rex" satisfaz
diet[1].food
com food = meat ediet[1].likes
com likes = true - "diplodocus" satisfaz
diet[1].food
com food = meat ediet[0].likes
com likes = true
Para obter apenas pontos que correspondam às condições dos elementos do array, por exemplo, o ponto com id 1 neste exemplo, é necessário usar filtros de objeto aninhado.
Filtros de objeto aninhado permitem consultar matrizes de objetos independentemente.
Isso pode ser alcançado usando o tipo de condição nested
, que consiste na chave de interesse da carga e o filtro a aplicar.
A chave deve apontar para um array de objetos e opcionalmente usar a notação de colchetes ("dados" ou "dados[]").
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "meat"
}
},
{
"key": "likes",
"match": {
"value": true
}
}
]
}
}
}
]
}
}
A lógica de correspondência é modificada para se aplicar ao nível do elemento do array dentro da carga.
O filtro aninhado funciona da mesma maneira que quando se aplica um filtro aninhado a um único elemento de um array. Contanto que pelo menos um elemento do array corresponda ao filtro aninhado, o documento pai é considerado como correspondente à condição.
Limitação
Filtros de objeto aninhado não suportam a condição has_id
. Se precisar usá-la, coloque-a em uma cláusula must
adjacente.
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
"nested": {
{
"key": "diet",
"filter": {
"must": [
{
"key": "food",
"match": {
"value": "meat"
}
},
{
"key": "likes",
"match": {
"value": true
}
}
]
}
}
},
{ "has_id": [1] }
]
}
}
Correspondência Exata de Texto
Disponível desde a versão 0.10.0
Um caso especial da condição de correspondência match
é a condição de correspondência text
. Isso permite buscar subcadeias específicas, tokens ou frases dentro do campo de texto.
Os textos exatos que satisfazem esta condição dependem da configuração do índice de texto completo. A configuração é definida quando o índice é criado e é descrita dentro do índice de texto completo.
Se o campo não tiver um índice de texto completo, esta condição funcionará com base na correspondência exata de subcadeia.
{
"key": "description",
"match": {
"text": "bom e barato"
}
}
Se a consulta tiver várias palavras, esta condição só será satisfeita quando todas as palavras aparecerem no texto.
Intervalo
{
"chave": "preço",
"intervalo": {
"gt": null,
"gte": 100.0,
"lt": null,
"lte": 450.0
}
}
A condição intervalo
estabelece o intervalo possível de valores para a carga armazenada. Se múltiplos valores estiverem armazenados, pelo menos um valor deve atender à condição.
As operações de comparação disponíveis incluem:
-
gt
- maior que -
gte
- maior que ou igual a -
lt
- menor que -
lte
- menor que ou igual a
Pode ser aplicado a números de ponto flutuante e cargas úteis inteiras.
Caixa de Limite Geográfico
{
"chave": "localização",
"caixa_delimitadora_geográfica": {
"inferior_direito": {
"lat": 52.495862,
"lon": 13.455868
},
"superior_esquerdo": {
"lat": 52.520711,
"lon": 13.403683
}
}
}
Corresponde à localização
dentro do retângulo com coordenadas no canto inferior direito como inferior_direito
e coordenadas no canto superior esquerdo como superior_esquerdo
.
Raio Geográfico
{
"chave": "localização",
"raio_geográfico": {
"centro": {
"lat": 52.520711,
"lon": 13.403683
},
"raio": 1000.0
}
}
Corresponde à localização
dentro do círculo com o centro em centro
e um raio de raio
metros.
Se múltiplos valores estiverem armazenados, pelo menos um valor deve atender à condição. Essas condições só podem ser aplicadas a cargas úteis que correspondam ao formato de dados geográficos.
Contagem de Valores
Além da comparação direta de valores, a filtragem também pode ser baseada no número de valores.
Por exemplo, dado os seguintes dados:
[
{ "id": 1, "nome": "Produto A", "comentários": ["Muito bom!", "Excelente"] },
{ "id": 2, "nome": "Produto B", "comentários": ["Regular", "Esperando mais", "Bom"] }
]
Podemos buscar apenas itens com mais de dois comentários:
{
"chave": "comentários",
"contagem_valores": {
"gt": 2
}
}
O resultado será:
[{ "id": 2, "nome": "Produto B", "comentários": ["Regular", "Esperando mais", "Bom"] }]
Se o valor armazenado não for um array, é assumido que a contagem de valores é igual a 1.
Está Vazio
Às vezes, é útil filtrar registros que não possuem determinados valores. A condição Está Vazio
pode ajudar a alcançar isso:
{
"está_vazio": {
"chave": "relatórios"
}
}
Essa condição corresponderá a todos os registros onde o campo relatórios
não existe ou tem um valor de null
ou []
.
Está Vazio é frequentemente muito útil quando usado em conjunto com a negação lógica não_deve. Nesse caso, irá selecionar todos os valores não vazios.
Está Nulo
A condição combinação não pode testar valores NULL
. Devemos usar a condição Está Nulo
:
{
"está_nulo": {
"chave": "relatórios"
}
}
Essa condição corresponderá a todos os registros onde o campo relatórios
existe e tem um valor de NULL
.
Possui ID
Esse tipo de consulta não está relacionado a cargas úteis, mas é muito útil em certas situações. Por exemplo, os usuários podem querer marcar certos resultados de pesquisa como irrelevantes, ou podemos querer pesquisar apenas entre pontos específicos.
POST /coleções/{nome_da_coleção}/pontos/rolagem
{
"filtro": {
"deve": [
{ "possui_id": [1,3,5,7,9,11] }
]
}
...
}
Os pontos filtrados serão:
[
{ "id": 1, "cidade": "Londres", "cor": "verde" },
{ "id": 3, "cidade": "Londres", "cor": "azul" },
{ "id": 5, "cidade": "Moscou", "cor": "verde" }
]