Vector Store Backends

FFAI’s RAG system uses a pluggable vector store architecture. All backends implement the same VectorStoreBase interface, so you can swap backends without changing your indexing, search, or query code.

Available backends

Backend

Install

Mode

ChromaDB

Included with ffai[rag]

Local files (persistent client)

Qdrant

pip install qdrant-client

Local, server, or cloud

pgvector

pip install psycopg asyncpg

Server (Docker or managed PostgreSQL)

SQLite-vss

pip install sqlite-vss

Local files (SQLite extension)

Check which backends are available in your environment:

from ffai.rag.stores import list_stores, list_available_stores

print(list_stores())
# ['chroma', 'pgvector', 'qdrant', 'sqlite_vss']

print(list_available_stores())
# ['chroma', 'qdrant']

list_stores() returns all known backends. list_available_stores() returns only those whose dependencies are installed.

Choosing a backend

  • ChromaDB — best for getting started. Zero configuration, persistent local storage, included with ffai[rag].

  • Qdrant — best for production. Supports local mode (no server), dedicated server, and Qdrant Cloud. High performance with filtering.

  • pgvector — best when you already have PostgreSQL. Uses the pgvector extension for vector similarity search. Requires a running Postgres instance.

  • SQLite-vss — best for embedded and edge deployments. Zero infrastructure, single-file database.

Using get_store()

The get_store() factory creates a store instance by backend name. Each backend accepts different constructor arguments:

from ffai.rag.stores import get_store

# ChromaDB (default)
store = get_store("chroma", collection_name="my_kb", dir="./chroma_db")

# Qdrant local mode (no server needed)
store = get_store("qdrant", path="./qdrant_db", embedding_dim=1024)

# Qdrant server
store = get_store("qdrant", host="localhost", port=6333, embedding_dim=1024)

# Qdrant Cloud
store = get_store("qdrant", url="https://cluster.qdrant.io", api_key="...", embedding_dim=1024)

# pgvector (requires running PostgreSQL)
store = get_store("pgvector",
    connection_string="postgresql://ffai:ffai@localhost:5432/ffai",
    embedding_dim=1024)

# SQLite-vss
store = get_store("sqlite_vss", db_path="./vss.db", embedding_dim=1024)

All stores implement the same interface. After construction, the rest of your code stays the same:

from ffai.rag.embed import Embeddings
from ffai.rag import RAG

embed = Embeddings("mistral/mistral-embed", api_key="your-key")
rag = RAG(embed=embed, store=store, chunk_size=500, chunk_overlap=100)

rag.index("Python is a programming language...", source="python_intro")
hits = rag.search("programming")

Configuration via YAML

Set the backend in config/main.yaml under the rag: key:

rag:
  enabled: true
  store_backend: qdrant
  store_config:
    path: "./qdrant_db"
    embedding_dim: 1024
  embedding_model: "mistral/mistral-embed"
  chunker: recursive
  chunk_size: 1000
  chunk_overlap: 200

store_config passes backend-specific constructor arguments directly to the backend. Omit it (or pass {}) to use defaults.

Then use from_config():

from ffai.rag import RAG

rag = RAG.from_config()  # reads store_backend + store_config from YAML

Backend-specific notes

ChromaDB

The default backend. No additional installation beyond ffai[rag].

store = get_store("chroma", collection_name="my_kb", dir="./chroma_db")

Constructor arguments:

  • collection_name (str) — Collection name. Default: "ffai_kb"

  • dir (str) — Filesystem path for persistent storage. Default: "./chroma_db"

Qdrant

Supports four modes: local (file-based, no server), server (connect to a running Qdrant instance), cloud (Qdrant Cloud), and in-memory (ephemeral, data lost on exit).

Local mode uses asyncio.to_thread internally to avoid file-lock conflicts between sync and async clients. Non-UUID string IDs are automatically converted to deterministic UUID5 values. Payload indexes for source and chunking_strategy are created automatically when a new collection is initialized (required for filtering on Qdrant Cloud).

# Local mode (no server)
store = get_store("qdrant", path="./qdrant_db", embedding_dim=1024)

# In-memory mode (ephemeral)
store = get_store("qdrant", location=":memory:", embedding_dim=1024)

# Server mode
store = get_store("qdrant", host="localhost", port=6333, embedding_dim=1024)

# Cloud mode
store = get_store("qdrant", url="https://your-cluster.qdrant.io",
                  api_key="your-key", embedding_dim=1024)

Constructor arguments:

  • collection_name (str) — Default: "ffai_kb"

  • embedding_dim (int) — Vector dimensionality. Default: 1024

  • path (str or None) — Local storage path. Set this for local mode.

  • location (str or None) — ":memory:" for in-memory mode.

  • host (str) — Server host. Default: "localhost"

  • port (int) — Server port. Default: 6333

  • url (str or None) — Cloud URL.

  • api_key (str or None) — Cloud API key.

pgvector

Requires a running PostgreSQL instance with the pgvector extension. Use docker-compose.dev.yaml to start a local instance:

docker compose -f docker-compose.dev.yaml up -d
store = get_store("pgvector",
    connection_string="postgresql://ffai:ffai@localhost:5432/ffai",
    embedding_dim=1024)

Constructor arguments:

  • connection_string (str) — PostgreSQL connection string.

  • collection_name (str) — Table name prefix. Default: "ffai_kb"

  • embedding_dim (int) — Default: 1024

SQLite-vss

Zero-infrastructure option. Uses the SQLite vss0 extension for vector similarity search. Good for embedded deployments and prototyping.

store = get_store("sqlite_vss", db_path="./vss.db", embedding_dim=1024)

Constructor arguments:

  • db_path (str) — Path to the SQLite database file. Default: "./vss.db"

  • collection_name (str) — Table name. Default: "ffai_kb"

  • embedding_dim (int) — Default: 1024

VectorStoreBase interface

All backends implement these methods:

Method

Description

name

Backend identifier ("chroma", "qdrant", etc.)

aadd(ids, texts, ...)

Add documents with embeddings (async)

asearch(embedding,)

Search by vector similarity (async)

delete_by_source()

Delete all chunks for a source

delete_by_source_

Delete chunks matching source + chunking strategy

and_strategy()

count()

Total number of stored chunks

clear()

Delete all data and recreate

list_sources()

Sorted list of indexed source names

get_all()

All stored documents as dicts

needs_reindex()

Check if source needs re-indexing (checksum)

Docker setup for pgvector and Qdrant

A docker-compose.dev.yaml is provided for local development:

# Start both pgvector and Qdrant
docker compose -f docker-compose.dev.yaml up -d

# Stop
docker compose -f docker-compose.dev.yaml down

Example notebooks

The examples/vector_stores/ directory contains runnable Jupyter notebooks that demonstrate each backend and mode. All notebooks clean up after themselves — no data left on disk or in the cloud after execution.

Notebook

What it demonstrates

Cleanup

qdrant_memory.ipynb

In-memory mode: add, search, filter, delete

Data vanishes with kernel

qdrant_local.ipynb

Local file mode: persistence, re-open verify

shutil.rmtree() on temp dir

qdrant_server.ipynb

Server mode: auto-starts Docker, search

Clears collection, stops container

qdrant_cloud.ipynb

Cloud mode: indexes to remote cluster, queries

delete_collection() on cluster

backend_comparison.ipynb

ChromaDB vs Qdrant side-by-side via factory

Clears both stores + removes temp dir

All notebooks use synthetic embeddings (random unit vectors via NumPy) so no embedding API key is required. The qdrant_cloud notebook requires QDRANT_CLUSTER_ENDPOINT and QDRANT_KEY in your .env file. The qdrant_server notebook requires Docker.

See also