Skip to content

Rust · Python · serverless

An embedded engine whose database is a bucket. No cluster and no server: every process reads the index straight from storage, ranks exact, prefix, abbreviation, infix and misspelt matches together, and answers in a fraction of a millisecond.

pip install typemind
typemind completing music queries: prefix, abbreviation, spelling correction, word decomposition and infix
0.15 msmedian query, Hacker News titles
40,000queries a second on 8 threads
40 MMusicBrainz recordings in 3.1 GB
5 corporabenchmarked against tantivy, Typesense and Meilisearch
  • Every match kind


    Exact, prefix, infix, abbreviation, spelling-tolerant and word-decomposing completion, ranked together with popularity.

    Completion

  • Serverless by design


    Storage is the only shared component. Commits are create-only writes, and every process syncs itself from the bucket.

    Serverless deployment

  • Semantic and hybrid


    Embeddings from any model, quantised to 2 to 4 bits, fused with lexical results by reciprocal rank, weighted blending or lexical-first ordering.

    Semantic and hybrid

  • Override layers


    One catalogue, many views: tenants, languages, listeners, promotions and takedowns as small layers over the shared data, overriding it per document id, without a copy.

    Layers

  • Fast and deterministic


    Memory-mapped, checksummed segments. Typed queries take 0.1 to 0.2 ms at the median on short titles, and identical inputs give bit-identical results.

    Architecture

  • Rust and Python


    Documents as dicts, Document objects, or pandas, polars and Arrow tables. Typed stubs, an asyncio API and a command-line tool.

    Python API

Install

pip install typemind
cargo add typemind --features store    # databases on local disk and in memory
cargo add tokio --features macros,rt-multi-thread
cargo install typemind-cli

Quick start

import typemind
from typemind import Index

docs = [
    {"id": "bohemian", "text": "Bohemian Rhapsody – Queen", "popularity": 0.95},
    {"id": "dancing", "text": "Dancing Queen – ABBA", "popularity": 0.85},
    {"id": "dark", "text": "Dancing in the Dark – Bruce Springsteen", "popularity": 0.7},
    {"id": "bridge", "text": "Under the Bridge – Red Hot Chili Peppers", "popularity": 0.75, "abbreviations": ["RHCP"]},
]
index = Index.from_documents(docs)   # in memory
for query in ["danc", "RHCP", "bohemain rapsody", "queen"]:
    print(query, [(s.text, s.kind) for s in index.complete(query, limit=3)])

db = typemind.connect("./data")   # or "s3://bucket/prefix", "gs://...", "az://..."
txn = db.begin()
txn.append("songs", docs)
txn.commit()
engine = db.engine()   # loads new versions by itself, every 5 seconds
print([s.text for s in engine.complete("danc", ["songs"])])
use std::{sync::Arc, time::Duration};
use typemind::{Database, Document, Engine, Index, IndexOptions, Replica};

let docs = || [
    Document::keyed("bohemian", "Bohemian Rhapsody – Queen", 0.95),
    Document::keyed("dancing", "Dancing Queen – ABBA", 0.85),
    Document::keyed("dark", "Dancing in the Dark – Bruce Springsteen", 0.7),
    Document::keyed("bridge", "Under the Bridge – Red Hot Chili Peppers", 0.75).with_abbreviation("RHCP"),
];
let index = Index::from_documents(docs())?;   // in memory
for s in index.complete("danc", 3) {
    println!("{} {}", s.text, s.kind.as_str());
}

let database = Database::open("./data", Vec::<(String, String)>::new()).await?;
let mut txn = database.begin().await?;
txn.append_documents("songs", docs(), [])?;
txn.commit().await?;
let engine = Arc::new(Engine::new());
let replica = Arc::new(Replica::new(database, IndexOptions::default()));
replica.sync(&engine).await?;
let _follower = replica.follow(&engine, Duration::from_secs(5));
let hits = engine.complete(&["songs"], "danc", 10)?;
danc [('Dancing Queen – ABBA', MatchKind.PREFIX), ('Dancing in the Dark – Bruce Springsteen', MatchKind.PREFIX)]
RHCP [('Under the Bridge – Red Hot Chili Peppers', MatchKind.ABBREVIATION)]
bohemain rapsody [('Bohemian Rhapsody – Queen', MatchKind.FUZZY)]
queen [('Bohemian Rhapsody – Queen', MatchKind.INFIX), ('Dancing Queen – ABBA', MatchKind.INFIX)]
['Dancing Queen – ABBA', 'Dancing in the Dark – Bruce Springsteen']

Next, Getting started walks from an index in memory to a database in a bucket that several processes share, Concepts explains the model behind it, and Collections covers the convenience layer on top.