Spaces:
Sleeping
Sleeping
File size: 3,040 Bytes
a28ec65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """Exception hierarchy for the LLM backend layer.
Maps onto the failure-mode table in spec v0.4 §3.7 so the graceful-degradation
notice formatter (`interpretation/degrade.py`, future phase) can render the
right diagnostic + suggested remediation for each case.
All concrete errors carry a `diagnostic` string suitable for the user-facing
notice plus an optional `suggestion` for actionable next steps.
"""
from __future__ import annotations
class LLMError(RuntimeError):
"""Base class for all errors from `uofa_cli.llm`."""
def __init__(self, diagnostic: str, suggestion: str | None = None):
super().__init__(diagnostic)
self.diagnostic = diagnostic
self.suggestion = suggestion
class LLMUnavailable(LLMError):
"""Backend health check failed — service is not reachable.
Covers Ollama-not-running, network-unreachable, and similar transient
availability failures. Spec §3.7 graceful degradation engages on this.
"""
class BackendNotInstalled(LLMError):
"""Required Python dependency for the chosen backend is missing.
With the litellm-wrap design this typically means `litellm` itself is
not installed (i.e. `pip install uofa[extract]` was skipped).
"""
class AuthenticationError(LLMError):
"""Backend rejected the credentials.
Maps to spec §3.7 row "API key invalid". The `diagnostic` deliberately
avoids echoing the credential value (spec §6.4 Rule 2).
"""
class RateLimited(LLMError):
"""Backend returned 429 / quota exceeded.
`retry_after_seconds` is set when the backend reports a Retry-After
header so callers can decide whether to wait or fall back.
"""
def __init__(
self,
diagnostic: str,
suggestion: str | None = None,
retry_after_seconds: float | None = None,
):
super().__init__(diagnostic, suggestion)
self.retry_after_seconds = retry_after_seconds
class ModelNotFound(LLMError):
"""Configured model is not available on the chosen backend."""
class ContextWindowExceeded(LLMError):
"""Prompt is larger than the backend's max context window.
Extract-specific failure (spec §3.7 last row) — surfaces when the user
tries to extract from a document the local model cannot fit.
"""
def __init__(
self,
diagnostic: str,
suggestion: str | None = None,
prompt_tokens: int | None = None,
max_context_tokens: int | None = None,
):
super().__init__(diagnostic, suggestion)
self.prompt_tokens = prompt_tokens
self.max_context_tokens = max_context_tokens
class TimeoutError(LLMError): # noqa: A001 — shadows builtin intentionally; scoped to llm package
"""Backend did not respond within the configured timeout."""
class ConfigError(LLMError):
"""LLM configuration is malformed.
Raised by `config.resolve_llm_config()` for invalid backend names,
literal API keys in TOML files (spec §6.4 Rule 1), missing required
fields, etc.
"""
|