Cognitive-rag / 06_conversation_memory / error_handling.py
error_handling.py
Raw
"""
Error Handling for Conversation Memory System.

Provides retry logic for Pinecone operations.
"""

import asyncio
import logging
from typing import Optional, Any, Callable
from pathlib import Path
import importlib.util

# Load config explicitly from same directory
_config_path = Path(__file__).parent / "config.py"
_spec = importlib.util.spec_from_file_location("memory_config", _config_path)
config = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(config)

# Configure logging
logger = logging.getLogger("conversation_memory")
if not logger.handlers:
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
    logger.addHandler(handler)
    logger.setLevel(logging.INFO)


# ============================================================================
# RETRY LOGIC
# ============================================================================

async def with_retry(
    async_func: Callable,
    *args,
    max_retries: int = None,
    retry_delay: float = None,
    **kwargs
) -> Optional[Any]:
    """
    Execute async function with exponential backoff retry.
    """
    max_retries = max_retries or config.MAX_RETRIES
    retry_delay = retry_delay or config.RETRY_DELAY
    
    for attempt in range(max_retries):
        try:
            return await async_func(*args, **kwargs)
        except Exception as e:
            wait_time = retry_delay * (2 ** attempt)
            logger.warning(
                f"Attempt {attempt + 1}/{max_retries} failed: {e}. "
                f"Retrying in {wait_time}s..."
            )
            if attempt < max_retries - 1:
                await asyncio.sleep(wait_time)
            else:
                logger.error(f"All retries exhausted for {async_func.__name__}")
                return None


# ============================================================================
# SAFE PINECONE UPSERT
# ============================================================================

async def safe_upsert(
    index,
    vectors: list,
    namespace: str
) -> bool:
    """
    Safely upsert to Pinecone with retry logic.
    
    Args:
        index: Pinecone index object
        vectors: List of (id, embedding, metadata) tuples
        namespace: Pinecone namespace
    
    Returns:
        True on success, False on failure
    """
    async def _upsert():
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(
            None,
            lambda: index.upsert(vectors=vectors, namespace=namespace)
        )
    
    result = await with_retry(_upsert)
    return result is not None