File size: 1,565 Bytes
210ef30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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."""