Поиск по первичному ключуCompatible with Milvus 2.6.9+

При проведении поиска по схожести вам всегда предлагается указать один или несколько векторов запроса, даже если эти векторы уже присутствуют в целевой коллекции. Чтобы избежать извлечения векторов перед началом поиска, можно вместо них использовать первичные ключи.

Обзор

На платформах электронной коммерции пользователи могут вводить ключевое слово для поиска соответствующих товаров. Как только пользователь переходит на страницу с подробной информацией о товаре, платформа также отображает внизу страницы список похожих товаров для тех, кто хочет их сравнить.

Рекомендации сортируются по степени схожести с ключевым словом или текущим товаром. Для этого разработчикам платформы необходимо извлечь векторное представление ключевого слова или текущего товара из Milvus до начала собственно поиска по схожести, что увеличивает количество обменных циклов между платформой и Milvus и приводит к передаче по сети большого количества высокоразмерных чисел с плавающей запятой.

Чтобы упростить логику взаимодействия между вашими приложениями и Milvus, сократить количество циклов обмена данными и избежать передачи по сети больших объемов высокоразмерных чисел с плавающей запятой, рекомендуется использовать поиск по первичным ключам.

При поиске по первичному ключу вам не нужно предоставлять какие-либо векторы запроса. Вместо этого вам предлагается указать первичные ключи (ids) сущностей, содержащих векторы запроса.

Ограничения

  • Поиск с использованием первичных ключей применим ко всем векторным типам данных, за исключением разреженных векторных полей, полученных из полей VarChar, как, например, в функциях BM25.

  • Вы можете использовать первичные ключи вместо векторов запроса в фильтрованном поиске, поиске по диапазону и групповом поиске, при желании с включенной пагинацией. Однако эта функция не распространяется на гибридный поиск и итераторы поиска.

  • Для поиска по схожести с использованием списков вложений по-прежнему необходимо извлечь векторы запроса, сгруппировать их в списки вложений и запустить поиск.

  • В RESTful API нельзя использовать первичные ключи вместо векторов запроса.

  • При наличии несуществующих первичных ключей или ключей в некорректном формате Milvus выдаст ошибки.

  • Первичные ключи и векторы запросов являются взаимоисключающими. Указание обоих также приводит к ошибкам.

Примеры

В следующих примерах предполагается, что все указанные идентификаторы типа Int64 присутствуют в целевой коллекции.

Первичные ключи не используются для фильтрации; они используются только для поиска векторов.

Чтобы выполнить базовый поиск по первичному ключу, просто замените векторы запроса на первичные ключи.

from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.search(
    collection_name="quick_setup",
    anns_field="vector",
    ids=[551, 296, 43], # a list of primary keys
    limit=3,
    search_params={"metric_type": "IP"}
)

for hits in res:
    for hit in hits:
        print(hit)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.response.SearchResp;
import io.milvus.v2.common.IndexParam;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build());

List<Object> ids = Arrays.asList(551L, 296L, 43L);
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("quick_setup")
        .annsField("vector")
        .ids(ids)
        .limit(3)
        .metricType(IndexParam.MetricType.IP)
        .build());
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
    System.out.println("TopK results:");
    for (SearchResp.SearchResult result : results) {
        System.out.println(result);
    }
}
import { MilvusClient } from "@zilliz/milvus2-sdk-node";

const client = new MilvusClient({
    address: "http://localhost:19530",
    token: "root:Milvus",
});

const res = await client.search({
    collection_name: "quick_setup",
    anns_field: "vector",
    ids: [551, 296, 43], // a list of primary keys
    limit: 3,
    metric_type: "IP",
});

console.log(res.results);
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v3/column"
    "github.com/milvus-io/milvus/client/v3/milvusclient"
)

ctx := context.Background()

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: "localhost:19530",
    APIKey:  "root:Milvus",
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

ids := column.NewColumnInt64("id", []int64{551, 296, 43}) // a list of primary keys
resultSets, err := client.Search(ctx, milvusclient.NewSearchByIDsOption(
    "quick_setup", // collectionName
    3,             // limit
    ids,
).WithANNSField("vector").
    WithSearchParam("metric_type", "IP"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs)
    fmt.Println("Scores: ", resultSet.Scores)
}
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer root:Milvus" \
  -H "Request-Timeout: 10" \
  -d '{
    "collectionName": "quick_setup",
    "annsField": "vector",
    "ids": [551, 296, 43],
    "limit": 3
  }' 
auto searchRequest = milvus::SearchRequest()
                         .WithCollectionName("quick_setup")
                         .WithAnnsField("vector")
                         .WithIDs({551, 296, 43})
                         .WithLimit(3)
                         .WithMetricType(milvus::MetricType::IP);

milvus::SearchResponse searchResponse;
auto status = client->Search(searchRequest, searchResponse);
if (!status.IsOk()) {
    std::cerr << "Search failed: " << status.Message() << std::endl;
    return;
}

for (const auto& result : searchResponse.Results().Results()) {
    const auto ids = result.Ids().IntIDArray();
    for (size_t i = 0; i < result.Scores().size(); ++i) {
        std::cout << "id=" << ids[i] << ", score=" << result.Scores()[i] << std::endl;
    }
}

Пример 2: Поиск с фильтрацией по первичным ключам

В следующем примере предполагается, что color и likes — это два поля, определённые в схеме целевой коллекции.

res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43], #
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"],
    limit=3,
)
List<Object> ids = Arrays.asList(551L, 296L, 43L);
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .ids(ids)
        .filter("color like \"red%\" and likes > 50")
        .limit(3)
        .outputFields(Arrays.asList("color", "likes"))
        .build());
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
    System.out.println("TopK results:");
    for (SearchResp.SearchResult result : results) {
        System.out.println(result);
    }
}
const res = await client.search({
    collection_name: "my_collection",
    ids: [551, 296, 43],
    filter: 'color like "red%" and likes > 50',
    output_fields: ["id", "color", "likes"],
    limit: 3,
});

console.log(res.results);
ids := column.NewColumnInt64("id", []int64{551, 296, 43})
resultSets, err := client.Search(ctx, milvusclient.NewSearchByIDsOption(
    "my_collection", // collectionName
    3,               // limit
    ids,
).WithFilter(`color like "red%" and likes > 50`).
    WithOutputFields("color", "likes"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs)
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("color: ", resultSet.GetColumn("color"))
    fmt.Println("likes: ", resultSet.GetColumn("likes"))
}
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer root:Milvus" \
  -H "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "annsField": "vector",
    "ids": [551, 296, 43],
    "filter": "color like \\"red%\\" and likes > 50",
    "outputFields": ["color", "likes"],
    "limit": 3
  }' 
auto searchRequest = milvus::SearchRequest()
                         .WithCollectionName("my_collection")
                         .WithIDs({551, 296, 43})
                         .WithFilter(R"(color like "red%" and likes > 50)")
                         .WithOutputFields({"color", "likes"})
                         .WithLimit(3);

milvus::SearchResponse searchResponse;
auto status = client->Search(searchRequest, searchResponse);
if (!status.IsOk()) {
    std::cerr << "Search failed: " << status.Message() << std::endl;
    return;
}

for (const auto& result : searchResponse.Results().Results()) {
    const auto ids = result.Ids().IntIDArray();
    const auto colors = result.OutputField<milvus::VarCharFieldData>("color");
    const auto likes = result.OutputField<milvus::Int64FieldData>("likes");
    for (size_t i = 0; i < result.Scores().size(); ++i) {
        std::cout << "id=" << ids[i]
                  << ", score=" << result.Scores()[i]
                  << ", color=" << colors->Data()[i]
                  << ", likes=" << likes->Data()[i] << std::endl;
    }
}

Пример 3: Поиск по диапазону с использованием первичных ключей

res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43],
    limit=3,
    search_params={
        "params": {
            "radius": 0.4,
            "range_filter": 0.6
        }
    }
)
Map<String, Object> params = new HashMap<>();
params.put("radius", "0.4");
params.put("range_filter", "0.6");

List<Object> ids = Arrays.asList(551L, 296L, 43L);
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .ids(ids)
        .limit(3)
        .searchParams(params)
        .build());
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
    System.out.println("TopK results:");
    for (SearchResp.SearchResult result : results) {
        System.out.println(result);
    }
}
const res = await client.search({
    collection_name: "my_collection",
    ids: [551, 296, 43],
    limit: 3,
    params: {
        radius: 0.4,
        range_filter: 0.6,
    },
});

console.log(res.results);
annParam := index.NewCustomAnnParam()
annParam.WithRadius(0.4)
annParam.WithRangeFilter(0.6)

ids := column.NewColumnInt64("id", []int64{551, 296, 43})
resultSets, err := client.Search(ctx, milvusclient.NewSearchByIDsOption(
    "my_collection", // collectionName
    3,               // limit
    ids,
).WithANNSField("vector").
    WithAnnParam(annParam))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs)
    fmt.Println("Scores: ", resultSet.Scores)
}
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer root:Milvus" \
  -H "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "annsField": "vector",
    "ids": [551, 296, 43],
    "limit": 3,
    "searchParams": {
      "params": {
        "radius": 0.4,
        "range_filter": 0.6
      }
    }
  }' 
auto searchRequest = milvus::SearchRequest()
                         .WithCollectionName("my_collection")
                         .WithAnnsField("vector")
                         .WithIDs({551, 296, 43})
                         .WithRadius(0.4)
                         .WithRangeFilter(0.6)
                         .WithLimit(3);

milvus::SearchResponse searchResponse;
auto status = client->Search(searchRequest, searchResponse);
if (!status.IsOk()) {
    std::cerr << "Search failed: " << status.Message() << std::endl;
    return;
}

for (const auto& result : searchResponse.Results().Results()) {
    const auto ids = result.Ids().IntIDArray();
    for (size_t i = 0; i < result.Scores().size(); ++i) {
        std::cout << "id=" << ids[i] << ", score=" << result.Scores()[i] << std::endl;
    }
}

Пример 4: Групповой поиск с использованием первичных ключей

В следующем примере предполагается, что « docId » является полем, определённым в схеме целевой коллекции.

res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43],
    limit=3,
    group_by_field="docId",
    output_fields=["docId"]
)
List<Object> ids = Arrays.asList(551L, 296L, 43L);
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .ids(ids)
        .limit(3)
        .groupByFieldName("docId")
        .outputFields(Collections.singletonList("docId"))
        .build());
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
    System.out.println("TopK results:");
    for (SearchResp.SearchResult result : results) {
        System.out.println(result);
    }
}
const res = await client.search({
    collection_name: "my_collection",
    ids: [551, 296, 43],
    limit: 3,
    group_by_field: "docId",
    output_fields: ["id", "docId"],
});

console.log(res.results);
ids := column.NewColumnInt64("id", []int64{551, 296, 43})
resultSets, err := client.Search(ctx, milvusclient.NewSearchByIDsOption(
    "my_collection", // collectionName
    3,               // limit
    ids,
).WithGroupByField("docId").
    WithOutputFields("docId"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs)
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("docId: ", resultSet.GetColumn("docId"))
}
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer root:Milvus" \
  -H "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "annsField": "vector",
    "ids": [551, 296, 43],
    "limit": 3,
    "groupingField": "docId",
    "outputFields": ["docId"]
  }' 
auto searchRequest = milvus::SearchRequest()
                         .WithCollectionName("my_collection")
                         .WithAnnsField("vector")
                         .WithIDs({551, 296, 43})
                         .WithGroupByField("docId")
                         .WithOutputFields({"docId"})
                         .WithLimit(3);

milvus::SearchResponse searchResponse;
auto status = client->Search(searchRequest, searchResponse);
if (!status.IsOk()) {
    std::cerr << "Search failed: " << status.Message() << std::endl;
    return;
}

for (const auto& result : searchResponse.Results().Results()) {
    const auto ids = result.Ids().IntIDArray();
    const auto docIds = result.OutputField<milvus::Int64FieldData>("docId");
    for (size_t i = 0; i < result.Scores().size(); ++i) {
        std::cout << "id=" << ids[i]
                  << ", score=" << result.Scores()[i]
                  << ", docId=" << docIds->Data()[i] << std::endl;
    }
}