cs-01/Lead Architecture/B2B SaaS

Enterprise Multi-Tenant AI Integration

12 min read
Advanced
QdrantFastAPIPostgreSQLRAGMulti-tenancy

Executive Summary

Architected and deployed a multi-tenant Generative AI system that enforces strict row-level SQL permissions directly within Vector DB payload filters, eliminating cross-tenant data leakage while maintaining sub-second latency.

Business Problem

The client needed to integrate semantic search and RAG capabilities into their existing ERP. However, their data model relied heavily on complex hierarchical permissions. Traditional post-filtering RAG was too slow and risked exposing metadata to the LLM.

Requirements

  • Zero cross-tenant data leakage
  • Sub-200ms vector retrieval
  • Seamless integration with existing JWT auth
  • Cost-effective embedding generation

Solution Architecture

We designed a completely decoupled architecture where the Vector Database (Qdrant) acts as a high-speed cache for embeddings, but never holds the source of truth for permissions. Permissions are injected dynamically via JWT claims at request time.

[Client Application]
       │ (JWT: Tenant A, Role: User)
       ▼
[API Gateway (FastAPI)] ── [Auth Service]
       │
       ├─► (Filter: tenant_id='A')
       │         ▼
       │   [Qdrant Vector DB]
       │         ▲
       │         │ (Async Sync via gRPC)
       │   [Master ERP DB]
       │
       └─► (LLM Context Window)

Implementation & Code

The critical path required ensuring that the LLM could never synthesize a response using documents outside the user's tenant. We achieved this by pushing the filtering down to the C++ core of Qdrant via payload filters.

# Qdrant Payload Filter Enforcement
async def retrieve_secure_context(query: str, user_jwt: dict):
    tenant_id = user_jwt.get("tenant")
    role = user_jwt.get("role")

    # The LLM NEVER sees data it shouldn't
    results = await qdrant.search(
        collection_name="enterprise_docs",
        query_vector=embed(query),
        query_filter=Filter(
            must=[
                FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id)),
                FieldCondition(key="access_level", match=MatchValue(value=role))
            ]
        )
    )
    return results

Tradeoffs & Problems Faced

Memory Overhead

Storing complex permission metadata inside the vector payload increased memory usage by 15%. We mitigated this by converting string IDs to optimized integer mappings.

Sync Latency

Keeping ERP permissions in sync with Vector metadata required an asynchronous event bus to prevent blocking the main thread during heavy writes.

Results & Lessons Learned

The system successfully processed over 10M queries in its first month with zero cross-tenant breaches. The primary lesson learned was that security in generative AI must be enforced at the retrieval layer, not the generation layer. Prompt engineering is not a substitute for hard access controls.