Most vector-search libraries hand you an index and stop there. txtai, from NeuML, takes the wider view: it is an embeddings database — a Python platform where you index text, documents, audio, images and video, keep the original content in SQL, link records into a graph network, and then query everything semantically, lexically, or through LLM-driven agents. The README’s pitch is refreshingly blunt: up and running in minutes with pip, run local so your data never leaves the machine, scale up only when you need to.
I spent an afternoon walking the source tree on the master branch. txtai targets Python 3.11+ and is built on Hugging Face Transformers, Sentence Transformers and FastAPI, all under an Apache 2.0 license. What struck me most is how small the core object graph is: one Embeddings facade owns every subsystem, and each subsystem gets its own package with a tiny factory that reads your YAML config. The two diagrams below were generated directly from the code paths I traced, and every node in them links back to the exact file and line in the repository — so you can treat this post as a map and jump straight into the code whenever something looks interesting.
This is a read-order tour: the engine first, then the storage and scoring plumbing underneath it, then the runtime layer that turns a local index into an API service, a task graph, or an agent’s memory.
txtai at a glance: the Embeddings facade on the left, its vectorization and storage subsystems in the middle, and the serving, orchestration and agent runtimes on the right.
Reading the overview from left to right
- Embeddings is the center of gravity. The class holds every handle the engine needs —
ann,database,graph,scoring,model— and exposes the verbs you actually call:index()at L102,upsert()at L154,search()at L355 andsimilarity()at L405. - Vectors is the vectorization base class:
index()at L119 batches documents,transform()at L205 converts one document into a similarity vector. The VectorsFactory picks a concrete backend, with STVectors wrapping sentence-transformers models as the everyday choice. - ANN is the vector index wrapper:
append()at L51 ingests vectors,search()at L71 runs batched nearest-neighbor queries. The ANNFactory selects the backend — the zero-dependency NumPy scan, Faiss, or HNSW via hnswlib. - Database is the content store:
insert()at L43 writes rows,search()at L116 executes SQL with similarity ordering. The SQLite backend is the default, and DatabaseFactory also supports server RDBMS options. - Scoring powers lexical and sparse retrieval: TFIDF at ScoringFactory, BM25 extending TFIDF, and the Sparse backend for learned sparse embeddings.
- Graph builds a relationship network over indexed rows via GraphFactory, enabling path queries, topic modeling and graph-enhanced ranking.
- Workflow orchestrates callable tasks as a DAG;
__call__()at L51 streams elements through it, backed by the Execute scheduler. - Agent is the LLM loop:
__call__()at L67 runs tool-augmented sessions whose tools are your own embeddings instances and task graphs, assembled by ProcessFactory. - API extends Application to serve a configured index over HTTP;
create()at L32 assembles the FastAPI app that uvicorn runs. There is also a Console REPL for poking at an index interactively.
The engine’s two paths
Almost everything in txtai happens along two paths. The indexing path starts at Embeddings.index(): documents stream in through a Stream helper, each one goes to the vector model, the resulting vectors are appended to the ANN index, the raw content is inserted into the database, term weights land in the scoring index, and — if you enabled graph support — relationships are linked as it goes. The query path starts at Embeddings.search(): the query is vectorized, the ANN returns candidate neighbors, the database applies SQL where-clauses and joins content back, and hybrid mode fuses the dense ranking with the sparse one.
That hybrid search is worth pausing on. The Hybrid class blends dense vector results with sparse scoring results, which is how txtai gets both “matches the meaning” and “matches the exact keyword” behavior from one call. Dense retrieval alone is famously bad at rare terms — product codes, error strings, names — and a BM25 term index alone misses paraphrases. txtai runs both and merges the rankings, and the whole mechanism lives in one small file you can read in minutes.
The third interesting path is explanation. Embeddings.explain() at L446 annotates results with the terms that drove the match, which sounds cosmetic until you have to debug why a query keeps surfacing the wrong documents — then it is the difference between guessing and knowing.
The detail view: configuration factories on the left build the concrete backends, the index and search paths wire them together, and the workflow, agent and API runtimes sit on top.
Backends everywhere, config decides
The detail diagram makes txtai’s design philosophy obvious: every subsystem is an interface plus a factory plus N interchangeable backends. Dense vectors come from sentence-transformers (STVectors) or an ONNX encoder graph for optimized inference — the same model-serialization trick we covered in our ONNX runtime tour, and a reminder of how far a quantized encoder graph goes. ANN indexes come from NumPy, faiss, or HNSW, and there is even a pgvector option for teams that want Postgres to own the vectors — our pgvector walkthrough covers that side of the world. Content lives in SQLite by default or in an external RDBMS.
Because every backend is chosen by a factory reading your YAML config, swapping faiss for hnswlib or SQLite for Postgres is a configuration edit, not a code change. The factories are all tiny — ANNFactory at L22, ScoringFactory at L15, DatabaseFactory at L15, GraphFactory at L12 — and they follow the same registry shape, so once you have read one you have effectively read them all.
From Install to Your First Model
Everything below is straight from the README, and it genuinely is two lines before you have a working semantic index.
First, install the package:
pip install txtai
Then build and query an index in Python:
# Get started in a couple lines
import txtai
embeddings = txtai.Embeddings()
embeddings.index(["Correct", "Not what we hoped"])
embeddings.search("positive", 1)
# [(0, 0.29862046241760254)]
No model name, no vector dimension, no preprocessing script: the defaults pick a sentence-transformers encoder, download it on first use, and vectorize your documents. The returned tuple is the document id and its similarity score — 0 is the id of "Correct", the only positive sentence in the corpus.
The second way to run txtai is as a service. Point a YAML file at the model and start uvicorn:
# app.yml
embeddings:
path: sentence-transformers/all-MiniLM-L6-v2
CONFIG=app.yml uvicorn "txtai.api:app"
curl -X GET "http://localhost:8000/search?query=positive"
That is the whole deployment story for a single-node instance: the YAML config instantiates the same Embeddings object you built in Python, and the FastAPI application exposes search over HTTP. From there the config can declare workflows, agents and more, all served by the same process — the README’s stated model is “simple microservices or multi-model workflows” joined by task graphs.
Workflows and agents: the engine as a component
The runtime layer is where txtai stops being a library and starts being a platform. A Workflow is a set of tasks with declared connections; WorkflowFactory at L9 — loaded from config — and the Execute engine at L13 stream elements through the graph, scheduling independent tasks in parallel. The tasks themselves are ordinary callables: a transcription model, a summarizer, an embeddings index — each one consumes the output of the previous stage. In other words, the “workflow tasks” in the YAML are component runners over txtai’s own processing tasks, and the API service exposes them as HTTP endpoints.
An Agent then closes the loop: it wraps an LLM, binds tools — your embeddings instances, your workflows, even other agents — and runs the tool-augmented reasoning loop in __call__() at L67. A retrieval-augmented generation system stops being a script you wrote and becomes a named process with a config entry, which is exactly what you want once a second application needs the same behavior. The README also documents web and MCP APIs, with bindings for JavaScript, Java, Rust and Go, so the same index can back a web app, a chat client and an MCP-connected assistant.
This layered shape — one engine, many task graphs over it, agents on top — is the same trajectory we saw in our LlamaIndex tour, but txtai arrives at it from the database direction rather than the framework direction, and the difference shows in how little ceremony surrounds the engine itself. The Transformers substrate underneath is the same one we walked through in our Transformers architecture post.
Honest Limits
A few things worth knowing before you build on it, all verifiable in the source or README:
- Writes need synchronization. The
Embeddingsdocstring at L31 states it plainly: indexes are thread-safe for read operations, but writes must be synchronized by your application. Reads can scale across threads; a concurrent write strategy is your problem to coordinate. - Python 3.11+ only. The README is explicit about the floor, so older environments need to upgrade first.
- The batteries-included core pulls real dependencies. A minimal install gets you far, but the README also advertises a low footprint with optional extras — the trade is that faiss, hnswlib, audio, video and LLM integrations are additional installs you opt into per feature.
- First use downloads models. The default path needs a sentence-transformers encoder, so the very first index build involves a model download; offline environments must pre-seed the model cache.
- Quality is a function of your chosen encoder. txtai is model-agnostic, which means result quality tracks whatever embedding model you configure — the engine will faithfully organize whatever representations that model produces.
- No benchmark theater. The README makes capability claims, not speed claims; for the numbers that matter you will want to run your own queries against your own corpus, which the example notebooks make straightforward.
None of these are disqualifying — they are the honest edges of a design that puts one very solid engine in the middle and lets configuration decide everything else. If your problem is “index my content, query it semantically, serve it over an API, and let an LLM reason over it,” the source shows a clean path from two lines of Python to exactly that.
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "txtai Source Tour: Inside the Embeddings Database Engine",
"description": "A guided architecture tour of neuml/txtai covering the Embeddings engine, vectorization, ANN backends, SQL content storage, and the workflow, agent and API runtimes.",
"datePublished": "2026-10-29",
"author": { "@type": "Person", "name": "PyShine" },
"publisher": { "@type": "Organization", "name": "PyShine" },
"keywords": "txtai, embeddings database, vector search, semantic search, sqlite, faiss, hnsw, pgvector, onnx",
"about": "Source code architecture of the txtai embeddings database"
}
Enjoyed this post? Never miss out on future posts by following us