PaveDB Elixir Client

Elixir client package for PaveDB.

Scope

Basic Usage

client =
  PaveDBClient.connect("http://localhost:8086",
    tenant: "tenant",
    api_key: "secret"
  )

{:ok, books} =
  PaveDBClient.create_collection(client, "books", display_name: "Books")

{:ok, _doc} =
  PaveDBClient.Collection.add(books, "Captain Nemo commands the Nautilus.",
    docid: "note-1",
    metadata: %{"kind" => "note"}
  )

{:ok, response} =
  PaveDBClient.Collection.search(books, "captain", k: 3)

matches = response["matches"]

The collection handle uses the tenant stored on the client. When tenant: is omitted, the client uses PaveDB's default tenant.

Requests time out after 30 seconds, with 5 seconds to connect. Both are per-client options:

PaveDBClient.connect(timeout: 120_000, connect_timeout: 2_000)

Lower-level helpers are also available when a provider already has tenant and collection values:

client = PaveDBClient.connect()

PaveDBClient.create_collection(client, "tenant", "collection", %{
  display_name: "Demo"
})

PaveDBClient.add_text(client, "tenant", "collection", "hello world")

PaveDBClient.ingest_file(client, "tenant", "collection", "items.csv",
  docid: "items",
  metadata: %{"source" => "csv"},
  csv_options: [has_header: "yes", meta_cols: ["id"]]
)

PaveDBClient.search(client, "tenant", "collection", "hello",
  k: 5,
  filters: %{"source" => "csv"}
)

All request functions return {:ok, value} or {:error, %PaveDBClient.Error{}}. PaveDBClient.format_error/1 formats errors for logs and operator messages.

Raw vectors

Collections also take precomputed embeddings. PaveDB accepts either text or a raw vector on a given document or query, never both:

{:ok, _} = PaveDBClient.Collection.add_vector(books, [0.12, 0.98], docid: "vec-1")
{:ok, response} = PaveDBClient.Collection.search_vector(books, [0.12, 0.98], k: 3)

Batch items carry vectors too: %{vector: [0.1, 0.2], docid: "vec-2"}.

Shared scope

A server can designate one collection as shared across tenants. Searching it needs no tenant or collection, and answers with an empty match list when the server has no shared scope enabled:

{:ok, %{"matches" => matches}} = PaveDBClient.search_shared(client, "captain", k: 3)

Text only — PaveDB's shared endpoint rejects raw query vectors.

PaveDB 0.9.7 collection options

The client sends PaveDB's collection creation settings, including an embedder selector, default search mode, chunking, and priority field:

{:ok, books} =
  PaveDBClient.create_collection(client, "books",
    embedder: "native",
    search_mode: "boost",
    chunking: %{strategy: "fixed", size: 800, overlap: 100},
    priority_key: "rank"
  )

{:ok, %{"matches" => matches, "mode" => "hybrid"}} =
  PaveDBClient.Collection.search(books, "captain nemo",
    mode: "hybrid",
    content_filter: %{op: "phrase", value: "captain nemo"}
  )

search_shared/3 takes the same :mode and :content_filter options. "native" is the default configured instance used in PaveDB's core examples. Collection creation accepts another :embedder selector if the server has it configured. PaveDB chooses the instance for each operation; the client has no per-operation embedder option.

Tenant keys can inspect their available embedder instances:

{:ok, %{"embedders" => embedders, "default" => default}} =
  PaveDBClient.list_embedders(client)

Collection portability

Collection archives are binary zip files and restore only on a PaveDB server of the same version. restore_archive/3 creates a new collection by default; replace: true replaces an existing one.

{:ok, archive} = PaveDBClient.Collection.export_archive(books)
copy = PaveDBClient.collection(client, "books-copy")
{:ok, _} = PaveDBClient.Collection.restore_archive(copy, archive)

{:ok, %{"job_id" => job_id}} =
  PaveDBClient.Collection.start_reindex(copy,
    embedder_type: "sbert",
    embed_model: "all-MiniLM-L6-v2"
  )
{:ok, job} = PaveDBClient.Collection.get_reindex(copy, job_id)

The target reindex model must be configured on the server. Use PaveDBClient.Collection.cancel_reindex(copy, job_id) to cancel a job.

Query replay

Collection handles also expose PaveDB's query log:

{:ok, %{"queries" => queries}} =
  PaveDBClient.Collection.list_queries(books, limit: 20)

query_id = hd(queries)["query_id"]
{:ok, %{"query" => original}} = PaveDBClient.Collection.get_query(books, query_id)
{:ok, replay} = PaveDBClient.Collection.replay_query(books, query_id)

See the runnable concurrent evaluation and query replay drift examples.

Managing collections

The client mirrors PaveDB's user-facing /v1 catalog and chunk surface, plus a root /health connection check:

{:ok, %{"status" => "ready"}} = PaveDBClient.health(client)

{:ok, %{"collections" => collections}} = PaveDBClient.list_collections(client)

{:ok, detail} = PaveDBClient.Collection.detail(books)
{:ok, _} = PaveDBClient.Collection.update(books, display_name: "Great Books")
{:ok, tomes} = PaveDBClient.Collection.rename(books, "tomes")

{:ok, %{"chunks" => chunks}} = PaveDBClient.Collection.list_chunks(tomes, "note-1")
{:ok, chunk} = PaveDBClient.Collection.get_chunk(tomes, hd(chunks)["rid"])
{:ok, %{"content" => text}} = PaveDBClient.Collection.get_chunk_content(tomes, chunk["rid"])

{:ok, _} = PaveDBClient.delete_collection(client, "tenant", "tomes")

/admin and /metrics are intentionally out of scope.

Development

Needs Elixir 1.17 or later on OTP 27 or later: the client encodes and decodes with OTP's built-in :json and takes no runtime dependency of its own.

mix deps.get
mix compile --warnings-as-errors
mix test