"""What can go wrong on the way out to the model. The distinction that matters is between an error another key can fix and one it cannot. Quota is the only error worth rotating on: a bad model name or a dead network burns all five keys and then reports the wrong problem. """ class LlmError(Exception): """Base for everything in this module.""" class NoKeyConfigured(LlmError): def __init__(self, provider: str = "gemini") -> None: super().__init__(f"No API key configured for {provider}") self.provider = provider class QuotaExhausted(LlmError): """429 / RESOURCE_EXHAUSTED. The one error that rotates to the next key.""" def __init__(self, status: int, message: str) -> None: super().__init__(f"{status}: {message}") self.status = status class ModelUnavailable(LlmError): """404 / NOT_FOUND on the model. Falls through to the next model without burning keys.""" def __init__(self, status: int, message: str) -> None: super().__init__(f"{status}: {message}") self.status = status class Rejected(LlmError): """Anything else the API said no to. Fails fast.""" def __init__(self, status: int, message: str) -> None: super().__init__(f"{status}: {message}") self.status = status class AllKeysExhausted(LlmError): def __init__(self, count: int) -> None: super().__init__(f"All {count} configured keys are out of quota") self.count = count class EmptyReply(LlmError): """The call succeeded and the model returned nothing usable."""