探索数据

在掌握了搜索相关的概念后,您可以开始以其他方式探索数据。Qdrant 提供了一系列 API,允许您以不同的方式查找相似向量,以及查找最不相似的向量。这些工具对于推荐系统、数据探索和数据清洗非常有用。

推荐 API

除了常规搜索外,Qdrant 还允许您根据多个正例和负例进行搜索。该 API 名为 recommend。示例可以是点 ID,这样您就可以利用已经编码的对象;从 v1.6 版本开始,您还可以使用原始向量作为输入,这样您就可以在不上传点的情况下即时创建向量。

REST API - API 模式定义可在此处查看

POST /collections/{collection_name}/points/query
{
  "query": {
    "recommend": {
      "positive": [100, 231],
      "negative": [718, [0.2, 0.3, 0.4, 0.5]],
      "strategy": "average_vector"
    }
  },
  "filter": {
    "must": [
      {
        "key": "city",
        "match": {
          "value": "London"
        }
      }
    ]
  }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.query_points(
    collection_name="{collection_name}",
    query=models.RecommendQuery(
        recommend=models.RecommendInput(
            positive=[100, 231],
            negative=[718, [0.2, 0.3, 0.4, 0.5]],
            strategy=models.RecommendStrategy.AVERAGE_VECTOR,
        )
    ),
    query_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="city",
                match=models.MatchValue(
                    value="London",
                ),
            )
        ]
    ),
    limit=3,
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: {
        recommend: {
            positive: [100, 231],
            negative: [718, [0.2, 0.3, 0.4, 0.5]],
            strategy: "average_vector"
        }
    },
    filter: {
        must: [
            {
                key: "city",
                match: {
                    value: "London",
                },
            },
        ],
    },
    limit: 3
});
use qdrant_client::qdrant::{
    Condition, Filter, QueryPointsBuilder, RecommendInputBuilder, RecommendStrategy,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("https://:6334").build()?;

client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(
                RecommendInputBuilder::default()
                    .add_positive(100)
                    .add_positive(231)
                    .add_positive(vec![0.2, 0.3, 0.4, 0.5])
                    .add_negative(718)
                    .strategy(RecommendStrategy::AverageVector)
                    .build(),
            )
            .limit(3)
            .filter(Filter::must([Condition::matches(
                "city",
                "London".to_string(),
            )])),
    )
    .await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.QueryFactory.recommend;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.RecommendInput;
import io.qdrant.client.grpc.Points.RecommendStrategy;
import java.util.List;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .setQuery(recommend(RecommendInput.newBuilder()
                .addAllPositive(List.of(vectorInput(100), vectorInput(200), vectorInput(100.0f, 231.0f)))
                .addAllNegative(List.of(vectorInput(718), vectorInput(0.2f, 0.3f, 0.4f, 0.5f)))
                .setStrategy(RecommendStrategy.AverageVector)
                .build()))
        .setFilter(Filter.newBuilder().addMust(matchKeyword("city", "London")))
        .setLimit(3)
        .build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
    collectionName: "{collection_name}",
    query: new RecommendInput {
        Positive = { 100, 231 },
        Negative = { 718 }
    },
    filter: MatchKeyword("city", "London"),
    limit: 3
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
    CollectionName: "{collection_name}",
    Query: qdrant.NewQueryRecommend(&qdrant.RecommendInput{
        Positive: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
            qdrant.NewVectorInputID(qdrant.NewIDNum(231)),
        },
        Negative: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
        },
    }),
    Filter: &qdrant.Filter{
        Must: []*qdrant.Condition{
            qdrant.NewMatch("city", "London"),
        },
    },
})

此 API 的返回结果示例如下

{
  "result": [
    { "id": 10, "score": 0.81 },
    { "id": 14, "score": 0.75 },
    { "id": 11, "score": 0.73 }
  ],
  "status": "ok",
  "time": 0.001
}

用于获取推荐的算法是从可用的 strategy(策略)选项中选择的。每种策略都有其优缺点,因此请进行实验并选择最适合您情况的一种。

平均向量策略

Qdrant 中添加的默认且首个策略称为 average_vector。它会对输入示例进行预处理,创建一个用于搜索的单一向量。由于预处理步骤非常快,因此该策略的性能与常规搜索相当。这种推荐背后的直觉是,每个向量分量都代表数据的独立特征,因此,通过对示例取平均值,我们应该能得到很好的推荐。

生成搜索向量的方法是:首先分别对所有正例和负例取平均值,然后使用以下公式将它们组合成一个单一向量:

avg_positive + avg_positive - avg_negative

如果没有负例,搜索向量将直接等于 avg_positive

这是默认的隐式策略,但您也可以通过在推荐请求中设置 "strategy": "average_vector" 来明确指定它。

最佳得分策略

v1.6.0 版本起可用

v1.6 引入了一种名为 best_score 的新策略。其核心思想是:寻找相似向量的最佳方法是找到那些更接近正例,同时避开那些更接近负例的向量。其工作方式是:将每个候选向量与每个示例进行对比,然后选择最佳的正分和最佳的负分。最终得分通过以下公式确定:

// Sigmoid function to normalize the score between 0 and 1
let sigmoid = |x| 0.5 * (1.0 + (x / (1.0 + x.abs())));

let score = if best_positive_score > best_negative_score {
    sigmoid(best_positive_score)
} else {
    -sigmoid(best_negative_score)
};

由于我们在搜索的每一步都要计算与每个示例的相似度,因此该策略的性能会受示例数量的线性影响。这意味着您提供的示例越多,搜索速度就越慢。然而,这种策略非常强大,且对嵌入(embedding)模型更具通用性。

要使用此算法,您需要在推荐请求中设置 "strategy": "best_score"

仅使用负例

best_score 策略的一个有益副作用是,您可以仅使用负例。这将允许您找到与您提供的示例最不相似的向量。这对于在数据中查找异常值或查找与给定向量最不相似的向量非常有用。

将“仅负例”与过滤功能结合使用,是进行数据探索和清洗的强大工具。

得分求和策略

另一种同时使用多个查询向量的策略是直接将它们对候选向量的得分相加。在 Qdrant 中,这被称为 sum_scores 策略。

该策略在 这篇论文 中被使用,由 UKP Labhessian.aicohere.ai 共同提出,用于将相关性反馈整合到后续搜索中。在论文中,当使用 2-8 个正反馈文档时,这使 nDCG@20 的性能提高了 5.6%。

该策略实现的公式为:

$$ s_i = \sum_{v_q\in Q^+}s(v_q, v_i) - \sum_{v_q\in Q^-}s(v_q, v_i) $$

其中 $Q^+$ 是正例集合,$Q^-$ 是负例集合,$s(v_q, v_i)$ 是向量 $v_q$ 对向量 $v_i$ 的得分。

best_score 一样,此策略也允许仅使用负例。

多向量

自 v0.10.0 起可用

如果集合是在创建时包含了多个向量,则应在推荐请求中指定向量的名称。

POST /collections/{collection_name}/points/query
{
  "query": {
    "recommend": {
      "positive": [100, 231],
      "negative": [718]
    }
  },
  "using": "image",
  "limit": 10
}
client.query_points(
    collection_name="{collection_name}",
    query=models.RecommendQuery(
        recommend=models.RecommendInput(
            positive=[100, 231],
            negative=[718],
        )
    ),
    using="image",
    limit=10,
)
client.query("{collection_name}", {
    query: {
        recommend: {
            positive: [100, 231],
            negative: [718],
        }
    },
    using: "image",
    limit: 10
});
use qdrant_client::qdrant::{QueryPointsBuilder, RecommendInputBuilder};

client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(
                RecommendInputBuilder::default()
                    .add_positive(100)
                    .add_positive(231)
                    .add_negative(718)
                    .build(),
            )
            .limit(10)
            .using("image"),
    )
    .await?;
import static io.qdrant.client.QueryFactory.recommend;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.RecommendInput;
import java.util.List;

client.queryAsync(QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .setQuery(recommend(RecommendInput.newBuilder()
                .addAllPositive(List.of(vectorInput(100), vectorInput(231)))
                .addAllNegative(List.of(vectorInput(718)))
                .build()))
        .setUsing("image")
        .setLimit(10)
        .build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
    collectionName: "{collection_name}",
    query: new RecommendInput {
        Positive = { 100, 231 },
        Negative = { 718 }
    },
    usingVector: "image",
    limit: 10
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
    CollectionName: "{collection_name}",
    Query: qdrant.NewQueryRecommend(&qdrant.RecommendInput{
        Positive: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
            qdrant.NewVectorInputID(qdrant.NewIDNum(231)),
        },
        Negative: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
        },
    }),
    Using: qdrant.PtrOf("image"),
})

参数 using 指定了推荐时要使用的存储向量。

从另一个集合中查找向量

v0.11.6 版本起可用

如果您拥有维度相同的向量集合,并且希望基于另一个集合的向量在当前集合中查找推荐,可以使用 lookup_from 参数。

这在“物品到用户”推荐场景中非常有用。虽然用户和物品的嵌入具有相同的向量参数(距离类型和维度),但它们通常存储在不同的集合中。

POST /collections/{collection_name}/points/query
{
  "query": {
    "recommend": {
      "positive": [100, 231],
      "negative": [718]
    }
  },
  "limit": 10,
  "lookup_from": {
    "collection": "{external_collection_name}",
    "vector": "{external_vector_name}"
  }
}
client.query_points(
    collection_name="{collection_name}",
    query=models.RecommendQuery(
        recommend=models.RecommendInput(
            positive=[100, 231],
            negative=[718],
        )
    ),
    using="image",
    limit=10,
    lookup_from=models.LookupLocation(
        collection="{external_collection_name}", vector="{external_vector_name}"
    ),
)
client.query("{collection_name}", {
    query: {
        recommend: {
            positive: [100, 231],
            negative: [718],
        }
    },
    using: "image",
    limit: 10,
    lookup_from: {
        collection: "{external_collection_name}",
        vector: "{external_vector_name}"
    }
});
use qdrant_client::qdrant::{LookupLocationBuilder, QueryPointsBuilder, RecommendInputBuilder};

client
    .query(
        QueryPointsBuilder::new("{collection_name}")
            .query(
                RecommendInputBuilder::default()
                    .add_positive(100)
                    .add_positive(231)
                    .add_negative(718)
                    .build(),
            )
            .limit(10)
            .using("image")
            .lookup_from(
                LookupLocationBuilder::new("{external_collection_name}")
                    .vector_name("{external_vector_name}"),
            ),
    )
    .await?;
import static io.qdrant.client.QueryFactory.recommend;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.grpc.Points.LookupLocation;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.RecommendInput;
import java.util.List;

client.queryAsync(QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .setQuery(recommend(RecommendInput.newBuilder()
                .addAllPositive(List.of(vectorInput(100), vectorInput(231)))
                .addAllNegative(List.of(vectorInput(718)))
                .build()))
        .setUsing("image")
        .setLimit(10)
        .setLookupFrom(
                LookupLocation.newBuilder()
                        .setCollectionName("{external_collection_name}")
                        .setVectorName("{external_vector_name}")
                        .build())
        .build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
    collectionName: "{collection_name}",
    query: new RecommendInput {
        Positive = { 100, 231 },
        Negative = { 718 }
    },
    usingVector: "image",
    limit: 10,
    lookupFrom: new LookupLocation
    {
        CollectionName = "{external_collection_name}",
        VectorName = "{external_vector_name}",
    }
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
    CollectionName: "{collection_name}",
    Query: qdrant.NewQueryRecommend(&qdrant.RecommendInput{
        Positive: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
            qdrant.NewVectorInputID(qdrant.NewIDNum(231)),
        },
        Negative: []*qdrant.VectorInput{
            qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
        },
    }),
    Using: qdrant.PtrOf("image"),
    LookupFrom: &qdrant.LookupLocation{
        CollectionName: "{external_collection_name}",
        VectorName:     qdrant.PtrOf("{external_vector_name}"),
    },
})

系统通过 positivenegative 列表中提供的 ID 从外部集合中检索向量。然后,这些向量被用于在当前集合中执行推荐,并与“using”或默认向量进行比较。

批量推荐 API

自 v0.10.0 起可用

在用法和优势上与批量搜索 API 类似,它支持批量处理推荐请求。

POST /collections/{collection_name}/points/query/batch
{
  "searches": [
    {
      "query": {
        "recommend": {
          "positive": [100, 231],
          "negative": [718]
        }
      },
      "filter": {
        "must": [
          {
            "key": "city",
            "match": {
              "value": "London"
            }
          }
        ]
      },
      "limit": 10
    },
    {
      "query": {
        "recommend": {
          "positive": [200, 67],
          "negative": [300]
        }
      },
      "filter": {
        "must": [
          {
            "key": "city",
            "match": {
              "value": "London"
            }
          }
        ]
      },
      "limit": 10
    }
  ]
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

filter_ = models.Filter(
    must=[
        models.FieldCondition(
            key="city",
            match=models.MatchValue(
                value="London",
            ),
        )
    ]
)

recommend_queries = [
    models.QueryRequest(
        query=models.RecommendQuery(
            recommend=models.RecommendInput(positive=[100, 231], negative=[718])
        ),
        filter=filter_,
        limit=3,
    ),
    models.QueryRequest(
        query=models.RecommendQuery(
            recommend=models.RecommendInput(positive=[200, 67], negative=[300])
        ),
        filter=filter_,
        limit=3,
    ),
]

client.query_batch_points(
    collection_name="{collection_name}", requests=recommend_queries
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

const filter = {
    must: [
        {
            key: "city",
            match: {
                value: "London",
            },
        },
    ],
};

const searches = [
    {
        query: {
            recommend: {
                positive: [100, 231],
                negative: [718]
            }
        },
        filter,
        limit: 3,
    },
    {
        query: {
            recommend: {
                positive: [200, 67],
                negative: [300]
            }
        },
        filter,
        limit: 3,
    },
];

client.queryBatch("{collection_name}", {
    searches,
});
use qdrant_client::qdrant::{
    Condition, Filter, QueryBatchPointsBuilder, QueryPointsBuilder,
    RecommendInputBuilder,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("https://:6334").build()?;

let filter = Filter::must([Condition::matches("city", "London".to_string())]);

let recommend_queries = vec![
    QueryPointsBuilder::new("{collection_name}")
        .query(
            RecommendInputBuilder::default()
                .add_positive(100)
                .add_positive(231)
                .add_negative(718)
                .build(),
        )
        .filter(filter.clone())
        .build(),
    QueryPointsBuilder::new("{collection_name}")
        .query(
            RecommendInputBuilder::default()
                .add_positive(200)
                .add_positive(67)
                .add_negative(300)
                .build(),
        )
        .filter(filter)
        .build(),
];

client
    .query_batch(QueryBatchPointsBuilder::new(
        "{collection_name}",
        recommend_queries,
    ))
    .await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.QueryFactory.recommend;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.QueryPoints;
import io.qdrant.client.grpc.Points.RecommendInput;
import java.util.List;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

Filter filter = Filter.newBuilder().addMust(matchKeyword("city", "London")).build();

List<QueryPoints> recommendQueries = List.of(
        QueryPoints.newBuilder()
                .setCollectionName("{collection_name}")
                .setQuery(recommend(
                        RecommendInput.newBuilder()
                                .addAllPositive(List.of(vectorInput(100), vectorInput(231)))
                                .addAllNegative(List.of(vectorInput(731)))
                                .build()))
                .setFilter(filter)
                .setLimit(3)
                .build(),
        QueryPoints.newBuilder()
                .setCollectionName("{collection_name}")
                .setQuery(recommend(
                        RecommendInput.newBuilder()
                                .addAllPositive(List.of(vectorInput(200), vectorInput(67)))
                                .addAllNegative(List.of(vectorInput(300)))
                                .build()))
                .setFilter(filter)
                .setLimit(3)
                .build());

client.queryBatchAsync("{collection_name}", recommendQueries).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

var filter = MatchKeyword("city", "london");

await client.QueryBatchAsync(
    collectionName: "{collection_name}",
    queries:
    [
        new QueryPoints()
        {
            CollectionName = "{collection_name}",
            Query = new RecommendInput {
                Positive = { 100, 231 },
                Negative = { 718 },
            },
            Limit = 3,
            Filter = filter,
        },
                new QueryPoints()
        {
            CollectionName = "{collection_name}",
            Query = new RecommendInput {
                Positive = { 200, 67 },
                Negative = { 300 },
            },
            Limit = 3,
            Filter = filter,
        }
    ]
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

filter := qdrant.Filter{
    Must: []*qdrant.Condition{
        qdrant.NewMatch("city", "London"),
    },
}
client.QueryBatch(context.Background(), &qdrant.QueryBatchPoints{
    CollectionName: "{collection_name}",
    QueryPoints: []*qdrant.QueryPoints{
        {
            CollectionName: "{collection_name}",
            Query: qdrant.NewQueryRecommend(&qdrant.RecommendInput{
                Positive: []*qdrant.VectorInput{
                    qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
                    qdrant.NewVectorInputID(qdrant.NewIDNum(231)),
                },
                Negative: []*qdrant.VectorInput{
                    qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
                },
            },
            ),
            Filter: &filter,
        },
        {
            CollectionName: "{collection_name}",
            Query: qdrant.NewQueryRecommend(&qdrant.RecommendInput{
                Positive: []*qdrant.VectorInput{
                    qdrant.NewVectorInputID(qdrant.NewIDNum(200)),
                    qdrant.NewVectorInputID(qdrant.NewIDNum(67)),
                },
                Negative: []*qdrant.VectorInput{
                    qdrant.NewVectorInputID(qdrant.NewIDNum(300)),
                },
            },
            ),
            Filter: &filter,
        },
    },
},
)

此 API 的返回结果中,每个推荐请求对应一个数组。

{
  "result": [
    [
        { "id": 10, "score": 0.81 },
        { "id": 14, "score": 0.75 },
        { "id": 11, "score": 0.73 }
    ],
    [
        { "id": 1, "score": 0.92 },
        { "id": 3, "score": 0.89 },
        { "id": 9, "score": 0.75 }
    ]
  ],
  "status": "ok",
  "time": 0.001
}

发现(Discovery)API

v1.7 版本起可用

REST API 模式定义可在此处查看

在此 API 中,Qdrant 引入了 context(上下文)的概念,用于分割空间。上下文是一组正负对,每对都会将空间划分为正区域和负区域。在该模式下,搜索操作会优先选择属于更多正区域(或尽可能避开负区域)的点。

提供上下文的接口类似于推荐 API(ID 或原始向量)。但在这种情况下,它们需要以正负对的形式提供。

发现 API 允许您执行两种新的搜索类型:

  • 发现搜索 (Discovery search):使用上下文(正负向量对)和一个目标向量,返回与目标最相似但受上下文约束的点。
  • 上下文搜索 (Context search):仅使用上下文对,获取位于最佳区域(即损失最小化区域)的点。

正例和负例在上下文对中的排列方式完全由您决定。因此,您可以根据自己的模型和数据灵活尝试不同的排列技术。

这种搜索类型特别适合结合多模态、向量约束搜索。Qdrant 已经对过滤器提供了广泛的支持,可以根据有效载荷约束搜索。但通过发现搜索,您还可以约束执行搜索的向量空间。

Discovery search

发现得分的公式可以表示为:

$$ \text{rank}(v^+, v^-) = \begin{cases} 1, &\quad s(v^+) \geq s(v^-) \\ -1, &\quad s(v^+) < s(v^-) \end{cases} $$ 其中 $v^+$ 代表正例,$v^-$ 代表负例,$s(v)$ 是向量 $v$ 到目标向量的相似度得分。发现得分计算如下:$$ \text{discovery score} = \text{sigmoid}(s(v_t))+ \sum \text{rank}(v_i^+, v_i^-), $$ 其中 $s(v)$ 是相似度函数,$v_t$ 是目标向量,$v_i^+$ 和 $v_i^-$ 分别是正例和负例。Sigmoid 函数用于将得分归一化到 0 到 1 之间,排名总和用于惩罚比正例更接近负例的向量。换句话说,个体排名的总和决定了一个点处于多少个正区域中,而接近度层级则位居其次。

示例

POST /collections/{collection_name}/points/query
{
  "query": {
    "discover": {
      "target": [0.2, 0.1, 0.9, 0.7],
      "context": [
        {
          "positive": 100,
          "negative": 718
        },
        {
          "positive": 200,
          "negative": 300
        }
      ]
    }
  },
  "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

discover_queries = [
    models.QueryRequest(
        query=models.DiscoverQuery(
            discover=models.DiscoverInput(
                target=[0.2, 0.1, 0.9, 0.7],
                context=[
                    models.ContextPair(
                        positive=100,
                        negative=718,
                    ),
                    models.ContextPair(
                        positive=200,
                        negative=300,
                    ),
                ],
            )
        ),
        limit=10,
    ),
]

client.query_batch_points(
    collection_name="{collection_name}", requests=discover_queries
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: {
        discover: {
            target: [0.2, 0.1, 0.9, 0.7],
            context: [
                {
                    positive: 100,
                    negative: 718,
                },
                {
                    positive: 200,
                    negative: 300,
                },
            ],
        }
    },
    limit: 10,
});
use qdrant_client::qdrant::{ContextInputBuilder, DiscoverInputBuilder, QueryPointsBuilder};

client
    .query(
        QueryPointsBuilder::new("{collection_name}").query(
            DiscoverInputBuilder::new(
                vec![0.2, 0.1, 0.9, 0.7],
                ContextInputBuilder::default()
                    .add_pair(100, 718)
                    .add_pair(200, 300),
            )
            .build(),
        ),
    )
    .await?;
import static io.qdrant.client.QueryFactory.discover;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.ContextInput;
import io.qdrant.client.grpc.Points.ContextInputPair;
import io.qdrant.client.grpc.Points.DiscoverInput;
import io.qdrant.client.grpc.Points.QueryPoints;
import java.util.List;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .setQuery(discover(DiscoverInput.newBuilder()
                .setTarget(vectorInput(0.2f, 0.1f, 0.9f, 0.7f))
                .setContext(ContextInput.newBuilder()
                        .addAllPairs(List.of(
                                ContextInputPair.newBuilder()
                                        .setPositive(vectorInput(100))
                                        .setNegative(vectorInput(718))
                                        .build(),
                                ContextInputPair.newBuilder()
                                        .setPositive(vectorInput(200))
                                        .setNegative(vectorInput(300))
                                        .build()))
                        .build())
                .build()))
        .setLimit(10)
        .build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
    collectionName: "{collection_name}",
    query: new DiscoverInput {
        Target = new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
        Context = new ContextInput {
            Pairs = {
                new ContextInputPair {
                    Positive = 100,
                    Negative = 718
                },
                new ContextInputPair {
                    Positive = 200,
                    Negative = 300
                },
            }   
        },
    },
    limit: 10
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
    CollectionName: "{collection_name}",
    Query: qdrant.NewQueryDiscover(&qdrant.DiscoverInput{
        Target: qdrant.NewVectorInput(0.2, 0.1, 0.9, 0.7),
        Context: &qdrant.ContextInput{
            Pairs: []*qdrant.ContextInputPair{
                {
                    Positive: qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
                    Negative: qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
                },
                {
                    Positive: qdrant.NewVectorInputID(qdrant.NewIDNum(200)),
                    Negative: qdrant.NewVectorInputID(qdrant.NewIDNum(300)),
                },
            },
        },
    }),
})

相反,在没有目标的情况下,当使用 HNSW 这样的邻近图时,刚性的整数对整数函数并不能为搜索提供太多指导。因此,上下文搜索采用了源自 三元组损失 (triplet-loss) 概念的函数,该概念通常在模型训练期间应用。在上下文搜索中,该函数被调整为将搜索引导至负例较少的区域。

Context search

我们可以将得分函数直接与损失函数关联起来,其中 0.0 是点能获得的最高分,这意味着它仅处于正区域。一旦一个点存在于靠近负例的地方,其损失就简单地等于正相似度和负相似度之差。

$$ \text{context score} = \sum \min(s(v^+_i) - s(v^-_i), 0.0) $$

其中 $v^+_i$ 和 $v^-_i$ 是每对中的正例和负例,$s(v)$ 是相似度函数。

通过这种搜索,您可以预期输出结果不一定围绕某个单一的点,而是任何不靠近负例的点,从而产生受约束的多样化结果。因此,即使 API 不叫 recommend,推荐系统也可以使用这种方法并针对其特定用例进行调整。

示例

POST /collections/{collection_name}/points/query
{
  "query": {
    "context": [
      {
        "positive": 100,
        "negative": 718
      },
      {
        "positive": 200,
        "negative": 300
      }
    ]
  },
  "limit": 10
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

discover_queries = [
    models.QueryRequest(
        query=models.ContextQuery(
            context=[
                models.ContextPair(
                    positive=100,
                    negative=718,
                ),
                models.ContextPair(
                    positive=200,
                    negative=300,
                ),
            ],
        ),
        limit=10,
    ),
]

client.query_batch_points(
    collection_name="{collection_name}", requests=discover_queries
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.query("{collection_name}", {
    query: {
        context: [
            {
                positive: 100,
                negative: 718,
            },
            {
                positive: 200,
                negative: 300,
            },
        ]
    },
    limit: 10,
});
use qdrant_client::qdrant::{ContextInputBuilder, QueryPointsBuilder};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("https://:6334").build()?;

client
    .query(
        QueryPointsBuilder::new("{collection_name}").query(
            ContextInputBuilder::default()
                .add_pair(100, 718)
                .add_pair(200, 300)
                .build(),
        ),
    )
    .await?;
import static io.qdrant.client.QueryFactory.context;
import static io.qdrant.client.VectorInputFactory.vectorInput;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.ContextInput;
import io.qdrant.client.grpc.Points.ContextInputPair;
import io.qdrant.client.grpc.Points.QueryPoints;
import java.util.List;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client.queryAsync(QueryPoints.newBuilder()
        .setCollectionName("{collection_name}")
        .setQuery(context(ContextInput.newBuilder()
                .addAllPairs(List.of(
                        ContextInputPair.newBuilder()
                                .setPositive(vectorInput(100))
                                .setNegative(vectorInput(718))
                                .build(),
                        ContextInputPair.newBuilder()
                                .setPositive(vectorInput(200))
                                .setNegative(vectorInput(300))
                                .build()))
                .build()))
        .setLimit(10)
        .build()).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

await client.QueryAsync(
  collectionName: "{collection_name}",
  query: new ContextInput {
    Pairs = {
      new ContextInputPair {
        Positive = 100,
          Negative = 718
      },
      new ContextInputPair {
        Positive = 200,
          Negative = 300
      },
    }
  },
  limit: 10
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

client.Query(context.Background(), &qdrant.QueryPoints{
    CollectionName: "{collection_name}",
    Query: qdrant.NewQueryContext(&qdrant.ContextInput{
        Pairs: []*qdrant.ContextInputPair{
            {
                Positive: qdrant.NewVectorInputID(qdrant.NewIDNum(100)),
                Negative: qdrant.NewVectorInputID(qdrant.NewIDNum(718)),
            },
            {
                Positive: qdrant.NewVectorInputID(qdrant.NewIDNum(200)),
                Negative: qdrant.NewVectorInputID(qdrant.NewIDNum(300)),
            },
        },
    }),
})

距离矩阵

v1.12.0 版本起可用

距离矩阵 API 允许计算采样向量对之间的距离,并将结果作为稀疏矩阵返回。

该 API 能够实现新的数据探索用例,例如相似向量聚类、连接可视化或降维。

API 输入请求包含以下参数:

  • sample:要采样的向量数量
  • limit:每个样本要返回的得分数量
  • filter:用于约束样本的过滤器

让我们看一个 sample=100, limit=10 的基本示例

引擎首先从集合中随机选择 100 个点,然后对于每个选定的点,计算样本内部最接近的 10 个点。

这将产生总共 1000 个得分,并以稀疏矩阵的形式表示,以进行高效处理。

距离矩阵 API 提供两种输出格式,以方便与不同工具集成。

成对格式 (Pairwise format)

将距离矩阵作为点 ids 对及其相应得分的列表返回。

POST /collections/{collection_name}/points/search/matrix/pairs
{
    "sample": 10,
    "limit": 2,
    "filter": {
        "must": {
            "key": "color",
            "match": { "value": "red" }
        }
    }  
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.search_matrix_pairs(
    collection_name="{collection_name}",
    sample=10,
    limit=2,
    query_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="color", match=models.MatchValue(value="red")
            ),
        ]
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.searchMatrixPairs("{collection_name}", {
    filter: {
        must: [
            {
                key: "color",
                match: {
                    value: "red",
                },
            },
        ],
    },
    sample: 10,
    limit: 2,
});
use qdrant_client::qdrant::{Condition, Filter, SearchMatrixPointsBuilder};

client
    .search_matrix_pairs(
        SearchMatrixPointsBuilder::new("collection_name")
           .filter(Filter::must(vec![Condition::matches(
               "color",
               "red".to_string(),
           )]))
           .sample(10)
           .limit(2),
    )
    .await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.SearchMatrixPoints;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .searchMatrixPairsAsync(
        SearchMatrixPoints.newBuilder()
            .setCollectionName("{collection_name}")
            .setFilter(Filter.newBuilder().addMust(matchKeyword("color", "red")).build())
            .setSample(10)
            .setLimit(2)
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

await client.SearchMatrixPairsAsync(
    collectionName: "{collection_name}",
    filter: MatchKeyword("color", "red"),
    sample: 10,
    limit: 2
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

sample := uint64(10)
limit := uint64(2)
res, err := client.SearchMatrixPairs(context.Background(), &qdrant.SearchMatrixPoints{
    CollectionName: "{collection_name}",
    Sample:         &sample,
    Limit:          &limit,
    Filter: &qdrant.Filter{
        Must: []*qdrant.Condition{
            qdrant.NewMatch("color", "red"),
        },
    },
})

返回结果

{
    "result": {
        "pairs": [
            {"a": 1, "b": 3, "score": 1.4063001},
            {"a": 1, "b": 4, "score": 1.2531},
            {"a": 2, "b": 1, "score": 1.1550001},
            {"a": 2, "b": 8, "score": 1.1359},
            {"a": 3, "b": 1, "score": 1.4063001},
            {"a": 3, "b": 4, "score": 1.2218001},
            {"a": 4, "b": 1, "score": 1.2531},
            {"a": 4, "b": 3, "score": 1.2218001},
            {"a": 5, "b": 3, "score": 0.70239997},
            {"a": 5, "b": 1, "score": 0.6146},
            {"a": 6, "b": 3, "score": 0.6353},
            {"a": 6, "b": 4, "score": 0.5093},
            {"a": 7, "b": 3, "score": 1.0990001},
            {"a": 7, "b": 1, "score": 1.0349001},
            {"a": 8, "b": 2, "score": 1.1359},
            {"a": 8, "b": 3, "score": 1.0553}
        ]
    }
}

偏移格式 (Offset format)

将距离矩阵作为四个数组返回

  • offsets_rowoffsets_col 表示矩阵中非零距离值的位置。
  • scores 包含距离值。
  • ids 包含对应于距离值的点 ID。
POST /collections/{collection_name}/points/search/matrix/offsets
{
    "sample": 10,
    "limit": 2,
    "filter": {
        "must": {
            "key": "color",
            "match": { "value": "red" }
        }
    }
}
from qdrant_client import QdrantClient, models

client = QdrantClient(url="https://:6333")

client.search_matrix_offsets(
    collection_name="{collection_name}",
    sample=10,
    limit=2,
    query_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="color", match=models.MatchValue(value="red")
            ),
        ]
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

client.searchMatrixOffsets("{collection_name}", {
    filter: {
        must: [
            {
                key: "color",
                match: {
                    value: "red",
                },
            },
        ],
    },
    sample: 10,
    limit: 2,
});
use qdrant_client::qdrant::{Condition, Filter, SearchMatrixPointsBuilder};

client
    .search_matrix_offsets(
        SearchMatrixPointsBuilder::new("collection_name")
           .filter(Filter::must(vec![Condition::matches(
               "color",
               "red".to_string(),
           )]))
           .sample(10)
           .limit(2),
    )
    .await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Common.Filter;
import io.qdrant.client.grpc.Points.SearchMatrixPoints;

QdrantClient client =
    new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());

client
    .searchMatrixOffsetsAsync(
        SearchMatrixPoints.newBuilder()
            .setCollectionName("{collection_name}")
            .setFilter(Filter.newBuilder().addMust(matchKeyword("color", "red")).build())
            .setSample(10)
            .setLimit(2)
            .build())
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;

var client = new QdrantClient("localhost", 6334);

await client.SearchMatrixOffsetsAsync(
    collectionName: "{collection_name}",
    filter: MatchKeyword("color", "red"),
    sample: 10,
    limit: 2
);
import (
    "context"

    "github.com/qdrant/go-client/qdrant"
)

client, err := qdrant.NewClient(&qdrant.Config{
    Host: "localhost",
    Port: 6334,
})

sample := uint64(10)
limit := uint64(2)
res, err := client.SearchMatrixOffsets(context.Background(), &qdrant.SearchMatrixPoints{
    CollectionName: "{collection_name}",
    Sample:         &sample,
    Limit:          &limit,
    Filter: &qdrant.Filter{
        Must: []*qdrant.Condition{
            qdrant.NewMatch("color", "red"),
        },
    },
})

返回结果

{
    "result": {
        "offsets_row": [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7],
        "offsets_col": [2, 3, 0, 7, 0, 3, 0, 2, 2, 0, 2, 3, 2, 0, 1, 2],
        "scores": [
            1.4063001, 1.2531, 1.1550001, 1.1359, 1.4063001,
            1.2218001, 1.2531, 1.2218001, 0.70239997, 0.6146, 0.6353,
            0.5093, 1.0990001, 1.0349001, 1.1359, 1.0553
            ],
        "ids": [1, 2, 3, 4, 5, 6, 7, 8]
    }
}
此页面有用吗?

感谢您的反馈!🙏

很遗憾听到这个消息。😔 您可以在 GitHub 上编辑此页面,或创建一个 GitHub 问题。