File size: 3,621 Bytes
48d895c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
99
"""ModelSpec β€” the single source of truth for model metadata."""

from dataclasses import dataclass

from .enums import Capability, ModeId, Tier


@dataclass(slots=True, frozen=True)
class ModelSpec:
    """Immutable descriptor for one model variant.



    ``model_name``  is the public-facing identifier used in API requests.

    ``mode_id``     is the upstream ``modeId`` value (auto / fast / expert).

    ``tier``        determines which account pool is used (basic / super).

                    When ``prefer_best`` is True, ``tier`` only affects

                    ``pool_name()``/``pool_id()``; the actual selection order

                    is reversed (heavy β†’ super β†’ basic).

    ``capability``  is a bitmask of supported operations.

    ``enabled``     gates whether the model appears in ``/v1/models``.

    ``public_name`` is the human-readable display name.

    ``prefer_best`` when True, reverses pool priority to try higher-tier

                    pools first (hard priority, not soft preference).

    """

    model_name: str
    mode_id: ModeId
    tier: Tier
    capability: Capability
    enabled: bool
    public_name: str
    prefer_best: bool = False

    # --- convenience predicates ---

    def is_chat(self) -> bool:
        return bool(self.capability & Capability.CHAT)

    def is_image(self) -> bool:
        return bool(self.capability & Capability.IMAGE)

    def is_image_edit(self) -> bool:
        return bool(self.capability & Capability.IMAGE_EDIT)

    def is_video(self) -> bool:
        return bool(self.capability & Capability.VIDEO)

    def is_voice(self) -> bool:
        return bool(self.capability & Capability.VOICE)

    def is_console_chat(self) -> bool:
        """ι€šθΏ‡ console.x.ai/v1/responses θ·―η”±ηš„ζ¨‘εž‹γ€‚"""
        return bool(self.capability & Capability.CONSOLE_CHAT)

    def pool_name(self) -> str:
        """Return the canonical pool string for this tier."""
        if self.tier == Tier.SUPER:
            return "super"
        if self.tier == Tier.HEAVY:
            return "heavy"
        return "basic"

    def pool_id(self) -> int:
        """Return the integer PoolId for the dataplane account table."""
        return int(self.tier)

    # θΏ”ε›žζŒ‰δΌ˜ε…ˆηΊ§ζŽ’εΊηš„ε€™ι€‰ζ±  ID
    def pool_candidates(self) -> tuple[int, ...]:
        """Return pool IDs to try in priority order.



        When ``prefer_best`` is True the order is reversed so that the

        highest-tier pool is attempted first (hard priority β€” the first

        pool with any available account wins; lower pools are only reached

        when all accounts in higher pools are exhausted).



        Default (prefer_best=False):

          BASIC tier  β†’ try basic first, then super, then heavy

          SUPER tier  β†’ try super first, then heavy

          HEAVY tier  β†’ heavy only



        Reversed (prefer_best=True):

          BASIC tier  β†’ try heavy first, then super, then basic

          SUPER tier  β†’ try heavy first, then super

          HEAVY tier  β†’ heavy only

        """
        if self.prefer_best:
            if self.tier == Tier.HEAVY:
                return (2,)  # heavy only
            if self.tier == Tier.SUPER:
                return (2, 1)  # heavy, super
            return (2, 1, 0)  # heavy, super, basic
        if self.tier == Tier.BASIC:
            return (0, 1, 2)  # basic, super, heavy
        if self.tier == Tier.SUPER:
            return (1, 2)  # super, heavy
        return (2,)  # heavy only


__all__ = ["ModelSpec"]