Coverage for src / lexigram / ai / relay / gateway / config.py: 100%
28 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
1"""Typed gateway configuration for the relay gateway.
3Holds the static channel table plus model-suffix and provider-options
4metadata. The suffix and option maps are consumed at conversion time by
5the service layer; channel selection never reads them. The auto-test
6flags control the background channel health sweep (see
7:class:`~lexigram.ai.relay.gateway.operations.auto_test.RelayChannelAutoTester`).
8"""
10from __future__ import annotations
12from collections.abc import Mapping
13from dataclasses import dataclass, field
14from typing import Literal
16from lexigram.contracts.ai.relay import JsonValue, RelayChannel
18__all__ = ["RelayGatewayConfig"]
21@dataclass(frozen=True, slots=True)
22class RelayGatewayConfig:
23 """Static configuration backing ``RelayChannelRegistry`` selection.
25 Attributes:
26 channels: The ordered channel configurations. Selection filters
27 before sorting, so order is never observable in the result
28 except as the stable ``name`` tiebreak. Duplicate names are
29 rejected.
30 model_suffix: Channel name to a suffix (e.g. ``":thinking"``)
31 appended to the outbound model alias at the service layer.
32 Selection does not use this field.
33 provider_options: Channel name to provider-specific options merged
34 into ``RelayConversionContext`` at conversion time. Selection
35 does not use this field.
36 auto_test_channels: When ``True`` the provider starts a background
37 channel auto-tester that periodically probes every channel
38 and disables failed ones, re-enabling them on recovery.
39 Defaults to ``False`` (disabled).
40 auto_test_interval_seconds: Delay between auto-test sweeps in
41 seconds. Must be positive when defined. Defaults to ``600``.
42 max_upstream_retries: Number of retry attempts across *other*
43 channels after a retryable upstream failure on the buffered
44 path. Defaults to ``0`` (single attempt, today's behavior).
45 load_balancing: Channel-selection mode. ``"deterministic"``
46 (default) keeps today's name-sort tiebreak; ``"weighted"``
47 breaks ties among equal-priority eligible channels by
48 weighted-random pick driven by each channel's ``weight``.
49 job_ttl_seconds: Age in seconds after which a relay job record
50 (``POST /v1/videos`` style job relay) is evicted from the
51 in-memory job registry on its next poll. Must be positive.
52 Defaults to ``3600`` (one hour).
53 """
55 channels: tuple[RelayChannel, ...] = ()
56 model_suffix: Mapping[str, str] = field(default_factory=dict)
57 provider_options: Mapping[str, Mapping[str, JsonValue]] = field(
58 default_factory=dict
59 )
60 auto_test_channels: bool = False
61 auto_test_interval_seconds: int = 600
62 max_upstream_retries: int = 0
63 load_balancing: Literal["deterministic", "weighted"] = "deterministic"
64 job_ttl_seconds: int = 3600
66 def __post_init__(self) -> None:
67 """Reject duplicate names, bad auto-test intervals, and negative retries."""
68 names = [channel.name for channel in self.channels]
69 if len(names) != len(set(names)):
70 raise ValueError("duplicate channel names in RelayGatewayConfig")
71 if self.auto_test_interval_seconds <= 0:
72 raise ValueError("auto_test_interval_seconds must be a positive integer")
73 if self.max_upstream_retries < 0:
74 raise ValueError("max_upstream_retries must be non-negative")
75 if self.load_balancing not in ("deterministic", "weighted"):
76 raise ValueError("load_balancing must be 'deterministic' or 'weighted'")
77 if self.job_ttl_seconds <= 0:
78 raise ValueError("job_ttl_seconds must be a positive integer")