""" Context Management Engine Exceptions. Provides a hierarchy of exceptions for context assembly, token budget management, and related operations. """ from typing import Any class ContextError(Exception): """ Base exception for all context management errors. All context-related exceptions should inherit from this class to allow for catch-all handling when needed. """ def __init__(self, message: str, details: dict[str, Any] | None = None) -> None: """ Initialize context error. Args: message: Human-readable error message details: Optional dict with additional error context """ self.message = message self.details = details or {} super().__init__(message) def to_dict(self) -> dict[str, Any]: """Convert exception to dictionary for logging/serialization.""" return { "error_type": self.__class__.__name__, "message": self.message, "details": self.details, } class BudgetExceededError(ContextError): """ Raised when token budget is exceeded. This occurs when the assembled context would exceed the allocated token budget for a specific context type or total. """ def __init__( self, message: str = "Token budget exceeded", allocated: int = 0, requested: int = 0, context_type: str | None = None, ) -> None: """ Initialize budget exceeded error. Args: message: Error message allocated: Tokens allocated for this context type requested: Tokens requested context_type: Type of context that exceeded budget """ details: dict[str, Any] = { "allocated": allocated, "requested": requested, "overage": requested - allocated, } if context_type: details["context_type"] = context_type super().__init__(message, details) self.allocated = allocated self.requested = requested self.context_type = context_type class TokenCountError(ContextError): """ Raised when token counting fails. This typically occurs when the LLM Gateway token counting service is unavailable or returns an error. """ def __init__( self, message: str = "Failed to count tokens", model: str | None = None, text_length: int | None = None, ) -> None: """ Initialize token count error. Args: message: Error message model: Model for which counting was attempted text_length: Length of text that failed to count """ details: dict[str, Any] = {} if model: details["model"] = model if text_length is not None: details["text_length"] = text_length super().__init__(message, details) self.model = model self.text_length = text_length class CompressionError(ContextError): """ Raised when context compression fails. This can occur when summarization or truncation cannot reduce content to fit within the budget. """ def __init__( self, message: str = "Failed to compress context", original_tokens: int | None = None, target_tokens: int | None = None, achieved_tokens: int | None = None, ) -> None: """ Initialize compression error. Args: message: Error message original_tokens: Tokens before compression target_tokens: Target token count achieved_tokens: Tokens achieved after compression attempt """ details: dict[str, Any] = {} if original_tokens is not None: details["original_tokens"] = original_tokens if target_tokens is not None: details["target_tokens"] = target_tokens if achieved_tokens is not None: details["achieved_tokens"] = achieved_tokens super().__init__(message, details) self.original_tokens = original_tokens self.target_tokens = target_tokens self.achieved_tokens = achieved_tokens class AssemblyTimeoutError(ContextError): """ Raised when context assembly exceeds time limit. Context assembly must complete within a configurable time limit to maintain responsiveness. """ def __init__( self, message: str = "Context assembly timed out", timeout_ms: int = 0, elapsed_ms: float = 0.0, stage: str | None = None, ) -> None: """ Initialize assembly timeout error. Args: message: Error message timeout_ms: Configured timeout in milliseconds elapsed_ms: Actual elapsed time in milliseconds stage: Pipeline stage where timeout occurred """ details: dict[str, Any] = { "timeout_ms": timeout_ms, "elapsed_ms": round(elapsed_ms, 2), } if stage: details["stage"] = stage super().__init__(message, details) self.timeout_ms = timeout_ms self.elapsed_ms = elapsed_ms self.stage = stage class ScoringError(ContextError): """ Raised when context scoring fails. This occurs when relevance, recency, or priority scoring encounters an error. """ def __init__( self, message: str = "Failed to score context", scorer_type: str | None = None, context_id: str | None = None, ) -> None: """ Initialize scoring error. Args: message: Error message scorer_type: Type of scorer that failed context_id: ID of context being scored """ details: dict[str, Any] = {} if scorer_type: details["scorer_type"] = scorer_type if context_id: details["context_id"] = context_id super().__init__(message, details) self.scorer_type = scorer_type self.context_id = context_id class FormattingError(ContextError): """ Raised when context formatting fails. This occurs when converting assembled context to model-specific format fails. """ def __init__( self, message: str = "Failed to format context", model: str | None = None, adapter: str | None = None, ) -> None: """ Initialize formatting error. Args: message: Error message model: Target model adapter: Adapter that failed """ details: dict[str, Any] = {} if model: details["model"] = model if adapter: details["adapter"] = adapter super().__init__(message, details) self.model = model self.adapter = adapter class CacheError(ContextError): """ Raised when cache operations fail. This is typically non-fatal and should be handled gracefully by falling back to recomputation. """ def __init__( self, message: str = "Cache operation failed", operation: str | None = None, cache_key: str | None = None, ) -> None: """ Initialize cache error. Args: message: Error message operation: Cache operation that failed (get, set, delete) cache_key: Key involved in the failed operation """ details: dict[str, Any] = {} if operation: details["operation"] = operation if cache_key: details["cache_key"] = cache_key super().__init__(message, details) self.operation = operation self.cache_key = cache_key class ContextNotFoundError(ContextError): """ Raised when expected context is not found. This occurs when required context sources return no results or are unavailable. """ def __init__( self, message: str = "Required context not found", source: str | None = None, query: str | None = None, ) -> None: """ Initialize context not found error. Args: message: Error message source: Source that returned no results query: Query used to search """ details: dict[str, Any] = {} if source: details["source"] = source if query: details["query"] = query super().__init__(message, details) self.source = source self.query = query class InvalidContextError(ContextError): """ Raised when context data is invalid. This occurs when context content or metadata fails validation. """ def __init__( self, message: str = "Invalid context data", field: str | None = None, value: Any | None = None, reason: str | None = None, ) -> None: """ Initialize invalid context error. Args: message: Error message field: Field that is invalid value: Invalid value (may be redacted for security) reason: Reason for invalidity """ details: dict[str, Any] = {} if field: details["field"] = field if value is not None: # Avoid logging potentially sensitive values details["value_type"] = type(value).__name__ if reason: details["reason"] = reason super().__init__(message, details) self.field = field self.value = value self.reason = reason