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

1"""Typed gateway configuration for the relay gateway. 

2 

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""" 

9 

10from __future__ import annotations 

11 

12from collections.abc import Mapping 

13from dataclasses import dataclass, field 

14from typing import Literal 

15 

16from lexigram.contracts.ai.relay import JsonValue, RelayChannel 

17 

18__all__ = ["RelayGatewayConfig"] 

19 

20 

21@dataclass(frozen=True, slots=True) 

22class RelayGatewayConfig: 

23 """Static configuration backing ``RelayChannelRegistry`` selection. 

24 

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 """ 

54 

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 

65 

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")