Skip to content

VectorGroundTruthStore

Semantic vector store for RAG-based factual grounding. Ingest documents, then pass to CoherenceScorer for fact-checked scoring. Supports pluggable backends via a registry pattern.

Usage

from director_ai.core.retrieval.vector_store import VectorGroundTruthStore

store = VectorGroundTruthStore()
store.ingest([
    "Refunds are available within 30 days of purchase.",
    "Standard shipping takes 5-7 business days.",
    "Pro plan costs $49/month.",
])

# Use with scorer
from director_ai import CoherenceScorer

scorer = CoherenceScorer(
    threshold=0.6,
    ground_truth_store=store,
    use_nli=True,
)

VectorGroundTruthStore Parameters

Parameter Type Default Description
backend VectorBackend \| None None Backend instance (default: InMemoryBackend)
tenant_id str "" Default tenant ID for multi-tenant stores

Methods

add()

store.add(
    key="refund-policy",
    value="Refunds are available within 30 days.",
    metadata={"kb_version_bump": "patch"},  # patch, minor, or major
)

Facts start at 1.0.0. Replacing a fact with different content bumps the patch version by default. Pass kb_version_bump="minor" or "major" in metadata when the source change is a larger schema or policy change. The vector metadata is stamped with:

  • kb_version
  • kb_chunk_version
  • kb_content_hash
  • kb_previous_hash
  • kb_record_kind
  • kb_source_key
  • kb_chunk_index

Use fact_version(key), fact_version_record(key), or version_manifest() to inspect the in-process version ledger.

retract_fact()

store.retract_fact("refund-policy", reason="source withdrawn")

Retraction records mark a fact or derived chunk source as unusable for retrieval without deleting backend rows. retrieve_context() and retrieve_context_with_chunks() filter matching vector results and keyword fallback facts after retraction. Use retraction_records() to inspect the event log.

replace_fact()

store.replace_fact(
    "refund-policy",
    "Refunds are available within 45 days.",
    reason="policy update",
)

Replacement records preserve the superseded version and content hash while the new value is indexed under the same key. Use replacement_records() to inspect the event log.

kb_snapshot_root()

root = store.kb_snapshot_root(tenant_id="acme")
audit_payload = store.kb_snapshot_audit_record(tenant_id="acme")

kb_snapshot_root() returns a deterministic SHA-256 Merkle root over the tenant-visible KB version ledger. Leaves are sorted by tenant, key, record kind, and chunk index, so the same KB state produces the same root regardless of ingestion order. Retractions and replacements update the root because the snapshot includes status, current content hash, previous hash, and semantic version fields.

kb_snapshot_audit_record() returns a compact payload with:

  • event
  • tenant_id
  • revision
  • record_count
  • retraction_count
  • replacement_count
  • conflict_count
  • merkle_root

Pass this payload to AuditLogger.log_review(kb_snapshot=audit_payload) when a review decision must carry the KB state it was grounded against.

The local R9 evidence packet exercises this contract together with protected claim conflicts and provenance-chain verification:

PYTHONPATH=src python -m benchmarks.provenance_evidence --fact-count 4

The generated JSON records only tenant-safe hashes, roots, counts, conflict metadata, and pass/fail status. It does not serialise raw fact values.

conflict_reports()

store.add_fact(
    "signed-dose",
    "Dose is 5 mg.",
    metadata={
        "claim_id": "dose-claim",
        "signed_fact_id": "signed-1",
        "claim_source": "signed_fact",
    },
)

store.add_fact(
    "incoming-dose",
    "Dose is 10 mg.",
    metadata={"claim_id": "dose-claim"},
)

reports = store.conflict_reports()

conflict_reports() returns tenant-scoped records created during fact writes when a new fact overlaps a retracted ledger entry, differs from a protected signed fact, differs from a passport claim, or declares an explicit contradicts relation. Reports are advisory: ingestion continues, retrieval still uses the active version ledger, and callers can route reports to review queues or audit sinks.

freshness_status_signals()

store.add_fact(
    "trial-paper",
    "Trial X reported a 12 percent response rate.",
    metadata={
        "external_id": "doi:10.example/trial-paper",
        "source_timestamp": "1710000000",
        "citation_status": "active",
        "status_source": "publisher-feed",
    },
)

signals = store.freshness_status_signals()

Use freshness_status_signals() to pass KB source age and external citation status metadata into score_temporal_freshness(citation_statuses=signals). The method emits tenant-scoped dictionaries with source_id, status, status_source, and any available timestamp fields.

ingest()

store.ingest(texts: list[str], tenant_id: str = "") -> int

Add documents to the store. Each document is embedded and indexed as a derived vector chunk with kb_record_kind="derived_chunk" and a semantic chunk version.

retrieve_context()

context = store.retrieve_context(query: str, top_k: int = 3, tenant_id: str = "") -> str | None

Retrieve concatenated context string for a query (matching parent GroundTruthStore interface). Use retrieve_context_with_chunks() for structured EvidenceChunk results.


VectorBackend

Abstract protocol for vector storage backends. Implement add() and query() to create a custom backend.

from director_ai.core.retrieval.vector_store import VectorBackend

class MyBackend(VectorBackend):
    def add(self, texts: list[str], ids: list[str] | None = None) -> None:
        ...

    def query(self, text: str, top_k: int = 3) -> list[tuple[str, float]]:
        # Returns list of (text, distance) pairs
        ...

Built-in Backends

Backend Install Description
InMemoryBackend included TF-IDF cosine similarity. No deps, good for testing.
SentenceTransformerBackend pip install director-ai[embeddings] Dense embeddings via sentence-transformers. Production-quality.
ChromaBackend pip install director-ai[vector] ChromaDB persistent store. Scales to millions of documents.
FAISSBackend pip install director-ai[faiss] In-process FAISS index (flat exact or IVF); the default dense engine behind grounded() when the extra is installed.
QdrantBackend pip install director-ai[qdrant] Qdrant vector database; document ids map to deterministic UUID points so re-adds upsert.
WeaviateBackend pip install director-ai[weaviate] Weaviate database via the v4 collections API (HTTP + gRPC).
ElasticsearchBackend pip install director-ai[elasticsearch] Elasticsearch 8.x with hybrid BM25 + kNN retrieval.
PineconeBackend pip install director-ai[pinecone] Pinecone managed vector database.

ChromaBackend

from director_ai.core.retrieval.vector_store import ChromaBackend

backend = ChromaBackend(
    collection_name="legal_contracts",
    persist_directory="/data/chroma",
    embedding_model="BAAI/bge-large-en-v1.5",
)
store = VectorGroundTruthStore(backend=backend)

SentenceTransformerBackend

from director_ai.core.retrieval.vector_store import SentenceTransformerBackend

backend = SentenceTransformerBackend(
    model_name="BAAI/bge-large-en-v1.5",
)
store = VectorGroundTruthStore(backend=backend)

Backend Registry

Register custom backends for use with DirectorConfig.vector_backend:

from director_ai.core.retrieval.vector_store import register_vector_backend, get_vector_backend

register_vector_backend("qdrant", MyQdrantBackend)
BackendClass = get_vector_backend("qdrant")  # returns the class, not an instance
backend = BackendClass(**kwargs)
Function Purpose
register_vector_backend(name, cls) Register a backend class
get_vector_backend(name) Look up a registered backend class
list_vector_backends() List registered backend names

Retrieval Decorators

Each decorator wraps any VectorBackend and changes how queries are formed or how results are assembled:

Decorator Description
HybridBackend BM25 + dense fusion — weighted RRF by default, with convex, combmnz and zscore score-fusion strategies selectable via fusion_method; with_fusion() derives strategy views over one shared index.
RerankedBackend Cross-encoder reranking over an over-fetched candidate set.
TenantScopedBackend Binds any backend to one tenant: stamps the tenant on add, forces the tenant filter on every query, and verifies each returned row (drop + metric, or raise with strict=True).
HyDEBackend Generates a pseudo-document for the query before dense retrieval (HyDE).
MultiVectorBackend Indexes multiple representations per document and queries across all of them.
ParentChildBackend Indexes small child chunks for precision, returns the enclosing parent chunks for context.
ContextualCompressionBackend Compresses retrieved passages down to the query-relevant sentences.
QueryDecompositionBackend Splits compound queries, retrieves per sub-query, and merges results.

AdaptiveRouter sits in front of retrieval and classifies each query into a retrieve-or-skip decision, so trivial prompts bypass the store entirely.

Knowledge-Base Health

KBHealthCheck runs diagnostics over a store (document count, queryability, probe-query latency) and returns a KBHealthReport with healthy, per-check counts, and any issues or warnings:

from director_ai.core.retrieval.kb_health import KBHealthCheck

report = KBHealthCheck(store).run()

Reranking

Enable cross-encoder reranking for improved retrieval precision:

scorer = CoherenceScorer(
    ground_truth_store=store,
    reranker_enabled=True,
    reranker_model="cross-encoder/ms-marco-MiniLM-L-6-v2",
    reranker_top_k_multiplier=3,  # Retrieve 3x, rerank to top_k
)

Full API

director_ai.core.retrieval.vector_store.VectorGroundTruthStore

VectorGroundTruthStore(backend: VectorBackend | None = None, tenant_id: str = '', evidence_firewall: EvidenceFirewall | None = None)

Bases: VersionLedgerMixin, ConflictLedgerMixin, SnapshotAuditMixin, GroundTruthStore

Ground truth store with vector-based semantic retrieval.

Extends the keyword-based GroundTruthStore with embedding-based similarity search. Falls back to keyword matching when the vector backend returns no results. Version bookkeeping, conflict detection, and the snapshot audit come from the composed mixins.

Parameters:

Name Type Description Default
backend VectorBackend — vector DB backend (default: InMemoryBackend).
None

add_fact

add_fact(key: str, value: str, tenant_id: str = '', metadata: dict[str, Any] | None = None) -> None

Alias for add() — also populates parent keyword store.

ingest

ingest(texts: list[str], tenant_id: str = '') -> int

Bulk-add plain text documents into the vector backend.

add

add(key: str, value: str, metadata: dict[str, Any] | None = None, tenant_id: str = '') -> None

Add one fact to the vector backend with version metadata.

retrieve_context

retrieve_context(query: str, top_k: int = 3, tenant_id: str = '') -> str | None

Retrieve context as a string (matching parent interface).

Falls back to keyword-based parent if vector search returns nothing.

grounded classmethod

grounded(embedding_model: str = RECOMMENDED_EMBEDDING_MODEL, use_hybrid: bool = True, rrf_k: int = 60, tenant_id: str = '', use_ann: bool = True, use_reranker: bool = True, reranker_model: str = RECOMMENDED_RERANKER_MODEL, fusion_method: str = 'rrf', sparse_weight: float = 1.0, dense_weight: float = 1.0, enforce_tenant_isolation: bool = False) -> VectorGroundTruthStore

Build the recommended grounded retrieval recipe.

Sets up hybrid retrieval (BM25 + FAISS-indexed dense) with a sentence-transformer embedding model and cross-encoder reranking. This is the intended production path for domain profiles (medical, finance, legal) where NLI-only scoring has 100% FPR without KB grounding.

Every optional layer degrades gracefully: without faiss the dense path is the sentence-transformer linear scan, without sentence-transformers it is the keyword InMemoryBackend, and a missing reranker stack leaves retrieval un-reranked — each with a logged warning, never an exception.

Usage::

store = VectorGroundTruthStore.grounded()
store.ingest(["Your product documentation...", ...])
scorer = CoherenceScorer(ground_truth_store=store, use_nli=True)

Parameters:

Name Type Description Default
embedding_model str

HuggingFace model ID for dense embeddings. Default: BAAI/bge-large-en-v1.5.

RECOMMENDED_EMBEDDING_MODEL
use_hybrid bool

Wrap dense backend with BM25 + rank/score fusion (default True).

True
rrf_k int

Reciprocal Rank Fusion parameter (default 60).

60
tenant_id str

Default tenant scope for multi-tenant deployments.

''
use_ann bool

Index dense embeddings with FAISS instead of the Python linear scan when faiss is installed (default True).

True
use_reranker bool

Rerank fused candidates with a cross-encoder when the reranker stack is installed (default True).

True
reranker_model str

HuggingFace model ID for the cross-encoder reranker. Default: cross-encoder/ms-marco-MiniLM-L-6-v2.

RECOMMENDED_RERANKER_MODEL
fusion_method str

Hybrid fusion strategy — rrf (default), convex, combmnz or zscore; see :mod:director_ai.core.retrieval.vector_store.fusion.

'rrf'
sparse_weight float

BM25 run weight in the fusion (default 1.0).

1.0
dense_weight float

Dense run weight in the fusion (default 1.0).

1.0
enforce_tenant_isolation bool

Bind the whole retrieval stack to tenant_id with :class:TenantScopedBackend (default False). Requires a non-empty tenant_id; adds stamp the tenant into metadata and every returned row is verified against it.

False

retrieve_context_with_chunks

retrieve_context_with_chunks(query: str, top_k: int = 3, tenant_id: str = '') -> list[EvidenceChunk]

Retrieve context as EvidenceChunk objects.

director_ai.core.retrieval.vector_store.VectorBackend

Bases: ABC

Protocol for vector database backends.

add abstractmethod

add(doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None

Index text under doc_id with optional metadata.

query abstractmethod

query(text: str, n_results: int = 3, tenant_id: str = '') -> list[dict[str, Any]]

Return the n_results closest matches to text for the tenant.

count abstractmethod

count() -> int

Return the number of indexed documents.

delete

delete(doc_ids: list[str]) -> int

Delete documents by id when a backend supports mutation.

aadd async

aadd(doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None

Async add — delegates to sync add via executor by default.

aquery async

aquery(text: str, n_results: int = 3, tenant_id: str = '') -> list[dict[str, Any]]

Async query — delegates to sync query via executor by default.

director_ai.core.retrieval.vector_store.InMemoryBackend

InMemoryBackend()

Bases: VectorBackend

Simple in-memory cosine-similarity backend (no external deps).

Uses TF-IDF-like word overlap for embedding approximation. Suitable for testing and small fact stores.

add

add(doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None

Append a validated document to the in-memory store.

query

query(text: str, n_results: int = 3, tenant_id: str = '') -> list[dict[str, Any]]

Rank stored docs by word overlap and return the top n_results.

Filters by tenant_id when given, scores via the Rust word-overlap path (Python Jaccard fallback), and drops zero-overlap matches.

count

count() -> int

Return the number of documents held in memory.

delete

delete(doc_ids: list[str]) -> int

Remove documents by id and return how many were deleted.

director_ai.core.retrieval.vector_store.ChromaBackend

ChromaBackend(collection_name: str = 'director_ai_facts', persist_directory: str | None = None, embedding_model: str | None = None, embedding_function: _ChromaEmbeddingFunction | None = None)

Bases: VectorBackend

ChromaDB backend for production vector search.

Director uses Chroma in embedded mode only. The backend constructs either chromadb.PersistentClient(path=...) for local persistence or chromadb.Client() for ephemeral in-process storage; it never constructs an HTTP client or forwards remote-code execution flags. That boundary keeps server-only Chroma advisories outside Director's execution path while the optional dependency remains open to operator-managed upgrades.

Operators may inject a local Chroma embedding function for offline, deterministic, or pre-warmed deployments. That path avoids the Chroma default embedder and sentence-transformer model downloads while preserving real Chroma storage and query semantics.

Requires pip install chromadb sentence-transformers.

Create or open a Chroma collection with optional persistence.

add

add(doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None

Add one document to the Chroma collection.

query

query(text: str, n_results: int = 3, tenant_id: str = '') -> list[dict[str, Any]]

Query Chroma and normalise result rows to VectorBackend shape.

count

count() -> int

Return Chroma's current collection count.

delete

delete(doc_ids: list[str]) -> int

Delete document IDs from Chroma and return the removed count.

director_ai.core.retrieval.vector_store.SentenceTransformerBackend

SentenceTransformerBackend(model_name: str = 'BAAI/bge-large-en-v1.5', model: _EmbeddingModel | None = None)

Bases: VectorBackend

Embedding-based backend using sentence-transformers directly.

Recommended model: BAAI/bge-large-en-v1.5 (best quality/speed tradeoff). Alternative: Snowflake/snowflake-arctic-embed-l for multilingual.

Operators may inject a preloaded embedding object that implements encode(text, normalize_embeddings=True). This keeps embedded, offline, or model-managed deployments on the same retrieval path without requiring the backend to own model loading.

Requires pip install sentence-transformers unless model is given.

Load the embedding model and initialise the in-memory index.

add

add(doc_id: str, text: str, metadata: dict[str, Any] | None = None) -> None

Embed and append one document with optional metadata.

query

query(text: str, n_results: int = 3, tenant_id: str = '') -> list[dict[str, Any]]

Return positive-similarity nearest documents, optionally tenant-scoped.

count

count() -> int

Return the number of retained in-memory documents.

delete

delete(doc_ids: list[str]) -> int

Delete matching document IDs while keeping embeddings aligned.