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 ¶
- Variables
- func F(v float64) *float64
- type Client
- func (c *Client) BaseURL() string
- func (c *Client) CollectionExists(ctx context.Context, name string) (bool, error)
- func (c *Client) CollectionInfo(ctx context.Context, name string) (*CollectionInfo, error)
- func (c *Client) Count(ctx context.Context, collection string, f *Filter, exact bool) (int64, error)
- func (c *Client) CreateCollection(ctx context.Context, name string, cfg CollectionConfig) error
- func (c *Client) DeleteByFilter(ctx context.Context, collection string, f *Filter, opts ...WriteOptions) error
- func (c *Client) DeleteByIDs(ctx context.Context, collection string, ids []any, opts ...WriteOptions) error
- func (c *Client) DeleteCollection(ctx context.Context, name string) error
- func (c *Client) Do(ctx context.Context, method, path string, body, out any) (err error)
- func (c *Client) HTTPClient() *http.Client
- func (c *Client) Hook() drops.Hook
- func (c *Client) ListCollections(ctx context.Context) ([]string, error)
- func (c *Client) Ping(ctx context.Context) error
- func (c *Client) Recommend(ctx context.Context, collection string, req RecommendRequest) ([]Hit, error)
- func (c *Client) Retrieve(ctx context.Context, collection string, ids []any, opts ...RetrieveOptions) ([]Point, error)
- func (c *Client) Scroll(ctx context.Context, collection string, req ScrollRequest) (*ScrollPage, error)
- func (c *Client) Search(ctx context.Context, collection string, req SearchRequest) ([]Hit, error)
- func (c *Client) Upsert(ctx context.Context, collection string, points []Point, opts ...WriteOptions) error
- func (c *Client) WithHookFn(hook drops.Hook) *Client
- type ClientOption
- type CollectionConfig
- type CollectionInfo
- type Condition
- func Eq(field string, value any) Condition
- func GeoIn(field string, topLeft, bottomRight GeoPoint) Condition
- func HasID(ids ...any) Condition
- func In(field string, values ...any) Condition
- func IsEmpty(field string) Condition
- func IsNull(field string) Condition
- func MatchText(field, text string) Condition
- func Nest(f *Filter) Condition
- func NotIn(field string, values ...any) Condition
- func Range(field string, opts RangeOpts) Condition
- type Distance
- type Filter
- type GeoBoundingBox
- type GeoPoint
- type HNSWConfig
- type HTTPError
- type Hit
- type IsEmptyCondition
- type IsNullCondition
- type MatchCondition
- type Point
- type RangeCondition
- type RangeOpts
- type RecommendRequest
- type RetrieveOptions
- type ScrollPage
- type ScrollRequest
- type SearchRequest
- type VectorParams
- type WriteOptions
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var All = Must
All / Any are aliases that read naturally at call sites.
var Any = Should
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.
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.
var ErrNoPoints = errors.New("qdrant: no points supplied")
ErrNoPoints is returned by Upsert / DeleteByIDs when called with an empty batch.
Functions ¶
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) CollectionExists ¶
CollectionExists reports whether the collection exists.
func (*Client) CollectionInfo ¶
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 ¶
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 ¶
DeleteCollection drops a collection. Idempotent on the server side.
func (*Client) Do ¶
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 ¶
HTTPClient returns the underlying http.Client. Useful for tests that want to swap out a transport.
func (*Client) ListCollections ¶
ListCollections returns every collection's name.
func (*Client) Ping ¶
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.
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 Nest ¶
Nest wraps a Filter as a Condition so it can sit inside another block — useful for OR-of-ANDs and similar shapes.
type Distance ¶
type Distance string
Distance enumerates the supported similarity metrics. The values are the exact strings Qdrant expects in JSON.
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 ¶
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}}]}
type GeoBoundingBox ¶
type GeoBoundingBox struct {
TopLeft GeoPoint `json:"top_left"`
BottomRight GeoPoint `json:"bottom_right"`
}
GeoBoundingBox is the rectangle-shape geo filter.
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 ¶
HTTPError is returned for non-2xx responses. The body is captured so callers can extract Qdrant's error payload when needed.
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 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 ¶
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.