Skip to main content

Quickstart: Ahnlich DB

Use Ahnlich DB when you already have vectors — embeddings you've computed with your own model — and want a fast place to store and search them. Ahnlich indexes and ranks them; you bring the numbers.

We'll build a tiny movie library: each movie is a vector (its embedding) plus some metadata (title, genre). To keep things readable we use 4-number vectors, but the flow is identical for 384- or 1536-dimensional embeddings. Pick your language — or the CLI — once below, and every sample follows your choice.

New to vector search? The Ahnlich AI quickstart is gentler — you send text and it computes the vectors for you.

Prerequisites

You need the Ahnlich DB server running. The fastest way is Docker:

docker run -d --name ahnlich-db -p 1369:1369 ghcr.io/deven96/ahnlich-db:latest

See Installation for binaries and other options.

Install the client

pip install ahnlich-client-py

1. Connect

Open a client against the DB server (default port 1369).

connect.py
import asyncio
from grpclib.client import Channel
from ahnlich_client_py.grpc.services.db_service import DbServiceStub


async def main():
# Open a gRPC channel to the DB server (default port 1369).
# `async with` closes the connection automatically when you're done.
async with Channel(host="127.0.0.1", port=1369) as channel:
# The stub is your typed client — every Ahnlich call goes through it.
client = DbServiceStub(channel)
# ready to talk to Ahnlich DB

2. Create a store

A store fixes the dimension every vector must have, and the metadata predicates you'll filter on.

create_store.py
from ahnlich_client_py.grpc.db import query as db_query

await client.create_store(db_query.CreateStore(
store="movies", # name of the store
dimension=4, # every vector must be exactly this long
create_predicates=["title", "genre"], # metadata fields you can filter on
non_linear_indices=[], # optional ANN indexes (e.g. hnsw) — later
error_if_exists=True, # fail if a store with this name already exists
))
tip

dimension is fixed for the life of the store — it must match the length of the vectors your model produces. predicates are the metadata fields you'll filter on later; field names must match exactly between insert and query.

3. Insert your movies

Each entry is a key (the movie's vector) and a value (its metadata).

insert.py
from ahnlich_client_py.grpc import keyval, metadata

await client.set(db_query.Set(
store="movies",
inputs=[
# Each DbStoreEntry pairs a vector (key) with its metadata (value).
keyval.DbStoreEntry(
key=keyval.StoreKey(key=[0.10, 0.92, 0.05, 0.33]),
value=keyval.StoreValue(value={
"title": metadata.MetadataValue(raw_string="Inception"),
"genre": metadata.MetadataValue(raw_string="SciFi"),
}),
),
keyval.DbStoreEntry(
key=keyval.StoreKey(key=[0.14, 0.88, 0.09, 0.40]),
value=keyval.StoreValue(value={
"title": metadata.MetadataValue(raw_string="Interstellar"),
"genre": metadata.MetadataValue(raw_string="SciFi"),
}),
),
keyval.DbStoreEntry(
key=keyval.StoreKey(key=[0.85, 0.12, 0.40, 0.08]),
value=keyval.StoreValue(value={
"title": metadata.MetadataValue(raw_string="Pride & Prejudice"),
"genre": metadata.MetadataValue(raw_string="Romance"),
}),
),
],
))

4. Update a movie

Upsert finds a single entry with a predicate and updates it. Here we add a rating to Inception (merge_metadata keeps the existing fields).

upsert.py
from ahnlich_client_py.grpc import predicates

await client.upsert(db_query.Upsert(
store="movies",
# Match exactly one entry: the movie titled "Inception".
condition=predicates.PredicateCondition(
value=predicates.Predicate(
equals=predicates.Equals(
key="title",
value=metadata.MetadataValue(raw_string="Inception"),
)
)
),
# New metadata to apply.
new_value=keyval.StoreValue(value={
"rating": metadata.MetadataValue(raw_string="8.8"),
}),
merge_metadata=True, # keep existing fields (title, genre); just add rating
))

5. Search by similarity

Find the movies closest to a query vector (an embedding of what you're looking for), ranked by a similarity metric.

search.py
from ahnlich_client_py.grpc.algorithm import algorithms

# A vector for "a mind-bending sci-fi thriller" (from your own model).
response = await client.get_sim_n(db_query.GetSimN(
store="movies",
search_input=keyval.StoreKey(key=[0.12, 0.90, 0.07, 0.35]),
closest_n=2, # return the 2 nearest movies
algorithm=algorithms.Algorithm.CosineSimilarity,
))

# Results are ranked most-similar first.
for entry in response.entries:
title = entry.value.value["title"].raw_string
print(title, entry.similarity.value)

The two SciFi movies (Inception, Interstellar) come back ahead of the romance.

Pass a predicate as the condition to only rank vectors whose metadata matches — "nearest sci-fi movies".

filter.py
# genre = "SciFi"
condition = predicates.PredicateCondition(
value=predicates.Predicate(
equals=predicates.Equals(
key="genre",
value=metadata.MetadataValue(raw_string="SciFi"),
)
)
)

response = await client.get_sim_n(db_query.GetSimN(
store="movies",
search_input=keyval.StoreKey(key=[0.12, 0.90, 0.07, 0.35]),
closest_n=3,
algorithm=algorithms.Algorithm.CosineSimilarity,
condition=condition, # only entries where genre = SciFi
))

7. Retrieve by key

Already know the exact vector? Get by key fetches it directly — no similarity ranking.

get_key.py
response = await client.get_key(db_query.GetKey(
store="movies",
keys=[keyval.StoreKey(key=[0.10, 0.92, 0.05, 0.33])], # Inception's vector
))

for entry in response.entries:
print(entry.value.value["title"].raw_string)

8. Speed up filters with an index

On a large store, filtering scans every entry. A predicate index turns that into a direct lookup.

index.py
await client.create_pred_index(db_query.CreatePredIndex(
store="movies",
predicates=["genre"], # index the field you filter on most
))

9. Inspect your stores

List stores to see what's registered, with each store's entry count and size.

list_stores.py
response = await client.list_stores(db_query.ListStores())
for store in response.stores:
print(store.name, store.len)

10. Clean up

Remove a single entry with delete by key, or drop the whole store.

cleanup.py
# Remove one movie by its key.
await client.del_key(db_query.DelKey(
store="movies",
keys=[keyval.StoreKey(key=[0.85, 0.12, 0.40, 0.08])], # Pride & Prejudice
))

# ...or drop the entire store.
await client.drop_store(db_query.DropStore(
store="movies",
error_if_not_exists=True,
))

Where to next?

  • Concepts — vectors, metadata, similarity metrics, and indexes explained.
  • Stores — the full reference for every DB operation.
  • Client Libraries — deeper SDK docs for each language.

Found a bug or something unclear? Open an issue.