A named routing scene.
| 43 | |
| 44 | @dataclass |
| 45 | class SceneConfig: |
| 46 | """A named routing scene.""" |
| 47 | |
| 48 | name: str |
| 49 | primary: str |
| 50 | fallback: list[str] = field(default_factory=list) |
| 51 | hard_pin: bool = False |
| 52 | description: str = "" |
| 53 | |
| 54 | # Optional tier floor/cap — lets a scene say "at least MEDIUM". |
| 55 | tier_floor: Tier | None = None |
| 56 | tier_cap: Tier | None = None |
| 57 | |
| 58 | # Provider-level constraint (e.g. only allow anthropic/). |
| 59 | allowed_providers: list[str] = field(default_factory=list) |
| 60 | |
| 61 | # Per-scene spend limit ($/request). None = no limit. |
| 62 | max_cost_per_request: float | None = None |
| 63 | |
| 64 | def model_pool(self) -> list[str]: |
| 65 | """Return ordered candidate list: [primary, *fallback].""" |
| 66 | seen: set[str] = set() |
| 67 | pool: list[str] = [] |
| 68 | for model in [self.primary, *self.fallback]: |
| 69 | normalized = model.strip() |
| 70 | if normalized and normalized not in seen: |
| 71 | pool.append(normalized) |
| 72 | seen.add(normalized) |
| 73 | return pool |
| 74 | |
| 75 | def as_routing_constraints(self) -> RoutingConstraints: |
| 76 | """Build a RoutingConstraints from scene settings.""" |
| 77 | return RoutingConstraints( |
| 78 | allowed_models=tuple(self.model_pool()), |
| 79 | allowed_providers=tuple(self.allowed_providers), |
| 80 | max_cost=self.max_cost_per_request, |
| 81 | ) |
| 82 | |
| 83 | def as_selection_weights(self) -> None: |
| 84 | """Reserved for future use when route() supports injected weights.""" |
| 85 | return None |
| 86 | |
| 87 | |
| 88 | def _serialize_scene(scene: SceneConfig) -> dict[str, Any]: |
no outgoing calls