Pesquisa de vetor único
Uma vez inseridos os dados, a etapa seguinte consiste em efetuar pesquisas de semelhança da sua coleção no Milvus.
O Milvus permite-lhe efetuar dois tipos de pesquisas, em função do número de campos vectoriais da sua coleção:
- Pesquisa de um único vetor: Se a sua coleção tiver apenas um campo vetorial, utilize o método
search()
para encontrar as entidades mais semelhantes. Este método compara o seu vetor de consulta com os vectores existentes na sua coleção e devolve as IDs das correspondências mais próximas juntamente com as distâncias entre elas. Opcionalmente, também pode devolver os valores do vetor e os metadados dos resultados. - Pesquisa híbrida: Para colecções com dois ou mais campos vectoriais, utilize o método
hybrid_search()
método. Esse método executa várias solicitações de pesquisa ANN (Approximate Nearest Neighbor) e combina os resultados para retornar as correspondências mais relevantes após a reavaliação.
Este guia centra-se na forma de efetuar uma pesquisa de vetor único em Milvus. Para obter detalhes sobre a pesquisa híbrida, consulte Pesquisa híbrida.
Visão geral
Há uma variedade de tipos de pesquisa para atender a diferentes requisitos:
Pesquisa básica: Inclui pesquisa de vetor único, pesquisa de vetor em massa, pesquisa de partição e pesquisa com campos de saída especificados.
Pesquisa filtrada: Aplica critérios de filtragem baseados em campos escalares para refinar os resultados da pesquisa.
Pesquisa de intervalo: Encontra vetores dentro de um intervalo de distância específico do vetor de consulta.
Pesquisa de agrupamento: Agrupa os resultados da pesquisa com base num campo específico para garantir a diversidade dos resultados.
Preparações
O trecho de código abaixo reaproveita o código existente para estabelecer uma conexão com o Milvus e configurar rapidamente uma coleção.
from pymilvus import MilvusClient
import random
# 1. Set up a Milvus client
client = MilvusClient(
uri="http://localhost:19530"
)
# 2. Create a collection
client.create_collection(
collection_name="quick_setup",
dimension=5,
metric_type="IP"
)
# 3. Insert randomly generated vectors
colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
data = []
for i in range(1000):
current_color = random.choice(colors)
data.append({
"id": i,
"vector": [ random.uniform(-1, 1) for _ in range(5) ],
"color": current_color,
"color_tag": f"{current_color}_{str(random.randint(1000, 9999))}"
})
res = client.insert(
collection_name="quick_setup",
data=data
)
print(res)
# Output
#
# {
# "insert_count": 1000,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(990 more items hidden)"
# ]
# }
# 6.1 Create partitions
client.create_partition(
collection_name="quick_setup",
partition_name="red"
)
client.create_partition(
collection_name="quick_setup",
partition_name="blue"
)
# 6.1 Insert data into partitions
red_data = [ {"id": i, "vector": [ random.uniform(-1, 1) for _ in range(5) ], "color": "red", "color_tag": f"red_{str(random.randint(1000, 9999))}" } for i in range(500) ]
blue_data = [ {"id": i, "vector": [ random.uniform(-1, 1) for _ in range(5) ], "color": "blue", "color_tag": f"blue_{str(random.randint(1000, 9999))}" } for i in range(500) ]
res = client.insert(
collection_name="quick_setup",
data=red_data,
partition_name="red"
)
print(res)
# Output
#
# {
# "insert_count": 500,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(490 more items hidden)"
# ]
# }
res = client.insert(
collection_name="quick_setup",
data=blue_data,
partition_name="blue"
)
print(res)
# Output
#
# {
# "insert_count": 500,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(490 more items hidden)"
# ]
# }
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import com.alibaba.fastjson.JSONObject;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.collection.request.GetLoadStateReq;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;
String CLUSTER_ENDPOINT = "http://localhost:19530";
// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
.uri(CLUSTER_ENDPOINT)
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);
// 2. Create a collection in quick setup mode
CreateCollectionReq quickSetupReq = CreateCollectionReq.builder()
.collectionName("quick_setup")
.dimension(5)
.metricType("IP")
.build();
client.createCollection(quickSetupReq);
GetLoadStateReq loadStateReq = GetLoadStateReq.builder()
.collectionName("quick_setup")
.build();
boolean state = client.getLoadState(loadStateReq);
System.out.println(state);
// Output:
// true
// 3. Insert randomly generated vectors into the collection
List<String> colors = Arrays.asList("green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey");
List<JSONObject> data = new ArrayList<>();
for (int i=0; i<1000; i++) {
Random rand = new Random();
String current_color = colors.get(rand.nextInt(colors.size()-1));
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
InsertReq insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.build();
InsertResp insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 1000}
// 6.1. Create a partition
CreatePartitionReq partitionReq = CreatePartitionReq.builder()
.collectionName("quick_setup")
.partitionName("red")
.build();
client.createPartition(partitionReq);
partitionReq = CreatePartitionReq.builder()
.collectionName("quick_setup")
.partitionName("blue")
.build();
client.createPartition(partitionReq);
// 6.2 Insert data into the partition
data = new ArrayList<>();
for (int i=1000; i<1500; i++) {
Random rand = new Random();
String current_color = "red";
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color", current_color);
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.partitionName("red")
.build();
insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 500}
data = new ArrayList<>();
for (int i=1500; i<2000; i++) {
Random rand = new Random();
String current_color = "blue";
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color", current_color);
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.partitionName("blue")
.build();
insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 500}
const { MilvusClient, DataType, sleep } = require("@zilliz/milvus2-sdk-node")
const address = "http://localhost:19530"
// 1. Set up a Milvus Client
client = new MilvusClient({address});
// 2. Create a collection in quick setup mode
await client.createCollection({
collection_name: "quick_setup",
dimension: 5,
metric_type: "IP"
});
// 3. Insert randomly generated vectors
const colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
data = []
for (let i = 0; i < 1000; i++) {
current_color = colors[Math.floor(Math.random() * colors.length)]
data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: current_color,
color_tag: `${current_color}_${Math.floor(Math.random() * 8999) + 1000}`
})
}
var res = await client.insert({
collection_name: "quick_setup",
data: data
})
console.log(res.insert_cnt)
// Output
//
// 1000
//
await client.createPartition({
collection_name: "quick_setup",
partition_name: "red"
})
await client.createPartition({
collection_name: "quick_setup",
partition_name: "blue"
})
// 6.1 Insert data into partitions
var red_data = []
var blue_data = []
for (let i = 1000; i < 1500; i++) {
red_data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: "red",
color_tag: `red_${Math.floor(Math.random() * 8999) + 1000}`
})
}
for (let i = 1500; i < 2000; i++) {
blue_data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: "blue",
color_tag: `blue_${Math.floor(Math.random() * 8999) + 1000}`
})
}
res = await client.insert({
collection_name: "quick_setup",
data: red_data,
partition_name: "red"
})
console.log(res.insert_cnt)
// Output
//
// 500
//
res = await client.insert({
collection_name: "quick_setup",
data: blue_data,
partition_name: "blue"
})
console.log(res.insert_cnt)
// Output
//
// 500
//
Pesquisa básica
Ao enviar um pedido search
, pode fornecer um ou mais valores vectoriais que representam os embeddings da sua consulta e um valor limit
que indica o número de resultados a devolver.
Dependendo dos seus dados e do seu vetor de consulta, pode obter menos do que limit
resultados. Isto acontece quando limit
é maior do que o número de possíveis vectores de correspondência para a sua consulta.
Pesquisa de vetor único
A pesquisa de vetor único é a forma mais simples de search
operações em Milvus, concebida para encontrar os vectores mais semelhantes a um determinado vetor de consulta.
Para efetuar uma pesquisa de vetor único, especifique o nome da coleção de destino, o vetor de consulta e o número de resultados pretendido (limit
). Esta operação devolve um conjunto de resultados que inclui os vectores mais semelhantes, os respectivos IDs e as distâncias do vetor de consulta.
Aqui está um exemplo de pesquisa das 5 principais entidades que são mais semelhantes ao vetor de consulta:
# Single vector search
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
# Replace with your query vector
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}} # Search parameters
)
# Convert the output to a formatted JSON string
result = json.dumps(res, indent=4)
print(result)
// 4. Single vector search
List<List<Float>> query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.topK(3) // The number of results to return
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 3, // The number of results to return
})
console.log(res.results)
Parâmetro | Descrição |
---|---|
collection_name |
O nome de uma coleção existente. |
data |
Uma lista de incorporações vectoriais. Milvus procura as incorporações vectoriais mais semelhantes às especificadas. |
limit |
O número total de entidades a devolver. Pode utilizar este parâmetro em combinação com offset em param para ativar a paginação. A soma deste valor e de offset em param deve ser inferior a 16.384. |
search_params |
As definições de parâmetros específicas para esta operação.
|
Parâmetro | Descrição |
---|---|
collectionName |
O nome de uma coleção existente. |
data |
Uma lista de incorporações vectoriais. Milvus procura as incorporações vectoriais mais semelhantes às especificadas. |
topK |
O número de registos a devolver no resultado da pesquisa. Este parâmetro utiliza a mesma sintaxe que o parâmetro limit, pelo que só deve definir um deles. Pode utilizar este parâmetro em combinação com offset em param para ativar a paginação. A soma deste valor e de offset em param deve ser inferior a 16.384. |
Parâmetro | Descrição |
---|---|
collection_name |
O nome de uma coleção existente. |
data |
Uma lista de entidades vectoriais. O Milvus procura as entidades vectoriais mais semelhantes às especificadas. |
limit |
O número total de entidades a retornar. Você pode usar este parâmetro em combinação com offset em param para habilitar a paginação. A soma deste valor e offset em param deve ser menor que 16.384. |
O resultado é semelhante ao seguinte:
[
[
{
"id": 0,
"distance": 1.4093276262283325,
"entity": {}
},
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {}
},
{
"id": 5,
"distance": 0.7972343564033508,
"entity": {}
},
{
"id": 2,
"distance": 0.5928734540939331,
"entity": {}
}
]
]
{"searchResults": [[
{
"score": 1.263043,
"fields": {
"vector": [
0.9533119,
0.02538395,
0.76714665,
0.35481733,
0.9845762
],
"id": 740
}
},
{
"score": 1.2377806,
"fields": {
"vector": [
0.7411156,
0.08687937,
0.8254139,
0.08370924,
0.99095553
],
"id": 640
}
},
{
"score": 1.1869997,
"fields": {
"vector": [
0.87928146,
0.05324632,
0.6312755,
0.28005534,
0.9542448
],
"id": 455
}
}
]]}
[
{ score: 1.7463608980178833, id: '854' },
{ score: 1.744946002960205, id: '425' },
{ score: 1.7258622646331787, id: '718' }
]
O resultado mostra os 5 vizinhos mais próximos do seu vetor de consulta, incluindo os seus IDs únicos e as distâncias calculadas.
Pesquisa de vetor em massa
Uma pesquisa de vetor em massa estende o conceito de pesquisa de vetor único, permitindo que vários vetores de consulta sejam pesquisados em uma única solicitação. Este tipo de pesquisa é ideal para cenários em que é necessário encontrar vectores semelhantes para um conjunto de vectores de consulta, reduzindo significativamente o tempo e os recursos computacionais necessários.
Numa pesquisa de vectores em massa, é possível incluir vários vectores de consulta no campo data
. O sistema processa estes vectores em paralelo, devolvendo um conjunto de resultados separado para cada vetor de consulta, cada conjunto contendo as correspondências mais próximas encontradas na coleção.
Aqui está um exemplo de pesquisa de dois conjuntos distintos das entidades mais semelhantes de dois vectores de consulta:
# Bulk-vector search
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104],
[0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345]
], # Replace with your query vectors
limit=2, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}} # Search parameters
)
result = json.dumps(res, indent=4)
print(result)
// 5. Batch vector search
query_vectors = Arrays.asList(
Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f),
Arrays.asList(0.19886812562848388f, 0.06023560599112088f, 0.6976963061752597f, 0.2614474506242501f, 0.838729485096104f)
);
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.topK(2)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 5. Batch vector search
var query_vectors = [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]
]
res = await client.search({
collection_name: "quick_setup",
data: query_vectors,
limit: 2,
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 1,
"distance": 1.3017789125442505,
"entity": {}
},
{
"id": 7,
"distance": 1.2419954538345337,
"entity": {}
}
], # Result set 1
[
{
"id": 3,
"distance": 2.3358664512634277,
"entity": {}
},
{
"id": 8,
"distance": 0.5642921924591064,
"entity": {}
}
] # Result set 2
]
// Two sets of vectors are returned as expected
{"searchResults": [
[
{
"score": 1.263043,
"fields": {
"vector": [
0.9533119,
0.02538395,
0.76714665,
0.35481733,
0.9845762
],
"id": 740
}
},
{
"score": 1.2377806,
"fields": {
"vector": [
0.7411156,
0.08687937,
0.8254139,
0.08370924,
0.99095553
],
"id": 640
}
}
],
[
{
"score": 1.8654699,
"fields": {
"vector": [
0.4671427,
0.8378432,
0.98844475,
0.82763994,
0.9729997
],
"id": 638
}
},
{
"score": 1.8581753,
"fields": {
"vector": [
0.735541,
0.60140246,
0.86730254,
0.93152493,
0.98603314
],
"id": 855
}
}
]
]}
[
[
{ score: 2.3590476512908936, id: '854' },
{ score: 2.2896690368652344, id: '59' }
[
{ score: 2.664059638977051, id: '59' },
{ score: 2.59483003616333, id: '854' }
]
]
Os resultados incluem dois conjuntos de vizinhos mais próximos, um para cada vetor de consulta, demonstrando a eficiência das pesquisas de vectores em massa no tratamento de vários vectores de consulta de uma só vez.
Pesquisa por partição
A pesquisa por partição limita o âmbito da sua pesquisa a um subconjunto ou partição específica da sua coleção. Isto é particularmente útil para conjuntos de dados organizados em que os dados são segmentados em divisões lógicas ou categóricas, permitindo operações de pesquisa mais rápidas ao reduzir o volume de dados a pesquisar.
Para efetuar uma pesquisa de partição, basta incluir o nome da partição alvo em partition_names
do seu pedido de pesquisa. Isto especifica que a operação search
apenas considera os vectores dentro da partição especificada.
Aqui está um exemplo de pesquisa de entidades em red
:
# 6.2 Search within a partition
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=5,
search_params={"metric_type": "IP", "params": {"level": 1}},
partition_names=["red"]
)
print(res)
// 6.3 Search within partitions
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.partitionNames(Arrays.asList("red"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 6.2 Search within partitions
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
partition_names: ["red"],
limit: 5,
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 16,
"distance": 0.9200337529182434,
"entity": {}
},
{
"id": 14,
"distance": 0.4505271911621094,
"entity": {}
},
{
"id": 15,
"distance": 0.19924677908420563,
"entity": {}
},
{
"id": 17,
"distance": 0.0075093843042850494,
"entity": {}
},
{
"id": 13,
"distance": -0.14609718322753906,
"entity": {}
}
]
]
{"searchResults": [
[
{
"score": 1.1677284,
"fields": {
"vector": [
0.9986977,
0.17964739,
0.49086612,
0.23155272,
0.98438674
],
"id": 1435
}
},
{
"score": 1.1476475,
"fields": {
"vector": [
0.6952647,
0.13417172,
0.91045254,
0.119336545,
0.9338931
],
"id": 1291
}
},
{
"score": 1.0969629,
"fields": {
"vector": [
0.3363194,
0.028906643,
0.6675426,
0.030419827,
0.9735209
],
"id": 1168
}
},
{
"score": 1.0741848,
"fields": {
"vector": [
0.9980543,
0.36063594,
0.66427994,
0.17359233,
0.94954175
],
"id": 1164
}
},
{
"score": 1.0584627,
"fields": {
"vector": [
0.7187005,
0.12674773,
0.987718,
0.3110777,
0.86093885
],
"id": 1085
}
}
],
[
{
"score": 1.8030131,
"fields": {
"vector": [
0.59726167,
0.7054632,
0.9573117,
0.94529945,
0.8664103
],
"id": 1203
}
},
{
"score": 1.7728865,
"fields": {
"vector": [
0.6672442,
0.60448086,
0.9325822,
0.80272985,
0.8861626
],
"id": 1448
}
},
{
"score": 1.7536311,
"fields": {
"vector": [
0.59663296,
0.77831805,
0.8578314,
0.88818026,
0.9030075
],
"id": 1010
}
},
{
"score": 1.7520742,
"fields": {
"vector": [
0.854198,
0.72294194,
0.9245805,
0.86126596,
0.7969224
],
"id": 1219
}
},
{
"score": 1.7452049,
"fields": {
"vector": [
0.96419,
0.943535,
0.87611496,
0.8268136,
0.79786557
],
"id": 1149
}
}
]
]}
[
{ score: 3.0258803367614746, id: '1201' },
{ score: 3.004319190979004, id: '1458' },
{ score: 2.880324363708496, id: '1187' },
{ score: 2.8246407508850098, id: '1347' },
{ score: 2.797295093536377, id: '1406' }
]
Em seguida, procure entidades em blue
:
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=5,
search_params={"metric_type": "IP", "params": {"level": 1}},
partition_names=["blue"]
)
print(res)
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.partitionNames(Arrays.asList("blue"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
partition_names: ["blue"],
limit: 5,
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 20,
"distance": 2.363696813583374,
"entity": {}
},
{
"id": 26,
"distance": 1.0665391683578491,
"entity": {}
},
{
"id": 23,
"distance": 1.066049575805664,
"entity": {}
},
{
"id": 29,
"distance": 0.8353596925735474,
"entity": {}
},
{
"id": 28,
"distance": 0.7484277486801147,
"entity": {}
}
]
]
{"searchResults": [
[
{
"score": 1.1628494,
"fields": {
"vector": [
0.7442872,
0.046407282,
0.71031404,
0.3544345,
0.9819991
],
"id": 1992
}
},
{
"score": 1.1470042,
"fields": {
"vector": [
0.5505825,
0.04367262,
0.9985836,
0.18922359,
0.93255126
],
"id": 1977
}
},
{
"score": 1.1450152,
"fields": {
"vector": [
0.89994013,
0.052991092,
0.8645576,
0.6406729,
0.95679337
],
"id": 1573
}
},
{
"score": 1.1439825,
"fields": {
"vector": [
0.9253267,
0.15890503,
0.7999555,
0.19126713,
0.898583
],
"id": 1552
}
},
{
"score": 1.1029172,
"fields": {
"vector": [
0.95661926,
0.18777144,
0.38115507,
0.14323527,
0.93137646
],
"id": 1823
}
}
],
[
{
"score": 1.8005109,
"fields": {
"vector": [
0.5953582,
0.7794224,
0.9388869,
0.79825854,
0.9197286
],
"id": 1888
}
},
{
"score": 1.7714822,
"fields": {
"vector": [
0.56805456,
0.89422905,
0.88187534,
0.914824,
0.8944365
],
"id": 1648
}
},
{
"score": 1.7561421,
"fields": {
"vector": [
0.83421993,
0.39865613,
0.92319834,
0.42695504,
0.96633124
],
"id": 1688
}
},
{
"score": 1.7553532,
"fields": {
"vector": [
0.89994013,
0.052991092,
0.8645576,
0.6406729,
0.95679337
],
"id": 1573
}
},
{
"score": 1.7543385,
"fields": {
"vector": [
0.16542226,
0.38248396,
0.9888778,
0.80913955,
0.9501492
],
"id": 1544
}
}
]
]}
[
{ score: 2.8421106338500977, id: '1745' },
{ score: 2.838560104370117, id: '1782' },
{ score: 2.8134000301361084, id: '1511' },
{ score: 2.718268871307373, id: '1679' },
{ score: 2.7014894485473633, id: '1597' }
]
Os dados em red
são diferentes dos dados em blue
. Por conseguinte, os resultados da pesquisa serão limitados à partição especificada, reflectindo as caraterísticas únicas e a distribuição de dados desse subconjunto.
Pesquisa com campos de saída
A pesquisa com campos de saída permite-lhe especificar quais os atributos ou campos dos vectores correspondentes que devem ser incluídos nos resultados da pesquisa.
É possível especificar output_fields
num pedido para devolver resultados com campos específicos.
Aqui está um exemplo de retorno de resultados com valores de atributo color
:
# Search with output fields
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"] # Output fields to return
)
result = json.dumps(res, indent=4)
print(result)
// 7. Search with output fields
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 7. Search with output fields
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
output_fields: ["color"],
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 0,
"distance": 1.4093276262283325,
"entity": {
"color": "pink_8682"
}
},
{
"id": 16,
"distance": 1.0159327983856201,
"entity": {
"color": "yellow_1496"
}
},
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 14,
"distance": 0.9803846478462219,
"entity": {
"color": "green_2899"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
}
]
]
{"searchResults": [
[
{
"score": 1.263043,
"fields": {}
},
{
"score": 1.2377806,
"fields": {}
},
{
"score": 1.1869997,
"fields": {}
},
{
"score": 1.1748955,
"fields": {}
},
{
"score": 1.1720343,
"fields": {}
}
]
]}
[
{ score: 3.036271572113037, id: '59', color: 'orange' },
{ score: 3.0267879962921143, id: '1745', color: 'blue' },
{ score: 3.0069446563720703, id: '854', color: 'black' },
{ score: 2.984386682510376, id: '718', color: 'black' },
{ score: 2.916019916534424, id: '425', color: 'purple' }
]
Juntamente com os vizinhos mais próximos, os resultados da pesquisa incluirão o campo especificado color
, fornecendo um conjunto mais rico de informações para cada vetor correspondente.
Pesquisa filtrada
A pesquisa filtrada aplica filtros escalares a pesquisas vectoriais, permitindo-lhe refinar os resultados da pesquisa com base em critérios específicos. Pode obter mais informações sobre expressões de filtro em Regras de Expressão Booleana e exemplos em Obter e Consulta Escalar.
Usar o operador like
O operador like
melhora as pesquisas de cadeia de caracteres avaliando padrões, incluindo prefixos, infixos e sufixos:
- Correspondência de prefixo: para localizar valores que começam com um prefixo específico, use a sintaxe
'like "prefix%"'
. - Correspondência de infixos: Para encontrar valores que contenham uma sequência específica de caracteres em qualquer parte da cadeia, utilize a sintaxe
'like "%infix%"'
. - Correspondência de sufixos: Para encontrar valores que terminem com um sufixo específico, utilize a sintaxe
'like "%suffix"'
.
Para correspondência de um único carácter, o sublinhado (_
) actua como um wildcard para um carácter, por exemplo, 'like "y_llow"'
.
Caracteres especiais em cadeias de pesquisa
Se pretender procurar uma cadeia de caracteres que contenha caracteres especiais, como sublinhados (_
) ou sinais de percentagem (%
), que são normalmente utilizados como caracteres curinga em padrões de pesquisa (_
para qualquer carácter único e %
para qualquer sequência de caracteres), tem de escapar a estes caracteres para os tratar como caracteres literais. Utilize uma barra invertida (\
) para escapar a caracteres especiais e lembre-se de escapar à própria barra invertida. Por exemplo:
- Para procurar um sublinhado literal, utilize
\\_
. - Para procurar um sinal de percentagem literal, utilize
\\%
.
Por isso, se precisar de pesquisar o texto "_version_"
, a sua consulta deve ser formatada como 'like "\\_version\\_"'
para garantir que os sublinhados são tratados como parte do termo de pesquisa e não como caracteres selvagens.
Filtrar resultados cuja cor é prefixada com vermelho:
# Search with filter
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"], # Output fields to return
filter='color like "red%"'
)
result = json.dumps(res, indent=4)
print(result)
// 8. Filtered search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.filter("color_tag like \"red%\"")
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 8. Filtered search
// 8.1 Filter with "like" operator and prefix wildcard
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
filters: "color_tag like \"red%\"",
output_fields: ["color_tag"]
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
},
{
"id": 6,
"distance": -0.4113418459892273,
"entity": {
"color": "red_9392"
}
}
]
]
{"searchResults": [
[
{
"score": 1.1869997,
"fields": {"color_tag": "red_3026"}
},
{
"score": 1.1677284,
"fields": {"color_tag": "red_9030"}
},
{
"score": 1.1476475,
"fields": {"color_tag": "red_3744"}
},
{
"score": 1.0969629,
"fields": {"color_tag": "red_4168"}
},
{
"score": 1.0741848,
"fields": {"color_tag": "red_9678"}
}
]
]}
[
{ score: 2.5080761909484863, id: '1201', color_tag: 'red_8904' },
{ score: 2.491129159927368, id: '425', color_tag: 'purple_8212' },
{ score: 2.4889798164367676, id: '1458', color_tag: 'red_6891' },
{ score: 2.42964243888855, id: '724', color_tag: 'black_9885' },
{ score: 2.4004223346710205, id: '854', color_tag: 'black_5990' }
]
Filtra os resultados cuja cor contém as letras ll em qualquer parte da cadeia de caracteres:
# Infix match on color field
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"], # Output fields to return
filter='color like "%ll%"' # Filter on color field, infix match on "ll"
)
result = json.dumps(res, indent=4)
print(result)
// 8. Filtered search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.filter("color like \"%ll%\"")
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 8. Filtered search
// 8.1 Filter with "like" operator and prefix wildcard
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
filters: "color_tag like \"%ll%\"",
output_fields: ["color_tag"]
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 5,
"distance": 0.7972343564033508,
"entity": {
"color": "yellow_4222"
}
}
]
]
{"searchResults": [
[
{
"score": 1.1869997,
"fields": {"color_tag": "yellow_4222"}
}
]
]}
[
{ score: 2.5080761909484863, id: '1201', color_tag: 'yellow_4222' }
]
Pesquisa de intervalo
A pesquisa de intervalo permite-lhe encontrar vectores que se encontram dentro de um intervalo de distância especificado do vetor de consulta.
Definindo radius
e, opcionalmente, range_filter
, pode ajustar a amplitude da pesquisa para incluir vectores que são algo semelhantes ao vetor de consulta, proporcionando uma visão mais abrangente de potenciais correspondências.
radius
: Define o limite exterior do espaço de pesquisa. Apenas os vectores que estão dentro desta distância do vetor de consulta são considerados potenciais correspondências.range_filter
: Enquantoradius
define o limite exterior da pesquisa,range_filter
pode ser utilizado opcionalmente para definir um limite interior, criando um intervalo de distância dentro do qual os vectores têm de cair para serem considerados correspondências.
# Conduct a range search
search_params = {
"metric_type": "IP",
"params": {
"radius": 0.8, # Radius of the search circle
"range_filter": 1.0 # Range filter to filter out vectors that are not within the search circle
}
}
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=3, # Max. number of search results to return
search_params=search_params, # Search parameters
output_fields=["color"], # Output fields to return
)
result = json.dumps(res, indent=4)
print(result)
// 9. Range search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.searchParams(Map.of("radius", 0.1, "range", 1.0))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 9. Range search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
params: {
radius: 0.1,
range: 1.0
},
output_fields: ["color_tag"]
})
console.log(res.results)
O resultado é semelhante ao seguinte:
[
[
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 14,
"distance": 0.9803846478462219,
"entity": {
"color": "green_2899"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
}
]
]
{"searchResults": [
[
{
"score": 1.263043,
"fields": {"color_tag": "green_2052"}
},
{
"score": 1.2377806,
"fields": {"color_tag": "purple_3709"}
},
{
"score": 1.1869997,
"fields": {"color_tag": "red_3026"}
},
{
"score": 1.1748955,
"fields": {"color_tag": "black_1646"}
},
{
"score": 1.1720343,
"fields": {"color_tag": "green_4853"}
}
]
]}
[
{ score: 2.3387961387634277, id: '718', color_tag: 'black_7154' },
{ score: 2.3352415561676025, id: '1745', color_tag: 'blue_8741' },
{ score: 2.290485382080078, id: '1408', color_tag: 'red_2324' },
{ score: 2.285870313644409, id: '854', color_tag: 'black_5990' },
{ score: 2.2593345642089844, id: '1309', color_tag: 'red_8458' }
]
Verificará que todas as entidades devolvidas têm uma distância que se situa no intervalo de 0,8 a 1,0 do vetor de consulta.
As definições de parâmetros para radius
e range_filter
variam com o tipo de métrica em uso.
Tipo de métrica | Caraterísticas | Intervalo Definições de pesquisa |
---|---|---|
L2 | As distâncias L2 mais pequenas indicam uma maior semelhança. | Para excluir os vectores mais próximos dos resultados, certifique-se de que:range_filter <= distância < radius |
IP | As distâncias IP maiores indicam uma maior semelhança. | Para excluir os vectores mais próximos dos resultados, certifique-se de que:radius < distância <= range_filter |
COSINE | Um valor de cosseno maior indica maior similaridade. | Para excluir os vectores mais próximos dos resultados, certifique-se de que:radius < distance <= range_filter |
JACCARD | Distâncias de Jaccard menores indicam maior similaridade. | Para excluir os vectores mais próximos dos resultados, certifique-se de que:range_filter <= distance < radius |
HAMMING | Distâncias de Hamming menores indicam maior similaridade. | Para excluir os vectores mais próximos dos resultados, certifique-se de que:range_filter <= distance < radius |
Para saber mais sobre os tipos de métricas de distância, consulte Métricas de similaridade.
Pesquisa de agrupamento
No Milvus, a pesquisa de agrupamento foi concebida para melhorar a abrangência e a precisão dos resultados de pesquisa.
Considere um cenário no RAG, onde cargas de documentos são divididas em várias passagens, e cada passagem é representada por uma incorporação de vetor. Os utilizadores pretendem encontrar as passagens mais relevantes para que os LLMs possam ser solicitados com precisão. A função de pesquisa normal do Milvus pode satisfazer este requisito, mas pode dar origem a resultados muito distorcidos e tendenciosos: a maioria das passagens provém apenas de alguns documentos e a abrangência dos resultados da pesquisa é muito fraca. Isto pode prejudicar seriamente a precisão ou mesmo a correção dos resultados fornecidos pelo LLM e influenciar negativamente a experiência dos utilizadores do LLM.
A pesquisa por agrupamento pode resolver eficazmente este problema. Ao passar um campo_por_grupo e um tamanho_de_grupo, os utilizadores do Milvus podem agrupar os resultados da pesquisa em vários grupos e garantir que o número de entidades de cada grupo não excede um tamanho_de_grupo específico. Esta funcionalidade pode aumentar significativamente a abrangência e a equidade dos resultados da pesquisa, melhorando visivelmente a qualidade dos resultados do LLM.
Aqui está o código de exemplo para agrupar resultados de pesquisa por campo:
# Connect to Milvus
client = MilvusClient(uri='http://localhost:19530') # Milvus server address
# Load data into collection
client.load_collection("group_search") # Collection name
# Group search results
res = client.search(
collection_name="group_search", # Collection name
data=[[0.14529211512077012, 0.9147257273453546, 0.7965055218724449, 0.7009258593102812, 0.5605206522382088]], # Query vector
search_params={
"metric_type": "L2",
"params": {"nprobe": 10},
}, # Search parameters
limit=5, # Max. number of groups to return
group_by_field="doc_id", # Group results by document ID
group_size=2, # returned at most 2 passages per document, the default value is 1
group_strict_size=True, # ensure every group contains exactly 3 passages
output_fields=["doc_id", "passage_id"]
)
# Retrieve the values in the `doc_id` column
doc_ids = [result['entity']['doc_id'] for result in res[0]]
passage_ids = [result['entity']['passage_id'] for result in res[0]]
print(doc_ids)
print(passage_ids)
O resultado é semelhante ao seguinte:
["doc_11", "doc_11", "doc_7", "doc_7", "doc_3", "doc_3", "doc_2", "doc_2", "doc_8", "doc_8"]
[5, 10, 11, 10, 9, 6, 5, 4, 9, 2]
Na saída dada, pode observar-se que, para cada documento, são recuperadas exatamente duas passagens e que um total de 5 documentos compõem coletivamente os resultados.
Para comparação, vamos comentar os parâmetros relacionados com o grupo e efetuar uma pesquisa normal:
# Connect to Milvus
client = MilvusClient(uri='http://localhost:19530') # Milvus server address
# Load data into collection
client.load_collection("group_search") # Collection name
# Search without `group_by_field`
res = client.search(
collection_name="group_search", # Collection name
data=query_passage_vector, # Replace with your query vector
search_params={
"metric_type": "L2",
"params": {"nprobe": 10},
}, # Search parameters
limit=5, # Max. number of search results to return
# group_by_field="doc_id", # Group results by document ID
# group_size=2,
# group_strict_size=True,
output_fields=["doc_id", "passage_id"]
)
# Retrieve the values in the `doc_id` column
doc_ids = [result['entity']['doc_id'] for result in res[0]]
passage_ids = [result['entity']['passage_id'] for result in res[0]]
print(doc_ids)
print(passage_ids)
O resultado é semelhante ao seguinte:
["doc_11", "doc_11", "doc_11", "doc_11", "doc_11"]
[1, 10, 3, 12, 9]
No resultado apresentado, pode observar-se que o "doc_11" dominou completamente os resultados da pesquisa, ofuscando os parágrafos de alta qualidade de outros documentos, o que pode ser um mau aviso para o LLM.
Mais um ponto a notar: por defeito, a pesquisa_de_grupo devolverá resultados instantaneamente quando tiver grupos suficientes, o que pode levar a que o número de resultados em cada grupo não seja suficiente para satisfazer o tamanho_do_grupo. Se se preocupa com o número de resultados para cada grupo, defina group_strict_size=True como mostrado no código acima. Isto fará com que o Milvus se esforce por obter resultados suficientes para cada grupo, com um ligeiro custo para o desempenho.
Limitações
Indexação: Este recurso de agrupamento funciona apenas para coleções que são indexadas com o tipo HNSW, IVF_FLAT ou FLAT. Para obter mais informações, consulte Índice na memória.
Vetor: Atualmente, a pesquisa de agrupamento não suporta um campo de vetor do tipo BINARY_VECTOR. Para obter mais informações sobre tipos de dados, consulte Tipos de dados suportados.
Campo: Atualmente, a pesquisa de agrupamento permite apenas uma única coluna. Não é possível especificar vários nomes de campo na configuração
group_by_field
. Além disso, a pesquisa de agrupamento é incompatível com os tipos de dados JSON, FLOAT, DOUBLE, ARRAY ou campos de vetor.Impacto no desempenho: Lembre-se de que o desempenho diminui com o aumento da contagem de vetores de consulta. Usando um cluster com 2 núcleos de CPU e 8 GB de memória como exemplo, o tempo de execução da pesquisa de agrupamento aumenta proporcionalmente com o número de vetores de consulta de entrada.
Funcionalidade: Atualmente, a pesquisa de agrupamento não é suportada pela pesquisa de intervalo, iteradores de pesquisa
Parâmetros de pesquisa
Nas pesquisas acima, exceto na pesquisa de intervalos, aplicam-se os parâmetros de pesquisa predefinidos. Em casos normais, não é necessário definir manualmente os parâmetros de pesquisa.
# In normal cases, you do not need to set search parameters manually
# Except for range searches.
search_parameters = {
'metric_type': 'L2',
'params': {
'nprobe': 10,
'level': 1,
'radius': 1.0
'range_filter': 0.8
}
}
A tabela seguinte apresenta todas as definições possíveis nos parâmetros de pesquisa.
Nome do parâmetro | Descrição do parâmetro |
---|---|
metric_type | Como medir a semelhança entre as incorporações de vetor. Os valores possíveis são IP , L2 , COSINE , JACCARD , e HAMMING , e a predefinição é a do ficheiro de índice carregado. |
params.nprobe | Número de unidades a consultar durante a pesquisa. O valor situa-se no intervalo [1, nlist[1]]. |
params.level | Nível de precisão da pesquisa. Os valores possíveis são 1 , 2 , e 3 , e a predefinição é 1 . Valores mais elevados produzem resultados mais exactos mas um desempenho mais lento. |
params.radius | Define o limite externo do seu espaço de pesquisa. Somente os vetores que estão dentro dessa distância do vetor de consulta são considerados correspondências potenciais. O intervalo de valores é determinado pelo parâmetro metric_type . Por exemplo, se metric_type estiver definido como L2 , o intervalo de valores válido é [0, ∞] . Se metric_type estiver definido como COSINE , o intervalo de valores válido é [-1, 1] . Para obter mais informações, consulte Métricas de similaridade. |
params.range_filter | Enquanto radius define o limite exterior da pesquisa, range_filter pode ser utilizado opcionalmente para definir um limite interior, criando um intervalo de distância dentro do qual os vectores têm de se enquadrar para serem considerados correspondências.O intervalo de valores é determinado pelo parâmetro metric_type . Por exemplo, se metric_type estiver definido como L2 , o intervalo de valores válido é [0, ∞] . Se metric_type estiver definido como COSINE , o intervalo de valores válido é [-1, 1] . Para obter mais informações, consulte Métricas de similaridade. |
notas
[1] Número de unidades de cluster após a indexação. Ao indexar uma coleção, o Milvus subdivide os dados vectoriais em várias unidades de agrupamento, cujo número varia com as definições de indexação actuais.
[2] Número de entidades a devolver numa pesquisa.