qdrant

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package qdrant is a focused HTTP client for the Qdrant vector database (https://qdrant.tech).

Unlike drops/pg and drops/clickhouse, Qdrant doesn't speak SQL — it has a JSON API for collection management and vector search. This package exposes that API as plain Go types and methods, with no dependency on the upstream Go SDK (net/http + encoding/json only).

Quick start:

cli, _ := qdrant.NewClient("http://localhost:6333",
    qdrant.WithAPIKey(os.Getenv("QDRANT_API_KEY")))

_ = cli.CreateCollection(ctx, "embeddings", qdrant.CollectionConfig{
    Vectors: qdrant.VectorParams{Size: 384, Distance: qdrant.DistanceCosine},
})

_ = cli.Upsert(ctx, "embeddings", []qdrant.Point{
    {ID: "doc-1", Vector: vec1, Payload: map[string]any{"topic": "go"}},
    {ID: "doc-2", Vector: vec2, Payload: map[string]any{"topic": "rust"}},
})

hits, _ := cli.Search(ctx, "embeddings", qdrant.SearchRequest{
    Vector:      query,
    Limit:       10,
    Filter:      qdrant.Must(qdrant.Eq("topic", "go")),
    WithPayload: true,
})

Surface (current):

  • Collection ops: CreateCollection, DeleteCollection, CollectionExists, CollectionInfo, ListCollections
  • Point ops: Upsert, Delete, Retrieve, Count
  • Search ops: Search, Scroll, Recommend
  • Filter DSL: Must / Should / MustNot blocks with Eq / In / Range / IsEmpty / HasID / GeoBoundingBox conditions

Out of scope (drop down to raw HTTP via Do for these): gRPC, snapshots, sharding management, payload indexes, cluster topology.

Index

Examples

Constants

This section is empty.

Variables

View Source
var All = Must

All / Any are aliases that read naturally at call sites.

View Source
var Any = Should
View Source
var ErrCollectionMissing = errors.New("qdrant: collection not found")

ErrCollectionMissing is returned when a 404 references a missing collection. Use errors.Is to branch on it.

View Source
var ErrInvalidIdentifier = errors.New("qdrant: invalid identifier")

ErrInvalidIdentifier is returned when a collection / vector name fails validation. errors.Is(err, ErrInvalidIdentifier) is true for every wrapped instance.

View Source
var ErrNoPoints = errors.New("qdrant: no points supplied")

ErrNoPoints is returned by Upsert / DeleteByIDs when called with an empty batch.

Functions

func F

func F(v float64) *float64

F is a helper that returns a pointer to v — useful when filling RangeOpts' pointer fields inline.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps Qdrant's HTTP API.

Safe for concurrent use by multiple goroutines. The optional Hook fires after every HTTP request with method + path + duration + error so the same observability story (drops.LoggerHook, OTel adapter, metrics emitter) works against Qdrant just like it does against pg and clickhouse.

func NewClient

func NewClient(baseURL string, opts ...ClientOption) (*Client, error)

NewClient returns a Client for the supplied Qdrant base URL — e.g. "http://localhost:6333" or "https://my-cluster.eu.cloud.qdrant.io". Trailing slashes are trimmed.

Example

ExampleNewClient shows the minimal happy-path: connect, create a collection, upsert a point, search.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/bernardoforcillo/drops/qdrant"
)

// exampleServer returns an httptest.Server that mimics a tiny subset
// of the Qdrant API so the example tests stay self-contained and
// deterministic (no real Qdrant instance required).
func exampleServer() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		respond := func(result any) {
			_ = json.NewEncoder(w).Encode(map[string]any{
				"result": result, "status": "ok", "time": 0.0,
			})
		}
		switch {
		case r.Method == http.MethodPut && r.URL.Path == "/collections/docs":
			respond(true)
		case r.Method == http.MethodPut && r.URL.Path == "/collections/docs/points":
			respond(map[string]any{"operation_id": 1, "status": "completed"})
		case r.Method == http.MethodPost && r.URL.Path == "/collections/docs/points/search":
			respond([]map[string]any{
				{"id": "doc-1", "score": 0.93, "payload": map[string]any{"topic": "go"}},
			})
		default:
			http.NotFound(w, r)
		}
	}))
}

func main() {
	srv := exampleServer()
	defer srv.Close()

	cli, _ := qdrant.NewClient(srv.URL)
	ctx := context.Background()

	if err := cli.CreateCollection(ctx, "docs", qdrant.CollectionConfig{
		Vectors: qdrant.VectorParams{Size: 3, Distance: qdrant.DistanceCosine},
	}); err != nil {
		fmt.Println("create:", err)
		return
	}
	if err := cli.Upsert(ctx, "docs", []qdrant.Point{
		{ID: "doc-1", Vector: []float32{1, 0, 0},
			Payload: map[string]any{"topic": "go"}},
	}); err != nil {
		fmt.Println("upsert:", err)
		return
	}
	hits, _ := cli.Search(ctx, "docs", qdrant.SearchRequest{
		Vector:      []float32{1, 0, 0},
		Limit:       1,
		WithPayload: true,
	})
	fmt.Printf("found %d hit(s): %v at %.2f\n",
		len(hits), hits[0].ID, hits[0].Score)
}
Output:
found 1 hit(s): doc-1 at 0.93

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the configured base URL.

func (*Client) CollectionExists

func (c *Client) CollectionExists(ctx context.Context, name string) (bool, error)

CollectionExists reports whether the collection exists.

func (*Client) CollectionInfo

func (c *Client) CollectionInfo(ctx context.Context, name string) (*CollectionInfo, error)

CollectionInfo returns metadata about a collection.

func (*Client) Count

func (c *Client) Count(ctx context.Context, collection string, f *Filter, exact bool) (int64, error)

Count returns the number of points matching an optional filter. Pass nil for a full count.

func (*Client) CreateCollection

func (c *Client) CreateCollection(ctx context.Context, name string, cfg CollectionConfig) error

CreateCollection creates a new collection. Returns nil if the operation succeeds (Qdrant responds with `{"result": true}`).

func (*Client) DeleteByFilter

func (c *Client) DeleteByFilter(ctx context.Context, collection string, f *Filter, opts ...WriteOptions) error

DeleteByFilter deletes every point that matches the filter.

func (*Client) DeleteByIDs

func (c *Client) DeleteByIDs(ctx context.Context, collection string, ids []any, opts ...WriteOptions) error

DeleteByIDs deletes the listed points.

func (*Client) DeleteCollection

func (c *Client) DeleteCollection(ctx context.Context, name string) error

DeleteCollection drops a collection. Idempotent on the server side.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, path string, body, out any) (err error)

Do issues a request to <base>+path with optional JSON body and decodes the response into out (if non-nil). The Qdrant convention is `{"result": ..., "status": "ok", "time": float}`; out is decoded against the result field.

Every Do call fires the observability hook (if installed) exactly once after the request completes, with Kind="http", SQL set to "<METHOD> <path>", the elapsed duration, and any error.

Callers usually don't need Do; the typed methods (CreateCollection, Upsert, Search, …) wrap it.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the underlying http.Client. Useful for tests that want to swap out a transport.

func (*Client) Hook

func (c *Client) Hook() drops.Hook

Hook returns the currently attached observability hook, or nil.

func (*Client) ListCollections

func (c *Client) ListCollections(ctx context.Context) ([]string, error)

ListCollections returns every collection's name.

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping issues a request to /healthz to confirm the Qdrant instance is reachable and willing to serve traffic. Suitable as a Kubernetes readiness probe shape.

func (*Client) Recommend

func (c *Client) Recommend(ctx context.Context, collection string, req RecommendRequest) ([]Hit, error)

Recommend returns points similar to the positive examples and dissimilar to the negative examples.

func (*Client) Retrieve

func (c *Client) Retrieve(ctx context.Context, collection string, ids []any, opts ...RetrieveOptions) ([]Point, error)

Retrieve fetches the points with the given IDs.

func (*Client) Scroll

func (c *Client) Scroll(ctx context.Context, collection string, req ScrollRequest) (*ScrollPage, error)

Scroll iterates through a collection in deterministic order, optionally filtered. Pass the previous response's NextPageOffset as the next call's Offset until it comes back nil.

func (*Client) Search

func (c *Client) Search(ctx context.Context, collection string, req SearchRequest) ([]Hit, error)

Search performs a single-vector similarity search.

func (*Client) Upsert

func (c *Client) Upsert(ctx context.Context, collection string, points []Point, opts ...WriteOptions) error

Upsert inserts or replaces a batch of points. Empty batches return ErrNoPoints rather than issuing a no-op HTTP call.

func (*Client) WithHookFn

func (c *Client) WithHookFn(hook drops.Hook) *Client

WithHookFn returns a shallow copy of the client with hook installed (nil clears it). Useful when an existing client needs a request- scoped hook layered on top of a shared, hook-free client.

type ClientOption

type ClientOption func(*Client)

ClientOption configures a Client.

func WithAPIKey

func WithAPIKey(key string) ClientOption

WithAPIKey sets the Authorization header sent with every request. Qdrant Cloud expects the api-key header; the package wires both `api-key` and `Authorization: Bearer` so either deployment style works without further config.

func WithHTTPClient

func WithHTTPClient(h *http.Client) ClientOption

WithHTTPClient overrides the default http.Client (useful for custom transports, timeouts, instrumentation).

func WithHook

func WithHook(h drops.Hook) ClientOption

WithHook installs an observability hook that fires after every HTTP request the client makes. The same drops.Hook contract used by pg.DB and clickhouse.DB — compose with drops.ChainHooks and pair with drops.LoggerHook for instant request logging.

func WithTimeout

func WithTimeout(d time.Duration) ClientOption

WithTimeout sets the request timeout on the underlying http.Client. Ignored if WithHTTPClient was used.

type CollectionConfig

type CollectionConfig struct {
	Vectors           VectorParams `json:"vectors"`
	ShardNumber       *int         `json:"shard_number,omitempty"`
	ReplicationFactor *int         `json:"replication_factor,omitempty"`
	WriteConsistency  *int         `json:"write_consistency_factor,omitempty"`
	OnDiskPayload     *bool        `json:"on_disk_payload,omitempty"`
	HNSW              *HNSWConfig  `json:"hnsw_config,omitempty"`
}

CollectionConfig is the payload for CreateCollection.

type CollectionInfo

type CollectionInfo struct {
	Status        string         `json:"status"`
	VectorsCount  int            `json:"vectors_count"`
	PointsCount   int            `json:"points_count"`
	SegmentsCount int            `json:"segments_count"`
	Config        map[string]any `json:"config"`
}

CollectionInfo is the response from GET /collections/{name}.

type Condition

type Condition struct {
	Key     string            `json:"key,omitempty"`
	Match   *MatchCondition   `json:"match,omitempty"`
	Range   *RangeCondition   `json:"range,omitempty"`
	HasID   []any             `json:"has_id,omitempty"`
	IsEmpty *IsEmptyCondition `json:"is_empty,omitempty"`
	IsNull  *IsNullCondition  `json:"is_null,omitempty"`
	Geo     *GeoBoundingBox   `json:"geo_bounding_box,omitempty"`
	Nested  *Filter           `json:"filter,omitempty"`
}

Condition is a single filter clause — either a key/value test, a range, an ID membership test, or a nested Filter.

func Eq

func Eq(field string, value any) Condition

Eq tests payload.<field> == value.

func GeoIn

func GeoIn(field string, topLeft, bottomRight GeoPoint) Condition

GeoIn tests that payload.<field> falls inside the bounding box.

func HasID

func HasID(ids ...any) Condition

HasID tests that the point's ID is in the given set.

func In

func In(field string, values ...any) Condition

In tests payload.<field> ∈ values.

func IsEmpty

func IsEmpty(field string) Condition

IsEmpty tests that payload.<field> is missing or empty.

func IsNull

func IsNull(field string) Condition

IsNull tests that payload.<field> is null.

func MatchText

func MatchText(field, text string) Condition

MatchText runs a tokenised full-text match.

func Nest

func Nest(f *Filter) Condition

Nest wraps a Filter as a Condition so it can sit inside another block — useful for OR-of-ANDs and similar shapes.

func NotIn

func NotIn(field string, values ...any) Condition

NotIn tests payload.<field> ∉ values.

func Range

func Range(field string, opts RangeOpts) Condition

Range tests payload.<field> against a numeric range.

qdrant.Range("created_at", qdrant.RangeOpts{Gte: qdrant.F(1700000000)})

type Distance

type Distance string

Distance enumerates the supported similarity metrics. The values are the exact strings Qdrant expects in JSON.

const (
	DistanceCosine    Distance = "Cosine"
	DistanceEuclid    Distance = "Euclid"
	DistanceDot       Distance = "Dot"
	DistanceManhattan Distance = "Manhattan"
)

type Filter

type Filter struct {
	Must    []Condition `json:"must,omitempty"`
	Should  []Condition `json:"should,omitempty"`
	MustNot []Condition `json:"must_not,omitempty"`
}

Filter is Qdrant's predicate object: a tree of Must / Should / MustNot blocks containing Condition leaves.

func Must

func Must(conds ...Condition) *Filter

Must builds a Filter where every condition is required.

Example

ExampleMust shows the filter DSL: AND-of conditions on a payload.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/bernardoforcillo/drops/qdrant"
)

func main() {
	filter := qdrant.Must(
		qdrant.Eq("topic", "go"),
		qdrant.Range("created_at", qdrant.RangeOpts{Gte: qdrant.F(1700000000)}),
	)
	body, _ := json.Marshal(filter)
	fmt.Println(string(body))
}
Output:
{"must":[{"key":"topic","match":{"value":"go"}},{"key":"created_at","range":{"gte":1700000000}}]}

func MustNot

func MustNot(conds ...Condition) *Filter

MustNot builds a Filter where no condition may match.

func Should

func Should(conds ...Condition) *Filter

Should builds a Filter where at least one condition must match.

type GeoBoundingBox

type GeoBoundingBox struct {
	TopLeft     GeoPoint `json:"top_left"`
	BottomRight GeoPoint `json:"bottom_right"`
}

GeoBoundingBox is the rectangle-shape geo filter.

type GeoPoint

type GeoPoint struct {
	Lat float64 `json:"lat"`
	Lon float64 `json:"lon"`
}

type HNSWConfig

type HNSWConfig struct {
	M                 *int  `json:"m,omitempty"`
	EFConstruct       *int  `json:"ef_construct,omitempty"`
	FullScanThreshold *int  `json:"full_scan_threshold,omitempty"`
	OnDisk            *bool `json:"on_disk,omitempty"`
}

HNSWConfig is the HNSW-index tuning bag. Pointer fields let zero- valued integers reach Qdrant as "not set" rather than 0.

type HTTPError

type HTTPError struct {
	Status     int
	StatusText string
	Body       []byte
}

HTTPError is returned for non-2xx responses. The body is captured so callers can extract Qdrant's error payload when needed.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Hit

type Hit struct {
	ID      any            `json:"id"`
	Score   float32        `json:"score"`
	Vector  []float32      `json:"vector,omitempty"`
	Payload map[string]any `json:"payload,omitempty"`
}

Hit is one search result.

type IsEmptyCondition

type IsEmptyCondition struct {
	Key string `json:"key"`
}

IsEmptyCondition / IsNullCondition test for missing / null payloads.

type IsNullCondition

type IsNullCondition struct {
	Key string `json:"key"`
}

type MatchCondition

type MatchCondition struct {
	Value  any    `json:"value,omitempty"`
	Any    []any  `json:"any,omitempty"`
	Except []any  `json:"except,omitempty"`
	Text   string `json:"text,omitempty"`
}

MatchCondition expresses payload value equality / set membership / text-search. Exactly one of Value / Any / Text / Except should be non-zero.

type Point

type Point struct {
	ID      any            `json:"id"`
	Vector  []float32      `json:"vector"`
	Payload map[string]any `json:"payload,omitempty"`
}

Point is a single vector + payload + ID record.

ID may be an int (uint64) or a string (UUID); Qdrant accepts either and the package passes the value through to JSON as-is. Vector is the dense embedding; for named-vector collections use NamedVectors instead via a custom payload type.

type RangeCondition

type RangeCondition struct {
	Lt  *float64 `json:"lt,omitempty"`
	Lte *float64 `json:"lte,omitempty"`
	Gt  *float64 `json:"gt,omitempty"`
	Gte *float64 `json:"gte,omitempty"`
}

RangeCondition expresses numeric range tests. All bounds are optional; nil-pointer means "open end".

type RangeOpts

type RangeOpts struct {
	Lt  *float64
	Lte *float64
	Gt  *float64
	Gte *float64
}

RangeOpts is the argument bag for Range — pointer fields express "absent" cleanly.

type RecommendRequest

type RecommendRequest struct {
	Positive       []any          `json:"positive,omitempty"`
	Negative       []any          `json:"negative,omitempty"`
	Limit          int            `json:"limit"`
	Offset         int            `json:"offset,omitempty"`
	Filter         *Filter        `json:"filter,omitempty"`
	ScoreThreshold *float32       `json:"score_threshold,omitempty"`
	WithVector     bool           `json:"with_vector,omitempty"`
	WithPayload    bool           `json:"with_payload,omitempty"`
	Params         map[string]any `json:"params,omitempty"`
	Strategy       string         `json:"strategy,omitempty"` // "average_vector" | "best_score"
}

RecommendRequest is the body for /collections/{name}/points/recommend.

type RetrieveOptions

type RetrieveOptions struct {
	WithVector  bool
	WithPayload bool
}

RetrieveOptions configures Retrieve.

type ScrollPage

type ScrollPage struct {
	Points         []Point `json:"points"`
	NextPageOffset any     `json:"next_page_offset"`
}

ScrollPage is the response from Scroll.

type ScrollRequest

type ScrollRequest struct {
	Filter      *Filter `json:"filter,omitempty"`
	Limit       int     `json:"limit,omitempty"`
	Offset      any     `json:"offset,omitempty"` // page cursor; pass last page's NextPageOffset
	WithVector  bool    `json:"with_vector,omitempty"`
	WithPayload bool    `json:"with_payload,omitempty"`
}

ScrollRequest is the body for /collections/{name}/points/scroll.

type SearchRequest

type SearchRequest struct {
	Vector         []float32      `json:"vector"`
	Limit          int            `json:"limit"`
	Offset         int            `json:"offset,omitempty"`
	Filter         *Filter        `json:"filter,omitempty"`
	ScoreThreshold *float32       `json:"score_threshold,omitempty"`
	WithVector     bool           `json:"with_vector,omitempty"`
	WithPayload    bool           `json:"with_payload,omitempty"`
	Params         map[string]any `json:"params,omitempty"` // hnsw_ef, exact, etc.
}

SearchRequest is the body for /collections/{name}/points/search.

type VectorParams

type VectorParams struct {
	Size     int      `json:"size"`
	Distance Distance `json:"distance"`
	// OnDisk stores the vector data on disk instead of in memory.
	OnDisk *bool `json:"on_disk,omitempty"`
}

VectorParams configures a single named vector or the (default) unnamed vector of a collection.

type WriteOptions

type WriteOptions struct {
	Wait bool
	// Ordering hint; "weak" (default), "medium", "strong".
	Ordering string
}

WriteOptions tune the consistency of write operations. The default (zero value) is "fire and forget" — return as soon as Qdrant has queued the operation. Wait=true blocks until the data is durable in the local segments.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL