# Vector Search Optimization for ServiceBot **Date Created**: 2025-11-06 **Context**: Making ServiceBot the "most badass AI chatbot" with optimal vector search **Current State**: SQLite direct read works but isn't optimal for scale --- ## The Problem with SQLite Direct Read ### Current Implementation (servicebot/app/knowledge/rag_engine.py) ```python def _vector_search(self, query, kb_filter, limit): # 1. Generate query embedding response = self.openai_client.embeddings.create(model=self.embedding_model, input=query) query_embedding = np.array(response.data[0].embedding) # 2. Load ALL embeddings from SQLite cursor = self.conn.execute("SELECT * FROM embeddings") # 3. Calculate similarity for EVERY document (linear scan) for row in cursor: doc_embedding = np.frombuffer(row['embedding'], dtype=np.float32) similarity = cosine_similarity(query_embedding, doc_embedding) # 4. Sort all results and return top N ``` **Time Complexity:** O(n) where n = total documents **Performance:** - 10K docs: ~200-500ms ✅ Acceptable - 100K docs: ~2-5s ⚠️ Slow - 1M docs: 20s+ ❌ Unusable --- ## Optimization Levels ### Level 1: sqlite-vec Extension ⚡ FAST WIN **What:** Native vector similarity search in SQLite using SIMD optimizations **Install:** ```bash pip install sqlite-vec ``` **Usage:** ```python import sqlite3 import sqlite_vec conn = sqlite3.connect('data/pistn.db') conn.enable_load_extension(True) sqlite_vec.load(conn) # Create virtual table for vector search conn.execute(""" CREATE VIRTUAL TABLE vec_embeddings USING vec0( document_id INTEGER PRIMARY KEY, embedding FLOAT[768] ) """) # Insert embeddings conn.execute("INSERT INTO vec_embeddings SELECT document_id, embedding FROM embeddings") # FAST vector search using ANN (Approximate Nearest Neighbors) cursor = conn.execute(""" SELECT document_id, distance FROM vec_embeddings WHERE embedding MATCH ? ORDER BY distance LIMIT 10 """, [query_embedding.tobytes()]) ``` **Performance:** - 10K docs: ~50-100ms (5-10x faster) ✅ - 100K docs: ~100-200ms (10-25x faster) ✅ - 1M docs: ~200-500ms (40-100x faster) ✅ **Pros:** - ✅ Same SQLite file - ✅ No infrastructure changes - ✅ 10-100x faster - ✅ Portable **Cons:** - ⚠️ Still in SQLite (single file limitations) - ⚠️ Limited concurrent writes **Recommendation:** **DO THIS FIRST** - Easy win! --- ### Level 2: Specialized Vector Database 🚀 ULTIMATE PERFORMANCE **Options:** #### Option A: Qdrant (Open Source, Self-Hosted) ```python from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct # Initialize client = QdrantClient(host="localhost", port=6333) # Create collection client.create_collection( collection_name="pistn_knowledge", vectors_config=VectorParams(size=768, distance=Distance.COSINE) ) # Import from SQLite export import sqlite3 conn = sqlite3.connect('data/pistn.db') cursor = conn.execute("SELECT id, embedding FROM embeddings") points = [] for row in cursor: points.append(PointStruct( id=row[0], vector=np.frombuffer(row[1], dtype=np.float32).tolist(), payload={"doc_id": row[0]} )) client.upsert(collection_name="pistn_knowledge", points=points) # ULTRA-FAST search results = client.search( collection_name="pistn_knowledge", query_vector=query_embedding.tolist(), limit=10 ) ``` **Performance:** - 10K docs: ~10-30ms ✅✅ - 100K docs: ~20-50ms ✅✅ - 1M docs: ~30-80ms ✅✅ - 10M docs: ~50-150ms ✅✅ **Pros:** - ✅ HNSW index (O(log n) search) - ✅ Handles millions of vectors - ✅ Concurrent queries - ✅ Filtering and metadata search - ✅ Production-grade **Cons:** - ⚠️ Separate service to manage - ⚠️ More infrastructure - ⚠️ Memory requirements #### Option B: Chroma (Simple, Embedded) ```python import chromadb # Initialize client = chromadb.Client() collection = client.create_collection("pistn_knowledge") # Import from SQLite conn = sqlite3.connect('data/pistn.db') cursor = conn.execute("SELECT d.id, d.content, e.embedding FROM documents d JOIN embeddings e ON d.id = e.document_id") for row in cursor: collection.add( ids=[str(row[0])], embeddings=[np.frombuffer(row[2], dtype=np.float32).tolist()], documents=[row[1]] ) # Query results = collection.query( query_embeddings=[query_embedding.tolist()], n_results=10 ) ``` **Pros:** - ✅ Embedded (no separate server) - ✅ Simple API - ✅ Good performance **Cons:** - ⚠️ Less mature than Qdrant - ⚠️ Limited scale (compared to Qdrant) #### Option C: Pinecone (Cloud-Hosted) ```python import pinecone # Initialize pinecone.init(api_key="your_key", environment="us-west1-gcp") index = pinecone.Index("pistn-knowledge") # Upsert vectors index.upsert(vectors=[ (str(doc_id), embedding.tolist(), metadata) for doc_id, embedding, metadata in documents ]) # Query (BLAZING FAST) results = index.query( vector=query_embedding.tolist(), top_k=10, include_metadata=True ) ``` **Pros:** - ✅ Zero infrastructure management - ✅ Scales to billions - ✅ Global CDN - ✅ Enterprise features **Cons:** - ❌ Not self-hosted - ❌ Cost (free tier: 1M vectors) - ❌ Vendor lock-in --- ### Level 3: Hybrid Approach 🎯 BEST OF BOTH WORLDS **Architecture:** ``` ┌────────────────────────────────────────────────┐ │ ServiceBot RAG Engine (Adaptive) │ │ │ │ IF deployment == "small": │ │ └─ Use: SQLite with sqlite-vec │ │ • <50K documents │ │ • Simple setup │ │ • Portable │ │ │ │ ELIF deployment == "medium": │ │ └─ Use: Qdrant (self-hosted) │ │ • 50K-1M documents │ │ • Local infrastructure │ │ • Full control │ │ │ │ ELIF deployment == "large": │ │ └─ Use: Pinecone (cloud) │ │ • 1M+ documents │ │ • Global scale │ │ • Managed service │ └────────────────────────────────────────────────┘ ``` **Implementation:** ```python # servicebot/app/knowledge/vector_backends.py class VectorBackend(ABC): @abstractmethod def search(self, query_embedding: np.ndarray, limit: int) -> List[SearchResult]: pass class SQLiteVecBackend(VectorBackend): """Fast for small-medium deployments""" def search(self, query_embedding, limit): # Use sqlite-vec extension ... class QdrantBackend(VectorBackend): """Best for self-hosted production""" def search(self, query_embedding, limit): # Use Qdrant client ... class PineconeBackend(VectorBackend): """Best for cloud scale""" def search(self, query_embedding, limit): # Use Pinecone client ... # Auto-select based on config class ServiceBotRAG: def __init__(self, knowledge_db_path: str): backend_type = settings.VECTOR_BACKEND # 'sqlite-vec', 'qdrant', 'pinecone' if backend_type == 'sqlite-vec': self.vector_backend = SQLiteVecBackend(knowledge_db_path) elif backend_type == 'qdrant': self.vector_backend = QdrantBackend(settings.QDRANT_URL) elif backend_type == 'pinecone': self.vector_backend = PineconeBackend(settings.PINECONE_API_KEY) else: # Fallback to current implementation self.vector_backend = NumpyBackend(knowledge_db_path) ``` --- ## Recommended Implementation Plan ### Phase 1: Add sqlite-vec Support (QUICK WIN) ⚡ **Effort:** 1-2 days **Impact:** 10-100x faster vector search ```bash # 1. Install sqlite-vec pip install sqlite-vec # 2. Update ServiceBotRAG to use it # 3. Migrate existing exports to use vec0 tables # 4. Test performance ``` **Files to modify:** - `servicebot/app/knowledge/rag_engine.py` - Add sqlite-vec support - `servicebot/app/knowledge/importer.py` - Migrate embeddings to vec0 table - `servicebot/requirements.txt` - Add sqlite-vec **Backward compatible:** Falls back to numpy if sqlite-vec not available ### Phase 2: Add Qdrant Backend (PRODUCTION SCALE) 🚀 **Effort:** 3-5 days **Impact:** Handles millions of documents, concurrent queries **Benefits:** - Sub-100ms search even with 1M+ docs - Filtering by metadata - Concurrent multi-user support - Production monitoring **Implementation:** 1. Create `VectorBackend` abstraction 2. Implement `QdrantBackend` 3. Add importer: SQLite export → Qdrant 4. Make backend configurable via environment variable ### Phase 3: Add Pinecone Option (CLOUD SCALE) ☁️ **Effort:** 2-3 days **Impact:** Global scale, zero infrastructure **For deployments that need:** - Multi-region support - Billions of vectors - Zero ops overhead --- ## Performance Comparison | Backend | 10K Docs | 100K Docs | 1M Docs | 10M Docs | Concurrent Users | |---------|----------|-----------|---------|----------|------------------| | **Current (numpy)** | 200ms | 2-5s | 20s+ | N/A | Low | | **sqlite-vec** | 50ms | 200ms | 500ms | N/A | Low | | **Qdrant** | 20ms | 50ms | 80ms | 150ms | High | | **Pinecone** | 15ms | 40ms | 60ms | 100ms | Very High | --- ## Cost Comparison | Backend | Infrastructure | Monthly Cost (100K docs) | Pros | |---------|---------------|------------------------|------| | **Current** | None | $0 | Simple, portable | | **sqlite-vec** | None | $0 | Fast, free, simple | | **Qdrant** | VPS/Server | $10-50 (server) | Full control, scalable | | **Pinecone** | Pinecone Cloud | $70+ | Zero ops, global | --- ## Recommendation for "Most Badass Chatbot" ### For Now (Testing Phase 1): ✅ **Use current SQLite direct read** - Testing export/import flow - Validating knowledge quality - Widget integration - < 50K documents ### Next (After Phase 1 Works): 🚀 **Add sqlite-vec extension** - Quick performance boost - No infrastructure changes - Production-ready for small-medium deployments ### Future (Production Scale): 💪 **Add Qdrant backend option** - When you have 100K+ documents - When you need multi-user concurrent access - When sub-100ms search matters --- ## Migration Path **The beauty of the export format:** ``` Claude OS Export (SQLite) ↓ ├─→ ServiceBot (SQLite direct) ← Phase 1: Testing ├─→ ServiceBot (SQLite + vec) ← Phase 2: Optimization ├─→ ServiceBot (Qdrant) ← Phase 3: Scale └─→ ServiceBot (Pinecone) ← Phase 4: Cloud ``` **Same export, different backends!** The export format is backend-agnostic. --- ## Implementation Priority 1. **NOW**: Finish Phase 1 testing with current SQLite read ✅ 2. **Next Week**: Add sqlite-vec for 10-100x speedup ⚡ 3. **Next Month**: Add Qdrant backend for production scale 🚀 4. **Future**: Add Pinecone for global deployments ☁️ **Don't optimize prematurely** - SQLite direct read is fine for initial testing! --- ## Key Takeaway **For the "most badass chatbot":** - ✅ Current approach: Good for testing and small-medium scale - ⚡ Quick win: Add sqlite-vec (10-100x faster, zero infrastructure) - 🚀 Production: Add Qdrant backend (handles millions, concurrent) - ☁️ Enterprise: Add Pinecone option (global scale, zero ops) **The export format supports all of these** - you can choose based on scale! --- **Next Steps:** 1. Test current setup with Pistn export 2. Validate knowledge quality 3. Then optimize based on actual usage patterns