Coverage for src / lexigram / contracts / ai / providers.py: 0%

47 statements  

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

1"""Provider registry contracts for multi-provider LLM selection and fallback.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from datetime import datetime 

7from enum import StrEnum 

8from typing import TYPE_CHECKING, Any 

9 

10from typing_extensions import Protocol, runtime_checkable 

11 

12from lexigram.contracts.ai.types import ModelCapability 

13 

14if TYPE_CHECKING: 

15 from lexigram.contracts.ai.llm import LLMClientProtocol 

16 

17 

18class SelectionStrategy(StrEnum): 

19 """Strategy for selecting which model/provider to use. 

20 

21 Attributes: 

22 COST_OPTIMAL: Choose cheapest option that meets requirements 

23 LATENCY_OPTIMAL: Choose fastest provider 

24 CAPABILITY_MATCH: Choose first model supporting all required capabilities 

25 ROUND_ROBIN: Rotate through healthy providers 

26 PREFERRED: Try preferred provider, fallback to others 

27 """ 

28 

29 COST_OPTIMAL = "cost_optimal" 

30 LATENCY_OPTIMAL = "latency_optimal" 

31 CAPABILITY_MATCH = "capability_match" 

32 ROUND_ROBIN = "round_robin" 

33 PREFERRED = "preferred" 

34 

35 

36@dataclass(frozen=True) 

37class ModelInfo: 

38 """Information about an available LLM model. 

39 

40 Attributes: 

41 model_id: Unique model identifier (e.g., "gpt-4o") 

42 provider: Provider name (e.g., "openai") 

43 display_name: Human-readable name 

44 capabilities: Set of capabilities this model has 

45 context_window: Input context window size in tokens 

46 max_output_tokens: Maximum output tokens this model can generate 

47 input_cost_per_million: Cost per million input tokens (USD) 

48 output_cost_per_million: Cost per million output tokens (USD) 

49 is_available: Whether model is currently available 

50 metadata: Additional provider-specific metadata 

51 """ 

52 

53 model_id: str 

54 provider: str 

55 display_name: str 

56 capabilities: frozenset[ModelCapability] 

57 context_window: int 

58 max_output_tokens: int 

59 input_cost_per_million: float 

60 output_cost_per_million: float 

61 is_available: bool = True 

62 metadata: dict[str, Any] = field(default_factory=dict) 

63 

64 

65@dataclass(frozen=True) 

66class ProviderHealth: 

67 """Health status of a provider. 

68 

69 Mutable dataclass tracking provider health metrics including 

70 latency, error rate, and availability status. 

71 

72 Attributes: 

73 provider: Provider name 

74 is_healthy: Whether provider is considered healthy 

75 latency_ms: Average response latency in milliseconds 

76 error_rate: Error rate as a fraction (0-1) 

77 last_check: When health was last checked 

78 details: Additional health detail information 

79 """ 

80 

81 provider: str 

82 is_healthy: bool 

83 latency_ms: float 

84 error_rate: float 

85 last_check: datetime 

86 details: dict[str, Any] = field(default_factory=dict) 

87 

88 

89@runtime_checkable 

90class ProviderRegistryProtocol(Protocol): 

91 """Protocol for registering and discovering LLM providers. 

92 

93 Maintains a registry of supported providers and their models, 

94 with health tracking and capability-based filtering. 

95 """ 

96 

97 async def register_provider( 

98 self, name: str, client: LLMClientProtocol, models: list[ModelInfo] 

99 ) -> None: 

100 """Register a new provider with its available models. 

101 

102 Args: 

103 name: Provider name (e.g., "openai", "anthropic") 

104 client: The LLM client for this provider 

105 models: List of available models from this provider 

106 """ 

107 ... 

108 

109 async def get_client(self, provider: str) -> LLMClientProtocol | None: 

110 """Get the LLM client for a provider. 

111 

112 Args: 

113 provider: Provider name 

114 

115 Returns: 

116 The LLM client, or None if not registered 

117 """ 

118 ... 

119 

120 def list_providers(self) -> list[str]: 

121 """List all registered provider names. 

122 

123 Returns: 

124 List of provider names 

125 """ 

126 ... 

127 

128 def list_models( 

129 self, capabilities: set[ModelCapability] | None = None 

130 ) -> list[ModelInfo]: 

131 """List all available models with optional capability filtering. 

132 

133 Args: 

134 capabilities: If provided, return only models with all these capabilities 

135 

136 Returns: 

137 List of model information 

138 """ 

139 ... 

140 

141 def get_model_info(self, model_id: str) -> ModelInfo | None: 

142 """Get information about a specific model. 

143 

144 Args: 

145 model_id: The model identifier 

146 

147 Returns: 

148 Model information, or None if not found 

149 """ 

150 ... 

151 

152 

153@runtime_checkable 

154class ModelSelectorProtocol(Protocol): 

155 """Protocol for selecting models based on criteria. 

156 

157 Implements various selection strategies including cost optimization, 

158 latency optimization, and capability matching. 

159 """ 

160 

161 async def select( 

162 self, 

163 capabilities: set[ModelCapability], 

164 preferred_provider: str | None = None, 

165 max_cost_per_million: float | None = None, 

166 strategy: SelectionStrategy = SelectionStrategy.COST_OPTIMAL, 

167 ) -> ModelInfo | None: 

168 """Select a model based on the given criteria. 

169 

170 Args: 

171 capabilities: Required capabilities for the model 

172 preferred_provider: Prefer this provider if possible 

173 max_cost_per_million: Maximum cost threshold (if relevant to strategy) 

174 strategy: Selection strategy to use 

175 

176 Returns: 

177 Selected model info, or None if no suitable model found 

178 """ 

179 ... 

180 

181 

182@runtime_checkable 

183class FallbackChainProtocol(Protocol): 

184 """Protocol for executing requests with provider fallback. 

185 

186 Handles cascading requests across multiple providers with 

187 automatic fallback on failure. 

188 """ 

189 

190 async def execute(self, request: Any, providers: list[str]) -> Any: 

191 """Execute request with automatic fallback to other providers. 

192 

193 Tries providers in order until one succeeds. 

194 

195 Args: 

196 request: The request to execute 

197 providers: List of provider names to try in order 

198 

199 Returns: 

200 The response from the first successful provider 

201 

202 Raises: 

203 AllProvidersExhaustedError: If all providers failed 

204 """ 

205 ... 

206 

207 

208__all__ = [ 

209 "FallbackChainProtocol", 

210 "ModelInfo", 

211 "ModelSelectorProtocol", 

212 "ProviderHealth", 

213 "ProviderRegistryProtocol", 

214 "SelectionStrategy", 

215]