Zalgorithm

Create a persistent Chroma collection

GitHub: https://github.com/scossar/find_command/blob/master/create_embeddings.py

Embed the descriptions of tmux key bindings from the tmux_key_bindings.sqlite3 database (populated here: populate.py ) and save them to a persistent Chroma collection .

"""Embed tmux descriptions and save them in a persistent Chroma collection."""

import argparse
import sqlite3
from contextlib import closing
from pathlib import Path
from typing import cast

import chromadb
from chromadb.api.types import Embeddable, EmbeddingFunction
from chromadb.utils.embedding_functions import DefaultEmbeddingFunction


def create_embeddings(
    database: Path,
    chroma_path: Path,
    collection_name: str = "tmux-key-bindings",
) -> int:
    """Generate and upsert embeddings for the descriptions stored in SQLite."""
    with closing(
        sqlite3.connect(database.resolve().as_uri() + "?mode=ro", uri=True)
    ) as connection:
        connection.row_factory = sqlite3.Row
        rows = connection.execute("""
            SELECT id, key, description, prefix, tmux_version, source
            FROM key_bindings
            ORDER BY id
        """).fetchall()

    if not rows:
        return 0

    # Chroma's default model is all-MiniLM-L6-v2, running locally via ONNX.
    # The model files are downloaded and cached on the first use if needed.
    embedding_function = DefaultEmbeddingFunction()
    client = chromadb.PersistentClient(path=str(chroma_path))
    # Chroma types this parameter as accepting documents OR images, but its
    # default embedding function accepts only documents. This collection is
    # text-only, so cast at the API boundary to bridge that typing mismatch.
    # cast does not change the function or add image support at runtime.
    collection = client.get_or_create_collection(
        name=collection_name,
        embedding_function=cast(EmbeddingFunction[Embeddable], embedding_function),
        metadata={"embedding_model": "all-MiniLM-L6-v2"},
    )

    # Batching limits how many descriptions are embedded and written at once.
    for start in range(0, len(rows), 128):
        batch = rows[start : start + 128]
        documents = [row["description"] for row in batch]

        # This converts each original description into a numeric vector.
        # Do not stem the text: the model works with the original language.
        embeddings = embedding_function(documents)

        # Store both the vectors and original text. The key is a stable ID
        # within this dataset; metadata connects results back to SQLite.
        # Upsert inserts new IDs and updates existing IDs without duplicates.
        collection.upsert(
            ids=[f"tmux:{row['key']}" for row in batch],
            embeddings=embeddings,
            documents=documents,
            metadatas=[
                {
                    "sqlite_id": row["id"],
                    "key": row["key"],
                    "prefix": row["prefix"],
                    "tmux_version": row["tmux_version"],
                    "source": row["source"],
                }
                for row in batch
            ],
        )
    return len(rows)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--database",
        type=Path,
        default=Path("tmux_key_bindings.sqlite3"),
        help="Populated SQLite source (default: tmux_key_bindings.sqlite3)",
    )
    parser.add_argument(
        "--chroma",
        type=Path,
        default=Path("data/chroma"),
        help="Persistent Chroma directory (default: data/chroma)",
    )
    parser.add_argument(
        "--collection",
        default="tmux-key-bindings",
        help="Chroma collection name (default: tmux-key-bindings)",
    )
    args = parser.parse_args()
    count = create_embeddings(args.database, args.chroma, args.collection)
    print(f"Stored {count} descriptions and embeddings in {args.chroma} ({args.collection}).")


if __name__ == "__main__":
    main()

The Chroma embedding function #

What’s going on with:

embedding_function = DefaultEmbeddingFunction()
# ...
    embeddings = embedding_function(documents)

I’ll ignore the details related to batch processing.

documents is the value of the "description" rows from the tmux_key_bindings.sqlite3 database:

Chroma DefaultEmbeddingFunction #

Docs: https://docs.trychroma.com/docs/embeddings/embedding-functions#default-all-minilm-l6-v2

Chroma’s default embedding function uses the Sentence Transformers all-MiniLM-L6-v2 model to create embeddings. When the code calls embedding_function(documents), the flow is going to be something like:

Python strings
tokenizer
token IDs / attention masks
all-MiniLM-L6-v2
token-level representations
pooling / normalization
one 384-dimensional vector per document

The attention mask is dealing with padding that’s added to input tokens so that all inputs have the same length. E.g., shorter sequences get padded with zeros:

[101, 2243, 345, 9]
[101, 23,   885, 0]  # shorter sequence padded with a zero

The corresponding masks might be:

[1, 1, 1, 1]
[1, 1, 1, 0]  # the 0 tells the model that the final value shouldn't participate in attention or pooling

(I thought the masks were created inside the model(?). In any case, it’s an implementation detail.)

Difference between a sentence-embedding model and a “normal” GPT model #

The difference is the “pool into one vector” step:

text
→ tokens
→ token IDs
→ learned token embeddings
→ transformer
→ contextual token representations
→ pooling  # the difference starts here
→ one vector for the whole text input

What does get_or_create_collection create? #

It creates an SQLite database and some binary files:

find_command/data master
find-command ❯ tree
.
├── chroma
│   ├── 0ce30325-7299-4b73-94fd-573c1d2488e8
│   │   ├── data_level0.bin
│   │   ├── header.bin
│   │   ├── length.bin
│   │   └── link_lists.bin
│   └── chroma.sqlite3

The chroma.sqlite3 database has a lot of tables:

sqlite> .tables
acquire_write               embedding_fulltext_search_config    embedding_metadata         maintenance_log    tenants
collection_metadata         embedding_fulltext_search_content   embedding_metadata_array   max_seq_id
collections                 embedding_fulltext_search_data      embeddings                 migrations
databases                   embedding_fulltext_search_docsize   embeddings_queue           segment_metadata
embedding_fulltext_search   embedding_fulltext_search_idx       embeddings_queue_config    segments

The embeddings table doesn’t store the embeddings (the embeddings aren’t in the SQLite database):

sqlite> SELECT * FROM embeddings ORDER BY id ASC LIMIT 1;
╭────┬──────────────────────────────────────┬──────────────┬────────┬─────────────────────╮
│ id │              segment_id              │ embedding_id │ seq_id │     created_at      │
╞════╪══════════════════════════════════════╪══════════════╪════════╪═════════════════════╡
│  1 │ d4e48acc-25e0-42a5-8297-a88aad40bdca │ tmux:C-b     │    109 │ 2026-09-16 04:01:05 │
╰────┴──────────────────────────────────────┴──────────────┴────────┴─────────────────────╯
sqlite> SELECT * FROM segments WHERE id = 'd4e48acc-25e0-42a5-8297-a88aad40bdca';
╭────────────────────────────────────┬──────────────────────────────────┬────────┬────────────────────────────────────╮
│                 id                 │               type               │ scope  │             collection             │
╞════════════════════════════════════╪══════════════════════════════════╪════════╪════════════════════════════════════╡
│d4e48acc-25e0-42a5-8297-a88aad40bdca│urn:chroma:segment/metadata/sqlite│METADATA│c63eb109-a92b-4c7f-a5ba-277b59cd00cc│
╰────────────────────────────────────┴──────────────────────────────────┴────────┴────────────────────────────────────╯

sqlite> SELECT * FROM collections WHERE id = 'c63eb109-a92b-4c7f-a5ba-277b59cd00cc';
╭────────────────────────────────────┬─────────────────┬─────────┬────────────────────────────────────┬───────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                 id                 │      name       │dimension│            database_id             │config_json_str│                                              schema_str                                               │
╞════════════════════════════════════╪═════════════════╪═════════╪════════════════════════════════════╪═══════════════╪═══════════════════════════════════════════════════════════════════════════════════════════════════════╡
│c63eb109-a92b-4c7f-a5ba-277b59cd00cc│tmux-key-bindings│      384│00000000-0000-0000-0000-000000000000│{}             │{"defaults":{"string":{"fts_index":{"enabled":false,"config":{}},"string_inverted_index":{"enabled":   │
│                                    │                 │         │                                    │               │true,"config":{}}},"float_list":{"vector_index":{"enabled":false,"config":{"space":"l2","              │
│                                    │                 │         │                                    │               │embedding_function":{"type":"known","name":"default","config":{}},"hnsw":{"ef_construction":100,"      │
│                                    │                 │         │                                    │               │max_neighbors"...                                                                                      │
╰────────────────────────────────────┴─────────────────┴─────────┴────────────────────────────────────┴───────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────╯

Somewhere there’s a vector database, separate from the SQLite database. I think the issue is that to find the nearest neighbor between the vector that’s generated for a query, and every stored vector would be very inefficient if it was done with a normal database:

query vector
distance to vector 1
distance to vector 2
distance to vector 3
...
distance to vector N
sort

Vector databases create a specialized nearest-neighbor index, conceptually kind of similar to SQLite FTS5 indexes.