ocaml-qdrant
Pure OCaml client for Qdrant vector database.
The first OCaml Qdrant client! Combines best practices from official Python, Rust, Go, and TypeScript clients.
Features
- REST API client (no gRPC dependency)
- Collection management (create, delete, info, exists)
- Point operations (upsert, delete, get, batch upsert)
- Vector search with filters and score threshold
- Two Filter APIs: Sum types (explicit) + Fluent helpers (ergonomic)
- Batch operations with automatic chunking (Python-inspired)
- Recommend API with positive/negative examples
- Async I/O with Lwt
- Type-safe OCaml design
Install
opam install qdrant
Quick Start
let () = Lwt_main.run begin
let open Lwt.Syntax in
let open Qdrant in
(* Create collection *)
let* _ = create_collection
~name:"my_collection"
~vector_config:{ size = 1024; distance = Cosine }
() in
(* Insert points *)
let* _ = upsert ~collection:"my_collection" ~points:[
{ id = "1"; vector = my_embedding; payload = [("text", `String "hello")] }
] () in
(* Search *)
let* results = search
~collection:"my_collection"
~vector:query_embedding
~limit:10
() in
Lwt.return ()
end
Filter API
Two styles are available - choose based on your preference:
Style 1: Sum Types (Explicit, Pattern-matchable)
Direct use of algebraic data types. Best for:
- Pattern matching on filter conditions
- Compile-time exhaustiveness checking
- Maximum type safety
let open Qdrant.Filter in
(* Simple filter *)
let filter = Must [
MatchKeyword ("category", "tech");
Range ("price", { gt = None; gte = Some 10.0; lt = None; lte = Some 100.0 });
]
(* Complex filter with nesting *)
let filter = And [
Must [MatchKeyword ("status", "active")];
Should [
MatchAny ("tags", ["ai"; "ml"; "data"]);
GeoRadius ("location", { lat = 37.5; lon = 127.0 }, 1000.0);
];
MustNot [IsNull "deleted_at"];
]
Style 2: Fluent Helpers (Ergonomic, Go/Rust-inspired)
Convenience functions wrapping sum types. Best for:
- Quick prototyping
- Cleaner syntax for common cases
- Familiar to Go/Rust developers
let open Qdrant.Filter in
(* Simple filter *)
let filter = must [
match_keyword "category" "tech";
range "price" ~gte:10.0 ~lte:100.0 ();
]
(* Complex filter *)
let filter = combine [
must [match_keyword "status" "active"];
should [
match_any "tags" ["ai"; "ml"; "data"];
geo_radius "location" ~lat:37.5 ~lon:127.0 ~radius_m:1000.0;
];
must_not [is_null "deleted_at"];
]
(* Full-text search *)
let filter = must [full_text "content" "machine learning"]
(* Null checks *)
let filter = must [is_not_null "required_field"]
Both styles produce identical JSON and can be used with search_with_filter:
let* results = search_with_filter
~collection:"items"
~vector:query_vec
~limit:10
~filter:(Filter.to_json filter)
()
Batch Operations
Python-inspired batch upsert with automatic chunking:
(* Insert 10,000 points in chunks of 100 *)
let* count = batch_upsert
~collection:"embeddings"
~points:large_point_list
~chunk_size:100
() in
Printf.printf "Inserted %d points\n" count
Recommend API
Find similar items based on examples:
let* recommendations = recommend
~collection:"products"
~positive:["liked-item-1"; "liked-item-2"]
~negative:["disliked-item"]
~limit:10
()
Config
From Environment Variables (Recommended)
export QDRANT_URL="http://localhost:6333"
export QDRANT_API_KEY="your-api-key" # optional
(* Explicit is better than implicit - use config_from_env *)
let config = Qdrant.config_from_env () in
let* result = Qdrant.health ~config () in
...
Programmatic Configuration
let config = Qdrant.{
base_url = "https://your-qdrant.cloud";
api_key = Some "your-key";
timeout_s = 30.0;
}
Default Config
(* default_config = localhost:6333, no API key, 30s timeout *)
let* result = Qdrant.health () in (* uses default_config *)
API Reference
Configuration
default_config - Default config (localhost:6333, no API key)config_from_env - Create config from QDRANT_URL and QDRANT_API_KEY env vars
Health & Info
health - Check server statusversion - Get Qdrant version
Collections
list_collections - List all collectionscollection_exists - Check if collection existsget_collection - Get collection infocreate_collection - Create with vector configdelete_collection - Delete collection
Points
upsert - Insert/update pointsbatch_upsert - Bulk insert with chunkingdelete_points - Delete by IDsget_point - Get single point
Search
search - Vector similarity searchsearch_with_filter - Search with payload filterrecommend - Recommendation based on examples
scroll - Paginate through all pointscount - Count points in collection
Filter Module (Sum Types)
MatchKeyword, MatchAny, MatchInt, MatchBool, MatchTextRange, RangeIntIsNull, IsEmpty, HasValue, NotEmptyGeoRadiusMust, Should, MustNot, And
Filter Module (Fluent Helpers)
match_keyword, match_any, match_int, match_boolfull_textrange, range_intis_null, is_not_null, is_empty, is_not_emptygeo_radiusmust, should, must_not, combine
Inspiration
This client combines the best from official Qdrant clients:
- Python: Batch operations with chunking, comprehensive API
- Rust: Type safety, sum types for filters
- Go: Fluent filter API, builder patterns (qdrant-go)
- TypeScript: Clean async/await patterns
Testing
Unit Tests (No Qdrant Required)
dune runtest
27 tests covering types, filters, config, and error handling.
Integration Tests (Qdrant Required)
# Set up environment
export QDRANT_URL="http://localhost:6333"
# or for cloud: export QDRANT_URL="https://your-cluster.qdrant.io"
# export QDRANT_API_KEY="your-key"
# Run integration example
dune exec examples/basic.exe
Security
- Path injection prevention (validates collection names and IDs)
- HTTP header injection prevention (validates API keys)
- Timeout protection against hung connections
License
MIT