VectorStore is the main entry point. Use "inmemory" for development and "faiss" for local production:
from semantica.vector_store import VectorStore# In-memory (development / testing: no persistence)store = VectorStore(backend="inmemory", dimension=384)# FAISS (local, persists to disk via save/load)store = VectorStore(backend="faiss", dimension=384)# Add plain text documents (auto-embedded)ids = store.add_documents( documents=["Apple was founded by Steve Jobs.", "Microsoft was co-founded by Bill Gates."], metadata=[{"source": "wiki"}, {"source": "wiki"}])# Search by text query (auto-embedded)results = store.search("technology company founders", limit=5)for r in results: print(f"{r['id']}: score: {r['score']:.3f}")
Match vector dimension to your embedding model. The dimension parameter must exactly match your embedding model’s output size: BAAI/bge-small-en-v1.5 = 384, all-MiniLM-L6-v2 = 384, all-mpnet-base-v2 = 768, bge-large-en-v1.5 = 1024. A mismatch raises an error at insert time.
Use add_documents() for text, store_vectors() for pre-computed embeddings.add_documents() auto-embeds in parallel batches. If your embeddings are already computed (e.g. from a fine-tuned model), use store_vectors() directly to skip re-embedding.
# Search by text query (auto-embeds the query)results = store.search("machine learning", limit=10)# Search by pre-computed vectorresults = store.search_vectors(query_vector, k=10)for r in results: print(f"{r['id']}: score: {r['score']:.3f}")
4
Filter results by metadata
from semantica.vector_store import HybridSearch, MetadataFiltermf = MetadataFilter().eq("category", "research").gt("year", 2022)# Pass vector_store to the constructor: search() resolves vectors automaticallysearch = HybridSearch(vector_store=store)results = search.search(query=query_vector, k=10, metadata_filter=mf)
# In-memory: no persistence, for development and testingstore = VectorStore(backend="inmemory", dimension=384)# FAISS: local disk persistence via save() / load()store = VectorStore(backend="faiss", dimension=384)store.save("./my_store") # save to directorystore.load("./my_store") # restore from directory
No installation or API key required. FAISS requires pip install faiss-cpu.
HybridSearch combines vector similarity with metadata filtering. Pass vector_store at construction to avoid supplying raw vectors on every call:
from semantica.vector_store import HybridSearch, MetadataFilter# With vector_store: search() pulls vectors from the store automaticallysearch = HybridSearch(vector_store=store)mf = MetadataFilter().eq("category", "research").gt("year", 2022)results = search.search( query=query_vector, # np.ndarray or query string (auto-embedded) k=10, metadata_filter=mf)for r in results: print(f"{r['id']}: score: {r['score']:.3f} metadata: {r['metadata']}")
Use HybridSearch(vector_store=store) to avoid passing raw vectors on every call. When vector_store is set, search() pulls vectors and metadata from the store automatically: you only need to pass the query and filter.
Use NamespaceManager to assign vectors to named namespaces for multi-tenant isolation:
from semantica.vector_store import NamespaceManager, VectorStorestore = VectorStore(backend="inmemory", dimension=384)ns_manager = NamespaceManager()ns_manager.create_namespace("tenant_a", description="Customer A data")ns_manager.create_namespace("tenant_b", description="Customer B data")# Store vectors, then assign them to a namespaceids_a = store.store_vectors(embeddings_a, metadata=metadata_a)for vid in ids_a: ns_manager.add_vector_to_namespace(vid, "tenant_a")# List all namespace namesfor name in ns_manager.list_namespaces(): # returns List[str] print(name)# Get all vectors in a namespacevectors_in_a = ns_manager.get_namespace_vectors("tenant_a")# Look up which namespace a vector belongs tons = ns_manager.get_vector_namespace("vec_0")ns_manager.delete_namespace("tenant_a")
Use NamespaceManager for multi-tenant applications. Storing all tenants’ vectors in the same collection and filtering by metadata at query time is slow and risks data leakage if a filter is accidentally omitted. Namespace isolation is both faster (smaller search space) and safer (structural isolation).
store = VectorStore(backend="faiss", dimension=384)store.add_documents(documents=docs, metadata=meta)# Save to a directory: creates index.bin and store_data.pklstore.save("./vector_store_backup")# Restore in a new processstore2 = VectorStore(backend="faiss", dimension=384)store2.load("./vector_store_backup")
Cloud backends (Pinecone, Weaviate, Qdrant, Milvus, PgVector) manage persistence themselves. save()/load() are for the in-memory and FAISS backends only.
inmemory and faiss backends lose data on process exit without save(). Call store.save(path) after adding vectors. Cloud backends (Pinecone, Qdrant, Weaviate, Milvus, PgVector) persist automatically.
MetadataStore indexes structured metadata and lets you query by field values without a vector:
from semantica.vector_store import MetadataStoremeta_store = MetadataStore()# Store and retrieve metadatameta_store.store_metadata("doc1", {"author": "Alice", "year": 2024, "category": "research"})meta_store.store_metadata("doc2", {"author": "Bob", "year": 2023, "category": "review"})# Query: returns List[str] of matching vector IDsids = meta_store.query_metadata({"category": "research", "year": 2024})# OR queryids = meta_store.query_metadata({"category": "research"}, operator="OR")# Get and update metadata for a specific vectormeta = meta_store.get_metadata("doc1")meta_store.update_metadata("doc1", {"score": 0.92})# Get all unique values for a fieldyears = meta_store.get_field_values("year")# Statisticsstats = meta_store.get_stats()# {"total_vectors": 2, "indexed_fields": 3, "field_counts": {...}}
Update metadata without re-embedding.MetadataStore.update_metadata(id, {...}) changes attached fields (status, tags, review date) without re-running the embedding model. Use this for state changes that don’t affect semantic content.
FAISS index type is configured by creating a FAISSStore directly and calling create_index(). Use lowercase type names:
from semantica.vector_store import FAISSStorestore = FAISSStore(dimension=384)# flat: brute-force exact searchstore.create_index(index_type="flat", metric="L2")# ivf: inverted file indexstore.create_index(index_type="ivf", metric="L2", nlist=100)# hnsw: hierarchical navigable small world graphstore.create_index(index_type="hnsw", metric="L2", M=32)# pq: product quantization for memory efficiencystore.create_index(index_type="pq", metric="L2", m=8)
Index
Memory
Speed
Accuracy
When to Use
flat
High
Slow
Exact (100%)
< 100K vectors, correctness critical
ivf
Medium
Fast
~95–98%
100K–10M vectors, good balance
hnsw
Medium-High
Very fast
~97–99%
Low latency, production retrieval
pq
Low
Fast
~90–95%
Millions of vectors, memory-constrained
FAISS index type names are lowercase. The FAISSStore.create_index() method expects "flat", "ivf", "hnsw", "pq": not "Flat", "IVF", "HNSW", "PQ". Uppercase values raise ValidationError.
When using VectorStore(backend="faiss"), the underlying FAISSStore is initialised with a flat index by default. To use ivf/hnsw/pq, construct FAISSStore directly and call create_index() with the desired type.
from semantica.vector_store import VectorStorestore = VectorStore(backend="faiss", dimension=384)# Index documentsstore.add_documents( documents=corpus_texts, metadata=[{"source": src} for src in sources], batch_size=64,)# Persiststore.save("./corpus_index")# Queryresults = store.search("What is knowledge graph construction?", limit=5)for r in results: print(f"[{r['score']:.3f}] {r['metadata']['source']}")
from semantica.vector_store import VectorStore, HybridSearch, MetadataFilterstore = VectorStore(backend="inmemory", dimension=384)search = HybridSearch(vector_store=store)# Only return results from 2023+ with category "research"mf = MetadataFilter().gte("year", 2023).eq("category", "research")results = search.search(query=query_vector, k=10, metadata_filter=mf)