moon-qdrant

    A MoonBit client for the Qdrant vector database REST API.

    qdrant
    vector-database
    client
    rag
    embedding
    Download zip
    Author
    Version
    0.7.0
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    99

    #yuzhiblue/moon-qdrant

    CI Integration

    A MoonBit client for the Qdrant vector database REST API.

    moon-qdrant lets MoonBit programs manage Qdrant collections, upsert points, and run vector searches without leaving the MoonBit ecosystem. It is built on gaato/mttp_async for HTTP transport (HTTP/1.1 with retries, deadlines and transparent compression) and the MoonBit core JSON library.

    #Why

    Qdrant is one of the most widely used vector databases for RAG and embedding search. The MoonBit ecosystem already has vector engines and servers (e.g. trkbt10/vcdb), but no client library to talk to a running Qdrant service. moon-qdrant fills that gap with a small, explicit API surface that maps 1:1 to Qdrant REST endpoints.

    #Install

    moon add yuzhiblue/moon-qdrant

    #Quick start

    Start a local Qdrant server (Docker):

    docker run -p 6333:6333 qdrant/qdrant

    Then use the client:

    let client = QdrantClient::new("http://localhost:6333")

    // health check
    let ok = client.health()

    // create a collection with 4-dimensional cosine vectors
    client.create_collection(
    "demo",
    CollectionConfig::new(VectorParams::new(4, Distance::Cosine)),
    )

    // list collections
    let names = client.list_collections()

    // inspect a collection
    let info = client.collection_info("demo")

    // does it exist?
    let exists = client.collection_exists("demo")

    // write some points
    client.upsert_points("demo", [
    PointStruct::new(1, [0.1, 0.2, 0.3, 0.4], { "tag": "alpha" }),
    PointStruct::new(2, [0.5, 0.6, 0.7, 0.8], { "tag": "beta" }),
    ])

    // ids are Int64, matching Qdrant's u64 point ids. Even ids above 2^53,
    // where a Double can no longer hold every integer, round-trip exactly:
    client.upsert_points("demo", [
    PointStruct::new(
    9007199254740993L,
    [0.2, 0.3, 0.4, 0.5],
    { "tag": "big" },
    ),
    ])

    // read one back
    let point = client.get_point("demo", 1)

    // search for the most similar points
    let hits = client.search_points("demo", [0.1, 0.2, 0.3, 0.4], limit=2)

    // search with a payload filter
    let filtered = client.search_points(
    "demo",
    [0.1, 0.2, 0.3, 0.4],
    limit=2,
    filter={ "must": [{ "key": "tag", "match": { "value": "alpha" } }] },
    )

    // delete points by id
    client.delete_points("demo", [2])

    // drop the collection
    client.delete_collection("demo")

    search_points accepts optional score_threshold (keep only hits above a score), offset (pagination), using_vector (pick a named vector to query against) and search_params (HNSW tuning / exact search):

    // only hits scoring 0.9 or better, skipping the first hit

    ///|
    let hits = client.search_points(
    "demo",
    [0.1, 0.2, 0.3, 0.4],
    2,
    score_threshold=Some(0.9),
    offset=Some(1),
    search_params=Some(SearchParams::new(exact=Some(true))),
    )

    // several searches in one round trip

    ///|
    let batch = client.search_points_batch("demo", [
    { "vector": [0.1, 0.2, 0.3, 0.4], "limit": 2 },
    { "vector": [0.9, 0.8, 0.7, 0.6], "limit": 2 },
    ])

    #Named vectors

    Qdrant collections can store several vectors per point. moon-qdrant supports this end to end: create the collection with named configurations, upsert points with several vectors, and search against one of them.

    let named : Map[String, VectorParams] = Map([])
    named["text"] = VectorParams::new(4, Distance::Cosine)
    named["image"] = VectorParams::new(8, Distance::Dot)
    client.create_collection("multi", CollectionConfig::new_named(named))

    let vectors : Map[String, Array[Double]] = Map([])
    vectors["text"] = [0.1, 0.2, 0.3, 0.4]
    vectors["image"] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
    client.upsert_points("multi", [PointStruct::new_named(1, vectors, { "tag": "a" })])

    // search against the "text" vector
    let hits = client.search_points(
    "multi",
    [0.1, 0.2, 0.3, 0.4],
    1,
    using_vector=Some("text"),
    )
    // or via a NamedVector input
    let hits = client.search_points_named(
    "multi",
    NamedVector::new("image", [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]),
    1,
    )

    #Payload management

    // merge fields into the payload of points 1 and 2
    client.set_payload("demo", { "active": true }, ids=Some([1, 2]))
    // replace the whole payload of points matching a filter
    client.overwrite_payload(
    "demo",
    { "state": "new" },
    filter=Some(Filter::new().must(Condition::match_keyword("tag", "alpha"))),
    )
    // drop selected keys from point 1
    client.delete_payload_by_keys("demo", ["active"], ids=Some([1]))
    // drop the whole payload of all points matching a filter
    client.delete_payload("demo", filter=Some(Filter::new()))

    #Query API and facets

    The Qdrant query API (POST /collections/{name}/points/query) is exposed through query_nearest (nearest neighbor) and query_recommend (recommendation from positive / negative examples), plus a raw query_points for advanced queries:

    // nearest neighbor
    let hits = client.query_nearest("demo", [0.1, 0.2, 0.3, 0.4], 2)

    // recommendation: like the positive vectors, unlike the negative ones
    let positive : Array[Json] = [
    Json::array([0.1, 0.2, 0.3, 0.4]),
    Json::number(3), // point ids are accepted too
    ]
    let negative : Array[Json] = [Json::array([0.9, 0.8, 0.7, 0.6])]
    let recs = client.query_recommend(
    "demo",
    positive,
    negative=Some(negative),
    limit=5,
    )

    // facet: how many points carry each distinct value of a payload field
    let facet = client.facet_points("demo", "tag", 10)
    for hit in facet.hits {
    println(hit.value.stringify() + ": " + hit.count.to_string())
    }

    // grouped search: top hits per distinct payload value
    let groups = client.search_points_groups(
    "demo",
    [0.1, 0.2, 0.3, 0.4],
    2, // hits per group
    "tag", // group-by field
    10, // max groups
    )
    for group in groups {
    println(
    "group " + group.id.stringify() + " -> " +
    group.hits.length().to_string() + " hits",
    )
    }

    #Cluster, locks and server info

    // cluster state (works on single-node deployments too)
    let cluster = client.cluster_info()
    println("cluster status: " + cluster.status)
    println("peers: " + cluster.peers.length().to_string())

    // per-collection shard / replica layout
    let layout = client.collection_cluster_info("demo")
    for shard in layout.local_shards {
    println(
    "shard " + shard.shard_id.to_string() + ": " +
    shard.state + " (" + shard.points_count.to_string() + " points)",
    )
    }

    // shard lifecycle (multi-node clusters). Qdrant's cluster API takes a
    // single-key operation object; `create_shard_replica` maps to the
    // `replicate_shard` operation and `delete_shard_replica` to `drop_replica`,
    // the operation names Qdrant actually accepts. The source peer is looked up
    // automatically; pass `from_peer_id=...` to override. A single-node server
    // answers any cluster operation with "Distributed mode disabled" — that is a
    // server limit, not a malformed request.
    client.replicate_shard("demo", 0L, 1L, 2L)
    client.create_shard_replica("demo", 0L, 2L)
    client.delete_shard_replica("demo", 0L, 2L)

    // write lock + server version
    client.set_locks(true, error_message=Some("maintenance"))
    let lock = client.get_locks()
    println("writes locked: " + lock.write.to_string())
    let info = client.service_info()
    match info.version {
    Some(v) => println("qdrant version: " + v)
    None => ()
    }

    #Collection updates, payload indexes and snapshots

    // update replication settings
    client.update_collection(
    "demo",
    CollectionUpdate::new(replication_factor=Some(2)),
    )

    // index a payload field for fast filtered access
    client.create_payload_index("demo", "tag", "keyword")
    let indexes = client.list_payload_indexes("demo")
    client.delete_payload_index("demo", "tag")

    // snapshot the collection for backup / migration
    let snap = client.create_snapshot("demo")
    let snapshots = client.list_snapshots("demo")
    client.delete_snapshot("demo", snap.name)

    #Examples

    Health-check CLI (needs a running Qdrant server):

    moon run cmd/main -- http://localhost:6333

    End-to-end demo: create collection, upsert points, search (with and without a payload filter), read a point, delete points, drop the collection:

    docker run -p 6333:6333 qdrant/qdrant moon run examples/demo -- http://localhost:6333

    #Status / roadmap

    Implemented:

    Planned:

    #Development

    moon check --deny-warn moon test moon fmt

    #Integration testing against a real Qdrant

    Unit tests cover the pure logic (models, JSON conversion, request bodies). The end-to-end path is verified in CI against a real Qdrant server (.github/workflows/integration.yml starts a qdrant/qdrant container and runs examples/integration_check, which asserts every client method against the live API, then runs the demo end to end).

    Locally, start Qdrant and run the same checks:

    docker run -p 6333:6333 qdrant/qdrant moon run examples/integration_check -- http://localhost:6333 moon run examples/demo -- http://localhost:6333

    #Reproducibility

    For reviewers: a short reproducibility guide covers environment setup, the end-to-end demo, re-running the integration check, expected outputs, and troubleshooting.

    #License

    Apache-2.0

    VectorProvider

    pub trait VectorProvider {
    async fn create_collection(self : Self, name : String, config : CollectionConfig) -> Unit
    async fn delete_collection(self : Self, name : String) -> Unit
    async fn collection_exists(self : Self, name : String) -> Bool
    async fn upsert_points(self : Self, name : String, points : Array[PointStruct]) -> Unit
    async fn upsert_points_batch(self : Self, name : String, points : Array[PointStruct]) -> Unit
    async fn search_points(self : Self, name : String, vector : Array[Double], limit : Int, filter? :
    Json
    ?) -> Array[ScoredPoint]
    async fn search_with_filter(self : Self, name : String, vector : Array[Double], limit : Int, filter : Filter) -> Array[ScoredPoint]
    }

    A common interface implemented by vector-service clients.

    QdrantClient implements this trait. The trait exists so that higher-level code (RAG pipelines, embedding indexes) can be written against one shape and later switch to another vector service without changing call sites.

    AliasInfo

    pub struct AliasInfo {
    alias_name : String
    collection_name : String
    }

    One collection alias entry, as returned by GET /collections/aliases.

    AliasInfo::from_json

    Parse one alias entry: { "alias_name": "prod", "collection_name": "demo" }.

    ClusterInfo

    pub struct ClusterInfo {
    status : String
    peer_id : String
    peers : Map[String, PeerInfo]
    raft_info : RaftInfo?
    }

    Overall cluster state, from GET /cluster. peer_id is this node's peer id, kept as exact decimal text (Qdrant peer ids are random u64 values that cannot fit in Int64 without wrapping).

    ClusterInfo::from_json

    Parse the cluster info result object.

    CollectionClusterInfo

    pub struct CollectionClusterInfo {
    peer_id : String
    shard_count : Int64
    local_shards : Array[LocalShardInfo]
    remote_shards : Array[RemoteShardInfo]
    shard_transfers : Array[ShardTransferInfo]
    }

    Per-collection cluster layout, from GET /collections/{name}/cluster. peer_id is kept as exact decimal text (Qdrant peer ids are u64).

    CollectionClusterInfo::from_json

    Parse the collection cluster info result object.

    CollectionConfig

    pub struct CollectionConfig {
    vectors : VectorParams
    named_vectors : Map[String, VectorParams]?
    }

    Collection configuration accepted by PUT /collections/{name}.

    Either a single dense-vector configuration (vectors) or a set of named vector configurations (named_vectors, via CollectionConfig::new_named) can be provided. Qdrant serializes both under the vectors key: a single object for the former, an object keyed by vector name for the latter.

    CollectionConfig::new

    Create a collection with a single dense-vector configuration.

    CollectionConfig::new_named

    fn CollectionConfig::new_named(named_vectors : Map[String, VectorParams]) -> CollectionConfig

    Create a collection with named (multi-) vector configurations, e.g. { "text": VectorParams::new(4, Cosine), "image": VectorParams::new(8, Dot) }. The vectors slot holds a placeholder that is never serialized when named_vectors is set.

    CollectionConfig::to_json

    Convert to the JSON body fragment used by the Qdrant API.

    CollectionInfo

    pub struct CollectionInfo {
    status : String
    points_count : Int64?
    vectors : VectorParams?
    named_vectors : Map[String, VectorParams]?
    }

    Detailed information about a collection, from GET /collections/{name}.

    CollectionInfo::from_json

    Parse the result object of GET /collections/{name} into a CollectionInfo. points_count, vectors and named_vectors are optional because Qdrant may omit them while a collection is still building. For a dense collection vectors carries the single vector config; for a named-vector collection named_vectors maps each vector name to its config and vectors is None.

    CollectionList

    pub struct CollectionList {
    names : Array[String]
    }

    Parsed result of GET /collections: the list of collection names.

    CollectionList::from_json

    CollectionLock

    pub struct CollectionLock {
    write : Bool
    error_message : String?
    }

    Collection lock state, from GET /locks. When write is true, the collection rejects write operations; the optional error_message is returned to clients on rejected writes.

    CollectionLock::from_json

    Parse the lock object: { "write": true, "error_message": "..." }.

    CollectionSummary

    pub struct CollectionSummary {
    name : String
    }

    Minimal parsed view of one collection, from GET /collections.

    CollectionSummary::from_json

    CollectionUpdate

    pub struct CollectionUpdate {
    vectors : VectorParams?
    named_vectors : Map[String, VectorParams]?
    replication_factor : Int?
    write_consistency_factor : Int?
    }

    Update settings of an existing collection, via PATCH /collections/{name}.

    All fields are optional; unset fields are omitted from the request body. vectors / named_vectors update the vector configuration (changing the size of an existing named vector is not supported by Qdrant), while replication_factor and write_consistency_factor update the shard replication settings.

    CollectionUpdate::new

    fn CollectionUpdate::new(vectors? : VectorParams?, named_vectors? : Map[String, VectorParams]?, replication_factor? : Int?, write_consistency_factor? : Int?) -> CollectionUpdate

    CollectionUpdate::to_json

    Convert to the PATCH request body, omitting unset fields.

    Condition

    pub(all) enum Condition {
    MatchValue(String,
    Json
    )
    MatchKeyword(String, String)
    MatchValues(String, Array[
    Json
    ])
    Range(String, Double?, Double?, Double?, Double?)
    IsEmpty(String)
    IsNull(String)
    }

    A single payload filter condition, in the subset of Qdrant's filter DSL that covers the common exact-match, set-membership, range and existence cases. Construct one with the Condition::match_* / Condition::range_* helpers and combine them inside a Filter.

    Condition::is_empty

    fn Condition::is_empty(field : String) -> Condition

    Convenience constructor: payload field has no value.

    Condition::is_null

    fn Condition::is_null(field : String) -> Condition

    Convenience constructor: payload field is explicitly null.

    Condition::match_keyword

    fn Condition::match_keyword(field : String, keyword : String) -> Condition

    Convenience constructor: payload field equals a keyword string.

    Condition::match_value

    fn Condition::match_value(field : String, value :
    Json
    ) -> Condition

    Convenience constructor: payload field equals a single JSON value.

    Condition::match_values

    fn Condition::match_values(field : String, values : Array[
    Json
    ]) -> Condition

    Convenience constructor: payload field equals one of the given values.

    Condition::range

    fn Condition::range(field : String, gte? : Double?, lte? : Double?, gt? : Double?, lt? : Double?) -> Condition

    Convenience constructor: numeric range on a payload field. All four bounds are optional; omitted bounds are dropped from the serialized filter.

    Condition::to_json

    Serialize a Condition to its Qdrant filter JSON fragment.

    Distance

    pub(all) enum Distance {
    Cosine
    Euclid
    Dot
    }

    Distance metric used by a Qdrant vector collection. Wire values follow the Qdrant REST API enum names.

    Distance::from_wire

    fn Distance::from_wire(s : String) -> Distance?

    Parse a Distance from its Qdrant wire string. Returns None for unknown values.

    Distance::to_wire

    fn Distance::to_wire(self : Distance) -> String

    Convert a Distance to its Qdrant wire string.

    FacetHit

    pub struct FacetHit {
    value :
    Json

    count : Int64
    }

    One facet hit from POST /collections/{name}/points/facet: a distinct payload value together with the number of points carrying it.

    FacetHit::from_json

    Parse one facet hit: { "value": "alpha", "count": 3 }.

    FacetResult

    pub struct FacetResult {
    hits : Array[FacetHit]
    exact : Bool
    }

    Result of a facet query: the value-count hits and whether the counts are exact (false when Qdrant used an approximate estimation).

    FacetResult::from_json

    Parse the result object of a facet response: { "hits": [{ "value": ..., "count": n }], "exact": true }.

    Filter

    pub struct Filter {
    must : Array[Condition]
    should : Array[Condition]
    must_not : Array[Condition]
    }

    A Qdrant filter, composed of three conjunctive clause groups.

    Empty clause groups are omitted from the serialized JSON. Use Filter::new() to start, then chain must / should / must_not to add conditions.

    Filter::must

    fn Filter::must(self : Filter, c : Condition) -> Filter

    Append a condition to the must clause group (all must hold).

    Filter::must_not

    fn Filter::must_not(self : Filter, c : Condition) -> Filter

    Append a condition to the must_not clause group (none may hold).

    Filter::new

    fn Filter::new() -> Filter

    Create an empty filter.

    Filter::should

    fn Filter::should(self : Filter, c : Condition) -> Filter

    Append a condition to the should clause group (any may hold).

    Filter::to_json

    Serialize the filter to Qdrant's filter JSON. Empty clause groups are omitted so the serialized object only contains non-empty groups.

    LocalShardInfo

    pub struct LocalShardInfo {
    shard_id : Int64
    points_count : Int64
    state : String
    }

    A shard replica hosted on the local node.

    LocalShardInfo::from_json

    Parse one local shard entry: { "shard_id": 0, "points_count": 10, "state": "Active" }.

    NamedVector

    pub struct NamedVector {
    name : String
    vector : Array[Double]
    }

    A named vector used as the search input: { "name": "text", "vector": [...] }. Send it as the vector field of a search request to run the query against one specific named vector of the collection.

    NamedVector::new

    fn NamedVector::new(name : String, vector : Array[Double]) -> NamedVector

    NamedVector::to_json

    Convert to the Qdrant wire object: { "name": ..., "vector": [...] }.

    PayloadIndexInfo

    pub struct PayloadIndexInfo {
    field_name : String
    data_type : String
    points_count : Int64?
    field_schema :
    Json
    ?
    }

    One payload index entry, from the payload_schema of a collection info response. field_name is the payload field the index is on; field_schema is the index descriptor object exactly as Qdrant reports it (including data_type, points and any index params).

    PayloadIndexInfo::from_json

    Parse one payload index entry: { "data_type": "keyword", "field_schema": "keyword", "points": 3 }. (The field name is not part of this flat shape; it is only known from the payload_schema object's keys, see parse_payload_indexes.)

    PeerInfo

    pub struct PeerInfo {
    uri : String?
    last_hb_secs_ago : Int64?
    last_resp_secs_ago : Int64?
    last_update_secs_ago : Int64?
    }

    A remote peer of the cluster, as reported by GET /cluster.

    PeerInfo::from_json

    Parse one peer entry: { "uri": "...", "last_hb_secs_ago": 0, ... }.

    PointStruct

    pub struct PointStruct {
    id : Int64
    vector : Array[Double]
    named_vectors : Map[String, Array[Double]]?
    payload :
    Json

    }

    A single point: an id, either a dense vector or a set of named vectors, and an optional JSON payload.

    PointStruct::from_json

    Parse a point object as returned by the point read endpoint: { "id": 1, "vector": [...] | {"name": [...]}, "payload": {...} }. A dense vector array fills vector; a named-vector object fills named_vectors. Qdrant omits (or nulls) vector and payload when they are not requested, so an id on its own is a valid point.

    PointStruct::new

    fn PointStruct::new(id : Int64, vector : Array[Double], payload :
    Json
    ) -> PointStruct

    Create a point with a single dense vector.

    PointStruct::new_named

    fn PointStruct::new_named(id : Int64, named_vectors : Map[String, Array[Double]], payload :
    Json
    ) -> PointStruct

    Create a point with named vectors, e.g. { "text": [0.1, 0.2], "image": [0.5, 0.6, 0.7] }.

    PointStruct::to_json

    Convert to the point object used by PUT /collections/{name}/points: { "id": 1, "vector": [...] | {"name": [...]}, "payload": {...} }.

    PointStruct::vector_json

    The vector field of this point: a JSON array for dense vectors, a { "name": [...] } object for named vectors. Shared by the single-point and batch upsert serializers so both keep named vectors intact.

    QdrantClient

    pub struct QdrantClient {
    base_url : String
    api_key : String?
    http :
    Client

    }

    Configuration for a QdrantClient.

    QdrantClient::close

    fn QdrantClient::close(self : QdrantClient) -> Unit

    Release the client's connection pool. The client is unusable afterwards.

    QdrantClient::cluster_info

    async fn QdrantClient::cluster_info(self : QdrantClient) -> ClusterInfo

    Read the overall cluster state via GET /cluster: status, local peer id, the peer map and raft consensus info.

    QdrantClient::collection_cluster_info

    async fn QdrantClient::collection_cluster_info(self : QdrantClient, name : String) -> CollectionClusterInfo

    Read the per-collection cluster layout via GET /collections/{name}/cluster: local and remote shard replicas plus in-flight transfers.

    QdrantClient::collection_exists

    async fn QdrantClient::collection_exists(self : QdrantClient, name : String) -> Bool

    Check whether a collection exists via GET /collections/{name}. Returns false when the server answers 404; raises on other errors.

    QdrantClient::collection_info

    async fn QdrantClient::collection_info(self : QdrantClient, name : String) -> CollectionInfo

    Get detailed information about a collection via GET /collections/{name}. Raises when the collection does not exist or the server reports an error.

    QdrantClient::count_points

    async fn QdrantClient::count_points(self : QdrantClient, name : String, filter? : Filter?, exact? : Bool) -> Int64

    Count points in a collection via POST /collections/{name}/points/count.

    When exact is false (default) Qdrant answers with an estimate from the index; pass exact=true for an exact count, which is slower on large collections. An optional filter narrows the counted subset.

    QdrantClient::create_alias

    async fn QdrantClient::create_alias(self : QdrantClient, alias_name : String, collection : String) -> Unit

    Create an alias for a collection: POST /collections/aliases with a create_alias action.

    QdrantClient::create_collection

    async fn QdrantClient::create_collection(self : QdrantClient, name : String, config : CollectionConfig) -> Unit

    Create a collection via PUT /collections/{name}.

    QdrantClient::create_payload_index

    async fn QdrantClient::create_payload_index(self : QdrantClient, name : String, field : String, schema : String, wait? : Bool) -> Unit

    Create a payload index on a field via PUT /collections/{name}/index?wait={wait}.

    schema is the field schema, for example "keyword", "integer", "float", "geo", "bool", "text" or "uuid". Creating an index speeds up filtered access to that payload field.

    QdrantClient::create_shard_replica

    async fn QdrantClient::create_shard_replica(self : QdrantClient, name : String, shard_id : Int64, peer_id : String, from_peer_id? : String?) -> Bool

    Add a replica of a shard on peer_id.

    Qdrant has no create_shard_replica cluster operation: a replica is added with replicate_shard, which needs a source peer that holds the shard. When from_peer_id~ is omitted the client looks one up through GET /collections/{name}/cluster; pass it explicitly to skip the lookup.

    QdrantClient::create_snapshot

    async fn QdrantClient::create_snapshot(self : QdrantClient, name : String, wait? : Bool) -> SnapshotInfo

    Create a snapshot of a collection via POST /collections/{name}/snapshots. Snapshots can be downloaded from the storage and used to restore or migrate a collection.

    QdrantClient::delete_alias

    async fn QdrantClient::delete_alias(self : QdrantClient, alias_name : String) -> Unit

    Delete an alias: POST /collections/aliases with a delete_alias action.

    QdrantClient::delete_collection

    async fn QdrantClient::delete_collection(self : QdrantClient, name : String) -> Unit

    Delete a collection via DELETE /collections/{name}. Raises when the collection does not exist or the server reports an error.

    QdrantClient::delete_payload

    async fn QdrantClient::delete_payload(self : QdrantClient, name : String, ids? : Array[Int64]?, filter? : Filter?, wait? : Bool) -> Unit

    Delete the whole payload of the selected points via POST /collections/{name}/points/payload/clear.

    Pass either ids or filter to select the target points. To remove only some keys, use delete_payload_by_keys.

    QdrantClient::delete_payload_by_keys

    async fn QdrantClient::delete_payload_by_keys(self : QdrantClient, name : String, keys : Array[String], ids? : Array[Int64]?, filter? : Filter?, wait? : Bool) -> Unit

    Delete only the listed payload keys from the selected points via POST /collections/{name}/points/payload/delete.

    Pass either ids or filter to select the target points.

    QdrantClient::delete_payload_index

    async fn QdrantClient::delete_payload_index(self : QdrantClient, name : String, field : String, wait? : Bool) -> Unit

    Delete a payload index via DELETE /collections/{name}/index/{field}.

    QdrantClient::delete_points

    async fn QdrantClient::delete_points(self : QdrantClient, name : String, ids : Array[Int64], wait? : Bool) -> Unit

    Delete points by id via POST /collections/{name}/points/delete. Pass wait=false to return immediately.

    QdrantClient::delete_points_by_filter

    async fn QdrantClient::delete_points_by_filter(self : QdrantClient, name : String, filter : Filter, wait? : Bool) -> Unit

    Delete points selected by a filter via POST /collections/{name}/points/delete.

    This is the bulk-delete variant: every point matching filter is removed in one operation, without listing ids first.

    QdrantClient::delete_shard_replica

    async fn QdrantClient::delete_shard_replica(self : QdrantClient, name : String, shard_id : Int64, peer_id : String) -> Bool

    Drop a shard replica from peer_id via the drop_replica operation. delete_shard_replica is kept as the public method name, but Qdrant's cluster operation is called drop_replica.

    QdrantClient::delete_snapshot

    async fn QdrantClient::delete_snapshot(self : QdrantClient, name : String, snapshot : String, wait? : Bool) -> Unit

    Delete a snapshot via DELETE /collections/{name}/snapshots/{snapshot}.

    QdrantClient::facet_points

    async fn QdrantClient::facet_points(self : QdrantClient, name : String, key : String, limit : Int, filter? : Filter?, exact? : Bool) -> FacetResult

    Count the distinct values of a payload field via POST /collections/{name}/facet.

    key is the payload field to facet on; limit bounds how many distinct values are returned (by descending count). Pass exact=true for exact counts instead of the faster approximate estimation.

    QdrantClient::get_locks

    async fn QdrantClient::get_locks(self : QdrantClient) -> CollectionLock

    Read the global write lock state via GET /locks. Qdrant locks are service-wide (not per-collection); when write is true the service rejects write operations.

    QdrantClient::get_point

    async fn QdrantClient::get_point(self : QdrantClient, name : String, id : Int64, with_payload? : Bool, with_vector? : Bool) -> PointStruct?

    Fetch a single point via GET /collections/{name}/points/{id}. Returns None when the point does not exist (404).

    QdrantClient::health

    async fn QdrantClient::health(self : QdrantClient) -> Bool

    Check the server health via GET /healthz. Qdrant answers this endpoint with plain text ("healthz check passed"), so only the HTTP status code is inspected: 200 means healthy. Transport failures (server not reachable) also report unhealthy instead of raising.

    QdrantClient::list_aliases

    async fn QdrantClient::list_aliases(self : QdrantClient) -> Array[AliasInfo]

    List all collection aliases via GET /aliases.

    QdrantClient::list_collections

    async fn QdrantClient::list_collections(self : QdrantClient) -> Array[String]

    List all collection names via GET /collections.

    QdrantClient::list_payload_indexes

    async fn QdrantClient::list_payload_indexes(self : QdrantClient, name : String) -> Array[PayloadIndexInfo]

    List the payload indexes of a collection via GET /collections/{name} (the payload_schema field). Qdrant has no dedicated list-indexes endpoint.

    QdrantClient::list_snapshots

    async fn QdrantClient::list_snapshots(self : QdrantClient, name : String) -> Array[SnapshotInfo]

    List the snapshots of a collection via GET /collections/{name}/snapshots.

    QdrantClient::new

    fn QdrantClient::new(base_url : String, api_key? : String?, timeout_ms? : Int) -> QdrantClient

    Create a client for the given base URL.

    Example: QdrantClient::new("http://localhost:6333")

    api_key is optional and, when set, is sent as the api-key header expected by Qdrant's API-key authentication.

    timeout_ms is the per-attempt deadline; the whole call (retries included) is bounded by 12x that, with a floor of mttp_async's 120 s default when the per-attempt timeout is large.

    QdrantClient::overwrite_payload

    async fn QdrantClient::overwrite_payload(self : QdrantClient, name : String, payload :
    Json
    , ids? : Array[Int64]?, filter? : Filter?, wait? : Bool) -> Unit

    Overwrite the payload of the given points via PUT /collections/{name}/points/payload.

    Unlike set_payload, the whole payload is replaced by the given object; existing fields not in the given object are removed.

    QdrantClient::query_nearest

    async fn QdrantClient::query_nearest(self : QdrantClient, name : String, vector : Array[Double], limit : Int, filter? : Filter?, using_vector? : String?, score_threshold? : Double?, with_payload? : Bool, with_vector? : Bool) -> Array[ScoredPoint]

    Nearest-neighbor query via the query API: find points closest to vector. Equivalent to search_points but routed through /points/query.

    QdrantClient::query_points

    async fn QdrantClient::query_points(self : QdrantClient, name : String, query :
    Json
    , limit : Int, filter? : Filter?, using_vector? : String?, score_threshold? : Double?, with_payload? : Bool, with_vector? : Bool) -> Array[ScoredPoint]

    Run a query against the Qdrant query API via POST /collections/{name}/points/query.

    query is the raw query JSON, for example { "nearest": [0.1, 0.2, 0.3, 0.4] } or { "recommend": { "positive": [[...]], "negative": [[...]] } }. Prefer the query_nearest / query_recommend helpers for common cases.

    QdrantClient::query_recommend

    async fn QdrantClient::query_recommend(self : QdrantClient, name : String, positive : Array[
    Json
    ], negative? : Array[
    Json
    ]?, limit : Int, filter? : Filter?, using_vector? : String?, score_threshold? : Double?, with_payload? : Bool, with_vector? : Bool) -> Array[ScoredPoint]

    Recommendation query via the query API: find points similar to the positive inputs and dissimilar to the negative ones.

    Each element of positive / negative is either a dense vector (Json::array([...])) or a point id (Json::number(id)).

    QdrantClient::replicate_shard

    async fn QdrantClient::replicate_shard(self : QdrantClient, name : String, shard_id : Int64, from_peer_id : String, to_peer_id : String) -> Bool

    Replicate a shard to another peer via POST /collections/{name}/cluster with the replicate_shard operation. Peer ids are exact decimal texts of Qdrant's u64 values.

    QdrantClient::retrieve_points

    async fn QdrantClient::retrieve_points(self : QdrantClient, name : String, ids : Array[Int64], with_payload? : Bool, with_vector? : Bool) -> Array[PointStruct]

    Fetch several points at once by id via POST /collections/{name}/points.

    Points that do not exist are simply omitted from the returned list; check the length against the requested ids to detect missing ones.

    QdrantClient::scroll_points

    async fn QdrantClient::scroll_points(self : QdrantClient, name : String, limit : Int, filter? : Filter?, offset? : Int64?, with_payload? : Bool, with_vector? : Bool) -> ScrollResult

    Scroll through all points of a collection via POST /collections/{name}/points/scroll.

    Returns one page of points together with next_page_offset, which is Some(offset) when more pages remain and None on the last page. Pass it back as offset to fetch the following page.

    QdrantClient::search_points

    async fn QdrantClient::search_points(self : QdrantClient, name : String, vector : Array[Double], limit : Int, filter? :
    Json
    ?, with_payload? : Bool, with_vector? : Bool, score_threshold? : Double?, offset? : Int64?, using_vector? : String?, search_params? : SearchParams?) -> Array[ScoredPoint]

    Search the most similar points via POST /collections/{name}/points/search.

    limit bounds how many hits are returned. filter is an optional Qdrant filter JSON object, for example { "must": [{ "key": "tag", "match": { "value": "alpha" } }] }.

    Advanced options: score_threshold keeps only hits above the score, offset paginates search results, using_vector selects which named vector to query against, and search_params carries search-time tuning (hnsw_ef, exact).

    QdrantClient::search_points_batch

    async fn QdrantClient::search_points_batch(self : QdrantClient, name : String, searches : Array[
    Json
    ]) -> Array[Array[ScoredPoint]]

    Run several searches in one round trip via POST /collections/{name}/points/search/batch.

    Each element is a search request JSON object, for example { "vector": [...], "limit": 5, "with_payload": true }; the returned array has one entry (itself an array of hits) per request.

    QdrantClient::search_points_groups

    async fn QdrantClient::search_points_groups(self : QdrantClient, name : String, vector : Array[Double], limit : Int, group_by : String, group_size : Int, filter? :
    Json
    ?, with_payload? : Bool, with_vector? : Bool, score_threshold? : Double?, using_vector? : String?, search_params? : SearchParams?) -> Array[ScoredPointGroup]

    Grouped search via POST /collections/{name}/points/search/groups.

    Points are grouped by the payload field group_by; limit bounds the hits returned per group and group_size bounds the number of groups. The result is one entry per group with its top hits.

    QdrantClient::search_points_named

    async fn QdrantClient::search_points_named(self : QdrantClient, name : String, named_vector : NamedVector, limit : Int, filter? :
    Json
    ?, with_payload? : Bool, with_vector? : Bool, score_threshold? : Double?, offset? : Int64?, search_params? : SearchParams?) -> Array[ScoredPoint]

    Search with a named vector as input: the query vector is matched against the named vector with the given name (Qdrant supports several vectors per point). using is implied by the name carried in named_vector.

    QdrantClient::search_points_with_filter

    async fn QdrantClient::search_points_with_filter(self : QdrantClient, name : String, vector : Array[Double], limit : Int, filter : Filter, with_payload? : Bool, with_vector? : Bool, score_threshold? : Double?, offset? : Int64?, using_vector? : String?, search_params? : SearchParams?) -> Array[ScoredPoint]

    Search with a type-safe Filter built from Condition helpers. This is the preferred way to build payload filters; the raw-Json search_points variant remains for advanced cases.

    QdrantClient::search_with_filter

    async fn QdrantClient::search_with_filter(self : QdrantClient, name : String, vector : Array[Double], limit : Int, filter : Filter, with_payload? : Bool, with_vector? : Bool) -> Array[ScoredPoint]

    Trait-facing alias of search_points_with_filter: the trait method is named search_with_filter, so this method exists under that name and forwards to the full-featured search entry point. Defining it as a regular method (rather than inside the impl block) keeps the trait implementation free of deprecated implicit method promotion.

    QdrantClient::service_info

    async fn QdrantClient::service_info(self : QdrantClient) -> ServiceInfo

    Read server info via GET /: the Qdrant version and commit hash.

    QdrantClient::set_locks

    async fn QdrantClient::set_locks(self : QdrantClient, write : Bool, error_message? : String?) -> Unit

    Set the global write lock state via POST /locks.

    When write is true the service rejects write operations; provide an optional error_message returned to clients on rejected writes.

    QdrantClient::set_payload

    async fn QdrantClient::set_payload(self : QdrantClient, name : String, payload :
    Json
    , ids? : Array[Int64]?, filter? : Filter?, wait? : Bool) -> Unit

    Set (merge) a payload on the given points via POST /collections/{name}/points/payload.

    Pass either ids or filter to select the target points. The payload fields are merged into each point's existing payload; fields not in the given object are kept.

    QdrantClient::update_collection

    async fn QdrantClient::update_collection(self : QdrantClient, name : String, update : CollectionUpdate) -> Unit

    Update collection settings via PATCH /collections/{name}.

    See CollectionUpdate for which settings can be changed.

    QdrantClient::upsert_points

    async fn QdrantClient::upsert_points(self : QdrantClient, name : String, points : Array[PointStruct], wait? : Bool) -> Unit

    Upsert points into a collection via PUT /collections/{name}/points. Pass wait=false to return immediately; the operation may still be propagating on the server.

    QdrantClient::upsert_points_batch

    async fn QdrantClient::upsert_points_batch(self : QdrantClient, name : String, points : Array[PointStruct], wait? : Bool) -> Unit

    Upsert points through the batch (column-oriented) format via PUT /collections/{name}/points. The batch format packs ids, vectors and payloads into parallel arrays, which Qdrant applies as a single request. This is the classic batch loading endpoint; /points/batch is the newer multi-operation endpoint (body {"operations": [...]}) and is NOT used here.

    RaftInfo

    pub struct RaftInfo {
    term : Int64?
    commit : Int64?
    pending_operations : Int64?
    leader : String?
    role : String?
    is_voter : Bool?
    }

    Raft consensus state of the cluster. leader is the peer id of the current leader, kept as exact decimal text (Qdrant peer ids are u64).

    RaftInfo::from_json

    Parse the raft_info object of a cluster response.

    RemoteShardInfo

    pub struct RemoteShardInfo {
    shard_id : Int64
    peer_id : String
    state : String
    }

    A shard replica hosted on a remote peer. peer_id is kept as exact decimal text (Qdrant peer ids are u64).

    RemoteShardInfo::from_json

    Parse one remote shard entry: { "shard_id": 0, "peer_id": 1, "state": "Active" }.

    ScoredPoint

    pub struct ScoredPoint {
    id : Int64
    score : Double
    payload :
    Json
    ?
    }

    One hit returned by the search endpoint: a point id, its similarity score, and the payload attached to the point (when requested).

    ScoredPoint::from_json

    Parse a search hit object: { "id": 1, "score": 0.99, "payload": {...} }.

    ScoredPointGroup

    pub struct ScoredPointGroup {
    id :
    Json

    hits : Array[ScoredPoint]
    }

    One group returned by the grouped search endpoint: the group id (the payload value points were grouped by) and the top hits within the group.

    ScoredPointGroup::from_json

    Parse one search group: { "hits": [...], "id": "alpha" }.

    ScrollResult

    pub struct ScrollResult {
    points : Array[PointStruct]
    next_page_offset : Int64?
    }

    Result of POST /collections/{name}/points/scroll: the points on the current page, and the next page offset when more pages remain.

    ScrollResult::from_json

    Parse the result object of a scroll response.

    SearchParams

    pub struct SearchParams {
    hnsw_ef : Int?
    exact : Bool?
    }

    Search-time parameters passed in the params field of a search request. Both fields are optional; only the set ones are serialized.

    SearchParams::new

    fn SearchParams::new(hnsw_ef? : Int?, exact? : Bool?) -> SearchParams

    SearchParams::to_json

    Convert to the Qdrant wire object, omitting unset fields.

    ServiceInfo

    pub struct ServiceInfo {
    version : String?
    commit : String?
    }

    Server info from GET /service: the running Qdrant version and build metadata when present.

    ServiceInfo::from_json

    Parse the service info object.

    ShardTransferInfo

    pub struct ShardTransferInfo {
    shard_id : Int64
    from : String
    to : String
    method_name : String?
    }

    An in-flight shard transfer between two peers. from / to are peer ids kept as exact decimal text (Qdrant peer ids are u64).

    ShardTransferInfo::from_json

    Parse one shard transfer entry.

    SnapshotInfo

    pub struct SnapshotInfo {
    name : String
    creation_time : String?
    size : Int64?
    }

    One snapshot entry, from GET /collections/{name}/snapshots or GET /snapshots.

    SnapshotInfo::from_json

    Parse one snapshot entry: { "name": "snap-1", "creation_time": "...", "size": 123 }.

    VectorParams

    pub struct VectorParams {
    size : Int
    distance : Distance
    }

    Vector configuration for a collection, as accepted by PUT /collections/{name}.

    VectorParams::from_json

    Parse VectorParams from the config fragment returned by GET /collections/{name}.

    VectorParams::new

    fn VectorParams::new(size : Int, distance : Distance) -> VectorParams

    VectorParams::to_json

    Convert to the JSON body fragment used by the Qdrant API: { "size": 4, "distance": "Cosine" }.

    collection_cluster_op_body

    fn collection_cluster_op_body(op : String, shard_id : Int64, peer_id? : String?, from_peer_id? : String?, to_peer_id? : String?) ->
    Json

    Build the request body for a collection cluster operation (POST /collections/{name}/cluster).

    Qdrant's ClusterOperations is an untagged enum of single-key objects: the operation name is the key and its parameters are the value, e.g. {"replicate_shard": {"shard_id": 0, "from_peer_id": 1, "to_peer_id": 2}}. A flat {"method": "replicate_shard", ...} body matches no variant and is rejected with HTTP 400. op must be a real Qdrant operation name such as replicate_shard, move_shard, drop_replica or abort_transfer. Peer ids are exact decimal texts of Qdrant's u64 values.

    count_request_body

    fn count_request_body(filter : Filter?, exact : Bool) ->
    Json

    Build the request body for POST /collections/{name}/points/count.

    int64_of_json

    fn int64_of_json(json :
    Json
    ) -> Int64?

    Read an integer from a JSON number, using the original textual representation when available so u64 values survive without loss. Numbers within the exact double range (2^53) convert directly; the MoonBit JSON lexer only keeps the repr text for out-of-range values.

    int64_to_json

    fn int64_to_json(v : Int64) ->
    Json

    Serialize an Int64 as a JSON number, keeping the exact decimal text. Qdrant peer ids are u64 and routinely exceed 2^53, where a plain Double round-trip would silently change the id; the repr keeps the wire form identical to the original integer.

    join_url

    fn join_url(base : String, path : String) -> String

    Join a path fragment onto the base URL, keeping a single slash between them.

    parse_aliases

    fn parse_aliases(json :
    Json
    ) -> Array[AliasInfo]?

    Parse the result.aliases array of GET /collections/aliases.

    parse_count_result

    fn parse_count_result(json :
    Json
    ) -> Int64?

    Parse the result object of a count response: { "count": 42 }. Returns None when the shape does not match.

    parse_int64

    fn parse_int64(s : String) -> Int64?

    Parse a base-10 integer string into an Int64, tolerating a leading '-'. Used to decode large JSON numbers (peer ids, shard ids) from their original textual representation without precision loss.

    parse_payload_indexes

    fn parse_payload_indexes(json :
    Json
    ) -> Array[PayloadIndexInfo]?

    Parse the payload indexes of a collection. Qdrant has no dedicated "list indexes" endpoint; the schema is returned as payload_schema inside GET /collections/{name}. Each key is a field name and each value is an index descriptor like { "data_type": "keyword", "points": 3, ... }.

    parse_query_result

    fn parse_query_result(json :
    Json
    ) -> Array[ScoredPoint]?

    Parse the result of a query response into scored points.

    The query API returns { "result": { "points": [...], "next_page_offset": ... } } (an object), unlike the search API which returns { "result": [...] }. Both shapes are accepted here.

    parse_result

    Extract the result field of a Qdrant API response ({ "result": ..., "status": "ok", "time": ... }). Returns None when the response is not an object or has no result key.

    parse_retrieve_result

    fn parse_retrieve_result(json :
    Json
    ) -> Array[PointStruct]?

    Parse the result array of a retrieve response into points. Returns None when the response does not match the expected shape.

    parse_scored_array

    fn parse_scored_array(arr : Array[
    Json
    ]) -> Array[ScoredPoint]?

    Parse an array of scored point objects into ScoredPoint values. Returns None when any element does not match the expected shape.

    parse_search_groups

    fn parse_search_groups(json :
    Json
    ) -> Array[ScoredPointGroup]?

    Parse the result.groups array of a grouped search response.

    parse_search_result

    fn parse_search_result(json :
    Json
    ) -> Array[ScoredPoint]?

    Parse the result array of a search response into scored points. Returns None when the response does not match the expected shape.

    parse_snapshots

    fn parse_snapshots(json :
    Json
    ) -> Array[SnapshotInfo]?

    Parse the result array of a snapshots response.

    peer_id_to_json

    fn peer_id_to_json(text : String) ->
    Json

    Serialize a peer id given as decimal text as an exact JSON number. The number's repr carries the exact decimal text, so the wire form is identical to the original u64 id even when the in-memory double is only approximate (ids above 2^53) or unrepresentable as Int64 (above Int64::max, which roughly half of random u64 peer ids are).

    point_ids_to_json

    fn point_ids_to_json(ids : Array[Int64]) ->
    Json

    Serialize point ids as a JSON number array without losing precision.

    Qdrant point ids are u64 values. Above 2^53 a Double can no longer hold every integer: Json::number(id.to_double()) turns 9007199254740993 into 9007199254740992, so a lookup returns the neighbouring point and a delete removes it. int64_to_json keeps the exact decimal text in the JSON number's repr, which is what the wire serializer emits.

    scroll_request_body

    fn scroll_request_body(limit : Int, filter : Filter?, offset : Int64?, with_payload : Bool, with_vector : Bool) ->
    Json

    Build the request body for POST /collections/{name}/points/scroll.

    shard_source_peer

    fn shard_source_peer(layout : CollectionClusterInfo, shard_id : Int64) -> String

    Pick a peer that already holds shard_id as the replication source. local_shards belong to layout.peer_id; remote entries carry their own peer id. Falls back to the local peer when the shard is not listed.

    u64_text_of_json

    fn u64_text_of_json(json :
    Json
    ) -> String?

    Read a peer id as its exact decimal text. Qdrant peer ids are random u64 values and roughly half of them exceed Int64::max, so they cannot be modeled as Int64 without silent wrapping; the client keeps the decimal text instead. The MoonBit JSON lexer preserves the original number text for values outside the exact double range, and in-range integers convert exactly.

    url_encode

    fn url_encode(input : String) -> String

    Percent-encode a path segment for use in a Qdrant URL. Qdrant accepts a broad character set in collection, field and snapshot names (not just ASCII alphanumerics), so unreserved characters pass through and everything else is percent-encoded. Supplementary-plane characters (emoji and friends) arrive as UTF-16 surrogate pairs and are combined into their full code point, then encoded as 4-byte UTF-8.

    vector_to_json

    fn vector_to_json(vector : Array[Double]) ->
    Json

    Convert a dense vector to its JSON array representation. Shared by point serialization, batch upsert and search.