Easy API¶
The one-line interface. For full control see vectrixdb.VectrixDB and
vectrixdb.Collection.
vectrixdb.easy
¶
VectrixDB Easy API - The Simplest Vector Database in the World
Zero config. Text in, results out. One line for everything.
Example
from vectrixdb import Vectrix
Create and add - ONE LINE¶
db = Vectrix("my_docs").add(["Python is great", "Machine learning is fun"])
Search - ONE LINE¶
results = db.search("programming")
Full power - STILL ONE LINE¶
results = db.search("AI", mode="ultimate") # dense + sparse + rerank
Comparison with competitors:
# Chroma (4 lines)
client = chromadb.Client()
collection = client.create_collection("docs")
collection.add(documents=["text"], ids=["1"])
results = collection.query(query_texts=["query"])
# Pinecone (5+ lines + API key + manual embedding)
pinecone.init(api_key="...")
index = pinecone.Index("docs")
embedding = model.encode("text") # manual!
index.upsert(vectors=[...])
results = index.query(vector=embedding)
# VectrixDB (1 line each)
db = Vectrix("docs").add(["text"])
results = db.search("query")
Author: Kwadwo Daddy Nyame Owusu - Boakye
Vectrix
¶
Vectrix - The simplest vector database.
Example
db = Vectrix("my_collection") db.add(["doc 1", "doc 2", "doc 3"]) results = db.search("query") print(results.top.text)
Full power
results = db.search( ... "machine learning", ... mode="ultimate", # dense + sparse + late interaction ... rerank="mmr", # diversity reranking ... limit=10 ... )
index_build_id
property
¶
Which ingestion produced the index as it stands.
The thread that ties an answer back to where it came from: source system, document version and chunking configuration all hang off the write that stamped this. A collection written before 2.2 has none, which reads as None rather than as a guess.
pushdown_mode
property
¶
Where a filter runs for this collection: ENGINE or POST.
policy
property
¶
The entitlement policy this collection carries, if any.
principal
property
¶
The principal this view reads as, if any.
embedding_model
property
¶
The embedding model this collection was built with, as recorded.
dedupe_index
property
¶
The MinHash index over stored texts, built on first use.
parents_are_at
property
¶
Where this collection's parent sections are kept, for a log line.
page_ocr
property
¶
What reads a page of a PDF that has no text layer, or None.
A property rather than the private attribute, because a worker on
the other side of a queue reads it off the collection to read a blob
the same way add_document reads a file. The two used to differ
and the difference was invisible: the same scanned report indexed
well from a folder and emptily from a bucket.
documents
property
¶
The :class:~vectrixdb.documents.DocumentStore, or None.
sources
property
¶
The feeds and pages this collection keeps up with: :class:~vectrixdb.sources.Sources.
db.sources.add("https://example.com/feed.xml", every="6h"), then
db.sources.refresh() from a schedule writes what is new or changed.
kept_chunks
property
¶
The :class:~vectrixdb.documents.ChunkStore, or None.
markdown_first
property
¶
Whether a document's Markdown is kept before it is cut; see Vectrix(markdown_first=).
model
property
¶
Lazy load embedding model based on model_type.
sparse_embedder
property
¶
Lazy load sparse embedder with model selection.
reranker
property
¶
Lazy load cross-encoder reranker with model selection.
late_interaction
property
¶
Lazy load late interaction (ColBERT) embedder with model selection.
graph
property
¶
The knowledge graph pipeline, built on first access.
Only available in graph mode. Extraction defaults to the NLP extractor
rather than REBEL, because REBEL pulls a transformer download and the
point of this package is that it works offline; pass your own
GraphRAGConfig to choose otherwise.
memory
property
¶
Conversation memory over this collection.
Recency-weighted recall, feedback that reshapes ranking, corrections
that supersede, and a budgeted context block. See vectrixdb.memory
for the knobs; the four methods below are the everyday surface.
__init__(name='default', path='./vectrixdb_data', model=None, dimension=None, embed_fn=None, model_path=None, language=None, tier='dense', mode=None, dense_model=None, sparse_model=None, reranker_model=None, late_interaction_model=None, storage_backend=None, description=None, graphrag_config=None, token_counter=None, text_boosts=None, text_language='en', readonly=False, shard_size=None, embedding_cache=True, on_search=None, on_add=None, policy=None, on_retrieval=None, timing_floor=None, extractors=None, keep_source=None, keep_chunks=None, markdown_first=False, describe_figures=None, page_ocr=None, parent_store=None, chunk_store=None, collection_store=None, image_embedder=None, reranker=None)
¶
Create or open a VectrixDB collection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Collection name |
'default'
|
path
|
str
|
Storage path (default: ./vectrixdb_data) |
'./vectrixdb_data'
|
model
|
Optional[str]
|
Embedding model identifier (deprecated, use dense_model instead) |
None
|
dimension
|
Optional[int]
|
Vector dimension (auto-detected from model) |
None
|
embed_fn
|
Any
|
Custom embedding function: fn(texts: List[str]) -> np.ndarray |
None
|
model_path
|
Optional[str]
|
Path to custom ONNX model directory |
None
|
language
|
Optional[str]
|
Which bundled models to prefer. The default is English: a fresh collection records vectrixdb/bge-small-en-v1.5. "multi" selects the multilingual models, which are not bundled and have to be fetched. |
None
|
tier
|
str
|
Storage tier (deprecated, use 'mode' instead) |
'dense'
|
mode
|
Optional[Literal['dense', 'hybrid', 'ultimate', 'graph']]
|
Default search mode - validates required models at creation: - "dense": Vector similarity only (fastest) - "hybrid": Dense + Sparse + Reranker (balanced) - "ultimate": Dense + Sparse + Reranker + ColBERT (best quality) - "graph": Ultimate + Knowledge Graph (for GraphRAG) |
None
|
dense_model
|
Any
|
Dense embedding model (bundled alias or HuggingFace path) Bundled: "bge-small" (the default), "e5-small" (the default before 2.2, kept so older collections open unchanged) Fetch yourself: "bge-base", "e5-small-fp32". Naming one raises on first use with the URL and the directory to unpack into; nothing downloads on its own. HuggingFace: "BAAI/bge-large-en-v1.5", "intfloat/e5-large-v2", etc. |
None
|
sparse_model
|
Optional[str]
|
Sparse embedding model (bundled alias or HuggingFace path) Bundled: "bm25" Fetch yourself: "splade", "splade++". A learned sparse model also needs a storage backend with its own hybrid search; on a local collection the sparse half is always BM25, and naming one here warns. HuggingFace: "naver/splade-cocondenser-ensembledistil", etc. |
None
|
reranker_model
|
Optional[str]
|
Cross-encoder reranker model (bundled alias or HuggingFace path) Bundled: "L12" Fetch yourself: "L6", "bge-reranker" HuggingFace: "cross-encoder/ms-marco-MiniLM-L-12-v2", etc. |
None
|
late_interaction_model
|
Optional[str]
|
ColBERT model (bundled alias or HuggingFace path) Bundled: "colbert" Fetch yourself: "colbert-v2" HuggingFace: "colbert-ir/colbertv2.0", etc. |
None
|
storage_backend
|
Any
|
External storage backend (VectrixDB instance with Lakebase, DeltaLake, etc.) When provided, uses external storage instead of local SQLite. Schema adapts based on mode: - dense: dense_embedding only - hybrid: dense_embedding + sparse_embedding - ultimate: + late_interaction_embedding - graph: + graph tables |
None
|
description
|
Optional[str]
|
Optional description for the collection (stored in _vectrix_collections) |
None
|
token_counter
|
Any
|
fn(text) -> int used by |
None
|
text_boosts
|
Optional[Dict[str, float]]
|
BM25 weight per metadata field, e.g. |
None
|
readonly
|
bool
|
Memory-map the ANN index instead of loading it. Opening is
instant and a collection larger than RAM can be searched; any
write raises |
False
|
Examples:
Basic usage (bundled models, offline)¶
With mode and model selection (bundled)¶
>>> db = Vectrix(
... "docs",
... mode="hybrid",
... dense_model="bge-small",
... sparse_model="splade",
... reranker_model="L6",
... )
>>> results = db.search("query") # Uses hybrid by default
With HuggingFace models¶
>>> db = Vectrix(
... "docs",
... mode="ultimate",
... dense_model="BAAI/bge-large-en-v1.5",
... sparse_model="naver/splade-cocondenser-ensembledistil",
... reranker_model="cross-encoder/ms-marco-MiniLM-L-12-v2",
... late_interaction_model="colbert-ir/colbertv2.0",
... )
Mixed bundled + HuggingFace¶
>>> db = Vectrix(
... "docs",
... mode="hybrid",
... dense_model="bge-small", # Bundled
... sparse_model="splade", # Bundled
... reranker_model="cross-encoder/ms-marco-TinyBERT-L-2-v2", # HuggingFace
... )
With storage backend (Lakebase)¶
>>> from vectrixdb import VectrixDB
>>> lakebase = VectrixDB.with_lakebase(host="...", database="...", user="...", password="...")
>>> db = Vectrix(
... "products",
... mode="ultimate",
... dense_model="bge-small",
... sparse_model="splade",
... reranker_model="L6",
... late_interaction_model="colbert",
... storage_backend=lakebase,
... )
>>> db.add(texts=["Product A", "Product B"]) # Stores all embeddings in Lakebase
>>> results = db.search("query") # Full ultimate search from Lakebase
as_principal(principal, audit=None)
¶
A view of this collection that reads as one principal.
The principal is whatever the host resolved from its own directory, CRM or control room. Nothing here validates it and nothing here can: a library that believed a principal it was handed would be pretending to an authority it does not have.
The view shares this collection's index and model, so it is cheap, and closing either closes both.
>>> view = db.as_principal({"roles": ["credit_analyst"], ...})
>>> view.search("covenant thresholds")
audit carries what the library cannot work out for itself: who this
principal is, which request it belongs to, and when their entitlements
were actually resolved. That last one is where
principal_snapshot_age_ms comes from, and it is the field an access
decision gets challenged on months later.
add_document(source, *, chunk='recursive', chunk_size=1000, overlap=200, parent_size=None, metadata=None, doc_id=None, dedupe=None, kind=None, threshold=0.1, progress=None, on_low_quality='warn', quality_threshold=None, embed_heading=False, source_version=None, cut_with=None, context_with=None, late=False, index=True)
¶
Load, chunk and add one document. Returns the number of chunks added.
source is a file path (PDF, DOCX, PPTX, XLSX, CSV, HTML, Markdown
or text, or any suffix this collection has an extractor for), a
string of Markdown, or a :class:vectrixdb.ingest.LoadedDocument. Every
chunk carries _vx_doc (the document id), _vx_chunk (its
position), its character offsets, and page and heading when
the source has them, on top of metadata.
chunk is the strategy: recursive, sentence, semantic, markdown,
fixed or llm (see :func:vectrixdb.ingest.chunk). llm needs
cut_with, which :func:vectrixdb.chunk_models.llm_cutter makes
from a chat model. context_with, from
:func:vectrixdb.chunk_models.context_writer, has a model write a
note for every chunk placing it in its document, which goes in front
of the chunk for the embedder. late=True embeds late: each chunk
is the mean of its own tokens read in the whole document, with a
model that pools the mean of its tokens. parent_size turns on
parent-child retrieval: chunks are grouped into sections of about
that many characters (or by heading when there are headings), the
sections are stored beside the collection, and search(parents=True)
returns the section a matching chunk belongs to. dedupe skips
chunks whose MinHash similarity to a stored text is at or above that
value; see last_add_report for what was skipped.
Every chunk is scored for extraction quality and carries the score as
_vx_quality. A document whose mean score falls below
quality_threshold (the measured default in vectrixdb.quality)
reads as a failed extraction: on_low_quality is "warn" to
write it and say so, "reject" to refuse it with
:class:ExtractionQualityError, "allow" for silence. Warn by
default rather than reject, because the detector is measured on
English prose and a deployment ingesting something else should see
the scores before it lets them refuse anything.
index=False keeps the document's Markdown, with its metadata, and
stops: nothing is cut, embedded or written to the index, and 0 is
returned. It needs keep_source. The document waits in the store
until :meth:rechunk cuts it, which is how a deployment reads files
before it has decided how to cut them.
embed_heading puts the headings a chunk sits under in front of
the text handed to the embedder, Terms > Late fees: Payment is due
within 30 days, and nowhere else: the stored text, the keyword
index and what a result shows are the chunk as written. A section
that never names its own subject is found by it that way. The
prefix is kept on the chunk as _vx_embed_prefix, so
reembed() embeds the same thing again.
A Markdown file's front matter becomes the document's metadata, and so
every chunk's; metadata= wins where both name a key, and a
doc_id there is the document's id when none is passed. With
keep_source the Markdown as chunked is kept under that id, with
source_version, whatever the original's store calls its version,
an ETag or a hash of its bytes, recorded beside it.
wants_images()
¶
Whether reading a document should keep its pictures.
True when something here does anything with one: a describer to write
what it shows, or an image embedder to index what it looks like.
Asked by :meth:add_document and by the ingestion worker, so a
document read from a file and the same document read from a bucket
come back with the same figures in them.
vector_names()
¶
The dense vectors a search here can be answered from: own for
the collection's model, then each named one. On a storage backend,
the names the backend holds.
with_figures(results)
¶
The same results, with the figures their text refers to brought along.
A paragraph that says as shown in Figure 3 is an answer with a
hole in it. The figure is fetched the way get() fetches anything,
so under a policy a figure this principal may not see stays out. It
is placed straight after the chunk that named it, at that chunk's
score, and is not added twice.
delete_document(doc_id)
¶
Delete every chunk of a document added with add_document, and
its parent sections. Returns the number of chunks removed.
document(doc_id, images=False)
¶
The document as it was indexed: its text, pages, headings and
figures. Needs keep_source.
figure_bytes(hit)
¶
The image behind a figure result, from the document store. None when the hit is not a figure, or its image was not kept.
rechunk(doc_id=None, where=None, **options)
¶
Cut kept documents again and re-index them. Returns the chunks written.
Reads the kept Markdown and never calls an extractor, so a new chunk
size over ten thousand OCRed pages costs an embedding pass and not
an OCR bill. options are add_document's, chunk,
chunk_size, overlap, parent_size, embed_heading; what
is not given is what the document was last indexed with, and its
metadata is what it was written with. With no doc_id every kept
document is done, which is also how an empty index is filled from
the store alone. where takes a function of a document's entry.
Every document is its own ingestion, with its own record and its own build, as it was when it first went in.
rechunk_preview(doc_id=None, where=None, **options)
¶
What :meth:rechunk with the same arguments would do, without doing it.
Each kept document is cut with the settings it would get and the
chunks are counted beside the ones the index holds for it now.
Nothing is written, deleted or embedded, so a preview over a whole
collection costs reading its Markdown, apart from what semantic
chunking asks the embedder and llm chunking asks cut_with. A
model's note (context_with) is not written: it does not move a
cut. The counts are before dedupe, which can only lower them.
reextract(where=None, fetcher=None, doc_id=None)
¶
Read originals again, for the documents where picks. Returns how many.
For when the reader got better: where=lambda e: e.get("extractor") !=
current re-reads only what the old one read. The original is fetched
from the source kept with the document, through fetcher, a
:class:~vectrixdb.worker.LocalFetcher by default, and goes through
this collection's extractors. A document whose text comes back the
same is left alone.
reembed(progress=None, batch_size=256)
¶
Re-embed every stored text with the current model and rebuild the index. Returns the number of documents re-embedded.
This is the migration for a model change: open the collection with
the new model (which warns), call reembed(), and the warning goes
away because the recorded model is updated at the end. The new model
must have the collection's dimension; a different width needs a new
collection, which export() and add() cover.
embed(texts)
¶
Embed text with the collection's model: shape (n, dimension), float32.
The same vectors add() stores and search() queries with, so
anything computed on them lines up with what the collection does.
add(texts, metadata=None, ids=None, vectors=None, progress=None, dedupe=None)
¶
Add texts to the collection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
Union[str, List[str]]
|
Single text or list of texts |
required |
metadata
|
Union[Dict, List[Dict], None]
|
Optional metadata for each text |
None
|
ids
|
Optional[List[str]]
|
Optional custom IDs (auto-generated if not provided) |
None
|
vectors
|
Optional['np.ndarray']
|
Precomputed embeddings, shape (n, dimension). Skips the model, for callers who already have vectors from elsewhere. |
None
|
progress
|
Optional[bool]
|
Show a progress bar while embedding. None means "when there is enough to watch" (200 texts or more, tqdm installed); True forces it, False silences it. |
None
|
dedupe
|
Optional[float]
|
Skip a text whose MinHash similarity (word 3-shingles) to
a stored text, or to an earlier text in this call, is at or
above this value; 0.9 catches re-pastes and near-identical
paragraphs. What was skipped is in |
None
|
Returns:
| Type | Description |
|---|---|
Vectrix
|
Self for chaining |
Example
db = Vectrix("docs").add(["text 1", "text 2"]) db.add("another text", metadata={"source": "web"})
With storage backend, embeddings are generated based on mode: - dense: dense_embedding only - hybrid: dense_embedding + sparse_embedding - ultimate: + late_interaction_embedding - graph: + graph relationships
deferred_saves()
¶
Save the vector index once, when the block ends, rather than after
every add() inside it.
add() saves the index file each time, which is what makes a single
write durable and what makes a thousand of them slow: the file is
rewritten a thousand times. Inside this block the rows still land in
SQLite one by one, and the index is written when the block exits,
whether it exits normally or not. What is given up is durability of
the index between the first write and the end of the block: a process
killed in between reopens with the rows and an index to rebuild with
rebuild_index().
add_many(texts, metadata=None, ids=None, batch_size=256, progress=None)
¶
Add from any iterable, a generator included, in batches. Returns the count.
add() wants a list it can embed in one call; this feeds it batches
so a corpus streamed from a file or a database never has to be held
in memory at once.
revoke(where, set=None, unset=None)
¶
Change entitlement metadata on every document matching where.
The operational half of an entitlement policy. Entitlements are
denormalised onto the chunk, which is what makes a query fast and a
permission change expensive: one person leaving a deal team means
every chunk of every document for that client. Doing it as a loop over
update_metadata is doing it by hand, and the hand slips.
set merges fields in. unset removes them, which is not the
same as setting them to null: the filter engine tells an absent field
from a null one, and under a policy an absent field denies while a
null one is a value like any other.
Administrative, so it belongs to whoever holds the collection rather
than to a principal, the same as export.
Example
report = db.revoke( ... where={"entitlements.client_id": "CL-40219"}, ... unset=["entitlements.need_to_know_group"], ... ) report.matched, report.changed (412, 412)
reproduce(record, snapshot=None)
¶
Restate a decision: which chunks that principal could reach.
record is the decision record, or the dict a sink stored; find it
with sink.find(decision_id) where the sink can read. The result is
exact when the index build and the policy on the record are the ones
this collection holds now, and says so when they are not. Pass
snapshot to run against an export of the build the record names.
The host's question, so a principal's view refuses it: which chunks a principal could reach is the map of the wall.
Example
record = audit.find(results.decision_id) print(db.reproduce(record).report())
provenance(ids)
¶
Where these chunks came from: document, version, source, chunking,
and the build that stored them. The host's question; see reproduce.
evidence_pack(directory, *, records=None)
¶
Write the model risk evidence artifacts for this collection.
What a validator asks for, generated rather than assembled by hand:
what the model is, what it answers from, which controls are in force,
and what the audit trail holds. Not a compliance claim; the files.
records is the audit trail to summarise when the attached sink
cannot read it back.
export(target)
¶
Write the whole collection to one zip: vectors, index, texts, graph.
Example
db.export("backups/notes.zip")
import_snapshot(snapshot, path, name=None, **kw)
classmethod
¶
Unpack a zip written by export under path and open it.
Example
db = Vectrix.import_snapshot("backups/notes.zip", path="./restored")
rebuild_index()
¶
Rebuild the ANN index from the live vectors without blocking readers.
Deletions leave tombstones behind and recall drifts as the graph is edited in place; a rebuild starts clean. Returns the vector count.
graph_path(a, b, max_depth=4)
¶
Shortest chain of relationships between two entities (graph mode).
Example
db.graph_path("Marie Curie", "Nobel Prize")
graph_explain(a, b=None, max_depth=4)
¶
Everything the graph knows about an entity, or about a pair (graph mode).
Example
db.graph_explain("Marie Curie")["communities"] db.graph_explain("Marie Curie", "Pierre Curie")["direct"]
search(query, limit=10, mode=None, rerank=None, filter=None, diversity=0.7, token_budget=None, score_gap=None, explain=False, fusion='rrf', alpha=0.5, expand=None, hyde=None, parents=False, vectors=None, homes=None)
¶
Search the collection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
Search query text |
required |
limit
|
int
|
Number of results (default: 10) |
10
|
mode
|
Optional[SearchMode]
|
Search mode (defaults to mode set in constructor) - "dense": Semantic search only (fastest) - "sparse": Keyword/BM25 only - "hybrid": Dense + Sparse + Reranker (balanced) - "ultimate": Dense + Sparse + Reranker + ColBERT (best quality) - "graph": Ultimate + Knowledge Graph (for GraphRAG) |
None
|
rerank
|
Literal[None, False, 'mmr', 'exact', 'cross-encoder', 'semantic']
|
Reranking. In dense and sparse modes None means no reranking and "mmr", "exact" or "cross-encoder" adds one. Hybrid, ultimate and graph modes rerank their candidates with the cross-encoder by default; False turns that off, which matters for large limits (the cross-encoder costs about 170 ms per long document on a CPU). On Azure AI Search, "semantic" is the service's semantic ranker, for keyword and hybrid searches on an index that has a semantic configuration; "cross-encoder" is MiniLM here instead of it, and False is neither. Left out, it is how the store was opened. |
None
|
filter
|
Optional[Dict[str, Any]]
|
Metadata filter (e.g., {"category": "tech"}); see docs/reference/filters.md for the grammar |
None
|
diversity
|
float
|
Diversity parameter for MMR (0-1, default: 0.7) |
0.7
|
token_budget
|
Optional[int]
|
Stop returning results once their text would cost
more than this many tokens. The top result always ships, and
|
None
|
score_gap
|
Optional[float]
|
Drop results scoring below this fraction of the top score, so a specific question returns one strong answer instead of a padded list. The right value depends on the score scale: cosine scores from the bundled models sit in a narrow band, so 0.9 is typical there, while BM25 or graph scores want 0.2. None disables. |
None
|
explain
|
bool
|
Put the parts of each score in |
False
|
fusion
|
Literal['rrf', 'weighted']
|
How hybrid and ultimate modes combine dense and sparse:
"rrf" (reciprocal rank fusion, rank-based, robust) or
"weighted" (min-max normalised scores blended by |
'rrf'
|
alpha
|
float
|
The dense share of the blend, 0 to 1. 0.5 treats both equally; raise it when keyword matches are dragging results. |
0.5
|
expand
|
Optional[Callable[[str], List[str]]]
|
A function from the query to alternative phrasings. Each is searched and the runs are fused by rank, so a question the corpus phrases differently still finds its document. Pass any LLM call; nothing is bundled. |
None
|
hyde
|
Optional[Callable[[str], str]]
|
A function from the query to a hypothetical answer. The answer is embedded instead of the question, because a document that would answer it sits closer to real documents than the question does. Pass any LLM call; nothing is bundled. |
None
|
parents
|
bool
|
Return, for each hit, the section of its document it
belongs to rather than the chunk itself, for a document
added with |
False
|
Returns:
| Type | Description |
|---|---|
Results
|
Results object with search results |
Example
results = db.search("python programming") results = db.search("AI", mode="dense", explain=True) results.top.explain
vectors picks whose dense vectors answer, on a store that holds
more than one: "vectrixdb", "azure" or "both". Left
out, every vector the store holds answers. An index built with
embeddings="both" can be asked all three ways, which is what
makes a comparison between them fair: same chunks, same filter,
same ranker.
homes is for a collection kept on a store, Azure AI Search or
OpenSearch. Every write already reaches both the store and the index
beside this process, under the same ids, so the collection has two
homes. "store" asks the service and raises when it does not
answer; "local" asks the index here and never leaves the machine;
"both" asks each and fuses the two lists by rank, and when the
service does not answer it returns the local list alone and says so
in degraded. Left out, nothing changes. Dense, keyword and hybrid
searches; not offered under an entitlement policy.
delete(ids)
¶
Delete documents by ID.
Example
db.delete("doc_id") db.delete(["id1", "id2"])
clear()
¶
Clear all documents from collection.
Example
db.clear()
count()
¶
How many documents there are.
Through a principal view this counts what that principal can see, because the total is a fact about documents they may not know exist: a walled analyst watching the number move learns that a client they were refused is being written to. The host's own handle still counts the collection.
That costs a pass over the collection, deciding each document, where an unpoliced count is a single SELECT. A backend that pushes filters into SQL could answer it in one query; see ROADMAP.
Example
print(db.count())
get(ids)
¶
Get documents by ID.
Always a list, whether given one id or many, so a document that is
not there is an empty list rather than None. For a single lookup
that answers None on a miss, use :meth:get_one.
Example
docs = db.get(["id1", "id2"]) one = db.get_one("id1")
get_one(id)
¶
One document by id, or None if there is no such document.
get() takes one id or many and always answers with a list, so a
miss is [] rather than None and a hit has to be unwrapped.
That is the right shape for a batch and the wrong one for a single
lookup, which is what this is for.
Example
doc = db.get_one("doc_id") if doc is not None: ... print(doc.text)
similar(id, limit=10)
¶
Find similar documents to a given document.
Example
similar = db.similar("doc_id", limit=5)
remember(text, session=None, role='user', **kwargs)
¶
Store a conversation turn or a fact. Returns the memory id.
Example
db.remember("I prefer dark mode", session="u1", role="user") db.remember("Ships to Canada only", pinned=True)
recall(query, session=None, **kwargs)
¶
Memories relevant to query, weighted by recency and feedback.
Example
db.recall("theme preference", session="u1", token_budget=300)
feedback(id, outcome, correction=None, **kwargs)
¶
Grade a recalled memory: "useful", "dead_end" or "corrected".
Example
db.feedback(mem_id, "corrected", correction="I prefer light mode")
forget(session=None, **kwargs)
¶
Delete memories: by ids, or by kind, session and age. Returns the count.
Example
db.forget(session="u1", older_than=90) # turns older than 90 days db.forget(session="u1", kinds=("fact",), superseded=True)
consolidate(session, summarize, **kwargs)
¶
Turn a session's old turns into standalone facts with your LLM, then drop the turns.
Example
report = db.consolidate("u1", summarize=my_llm_summary, keep_recent=6) len(report.fact_ids), report.turns_removed
context(query, session=None, **kwargs)
¶
Pinned facts, recent turns and relevant memories under one token budget.
Example:
>>> block = db.context("what did we decide?", session="u1", token_budget=1200)
>>> prompt = block.text + "
" + user_message
close()
¶
Save and close. Safe to call twice; any use afterwards raises.
The index is already saved on every add(), so this is about the other half: releasing the SQLite connections and, in graph mode, the pipeline. Nothing relies on del, which runs at an unpredictable time or, at interpreter exit, not at all.
Result
dataclass
¶
Single search result.
citation
property
¶
What to write in square brackets to cite this chunk.
The string add_document() stamped, or one built from the source, page and heading the chunk carries, or the id when it carries none.
readable_citation
property
¶
The citation as a person reads it: report.pdf, pp. 39-40.
The number printed on the page where the PDF numbers its own pages,
a range when the chunk runs onto the next, slide 3, 11:05 in
a recording. What to show beside an answer; citation is what the
answer cites, and the link that opens the page.
to_dict()
¶
A plain dict, JSON-serialisable when the metadata is.
Results
dataclass
¶
Search results with convenient access.
texts
property
¶
Get all result texts.
ids
property
¶
Get all result IDs.
scores
property
¶
Get all scores.
top
property
¶
Get top result.
figures(top=None)
¶
The results that are figures, best first, top of them at most.
What to hand a model that can see, beside the text.
citations()
¶
Every distinct citation among the results, best first.
sources(max_chars=None)
¶
The results as a sources block for a prompt: one [citation]: text per result.
Newlines inside a text become spaces, so one source stays one
line, and max_chars stops adding sources once the block would
pass it, keeping whole sources rather than cutting one. Put
CITATION_INSTRUCTION in the system prompt beside it and check
the answer with validate_answer().
to_dict()
¶
Everything, as plain data. from_dict reverses it.
to_pandas()
¶
A DataFrame with id, score, text and metadata columns. Needs pandas.
to_polars()
¶
A polars DataFrame with id, score, text and metadata columns. Needs polars.
create(name='default', **kwargs)
¶
Create a new Vectrix collection.
Example
db = create("my_docs") db.add(["text 1", "text 2"])
open(name='default', path='./vectrixdb_data')
¶
Open an existing Vectrix collection.
Example
db = open("my_docs") results = db.search("query")
quick_search(texts, query, limit=5)
¶
One-liner: Index texts and search immediately.
Example
results = quick_search( ... texts=["Python is great", "Java is verbose", "Rust is fast"], ... query="programming language" ... ) print(results.top.text)