Spaces:
Sleeping
Sleeping
| """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. | |
| """ | |