Knowledge Backends
Configure and use different knowledge storage backends in PraisonAI
Choose the right storage backend for your knowledge base — from local development with Chroma to multi-tenant production with mem0.
from praisonaiagents import Agent
agent = Agent(
name="Research Assistant",
instructions="You are a research assistant.",
knowledge=["./documents/"],
)
agent.start("What are the main findings?")The user asks a question; the agent retrieves from mem0, Chroma, or a custom knowledge store you configured.
How It Works
Quick Start
- Simple — enable with True
python from praisonaiagents import Agent agent = Agent( name="ResearchAssistant", instructions="You are a research assistant.", knowledge=["./documents/"], memory={"user_id": "user123"}, ) response = agent.chat("What are the main findings?") - With backend config
python from praisonaiagents import Agent agent = Agent( name="ResearchAssistant", instructions="You are a research assistant.", knowledge={ "vector_store": { "provider": "chroma", "config": { "collection_name": "my_docs", "path": "./.praison/knowledge/my_docs", } } }, memory={"user_id": "user123"}, )
Available Backends
| Backend | Description | Best For |
|---|---|---|
| mem0 (default) | Long-term memory with semantic search | Multi-user apps, persistent memory |
| chroma | Local vector database | Development, single-user apps |
| sqlite | Zero-dependency local store | Lightweight fallback, tests |
| mongodb | Document store with Atlas $vectorSearch | Multi-tenant production, existing MongoDB |
| internal | Built-in lightweight storage | Simple use cases |
All four external backends (mem0, chroma, sqlite, mongodb) share the same tenant-scope contract — see the Warning below.
Scope Identifiers
Knowledge backends support three scope identifiers for multi-tenant isolation:
| Identifier | Purpose | Example |
|---|---|---|
user_id | Isolate per user | "user_alice" |
agent_id | Isolate per agent type | "research_agent_v1" |
run_id | Isolate per session | "session_abc123" |
Example with Scope
from praisonaiagents import Agent
# User-scoped knowledge
agent = Agent(
name="PersonalAssistant",
instructions="You are a personal assistant.",
knowledge=["./user_docs/"],
memory={"user_id": "alice"} , # Knowledge scoped to Alice
)
# Agent-scoped knowledge (shared across users)
shared_agent = Agent(
name="CompanyBot",
instructions="You answer company policy questions.",
knowledge=["./policies/"],
agent_id="company_bot_v1", # Shared knowledge
)Combining Multiple Scopes
Combine user_id, agent_id, and run_id to isolate knowledge down to a specific session for a specific agent and user.
from praisonaiagents import Agent, MemoryConfig
agent = Agent(
name="SupportBot",
instructions="Answer using the customer's session history.",
knowledge=["./support_docs/"],
memory=MemoryConfig(user_id="customer_42", session_id="session_2026_05_30"),
)
agent.start("What did we discuss about my refund?")You can also use the direct API for more control:
results = knowledge.search(
"refund discussion",
user_id="customer_42",
agent_id="support_bot_v1",
run_id="session_2026_05_30",
)graph LR
subgraph "Combined Scope Filtering"
A[📋 user_id] --> D[🔍 ChromaDB where = $and[...]]
B[🤖 agent_id] --> D
C[💬 run_id] --> D
D --> E[✅ scoped results]
end
classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
classDef output fill:#10B981,stroke:#7C90A0,color:#fff
class A,B,C input
class D process
class E outputFilters vs Scope
Metadata Filters
Use filters= on search() to narrow results by any metadata field.
from praisonaiagents import Agent
# Agent-first: the agent's memory scope carries user_id; filters narrow by metadata
agent = Agent(
name="PolicyBot",
instructions="Answer policy questions from the knowledge base.",
knowledge=["./policies/"],
memory={"user_id": "customer_42"},
)
agent.start("What is the refund policy?")from praisonaiagents import Knowledge
kb = Knowledge(config={"vector_store": {"provider": "sqlite"}})
# Direct API — scope + metadata filter together
kb.search("refund policy", user_id="customer_42", filters={"category": "policy"})Since PR #5320 all four adapters (chroma, sqlite, mongodb, mem0) honor the filters= kwarg on search(). Previously SQLite and MongoDB silently dropped filters, returning an unfiltered superset without any warning.
- Chroma — merged into the ChromaDB
whereclause (as$andwhen combined with scope; scope keys are protected from being overwritten). - SQLite — applied as
AND json_extract(metadata, '$."<key>"') = ?; a key that itself contains a dot (e.g."source.type") is matched literally, not as a nested JSON path. - MongoDB — folded into the scope filter as
metadata.<key>alongsideuser_id/agent_id/run_id, on both Atlas$vectorSearchand the text-search fallback. - mem0 — unchanged; already forwarded to the mem0 backend.
MongoDB backend
The MongoDB knowledge adapter honors the same user_id / agent_id / run_id scope on add() and search().
from praisonaiagents import Agent, MemoryConfig
agent = Agent(
name="SupportBot",
instructions="Answer using the customer's session history.",
knowledge={
"vector_store": {
"provider": "mongodb",
"config": {
"connection_string": "mongodb+srv://...",
"database": "praison",
"collection": "support_knowledge",
},
},
},
memory=MemoryConfig(user_id="customer_42", session_id="session_2026_08_09"),
)
agent.start("What did we agree on for the refund?")Direct API — same scope keywords:
from praisonaiagents import Knowledge
kb = Knowledge(config={
"vector_store": {
"provider": "mongodb",
"config": {
"connection_string": "mongodb+srv://...",
"database": "praison",
"collection": "support_knowledge",
},
}
})
kb.add("Refund promised: $49 by Aug 12.",
user_id="customer_42", agent_id="support_bot_v1", run_id="session_2026_08_09")
kb.search("refund promise",
user_id="customer_42", agent_id="support_bot_v1", run_id="session_2026_08_09")On Atlas $vectorSearch, the scope is injected as the stage-level filter; on the text-search fallback, it is merged into the find() query. Only provided scopes are applied — omit an identifier to broaden the search on that dimension.
Multi-tenant SaaS application flow:
- Per-customer isolation → set
user_id - Per-agent isolation (e.g. SupportBot vs. SalesBot share infra but not data) → also set
agent_id - Per-conversation isolation (e.g. ephemeral session memory) → also set
run_id
Direct Knowledge API
For advanced use cases, you can use the Knowledge class directly:
from praisonaiagents import Knowledge
# Initialize with config
knowledge = Knowledge(config={
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "my_docs",
"path": "./.praison/knowledge/my_docs",
}
}
})
# Add documents
knowledge.add("./documents/", memory={"user_id": "user123"})
# Search
results = knowledge.search("query", user_id="user123", limit=10)Normalization Guarantees
PraisonAI normalizes all backend results to ensure consistent behavior:
- metadata is ALWAYS a dict (never
None) - text field is always present (mapped from
memoryfor mem0) - score is always a float (defaults to 0.0)
This means you can safely access metadata without null checks:
# Safe - metadata is guaranteed to be a dict
for result in results['results']:
source = result.get('metadata', {}).get('source', 'unknown')
# This works even if the backend returns metadata=NoneProtocol-Driven Architecture
All backends implement the KnowledgeStoreProtocol:
from praisonaiagents import KnowledgeStoreProtocol
class MyCustomBackend:
"""Custom backend implementing the protocol."""
def search(self, query, *, user_id=None, agent_id=None, run_id=None, **kwargs):
# Your implementation
pass
def add(self, content, *, user_id=None, agent_id=None, run_id=None, **kwargs):
# Your implementation
pass
# ... other methodsSupported Providers
Set vector_store.provider to any name below. The value resolves through the Knowledge adapter registry.
| Provider | Resolves to | Notes |
|---|---|---|
mem0 | Mem0Adapter | Default when no provider set. Multi-tenant, scope-required. |
chroma | ChromaAdapter | Local persistent vector DB. |
chromadb | ChromaAdapter | Alias for chroma. |
rag | ChromaAdapter | Alias for chroma. |
mongodb | MongoDBKnowledgeAdapter | Requires connection string. |
sqlite | SQLiteKnowledgeAdapter | Zero-dependency fallback. |
anything registered via register_knowledge_adapter() | Custom adapter | See Custom Knowledge Adapters. |
Configuration Options
mem0 Backend (Default)
Top-level provider is mem0; qdrant, pgvector, and similar stores live inside mem0’s nested config.
config = {
"vector_store": {
"provider": "mem0",
"config": {
"vector_store": {
"provider": "qdrant", # mem0's inner backend
"config": {
"collection_name": "my_collection",
}
}
}
}
}Chroma Backend
path is optional. Omit it and the store persists under an absolute per-project directory: <project>/.praisonai/knowledge/chroma. The store is isolated per project, so multiple projects (or Windows setups) never share one on-disk sqlite.
config = {
"vector_store": {
"provider": "chroma",
"config": {
"collection_name": "my_collection",
"path": "./.praison/knowledge/my_collection",
}
}
}Error Handling
from praisonaiagents import (
ScopeRequiredError,
BackendNotAvailableError,
)
try:
results = knowledge.search("query") # Missing scope!
except ScopeRequiredError as e:
print(f"Please provide user_id, agent_id, or run_id: {e}")
except BackendNotAvailableError as e:
print(f"Backend not available: {e}")Collection Naming Rules
Knowledge stores that interpolate collection names into DDL/DML now require collection names to match ^[A-Za-z0-9_]+$. Affected backends:
- Cassandra
- pgvector
- SingleStore vector
Invalid names raise: ValueError("collection_name must be non-empty and contain only alphanumerics and underscores")
Valid examples:
my_collectionUserData123agent_v2_docs
Invalid examples:
my-collection(contains hyphen)user.docs(contains dot)data collection(contains space)../../etc(path traversal attempt)
Best Practices
Always provide scope identifiers for mem0
The mem0 backend requires at least one scope identifier. Without it, operations raise ScopeRequiredError.
agent = Agent(
knowledge=["./docs"],
memory={"user_id": "alice"},
)Use alphanumeric collection names
Collection names must match ^[A-Za-z0-9_]+$ for backends that use DDL/DML (Cassandra, pgvector, SingleStore).
Valid: my_collection, UserData123. Invalid: my-collection, user.docs.
Prefer Agent API over direct Knowledge API
The Agent API handles scoping, retrieval, and context injection automatically. Use the direct Knowledge class only when you need custom control over indexing or search.
Implement KnowledgeStoreProtocol for custom backends
Any class implementing KnowledgeStoreProtocol works as a backend — no base class inheritance needed.
from praisonaiagents import KnowledgeStoreProtocol
class MyBackend:
def search(self, query, *, user_id=None, **kwargs): ...
def add(self, content, *, user_id=None, **kwargs): ...Extension: Custom Adapters
Register your own backend under any provider name, then reach it through vector_store.provider.
from praisonaiagents import Agent, Knowledge
from praisonaiagents.knowledge.adapters import register_knowledge_adapter
class QdrantKnowledgeAdapter:
def __init__(self, config=None, verbose=0):
self.config = config
def store(self, *args, **kwargs): ...
def search(self, *args, **kwargs): return []
def delete(self, *args, **kwargs): ...
register_knowledge_adapter("qdrant", QdrantKnowledgeAdapter)
agent = Agent(
name="Researcher",
instructions="Answer using the knowledge base.",
knowledge=Knowledge(config={"vector_store": {"provider": "qdrant"}}),
)Register before the first Agent(...) or Knowledge(...) call reads .memory. See Custom Knowledge Adapters for the full protocol contract and registration API.
Behaviour on Unknown Provider
An explicitly-configured provider that isn’t a built-in adapter and hasn’t been registered raises ValueError.
from praisonaiagents import Knowledge
Knowledge(config={"vector_store": {"provider": "definitely_not_a_store"}})
# ValueError: unknown knowledge vector_store provider 'definitely_not_a_store'.
# Valid: chroma, chromadb, mem0, mongodb, rag, sqliteConfirm which adapter you actually got:
from praisonaiagents import Knowledge
kb = Knowledge(config={
"vector_store": {
"provider": "chroma",
"config": {"collection_name": "my_docs"},
}
})
print(type(kb.memory).__name__) # 'ChromaAdapter'Shipped vs Plugin Providers
Top-level vector_store.provider accepts only providers with a registered Knowledge adapter.
| Provider | Status | How to use |
|---|---|---|
chroma | Shipped | Works out of the box — recommended default |
qdrant, pinecone, weaviate, milvus, pgvector | Plugin | Call register_knowledge_adapter(name, adapter_cls) first (see Extension: Custom Adapters) |