ai-lib-python 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. ai_lib_python/__init__.py +43 -0
  2. ai_lib_python/batch/__init__.py +15 -0
  3. ai_lib_python/batch/collector.py +244 -0
  4. ai_lib_python/batch/executor.py +224 -0
  5. ai_lib_python/cache/__init__.py +26 -0
  6. ai_lib_python/cache/backends.py +380 -0
  7. ai_lib_python/cache/key.py +237 -0
  8. ai_lib_python/cache/manager.py +332 -0
  9. ai_lib_python/client/__init__.py +37 -0
  10. ai_lib_python/client/builder.py +528 -0
  11. ai_lib_python/client/cancel.py +368 -0
  12. ai_lib_python/client/core.py +433 -0
  13. ai_lib_python/client/response.py +134 -0
  14. ai_lib_python/embeddings/__init__.py +36 -0
  15. ai_lib_python/embeddings/client.py +339 -0
  16. ai_lib_python/embeddings/types.py +234 -0
  17. ai_lib_python/embeddings/vectors.py +246 -0
  18. ai_lib_python/errors/__init__.py +41 -0
  19. ai_lib_python/errors/base.py +316 -0
  20. ai_lib_python/errors/classification.py +210 -0
  21. ai_lib_python/guardrails/__init__.py +35 -0
  22. ai_lib_python/guardrails/base.py +336 -0
  23. ai_lib_python/guardrails/filters.py +583 -0
  24. ai_lib_python/guardrails/validators.py +475 -0
  25. ai_lib_python/pipeline/__init__.py +55 -0
  26. ai_lib_python/pipeline/accumulate.py +248 -0
  27. ai_lib_python/pipeline/base.py +240 -0
  28. ai_lib_python/pipeline/decode.py +281 -0
  29. ai_lib_python/pipeline/event_map.py +506 -0
  30. ai_lib_python/pipeline/fan_out.py +284 -0
  31. ai_lib_python/pipeline/select.py +297 -0
  32. ai_lib_python/plugins/__init__.py +32 -0
  33. ai_lib_python/plugins/base.py +294 -0
  34. ai_lib_python/plugins/hooks.py +296 -0
  35. ai_lib_python/plugins/middleware.py +285 -0
  36. ai_lib_python/plugins/registry.py +294 -0
  37. ai_lib_python/protocol/__init__.py +71 -0
  38. ai_lib_python/protocol/loader.py +317 -0
  39. ai_lib_python/protocol/manifest.py +385 -0
  40. ai_lib_python/protocol/validator.py +460 -0
  41. ai_lib_python/py.typed +1 -0
  42. ai_lib_python/resilience/__init__.py +102 -0
  43. ai_lib_python/resilience/backpressure.py +225 -0
  44. ai_lib_python/resilience/circuit_breaker.py +318 -0
  45. ai_lib_python/resilience/executor.py +343 -0
  46. ai_lib_python/resilience/fallback.py +341 -0
  47. ai_lib_python/resilience/preflight.py +413 -0
  48. ai_lib_python/resilience/rate_limiter.py +291 -0
  49. ai_lib_python/resilience/retry.py +299 -0
  50. ai_lib_python/resilience/signals.py +283 -0
  51. ai_lib_python/routing/__init__.py +118 -0
  52. ai_lib_python/routing/manager.py +593 -0
  53. ai_lib_python/routing/strategy.py +345 -0
  54. ai_lib_python/routing/types.py +397 -0
  55. ai_lib_python/structured/__init__.py +33 -0
  56. ai_lib_python/structured/json_mode.py +281 -0
  57. ai_lib_python/structured/schema.py +316 -0
  58. ai_lib_python/structured/validator.py +334 -0
  59. ai_lib_python/telemetry/__init__.py +127 -0
  60. ai_lib_python/telemetry/exporters/__init__.py +9 -0
  61. ai_lib_python/telemetry/exporters/prometheus.py +111 -0
  62. ai_lib_python/telemetry/feedback.py +446 -0
  63. ai_lib_python/telemetry/health.py +409 -0
  64. ai_lib_python/telemetry/logger.py +389 -0
  65. ai_lib_python/telemetry/metrics.py +496 -0
  66. ai_lib_python/telemetry/tracer.py +473 -0
  67. ai_lib_python/tokens/__init__.py +25 -0
  68. ai_lib_python/tokens/counter.py +282 -0
  69. ai_lib_python/tokens/estimator.py +286 -0
  70. ai_lib_python/transport/__init__.py +34 -0
  71. ai_lib_python/transport/auth.py +141 -0
  72. ai_lib_python/transport/http.py +364 -0
  73. ai_lib_python/transport/pool.py +425 -0
  74. ai_lib_python/types/__init__.py +41 -0
  75. ai_lib_python/types/events.py +343 -0
  76. ai_lib_python/types/message.py +332 -0
  77. ai_lib_python/types/tool.py +191 -0
  78. ai_lib_python/utils/__init__.py +21 -0
  79. ai_lib_python/utils/tool_call_assembler.py +317 -0
  80. ai_lib_python-0.5.0.dist-info/METADATA +837 -0
  81. ai_lib_python-0.5.0.dist-info/RECORD +84 -0
  82. ai_lib_python-0.5.0.dist-info/WHEEL +4 -0
  83. ai_lib_python-0.5.0.dist-info/licenses/LICENSE-APACHE +201 -0
  84. ai_lib_python-0.5.0.dist-info/licenses/LICENSE-MIT +21 -0
@@ -0,0 +1,593 @@
1
+ """
2
+ Model manager for routing and load balancing.
3
+
4
+ Provides model management, selection, and load balancing capabilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+
12
+ from ai_lib_python.routing.strategy import (
13
+ EndpointSelector,
14
+ LoadBalancingStrategy,
15
+ ModelSelectionStrategy,
16
+ ModelSelector,
17
+ create_endpoint_selector,
18
+ create_model_selector,
19
+ )
20
+ from ai_lib_python.routing.types import (
21
+ HealthCheckConfig,
22
+ ModelCapabilities,
23
+ ModelEndpoint,
24
+ ModelInfo,
25
+ PerformanceMetrics,
26
+ PricingInfo,
27
+ QualityTier,
28
+ SpeedTier,
29
+ )
30
+
31
+
32
+ class ModelManager:
33
+ """Manager for model registration and selection.
34
+
35
+ Provides methods for registering models, selecting models based on
36
+ various strategies, and filtering by capabilities.
37
+
38
+ Example:
39
+ >>> manager = ModelManager(provider="openai")
40
+ >>> manager.add_model(ModelInfo(
41
+ ... name="gpt-4o",
42
+ ... capabilities=ModelCapabilities(multimodal=True),
43
+ ... pricing=PricingInfo(2.5, 10.0),
44
+ ... ))
45
+ >>> model = manager.select_model()
46
+ >>> recommended = manager.recommend_for("multimodal")
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ provider: str = "",
52
+ strategy: ModelSelectionStrategy = ModelSelectionStrategy.ROUND_ROBIN,
53
+ ) -> None:
54
+ """Initialize model manager.
55
+
56
+ Args:
57
+ provider: Provider identifier
58
+ strategy: Model selection strategy
59
+ """
60
+ self.provider = provider
61
+ self._models: dict[str, ModelInfo] = {}
62
+ self._strategy = strategy
63
+ self._selector: ModelSelector = create_model_selector(strategy)
64
+
65
+ def add_model(self, model: ModelInfo) -> ModelManager:
66
+ """Add a model to the manager.
67
+
68
+ Args:
69
+ model: Model information
70
+
71
+ Returns:
72
+ Self for chaining
73
+ """
74
+ if not model.provider and self.provider:
75
+ model.provider = self.provider
76
+ self._models[model.name] = model
77
+ return self
78
+
79
+ def remove_model(self, model_name: str) -> ModelInfo | None:
80
+ """Remove a model from the manager.
81
+
82
+ Args:
83
+ model_name: Model name
84
+
85
+ Returns:
86
+ Removed model or None
87
+ """
88
+ return self._models.pop(model_name, None)
89
+
90
+ def get_model(self, model_name: str) -> ModelInfo | None:
91
+ """Get a model by name.
92
+
93
+ Args:
94
+ model_name: Model name
95
+
96
+ Returns:
97
+ Model info or None
98
+ """
99
+ return self._models.get(model_name)
100
+
101
+ def list_models(self) -> list[ModelInfo]:
102
+ """List all registered models.
103
+
104
+ Returns:
105
+ List of model info
106
+ """
107
+ return list(self._models.values())
108
+
109
+ def with_strategy(self, strategy: ModelSelectionStrategy) -> ModelManager:
110
+ """Set the selection strategy.
111
+
112
+ Args:
113
+ strategy: Selection strategy
114
+
115
+ Returns:
116
+ Self for chaining
117
+ """
118
+ self._strategy = strategy
119
+ self._selector = create_model_selector(strategy)
120
+ return self
121
+
122
+ def select_model(self) -> ModelInfo | None:
123
+ """Select a model using the current strategy.
124
+
125
+ Returns:
126
+ Selected model or None
127
+ """
128
+ models = list(self._models.values())
129
+ return self._selector.select(models)
130
+
131
+ def recommend_for(self, use_case: str) -> ModelInfo | None:
132
+ """Recommend a model for a specific use case.
133
+
134
+ Args:
135
+ use_case: Use case or capability name
136
+
137
+ Returns:
138
+ Recommended model or None
139
+ """
140
+ supported = [m for m in self._models.values() if m.supports(use_case)]
141
+ if not supported:
142
+ return None
143
+ return self._selector.select(supported)
144
+
145
+ def filter_by_capability(self, capability: str) -> list[ModelInfo]:
146
+ """Filter models by capability.
147
+
148
+ Args:
149
+ capability: Capability name
150
+
151
+ Returns:
152
+ List of models with the capability
153
+ """
154
+ return [m for m in self._models.values() if m.supports(capability)]
155
+
156
+ def filter_by_cost(self, max_cost_per_1k: float) -> list[ModelInfo]:
157
+ """Filter models by maximum cost.
158
+
159
+ Args:
160
+ max_cost_per_1k: Maximum cost per 1000 tokens
161
+
162
+ Returns:
163
+ List of affordable models
164
+ """
165
+ return [
166
+ m
167
+ for m in self._models.values()
168
+ if m.pricing and (m.pricing.input_cost_per_1k <= max_cost_per_1k)
169
+ ]
170
+
171
+ def filter_by_context_window(self, min_tokens: int) -> list[ModelInfo]:
172
+ """Filter models by minimum context window.
173
+
174
+ Args:
175
+ min_tokens: Minimum context window size
176
+
177
+ Returns:
178
+ List of models meeting the requirement
179
+ """
180
+ return [
181
+ m
182
+ for m in self._models.values()
183
+ if m.capabilities.context_window
184
+ and m.capabilities.context_window >= min_tokens
185
+ ]
186
+
187
+ def load_from_config(self, config_path: str | Path) -> ModelManager:
188
+ """Load models from a JSON configuration file.
189
+
190
+ Args:
191
+ config_path: Path to configuration file
192
+
193
+ Returns:
194
+ Self for chaining
195
+
196
+ Raises:
197
+ FileNotFoundError: If config file doesn't exist
198
+ json.JSONDecodeError: If config is invalid JSON
199
+ """
200
+ path = Path(config_path)
201
+ content = path.read_text()
202
+ models = json.loads(content)
203
+
204
+ if isinstance(models, list):
205
+ for model_data in models:
206
+ self.add_model(ModelInfo.from_dict(model_data))
207
+ elif isinstance(models, dict) and "models" in models:
208
+ for model_data in models["models"]:
209
+ self.add_model(ModelInfo.from_dict(model_data))
210
+
211
+ return self
212
+
213
+ def save_to_config(self, config_path: str | Path) -> None:
214
+ """Save models to a JSON configuration file.
215
+
216
+ Args:
217
+ config_path: Path to configuration file
218
+ """
219
+ path = Path(config_path)
220
+ models = [m.to_dict() for m in self._models.values()]
221
+ content = json.dumps(models, indent=2)
222
+ path.write_text(content)
223
+
224
+ @property
225
+ def strategy(self) -> ModelSelectionStrategy:
226
+ """Get current selection strategy."""
227
+ return self._strategy
228
+
229
+ def __len__(self) -> int:
230
+ """Get number of registered models."""
231
+ return len(self._models)
232
+
233
+ def __contains__(self, model_name: str) -> bool:
234
+ """Check if model is registered."""
235
+ return model_name in self._models
236
+
237
+
238
+ class ModelArray:
239
+ """Model array for load balancing across multiple endpoints.
240
+
241
+ Supports A/B testing and failover scenarios.
242
+
243
+ Example:
244
+ >>> array = ModelArray("gpt-4-cluster")
245
+ >>> array.add_endpoint(ModelEndpoint("primary", "gpt-4o", "https://api1.example.com"))
246
+ >>> array.add_endpoint(ModelEndpoint("secondary", "gpt-4o", "https://api2.example.com"))
247
+ >>> endpoint = array.select_endpoint()
248
+ """
249
+
250
+ def __init__(
251
+ self,
252
+ name: str,
253
+ strategy: LoadBalancingStrategy = LoadBalancingStrategy.ROUND_ROBIN,
254
+ health_check: HealthCheckConfig | None = None,
255
+ ) -> None:
256
+ """Initialize model array.
257
+
258
+ Args:
259
+ name: Array name/identifier
260
+ strategy: Load balancing strategy
261
+ health_check: Health check configuration
262
+ """
263
+ self.name = name
264
+ self._endpoints: list[ModelEndpoint] = []
265
+ self._strategy = strategy
266
+ self._selector: EndpointSelector = create_endpoint_selector(strategy)
267
+ self.health_check = health_check or HealthCheckConfig()
268
+
269
+ def add_endpoint(self, endpoint: ModelEndpoint) -> ModelArray:
270
+ """Add an endpoint to the array.
271
+
272
+ Args:
273
+ endpoint: Endpoint to add
274
+
275
+ Returns:
276
+ Self for chaining
277
+ """
278
+ self._endpoints.append(endpoint)
279
+ return self
280
+
281
+ def remove_endpoint(self, endpoint_name: str) -> ModelEndpoint | None:
282
+ """Remove an endpoint by name.
283
+
284
+ Args:
285
+ endpoint_name: Endpoint name
286
+
287
+ Returns:
288
+ Removed endpoint or None
289
+ """
290
+ for i, endpoint in enumerate(self._endpoints):
291
+ if endpoint.name == endpoint_name:
292
+ return self._endpoints.pop(i)
293
+ return None
294
+
295
+ def get_endpoint(self, endpoint_name: str) -> ModelEndpoint | None:
296
+ """Get an endpoint by name.
297
+
298
+ Args:
299
+ endpoint_name: Endpoint name
300
+
301
+ Returns:
302
+ Endpoint or None
303
+ """
304
+ for endpoint in self._endpoints:
305
+ if endpoint.name == endpoint_name:
306
+ return endpoint
307
+ return None
308
+
309
+ def with_strategy(self, strategy: LoadBalancingStrategy) -> ModelArray:
310
+ """Set the load balancing strategy.
311
+
312
+ Args:
313
+ strategy: Load balancing strategy
314
+
315
+ Returns:
316
+ Self for chaining
317
+ """
318
+ self._strategy = strategy
319
+ self._selector = create_endpoint_selector(strategy)
320
+ return self
321
+
322
+ def select_endpoint(self) -> ModelEndpoint | None:
323
+ """Select an endpoint using the current strategy.
324
+
325
+ Returns:
326
+ Selected endpoint or None
327
+ """
328
+ return self._selector.select(self._endpoints)
329
+
330
+ def mark_healthy(self, endpoint_name: str) -> bool:
331
+ """Mark an endpoint as healthy.
332
+
333
+ Args:
334
+ endpoint_name: Endpoint name
335
+
336
+ Returns:
337
+ True if endpoint was found
338
+ """
339
+ endpoint = self.get_endpoint(endpoint_name)
340
+ if endpoint:
341
+ endpoint.healthy = True
342
+ return True
343
+ return False
344
+
345
+ def mark_unhealthy(self, endpoint_name: str) -> bool:
346
+ """Mark an endpoint as unhealthy.
347
+
348
+ Args:
349
+ endpoint_name: Endpoint name
350
+
351
+ Returns:
352
+ True if endpoint was found
353
+ """
354
+ endpoint = self.get_endpoint(endpoint_name)
355
+ if endpoint:
356
+ endpoint.healthy = False
357
+ return True
358
+ return False
359
+
360
+ def is_healthy(self) -> bool:
361
+ """Check if any endpoint is healthy.
362
+
363
+ Returns:
364
+ True if at least one endpoint is healthy
365
+ """
366
+ return any(e.healthy for e in self._endpoints)
367
+
368
+ def healthy_endpoints(self) -> list[ModelEndpoint]:
369
+ """Get all healthy endpoints.
370
+
371
+ Returns:
372
+ List of healthy endpoints
373
+ """
374
+ return [e for e in self._endpoints if e.healthy]
375
+
376
+ @property
377
+ def endpoints(self) -> list[ModelEndpoint]:
378
+ """Get all endpoints."""
379
+ return list(self._endpoints)
380
+
381
+ @property
382
+ def strategy(self) -> LoadBalancingStrategy:
383
+ """Get current strategy."""
384
+ return self._strategy
385
+
386
+ def __len__(self) -> int:
387
+ """Get number of endpoints."""
388
+ return len(self._endpoints)
389
+
390
+
391
+ # Pre-configured model catalogs
392
+
393
+
394
+ def create_openai_models() -> ModelManager:
395
+ """Create a pre-configured OpenAI model manager.
396
+
397
+ Returns:
398
+ ModelManager with OpenAI models
399
+ """
400
+ manager = ModelManager(provider="openai")
401
+
402
+ # GPT-4o
403
+ manager.add_model(
404
+ ModelInfo(
405
+ name="gpt-4o",
406
+ display_name="GPT-4o",
407
+ description="Most capable GPT-4 model for complex tasks",
408
+ capabilities=ModelCapabilities(
409
+ chat=True,
410
+ code_generation=True,
411
+ multimodal=True,
412
+ function_calling=True,
413
+ context_window=128000,
414
+ ),
415
+ pricing=PricingInfo(2.5, 10.0),
416
+ performance=PerformanceMetrics(
417
+ speed=SpeedTier.BALANCED,
418
+ quality=QualityTier.EXCELLENT,
419
+ ),
420
+ )
421
+ )
422
+
423
+ # GPT-4o-mini
424
+ manager.add_model(
425
+ ModelInfo(
426
+ name="gpt-4o-mini",
427
+ display_name="GPT-4o Mini",
428
+ description="Smaller, faster GPT-4o variant",
429
+ capabilities=ModelCapabilities(
430
+ chat=True,
431
+ code_generation=True,
432
+ multimodal=True,
433
+ function_calling=True,
434
+ context_window=128000,
435
+ ),
436
+ pricing=PricingInfo(0.15, 0.60),
437
+ performance=PerformanceMetrics(
438
+ speed=SpeedTier.FAST,
439
+ quality=QualityTier.GOOD,
440
+ ),
441
+ )
442
+ )
443
+
444
+ # GPT-4-turbo
445
+ manager.add_model(
446
+ ModelInfo(
447
+ name="gpt-4-turbo",
448
+ display_name="GPT-4 Turbo",
449
+ description="GPT-4 Turbo with vision capabilities",
450
+ capabilities=ModelCapabilities(
451
+ chat=True,
452
+ code_generation=True,
453
+ multimodal=True,
454
+ function_calling=True,
455
+ context_window=128000,
456
+ ),
457
+ pricing=PricingInfo(10.0, 30.0),
458
+ performance=PerformanceMetrics(
459
+ speed=SpeedTier.BALANCED,
460
+ quality=QualityTier.EXCELLENT,
461
+ ),
462
+ )
463
+ )
464
+
465
+ # o1-preview
466
+ manager.add_model(
467
+ ModelInfo(
468
+ name="o1-preview",
469
+ display_name="o1 Preview",
470
+ description="Reasoning model for complex problems",
471
+ capabilities=ModelCapabilities(
472
+ chat=True,
473
+ code_generation=True,
474
+ multimodal=False,
475
+ function_calling=False,
476
+ context_window=128000,
477
+ ),
478
+ pricing=PricingInfo(15.0, 60.0),
479
+ performance=PerformanceMetrics(
480
+ speed=SpeedTier.SLOW,
481
+ quality=QualityTier.EXCELLENT,
482
+ ),
483
+ )
484
+ )
485
+
486
+ return manager
487
+
488
+
489
+ def create_anthropic_models() -> ModelManager:
490
+ """Create a pre-configured Anthropic model manager.
491
+
492
+ Returns:
493
+ ModelManager with Anthropic models
494
+ """
495
+ manager = ModelManager(provider="anthropic")
496
+
497
+ # Claude 3.5 Sonnet
498
+ manager.add_model(
499
+ ModelInfo(
500
+ name="claude-3-5-sonnet-20241022",
501
+ display_name="Claude 3.5 Sonnet",
502
+ description="Most intelligent Claude model",
503
+ capabilities=ModelCapabilities(
504
+ chat=True,
505
+ code_generation=True,
506
+ multimodal=True,
507
+ tool_use=True,
508
+ context_window=200000,
509
+ ),
510
+ pricing=PricingInfo(3.0, 15.0),
511
+ performance=PerformanceMetrics(
512
+ speed=SpeedTier.BALANCED,
513
+ quality=QualityTier.EXCELLENT,
514
+ ),
515
+ )
516
+ )
517
+
518
+ # Claude 3.5 Haiku
519
+ manager.add_model(
520
+ ModelInfo(
521
+ name="claude-3-5-haiku-20241022",
522
+ display_name="Claude 3.5 Haiku",
523
+ description="Fast and efficient Claude model",
524
+ capabilities=ModelCapabilities(
525
+ chat=True,
526
+ code_generation=True,
527
+ multimodal=True,
528
+ tool_use=True,
529
+ context_window=200000,
530
+ ),
531
+ pricing=PricingInfo(0.80, 4.0),
532
+ performance=PerformanceMetrics(
533
+ speed=SpeedTier.FAST,
534
+ quality=QualityTier.GOOD,
535
+ ),
536
+ )
537
+ )
538
+
539
+ # Claude 3 Opus
540
+ manager.add_model(
541
+ ModelInfo(
542
+ name="claude-3-opus-20240229",
543
+ display_name="Claude 3 Opus",
544
+ description="Most powerful Claude 3 model",
545
+ capabilities=ModelCapabilities(
546
+ chat=True,
547
+ code_generation=True,
548
+ multimodal=True,
549
+ tool_use=True,
550
+ context_window=200000,
551
+ ),
552
+ pricing=PricingInfo(15.0, 75.0),
553
+ performance=PerformanceMetrics(
554
+ speed=SpeedTier.SLOW,
555
+ quality=QualityTier.EXCELLENT,
556
+ ),
557
+ )
558
+ )
559
+
560
+ return manager
561
+
562
+
563
+ # Global model registry
564
+ _global_managers: dict[str, ModelManager] = {}
565
+
566
+
567
+ def get_model_manager(provider: str) -> ModelManager:
568
+ """Get or create a model manager for a provider.
569
+
570
+ Args:
571
+ provider: Provider identifier
572
+
573
+ Returns:
574
+ ModelManager instance
575
+ """
576
+ if provider not in _global_managers:
577
+ if provider == "openai":
578
+ _global_managers[provider] = create_openai_models()
579
+ elif provider == "anthropic":
580
+ _global_managers[provider] = create_anthropic_models()
581
+ else:
582
+ _global_managers[provider] = ModelManager(provider=provider)
583
+ return _global_managers[provider]
584
+
585
+
586
+ def register_model_manager(provider: str, manager: ModelManager) -> None:
587
+ """Register a custom model manager.
588
+
589
+ Args:
590
+ provider: Provider identifier
591
+ manager: ModelManager instance
592
+ """
593
+ _global_managers[provider] = manager