서버 쪽 추론: BM25
서버 쪽 추론: BM25 (inference-inference-bm25)
BM25 (Best Matching 25)는 텍스트 검색을 위한 랭킹 함수예요. BM25는 문서를 표현할 때 스파스(sparse) 벡터를 사용하는데, 각 차원이 하나의 단어(word)에 대응돼요. Qdrant는 서버에서 입력 텍스트를 받아 스파스 임베딩을 직접 생성할 수 있어요.
벡터 업서트(upsert)에 BM25 사용하기
포인트를 업서트할 때, 텍스트와 qdrant/bm25 임베딩 모델을 함께 제공하면 돼요. Qdrant가 이 모델을 사용해 임베딩을 생성하고, 결과 벡터와 함께 포인트를 저장해요.
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"vector": {
"my-bm25-vector": {
"text": "Recipe for baking chocolate chip cookies",
"model": "qdrant/bm25"
}
}
}
]
}
from qdrant_client import QdrantClient, models
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
vector={
"my-bm25-vector": models.Document(
text="Recipe for baking chocolate chip cookies",
model="Qdrant/bm25",
)
},
)
],
)
import { QdrantClient } from "@qdrant/js-client-rest";
client.upsert("{collection_name}", {
points: [
{
id: 1,
vector: {
'my-bm25-vector': {
text: 'Recipe for baking chocolate chip cookies',
model: 'Qdrant/bm25',
},
},
},
],
});
use qdrant_client::{
Payload, Qdrant,
qdrant::{DocumentBuilder, PointStruct, UpsertPointsBuilder},
};
use std::collections::HashMap;
client
.upsert_points(UpsertPointsBuilder::new(
"{collection_name}",
vec![PointStruct::new(
1,
HashMap::from([(
"my-bm25-vector".to_string(),
DocumentBuilder::new("Recipe for baking chocolate chip cookies", "qdrant/bm25")
.build(),
)]),
Payload::default(),
)],
))
.await?;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorFactory.vector;
import static io.qdrant.client.VectorsFactory.namedVectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.Document;
import io.qdrant.client.grpc.Points.Image;
import io.qdrant.client.grpc.Points.PointStruct;
import java.util.List;
import java.util.Map;
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(
namedVectors(
Map.of(
"my-bm25-vector",
vector(
Document.newBuilder()
.setModel("qdrant/bm25")
.setText("Recipe for baking chocolate chip cookies")
.build()))))))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new Dictionary<string, Vector>
{
["my-bm25-vector"] = new Document()
{
Model = "qdrant/bm25",
Text = "Recipe for baking chocolate chip cookies",
},
},
},
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(uint64(1)),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"my-bm25-vector": qdrant.NewVectorDocument(&qdrant.Document{
Model: "qdrant/bm25",
Text: "Recipe for baking chocolate chip cookies",
}),
}),
},
},
})
포인트를 조회하면 생성된 임베딩을 볼 수 있어요. 텍스트가 단어 단위로 분해되어 스파스 벡터(인덱스와 값의 쌍)로 저장된 걸 확인할 수 있죠.
....
"my-bm25-vector": {
"indices": [
112174620,
177304315,
662344706,
771857363,
1617337648
],
"values": [
1.6697302,
1.6697302,
1.6697302,
1.6697302,
1.6697302
]
}
....
]
쿼리 시점에 BM25 사용하기
마찬가지로 쿼리 시점에도 쿼리 문자열과 qdrant/bm25 임베딩 모델을 제공하면 돼요.
POST /collections/{collection_name}/points/query
{
"query": {
"text": "How to bake cookies?",
"model": "qdrant/bm25"
},
"using": "my-bm25-vector"
}
from qdrant_client import QdrantClient, models
client.query_points(
collection_name="{collection_name}",
query=models.Document(
text="How to bake cookies?",
model="Qdrant/bm25",
),
using="my-bm25-vector",
)
import { QdrantClient } from "@qdrant/js-client-rest";
client.query("{collection_name}", {
query: {
text: 'How to bake cookies?',
model: 'qdrant/bm25',
},
using: 'my-bm25-vector',
});
use qdrant_client::{
Qdrant,
qdrant::{Document, Query, QueryPointsBuilder},
};
client
.query(
QueryPointsBuilder::new("{collection_name}")
.query(Query::new_nearest(Document {
text: "How to bake cookies?".into(),
model: "qdrant/bm25".into(),
..Default::default()
}))
.using("my-bm25-vector")
.build(),
)
.await?;
import static io.qdrant.client.QueryFactory.nearest;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.Document;
import io.qdrant.client.grpc.Points;
client
.queryAsync(
Points.QueryPoints.newBuilder()
.setCollectionName("{collection_name}")
.setQuery(
nearest(
Document.newBuilder()
.setModel("qdrant/bm25")
.setText("How to bake cookies?")
.build()))
.setUsing("my-bm25-vector")
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
await client.QueryAsync(
collectionName: "{collection_name}",
query: new Document() { Model = "qdrant/bm25", Text = "How to bake cookies?" },
usingVector: "my-bm25-vector"
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.Query(context.Background(), &qdrant.QueryPoints{
CollectionName: "{collection_name}",
Query: qdrant.NewQueryNearest(
qdrant.NewVectorInputDocument(&qdrant.Document{
Model: "qdrant/bm25",
Text: "How to bake cookies?",
}),
),
Using: qdrant.PtrOf("my-bm25-vector"),
})
이렇게 하면 스파스 벡터를 위한 별도 임베딩 서비스를 운영하지 않아도, Qdrant 클러스터 안에서 키워드 기반 BM25 검색을 데이터와 가까운 곳에서 바로 수행할 수 있어요.
출처: Qdrant 공식문서