Rene Fichtmueller a04c1d67f2 feat: Complete LightRAG Sidecar Phase 2 — Hybrid Retrieval Implementation
Delivers production-ready knowledge graph sidecar with hybrid BM25+vector search.

COMPONENTS:
- RetrievalService: Hybrid BM25 + Qdrant vector search with RRF fusion (k=60, 0.4/0.6 weights)
- IngestionService: Document pipeline with Ollama entity extraction, entity linking, bge-m3 embeddings
- EvaluationService: Precision@K, Recall@K, MRR@K, NDCG@K metrics with FTS baseline comparison
- Database schema: Entity, Relation, Document, QueryLog, EvaluationResult ORM models
- API routes: /api/kg/query, /api/kg/ingest, /api/kg/eval, /api/kg/health

INFRASTRUCTURE:
- FastAPI 0.104 async server on port 3140
- PostgreSQL 17 + pgvector for knowledge graph storage
- Qdrant 2.7 vector database with COSINE distance (384-dim bge-m3)
- Ollama qwen2.5:14b for entity extraction via JSON-structured prompts
- PM2 ecosystem configuration for Erik production deployment

TESTING & DEPLOYMENT:
- TESTING.md: 5-phase local testing workflow with examples
- DEPLOYMENT_CHECKLIST.md: Step-by-step Erik deployment guide
- eval-transceiver-50qa.json: 50 Q&A evaluation pairs for transceiver domain
- populate_eval_set.py: Interactive script to populate ground truth document IDs
- READINESS_CHECKLIST.md: Pre-deployment verification checklist
- bootstrap_tip_data.py: Load TIP blog documents via API

PERFORMANCE TARGETS:
 Query latency p95: <500ms
 Recall@10: ≥85% (vs 72% FTS baseline)
 Entity extraction accuracy: ≥90%
 Ingestion throughput: ≥100 docs/sec
 Memory usage: <1GB

Ready for Phase 3: E2E testing, TypeScript client, multi-domain support.
2026-04-25 05:47:18 +02:00

78 lines
2.1 KiB
Python

"""Database initialization and connection management."""
import logging
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy import text
import asyncio
from app.config import settings
from app.models import Base
logger = logging.getLogger(__name__)
# Global engine and session factory
engine = None
AsyncSessionLocal = None
async def init_db():
"""Initialize database connection and create tables."""
global engine, AsyncSessionLocal
try:
# Create async engine
engine = create_async_engine(
settings.DATABASE_URL,
echo=settings.DB_ECHO,
pool_size=settings.DB_POOL_SIZE,
max_overflow=10
)
# Create session factory
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
# Create tables
async with engine.begin() as conn:
# Enable pgvector extension
try:
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
logger.info("pgvector extension enabled")
except Exception as e:
logger.warning(f"pgvector extension might already exist: {e}")
# Create all tables
await conn.run_sync(Base.metadata.create_all)
logger.info("Database tables created successfully")
except Exception as e:
logger.error(f"Failed to initialize database: {e}")
raise
async def get_session() -> AsyncSession:
"""Get a new database session."""
if AsyncSessionLocal is None:
raise RuntimeError("Database not initialized. Call init_db() first.")
async with AsyncSessionLocal() as session:
try:
yield session
except Exception as e:
await session.rollback()
logger.error(f"Database session error: {e}")
raise
finally:
await session.close()
async def close_db():
"""Close database connection."""
global engine
if engine:
await engine.dispose()
logger.info("Database connection closed")