"""
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