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.

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

Diagram

Knowledge Backends

default

local

custom

🤖 Agent

Backend?

☁️ mem0

🗄️ Chroma

🔌 KnowledgeStoreProtocol

How It Works

Diagram
Knowledge BackendsAgentUserKnowledge BackendsAgentUserRequestProcess requestResult Agent–>>User: Response

Quick Start

  1. 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?")
  2. 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

BackendDescriptionBest For
mem0 (default)Long-term memory with semantic searchMulti-user apps, persistent memory
chromaLocal vector databaseDevelopment, single-user apps
sqliteZero-dependency local storeLightweight fallback, tests
mongodbDocument store with Atlas $vectorSearchMulti-tenant production, existing MongoDB
internalBuilt-in lightweight storageSimple 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:

IdentifierPurposeExample
user_idIsolate per user"user_alice"
agent_idIsolate per agent type"research_agent_v1"
run_idIsolate per session"session_abc123"

Example with Scope

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

python
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:

python
results = knowledge.search(
    "refund discussion",
    user_id="customer_42",
    agent_id="support_bot_v1",
    run_id="session_2026_05_30",
)
Diagram
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 output

Filters vs Scope

Metadata Filters

Use filters= on search() to narrow results by any metadata field.

python
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?")
python
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 where clause (as $and when 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> alongside user_id / agent_id / run_id, on both Atlas $vectorSearch and the text-search fallback.
  • mem0 — unchanged; already forwarded to the mem0 backend.
Diagram

chroma

sqlite

mongodb

mem0

🔎 search(query, user_id, filters)

Which backend?

where clause
scope keys protected

json_extract(metadata, ‘$.key’)

metadata.key in scope filter

forwarded to mem0

✅ Filtered + scoped results

MongoDB backend

The MongoDB knowledge adapter honors the same user_id / agent_id / run_id scope on add() and search().

python
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:

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

Diagram

Yes

No

🔎 Query + scope

Atlas vector
search available?

$vectorSearch
filter: user_id / agent_id / run_id

find ($text) + scope filter

✅ Scoped results

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:

python
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 memory for mem0)
  • score is always a float (defaults to 0.0)

This means you can safely access metadata without null checks:

python
# 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=None

Protocol-Driven Architecture

All backends implement the KnowledgeStoreProtocol:

python
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 methods

Supported Providers

Set vector_store.provider to any name below. The value resolves through the Knowledge adapter registry.

ProviderResolves toNotes
mem0Mem0AdapterDefault when no provider set. Multi-tenant, scope-required.
chromaChromaAdapterLocal persistent vector DB.
chromadbChromaAdapterAlias for chroma.
ragChromaAdapterAlias for chroma.
mongodbMongoDBKnowledgeAdapterRequires connection string.
sqliteSQLiteKnowledgeAdapterZero-dependency fallback.
anything registered via register_knowledge_adapter()Custom adapterSee Custom Knowledge Adapters.

Configuration Options

mem0 Backend (Default)

Top-level provider is mem0; qdrant, pgvector, and similar stores live inside mem0’s nested config.

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

python
config = {
    "vector_store": {
        "provider": "chroma",
        "config": {
            "collection_name": "my_collection",
            "path": "./.praison/knowledge/my_collection",
        }
    }
}

Error Handling

python
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_collection
  • UserData123
  • agent_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.

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

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

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

python
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, sqlite

Confirm which adapter you actually got:

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

ProviderStatusHow to use
chromaShippedWorks out of the box — recommended default
qdrant, pinecone, weaviate, milvus, pgvectorPluginCall register_knowledge_adapter(name, adapter_cls) first (see Extension: Custom Adapters)

Related