Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ config

# Ignore qdrant_storage folder
qdrant_storage/

# Ignore .env file
.env
.envrc
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.16.3-otp-26
erlang 26.2.5.3
elixir 1.19.1-otp-28
erlang 28.1.1
171 changes: 153 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,188 @@ def deps do
[
{:qdrant, "~> 0.8.0"}
# Or use the latest version from GitHub | Recommended during development phase
{:qdrant, git: "git@github.com:marinac-dev/qdrant.git"},
{:qdrant, github: "marinac-dev/qdrant.git", branch: "master"},
]
end
```

## Config
## Configuration

Configure the Qdrant client in your `config/config.exs`:

### Local/Docker Setup (Default)

```elixir
config :qdrant,
port: 6333,
interface: "rest", # gRPC not yet supported
database_url: System.get_env("QDRANT_DATABASE_URL"),
# If you are using cloud version of Qdrant, add API key
api_key: System.get_env("QDRANT_API_KEY")
# Option 1: Use full URL (recommended)
url: System.get_env("QDRANT_URL") || "http://localhost:6333",
# Option 2: Use separate URL and port (for backward compatibility)
# database_url: System.get_env("QDRANT_DATABASE_URL") || "http://localhost",
# port: 6333,
require_api_key: false # Default: false for local/docker instances
```

### Qdrant Cloud Setup

```elixir
config :qdrant,
interface: "rest",
url: System.get_env("QDRANT_URL"), # e.g., "https://your-cluster.cloud.qdrant.io"
require_api_key: true, # Required for Qdrant Cloud (auto-detected if URL contains cloud.qdrant.io)
api_key: System.get_env("QDRANT_API_KEY") # Required when require_api_key is true
```

**Note:** The client automatically detects Qdrant Cloud instances and requires an API key when:
- The URL contains `cloud.qdrant.io`, or
- The URL uses HTTPS and is not localhost

You can also explicitly set `require_api_key: true` to force API key authentication.

Alternatively, you can set these via environment variables:
- `QDRANT_URL` - Full Qdrant server URL (e.g., `http://localhost:6333`) - takes priority if set
- `QDRANT_DATABASE_URL` - Qdrant server URL without port (default: `http://localhost`)
- `QDRANT_PORT` - Qdrant server port (default: `6333`)
- `QDRANT_REQUIRE_API_KEY` - Whether API key is required (default: `false`, auto-detected for Qdrant Cloud)
- `QDRANT_API_KEY` - API key for authentication (required if `require_api_key` is true)

**Configuration priority:** Application config takes priority over environment variables.

## Usage

The Qdrant Elixir Client provides a simple interface for interacting with the Qdrant API. For example, you can create a new collection, insert vectors, search, and delete data using the provided functions.
The Qdrant Elixir Client provides a simple interface for interacting with the Qdrant API.

### Collections

```elixir
collection_name = "my-collection"

# Create a new collection
# The vectors are 1536-dimensional (because of OpenAi embedding) and use the Cosine distance metric
Qdrant.create_collection(collection_name, %{vectors: %{size: 1536, distance: "Cosine"}})
# The vectors are 1536-dimensional (for OpenAI embeddings) and use the Cosine distance metric
{:ok, _} = Qdrant.create_collection(collection_name, %{
vectors: %{
size: 1536,
distance: "Cosine"
}
})

# List all collections
{:ok, collections} = Qdrant.list_collections()

# Get collection info
{:ok, info} = Qdrant.collection_info(collection_name)

# Check if collection exists
{:ok, %{"result" => %{"exists" => true}}} = Qdrant.collection_exists(collection_name)

# Update collection parameters
{:ok, _} = Qdrant.update_collection(collection_name, %{
optimizers_config: %{
deleted_threshold: 0.2
}
})

# Delete a collection
{:ok, _} = Qdrant.delete_collection(collection_name)
```

# Create embeddings for some text
vector1 = OpenAi.embed_text("Hello world")
vector2 = OpenAi.embed_text("This is OpenAI")
### Points

# Now we can insert the vectors with batch
Qdrant.upsert_points(collection_name, %{batch: %{ids: [1,2], vectors: [vector1, vector2]}})
# Or one by one
Qdrant.upsert_point(collection_name, %{points: [%{id: 1, vector: vector1}, %{id: 2, vector: vector2}]})
```elixir
collection_name = "my-collection"

# Create embeddings for some text
vector1 = [0.1, 0.2, 0.3] # Your embedding vector
vector2 = [0.4, 0.5, 0.6]

# Insert vectors with batch
{:ok, _} = Qdrant.upsert_point(collection_name, %{
batch: %{
ids: [1, 2],
vectors: [vector1, vector2]
}
})

# Or insert points one by one
{:ok, _} = Qdrant.upsert_point(collection_name, %{
points: [
%{id: 1, vector: vector1, payload: %{text: "Hello"}},
%{id: 2, vector: vector2, payload: %{text: "World"}}
]
})

# Search for similar vectors
vector3 = OpenAi.embed_text("Hello world!")
Qdrant.search_points(collection_name, %{vector: vector3, limit: 3})
query_vector = [0.15, 0.25, 0.35]
{:ok, results} = Qdrant.search_points(collection_name, %{
vector: query_vector,
limit: 3,
with_payload: true
})

# Get specific points by ID
{:ok, points} = Qdrant.get_points(collection_name, %{
ids: [1, 2],
with_payload: true,
with_vector: true
})

# Get a single point
{:ok, point} = Qdrant.get_point(collection_name, 1)

# Delete points
{:ok, _} = Qdrant.delete_points(collection_name, %{
points: [1, 2]
})
```

### Advanced Features

The library supports many advanced features including:

- **Aliases**: Manage collection aliases
- **Indexes**: Create and manage field indexes for faster filtering
- **Snapshots**: Backup and restore collections
- **Cluster operations**: Manage distributed setups
- **Service endpoints**: Health checks, telemetry, metrics

For full API documentation, see the [module documentation](https://hexdocs.pm/qdrant).

## Direct HTTP Module Access

You can also access the HTTP modules directly for more control:

```elixir
# Collections
alias Qdrant.Api.Http.Collections
{:ok, collections} = Collections.list_collections()

# Points
alias Qdrant.Api.Http.Points
{:ok, results} = Points.search_points("my-collection", %{vector: [0.1, 0.2], limit: 5})

# Service
alias Qdrant.Api.Http.Service
{:ok, info} = Service.root() # Get server version info
{:ok, health} = Service.healthz() # Health check
```

## Architecture

The client uses the modern Tesla HTTP client pattern with middleware for:
- Base URL configuration
- API key authentication
- JSON encoding/decoding

All modules follow consistent patterns and provide full coverage of the Qdrant REST API.

## Contributing

- Fork the repository
- Create a branch for your changes
- Make your changes
- Run `mix format` to format your code
- Run `mix compile` to ensure everything compiles
- Submit a pull request

## Change Log

Expand Down
87 changes: 78 additions & 9 deletions lib/qdrant.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
defmodule Qdrant do
@moduledoc """
Documentation for Qdrant.
Qdrant Elixir client for interacting with Qdrant vector database.

This module provides a high-level API for all Qdrant operations.
"""

use Qdrant.Api.Wrapper

@doc """
# Collections operations

@doc """
Creates a collection with the given name and body.

Body must be a map with the key `vectors`, example:

```elixir
Expand Down Expand Up @@ -35,17 +39,45 @@ defmodule Qdrant do
@doc """
Returns a list of all collections.
"""
def list_collections() do
def list_collections do
api_call("Collections", :list_collections, [])
end

@doc """
Returns information about a collection with the given name.
"""
def collection_info(collection_name) do
api_call("Collections", :collection_info, [collection_name])
api_call("Collections", :get_collection, [collection_name])
end

@doc """
Get detailed information about specified existing collection.

Example:
```elixir
Qdrant.get_collection("collection_name")
```
"""
def get_collection(collection_name) do
api_call("Collections", :get_collection, [collection_name])
end

@doc """
Check if a collection exists.
"""
def collection_exists(collection_name) do
api_call("Collections", :collection_exists, [collection_name])
end

@doc """
Update collection parameters.
"""
def update_collection(collection_name, body, timeout \\ nil) do
api_call("Collections", :update_collection, [collection_name, body, timeout])
end

# Points operations

@doc """
Perform insert + updates on points. If point with given ID already exists - it will be overwritten.

Expand Down Expand Up @@ -146,12 +178,13 @@ defmodule Qdrant do

@doc """
Delete multiple points that match filtering conditions

Parameters:
* `collection_name` - name of the collection to search in
* `body` - search body
* `consistency` - Define read consistency guarentees for the operation

* `wait` - wait for changes to actually happen
* `ordering` - Define ordering guarantees for the operation

Example:
```elixir
body = %{
Expand All @@ -160,7 +193,43 @@ defmodule Qdrant do
Qdrant.delete_points("collection_name", body)
```
"""
def delete_points(collection_name, body, consistency \\ nil) do
api_call("Points", :delete_points, [collection_name, body, consistency])
def delete_points(collection_name, body, wait \\ false, ordering \\ nil) do
api_call("Points", :delete_points, [collection_name, body, wait, ordering])
end

@doc """
Query points using a query string or vector query.

Parameters:
* `collection_name` - name of the collection to query
* `body` - query body with `query` key (required)
* `consistency` - Define read consistency guarantees for the operation

Body must be a map with the key `query`, example:

```elixir
%{
query: %{
vector: vector,
limit: 10
},
with_payload: true
}
```

Example:
```elixir
body = %{
query: %{
vector: vector,
limit: 10
},
with_payload: true
}
Qdrant.query_points("collection_name", body)
```
"""
def query_points(collection_name, body, consistency \\ nil) do
api_call("Points", :query_points, [collection_name, body, consistency])
end
end
Loading