Skip to content

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 token_budget. Without one, tokens are estimated at four characters each, which is within about twenty percent for English prose and needs no download.

None
text_boosts Optional[Dict[str, float]]

BM25 weight per metadata field, e.g. {"title": 2.0}, so a keyword hit in a title outranks the same hit in the body. Stored with the collection and applied on every open.

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 ConfigurationError.

False

Examples:

Basic usage (bundled models, offline)
>>> db = Vectrix("docs")
>>> results = db.search("query")
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 last_add_report.

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 Results.truncated / Results.cut_count say what was cut.

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 Result.explain: dense similarity, BM25, the fused score, ColBERT, the reranker and any graph boost. What you cannot see you cannot tune.

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 alpha).

'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 parent_size=: small chunks find, the section answers. Several hits in one section come back as that section once, and a chunk from a document added without parent_size comes back as it is.

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")

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)