Coverage for src / lexigram / ai / relay / gateway / channels.py: 99%

70 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-08 23:08 +0800

1"""Deterministic channel selection for the relay gateway. 

2 

3Selection is a pure function of the static channel table, the runtime 

4override table, and the routing intent: source format, model alias, 

5streaming flag, requested capability flags, and an optional preferred 

6channel. The registry never inspects request payloads to make an 

7undocumented routing decision. 

8 

9The runtime override table is written by the operator controls surface 

10(``set_runtime_enabled``); it can only take a configured channel out of 

11service (drain) or restore it — it can never enable a channel whose 

12config ``enabled`` flag is false. 

13""" 

14 

15from __future__ import annotations 

16 

17from collections.abc import Callable 

18import random 

19 

20from lexigram.ai.relay.gateway.config import RelayGatewayConfig 

21from lexigram.contracts.ai.relay import RelayChannel, RelayFormat, RelayGatewayError 

22from lexigram.contracts.core.result import Err, Ok, Result 

23 

24__all__ = ["RelayChannelRegistry"] 

25 

26 

27class RelayChannelRegistry: 

28 """Selects a ``RelayChannel`` deterministically from a static config. 

29 

30 By default selection is fully deterministic; when the config enables 

31 ``"weighted"`` load balancing, ties among an already-tied top tier 

32 (equal precedence, eligible, same priority) are broken by 

33 weighted-random pick instead of ascending name. 

34 

35 Note: 

36 "Healthy channel" in the plan is interpreted as the channel's 

37 ``enabled`` flag combined with the live runtime override table; 

38 drained channels are invisible to ``select`` until they are 

39 restored at runtime. 

40 

41 Attributes: 

42 _channels: The immutable channel table from ``RelayGatewayConfig``. 

43 _runtime_enabled: Operator overrides applied by the controls 

44 service; empty equals "all channels as configured". 

45 """ 

46 

47 def __init__( 

48 self, 

49 config: RelayGatewayConfig, 

50 *, 

51 random_source: Callable[[int], int] | None = None, 

52 ) -> None: 

53 """Bind the registry to a static channel table. 

54 

55 Args: 

56 config: Immutable gateway configuration. Selection never 

57 mutates it; the channel tuple is kept as configured. 

58 random_source: Callable receiving a weight sum and returning 

59 a value in ``[0, total)``; only used by the weighted 

60 tie-break. Defaults to ``random.SystemRandom().randrange``; 

61 tests pass a deterministic fake. 

62 """ 

63 self._config = config 

64 self._channels = config.channels 

65 self._runtime_enabled: dict[str, bool] = {} 

66 self._random_source = random_source or random.SystemRandom().randrange 

67 

68 @property 

69 def channels(self) -> tuple[RelayChannel, ...]: 

70 """The configured channel table. 

71 

72 Returns: 

73 The immutable channel tuple, in configuration order. 

74 """ 

75 return self._channels 

76 

77 def set_runtime_enabled(self, channel: str, enabled: bool) -> None: 

78 """Override the eligibility of *channel* at runtime. 

79 

80 Draining a channel (``enabled=False``) hides it from selection 

81 until restored; restoring removes the override entirely so the 

82 config ``enabled`` flag alone decides. A config-disabled channel 

83 can never be made eligible this way. 

84 

85 Args: 

86 channel: Channel name to override. 

87 enabled: Whether the channel should select new requests. 

88 """ 

89 if enabled: 

90 self._runtime_enabled.pop(channel, None) 

91 else: 

92 self._runtime_enabled[channel] = False 

93 

94 def runtime_enabled(self) -> dict[str, bool]: 

95 """Return the non-default runtime overrides. 

96 

97 Returns: 

98 Mapping of channel name to ``False`` set at runtime; a 

99 restored channel is absent. 

100 """ 

101 return dict(self._runtime_enabled) 

102 

103 def select( 

104 self, 

105 source: RelayFormat, 

106 model: str, 

107 stream: bool = False, 

108 capabilities: frozenset[str] = frozenset(), 

109 preferred: str | None = None, 

110 exclude: frozenset[str] = frozenset(), 

111 ) -> Result[RelayChannel, RelayGatewayError]: 

112 """Pick the best channel for the routing query. 

113 

114 Eligibility is computed first (enabled by config and runtime, 

115 target format differs from the source, model serves the 

116 requested alias, streaming and capability constraints), then the 

117 survivors are sorted: preferred channel first, then exact model 

118 match, then ascending priority (lower number wins), then 

119 ascending name as a stable tiebreak. 

120 The preferred channel still must pass every eligibility filter; 

121 otherwise it is skipped and normal ordering applies. 

122 

123 Args: 

124 source: Wire format the caller supplies; channels whose 

125 target format equals it would be no-op conversions and 

126 are never eligible. 

127 model: Requested model alias; only exact matches are 

128 eligible. 

129 stream: Whether the caller wants streaming. Channels that 

130 declare capabilities must declare ``"stream"`` to serve 

131 streaming requests; channels with no declared 

132 capabilities are unconstrained. 

133 capabilities: Requested capability flags; they must be a 

134 subset of the channel's declared capabilities. 

135 preferred: Optional channel name that ranks first when it is 

136 eligible. Defaults to ``None`` (no preference). 

137 exclude: Channel names to skip, e.g. for failover retries. 

138 Excluded names are filtered before the other eligibility 

139 filters run, so an excluded channel is never eligible and 

140 cannot be treated as preferred. Defaults to empty (no 

141 exclusion). 

142 

143 Returns: 

144 ``Ok(channel)`` for the best eligible channel, or 

145 ``Err(RelayGatewayError)`` when none is eligible. The error 

146 cause is classified in fixed order: no enabled channels 

147 (``CHANNEL_DISABLED``, 404), no enabled channel transforms 

148 the source format (``TARGET_FORMAT_UNSUPPORTED``, 500), no 

149 enabled channel satisfies the capability filters 

150 (``CAPABILITY_UNAVAILABLE``, 409), otherwise the model is 

151 not served (``MODEL_NOT_FOUND``, 404). 

152 

153 Note: 

154 Runtime-drained channels are treated exactly like disabled 

155 channels: they are invisible to selection until restored. 

156 """ 

157 enabled = self._enabled_channels() 

158 if not enabled: 

159 return Err( 

160 RelayGatewayError( 

161 code="CHANNEL_DISABLED", 

162 message="no enabled channels", 

163 status_code=404, 

164 request_id="", 

165 ) 

166 ) 

167 enabled = [channel for channel in enabled if channel.name not in exclude] 

168 transformable = [ 

169 channel for channel in enabled if channel.target_format != source 

170 ] 

171 if not transformable: 

172 return Err( 

173 RelayGatewayError( 

174 code="TARGET_FORMAT_UNSUPPORTED", 

175 message="no channel supports the requested target format", 

176 status_code=500, 

177 request_id="", 

178 ) 

179 ) 

180 capable = [ 

181 channel 

182 for channel in transformable 

183 if self._meets_capabilities(channel, stream, capabilities) 

184 ] 

185 if not capable: 

186 return Err( 

187 RelayGatewayError( 

188 code="CAPABILITY_UNAVAILABLE", 

189 message="no channel provides the requested capabilities", 

190 status_code=409, 

191 request_id="", 

192 ) 

193 ) 

194 matched = [channel for channel in capable if model in channel.models] 

195 if not matched: 

196 return Err( 

197 RelayGatewayError( 

198 code="MODEL_NOT_FOUND", 

199 message=f"no channel serves model {model!r}", 

200 status_code=404, 

201 request_id="", 

202 ) 

203 ) 

204 ordered = sorted( 

205 matched, 

206 key=lambda channel: ( 

207 channel.name != preferred, 

208 model not in channel.models, 

209 channel.priority, 

210 channel.name, 

211 ), 

212 ) 

213 if self._config.load_balancing == "weighted" and len(ordered) > 1: 

214 top = ordered[0] 

215 top_key = (top.name != preferred, model not in top.models, top.priority) 

216 tier = [ 

217 channel 

218 for channel in ordered 

219 if ( 

220 channel.name != preferred, 

221 model not in channel.models, 

222 channel.priority, 

223 ) 

224 == top_key 

225 ] 

226 if len(tier) > 1: 

227 return Ok(self._pick_weighted(tier)) 

228 return Ok(ordered[0]) 

229 

230 def select_for_endpoint( 

231 self, 

232 kind: str, 

233 model: str, 

234 *, 

235 exclude: frozenset[str] = frozenset(), 

236 ) -> Result[RelayChannel, RelayGatewayError]: 

237 """Pick the best channel serving an endpoint kind (e.g. ``"embeddings"``). 

238 

239 Passthrough entry point: eligibility is limited to channels 

240 declaring *kind* in ``endpoint_kinds`` (empty means chat-only, 

241 never eligible here), then survivors are sorted by ascending 

242 priority (lower number wins) and ascending name as a stable 

243 tiebreak. Model aliases and the ``enabled``/runtime-disabled 

244 filters behave exactly like ``select``. A chat-only channel is 

245 untouched by this method. 

246 

247 Args: 

248 kind: Endpoint kind the caller wants (e.g. ``"embeddings"``); 

249 only channels declaring it are eligible. 

250 model: Requested model alias; only exact matches are 

251 eligible. 

252 exclude: Channel names to skip, e.g. for failover retries. 

253 Defaults to empty (no exclusion). 

254 

255 Returns: 

256 ``Ok(channel)`` for the best eligible channel, or 

257 ``Err(RelayGatewayError)`` when none is eligible: no enabled 

258 channels (``CHANNEL_DISABLED``, 404), otherwise, no channel 

259 serves the kind or model (``MODEL_NOT_FOUND``, 404). 

260 """ 

261 enabled = self._enabled_channels() 

262 if not enabled: 

263 return Err( 

264 RelayGatewayError( 

265 code="CHANNEL_DISABLED", 

266 message="no enabled channels", 

267 status_code=404, 

268 request_id="", 

269 ) 

270 ) 

271 serving = [ 

272 channel 

273 for channel in enabled 

274 if kind in channel.endpoint_kinds and channel.name not in exclude 

275 ] 

276 matched = [channel for channel in serving if model in channel.models] 

277 if not matched: 

278 return Err( 

279 RelayGatewayError( 

280 code="MODEL_NOT_FOUND", 

281 message=f"no channel serves endpoint {kind!r} for model {model!r}", 

282 status_code=404, 

283 request_id="", 

284 ) 

285 ) 

286 ordered = sorted( 

287 matched, 

288 key=lambda channel: (channel.priority, channel.name), 

289 ) 

290 return Ok(ordered[0]) 

291 

292 def _enabled_channels(self) -> list[RelayChannel]: 

293 """Return channels enabled by both config and the runtime overrides.""" 

294 return [ 

295 channel 

296 for channel in self._channels 

297 if channel.enabled and self._runtime_enabled.get(channel.name, True) 

298 ] 

299 

300 def _pick_weighted(self, tier: list[RelayChannel]) -> RelayChannel: 

301 """Pick one channel from an already-tied tier by cumulative weight. 

302 

303 Channels with ``weight=0`` are excluded unless the whole tier is 

304 made of them; the walk is driven by ``self._random_source`` on 

305 the total weight so the pick is a pure function of the injected 

306 source (low values pick early channels, values near the sum pick 

307 late ones). 

308 """ 

309 participants = [channel for channel in tier if channel.weight > 0] or tier 

310 total = sum(channel.weight for channel in participants) or 1 

311 roll = self._random_source(total) 

312 for channel in participants: 

313 roll -= channel.weight 

314 if roll < 0: 

315 return channel 

316 return participants[-1] 

317 

318 @staticmethod 

319 def _meets_capabilities( 

320 channel: RelayChannel, stream: bool, capabilities: frozenset[str] 

321 ) -> bool: 

322 """Check whether *channel* satisfies the streaming and capability filters.""" 

323 if capabilities and not capabilities <= channel.capabilities: 

324 return False 

325 return not ( 

326 stream and channel.capabilities and "stream" not in channel.capabilities 

327 )