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,460 @@
1
+ """
2
+ Protocol validator using JSON Schema.
3
+
4
+ Validates manifests against the AI-Protocol JSON Schema specification.
5
+ Also provides protocol version validation and strict streaming validation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from ai_lib_python.errors import ProtocolError
17
+
18
+ if TYPE_CHECKING:
19
+ from ai_lib_python.protocol.manifest import ProtocolManifest
20
+
21
+ # Try to import fastjsonschema, fall back to jsonschema
22
+ try:
23
+ import fastjsonschema
24
+
25
+ _FAST_SCHEMA = True
26
+ except ImportError:
27
+ _FAST_SCHEMA = False
28
+
29
+ try:
30
+ import jsonschema
31
+
32
+ _JSON_SCHEMA = True
33
+ except ImportError:
34
+ _JSON_SCHEMA = False
35
+
36
+
37
+ # Supported protocol versions
38
+ SUPPORTED_PROTOCOL_VERSIONS = ["1.0", "1.1", "1.5", "2.0"]
39
+
40
+
41
+ @dataclass
42
+ class ValidationResult:
43
+ """Result of schema validation."""
44
+
45
+ valid: bool = True
46
+ errors: list[str] = field(default_factory=list)
47
+ warnings: list[str] = field(default_factory=list)
48
+
49
+ def __bool__(self) -> bool:
50
+ return self.valid
51
+
52
+ def add_error(self, error: str) -> None:
53
+ """Add an error."""
54
+ self.errors.append(error)
55
+ self.valid = False
56
+
57
+ def add_warning(self, warning: str) -> None:
58
+ """Add a warning."""
59
+ self.warnings.append(warning)
60
+
61
+
62
+ class ProtocolValidator:
63
+ """Validates protocol manifests against JSON Schema.
64
+
65
+ Uses fastjsonschema for performance if available, falls back to jsonschema.
66
+
67
+ Example:
68
+ >>> validator = ProtocolValidator()
69
+ >>> result = validator.validate(manifest_data)
70
+ >>> if not result:
71
+ ... print("Validation errors:", result.errors)
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ schema_path: str | Path | None = None,
77
+ offline: bool = False,
78
+ ) -> None:
79
+ """Initialize the validator.
80
+
81
+ Args:
82
+ schema_path: Path to custom schema file
83
+ offline: Use embedded schema only (no network)
84
+ """
85
+ self._schema_path = Path(schema_path) if schema_path else None
86
+ self._offline = offline
87
+ self._schema: dict[str, Any] | None = None
88
+ self._compiled_validator: Any = None
89
+
90
+ def _load_schema(self) -> dict[str, Any]:
91
+ """Load the JSON Schema."""
92
+ if self._schema is not None:
93
+ return self._schema
94
+
95
+ # 1. Try explicit schema path
96
+ if self._schema_path and self._schema_path.exists():
97
+ self._schema = json.loads(self._schema_path.read_text())
98
+ return self._schema
99
+
100
+ # 2. Try embedded schema
101
+ embedded_path = Path(__file__).parent / "embedded" / "schema_v1.json"
102
+ if embedded_path.exists():
103
+ self._schema = json.loads(embedded_path.read_text())
104
+ return self._schema
105
+
106
+ # 3. Try to load from protocol directory
107
+ if not self._offline:
108
+ from ai_lib_python.protocol.loader import ProtocolLoader
109
+
110
+ loader = ProtocolLoader()
111
+ base = loader.base_path
112
+ if base:
113
+ schema_path = base / "schemas" / "v1.json"
114
+ if schema_path.exists():
115
+ self._schema = json.loads(schema_path.read_text())
116
+ return self._schema
117
+
118
+ # 4. Return minimal schema as fallback
119
+ self._schema = self._get_minimal_schema()
120
+ return self._schema
121
+
122
+ def _get_minimal_schema(self) -> dict[str, Any]:
123
+ """Get minimal validation schema as fallback."""
124
+ return {
125
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
126
+ "type": "object",
127
+ "required": ["id", "endpoint"],
128
+ "properties": {
129
+ "id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-_]{1,63}$"},
130
+ "protocol_version": {"type": "string"},
131
+ "endpoint": {
132
+ "type": "object",
133
+ "required": ["base_url"],
134
+ "properties": {
135
+ "base_url": {"type": "string", "format": "uri"},
136
+ "protocol": {"type": "string"},
137
+ "timeout_ms": {"type": "integer", "minimum": 100},
138
+ },
139
+ },
140
+ },
141
+ }
142
+
143
+ def _compile_validator(self) -> None:
144
+ """Compile the validator for performance."""
145
+ schema = self._load_schema()
146
+
147
+ if _FAST_SCHEMA:
148
+ try:
149
+ self._compiled_validator = fastjsonschema.compile(schema)
150
+ return
151
+ except Exception:
152
+ pass
153
+
154
+ # Fallback to jsonschema (slower but more compatible)
155
+ if _JSON_SCHEMA:
156
+ self._compiled_validator = jsonschema.Draft202012Validator(schema)
157
+
158
+ def validate(self, data: dict[str, Any]) -> ValidationResult:
159
+ """Validate manifest data against schema.
160
+
161
+ Args:
162
+ data: Manifest data to validate
163
+
164
+ Returns:
165
+ ValidationResult with valid flag and any errors
166
+ """
167
+ if self._compiled_validator is None:
168
+ self._compile_validator()
169
+
170
+ errors: list[str] = []
171
+
172
+ if _FAST_SCHEMA and callable(self._compiled_validator):
173
+ try:
174
+ self._compiled_validator(data)
175
+ except fastjsonschema.JsonSchemaValueException as e:
176
+ errors.append(str(e.message))
177
+ except Exception as e:
178
+ errors.append(f"Validation error: {e}")
179
+
180
+ elif _JSON_SCHEMA and self._compiled_validator is not None:
181
+ for error in self._compiled_validator.iter_errors(data):
182
+ path = ".".join(str(p) for p in error.absolute_path)
183
+ if path:
184
+ errors.append(f"{path}: {error.message}")
185
+ else:
186
+ errors.append(error.message)
187
+
188
+ else:
189
+ # No validator available, do minimal checks
190
+ if not isinstance(data, dict):
191
+ errors.append("Manifest must be an object")
192
+ elif "id" not in data:
193
+ errors.append("Missing required field: id")
194
+ elif "endpoint" not in data:
195
+ errors.append("Missing required field: endpoint")
196
+
197
+ return ValidationResult(valid=len(errors) == 0, errors=errors)
198
+
199
+ def validate_or_raise(self, data: dict[str, Any]) -> None:
200
+ """Validate manifest data and raise on errors.
201
+
202
+ Args:
203
+ data: Manifest data to validate
204
+
205
+ Raises:
206
+ ProtocolError: If validation fails
207
+ """
208
+ result = self.validate(data)
209
+ if not result:
210
+ raise ProtocolError(
211
+ f"Schema validation failed: {'; '.join(result.errors)}",
212
+ protocol_path=data.get("id", "unknown"),
213
+ )
214
+
215
+ def is_valid(self, data: dict[str, Any]) -> bool:
216
+ """Check if manifest data is valid.
217
+
218
+ Args:
219
+ data: Manifest data to validate
220
+
221
+ Returns:
222
+ True if valid, False otherwise
223
+ """
224
+ return self.validate(data).valid
225
+
226
+
227
+ def validate_protocol_version(
228
+ manifest: ProtocolManifest | dict[str, Any],
229
+ supported_versions: list[str] | None = None,
230
+ ) -> ValidationResult:
231
+ """Validate protocol version compatibility.
232
+
233
+ Ensures the runtime can handle the protocol version specified.
234
+
235
+ Args:
236
+ manifest: Protocol manifest or data dict
237
+ supported_versions: List of supported versions (uses default if None)
238
+
239
+ Returns:
240
+ ValidationResult with compatibility status
241
+ """
242
+ result = ValidationResult()
243
+ versions = supported_versions or SUPPORTED_PROTOCOL_VERSIONS
244
+
245
+ # Extract version from manifest
246
+ if isinstance(manifest, dict):
247
+ version = manifest.get("protocol_version", "1.0")
248
+ else:
249
+ version = getattr(manifest, "protocol_version", "1.0")
250
+
251
+ if not version:
252
+ version = "1.0"
253
+
254
+ # Check if version is supported
255
+ if version not in versions:
256
+ result.add_error(
257
+ f"Unsupported protocol version: {version}. "
258
+ f"Runtime supports: {versions}"
259
+ )
260
+ else:
261
+ # Check for version-specific warnings
262
+ if version == "1.0":
263
+ result.add_warning(
264
+ "Protocol version 1.0 is deprecated. Consider upgrading to 1.5+"
265
+ )
266
+
267
+ return result
268
+
269
+
270
+ def validate_streaming_config(
271
+ manifest: ProtocolManifest | dict[str, Any],
272
+ strict: bool = False,
273
+ ) -> ValidationResult:
274
+ """Validate streaming configuration completeness.
275
+
276
+ When strict mode is enabled, performs fail-fast checks for streaming
277
+ config to avoid ambiguous runtime behavior.
278
+
279
+ Args:
280
+ manifest: Protocol manifest or data dict
281
+ strict: Enable strict validation mode
282
+
283
+ Returns:
284
+ ValidationResult with validation status
285
+ """
286
+ result = ValidationResult()
287
+
288
+ # Extract streaming config
289
+ if isinstance(manifest, dict):
290
+ streaming = manifest.get("streaming")
291
+ capabilities = manifest.get("capabilities", {})
292
+ tooling = manifest.get("tooling")
293
+ else:
294
+ streaming = getattr(manifest, "streaming", None)
295
+ capabilities = getattr(manifest, "capabilities", None)
296
+ tooling = getattr(manifest, "tooling", None)
297
+
298
+ # Check if streaming is claimed as a capability
299
+ supports_streaming = False
300
+ if isinstance(capabilities, dict):
301
+ supports_streaming = capabilities.get("streaming", False)
302
+ elif capabilities is not None:
303
+ supports_streaming = getattr(capabilities, "streaming", False)
304
+
305
+ if not supports_streaming and not streaming:
306
+ # No streaming configured, that's fine
307
+ return result
308
+
309
+ if supports_streaming and not streaming:
310
+ if strict:
311
+ result.add_error(
312
+ "strict_streaming: manifest.streaming is required when "
313
+ "capabilities.streaming is true"
314
+ )
315
+ else:
316
+ result.add_warning(
317
+ "capabilities.streaming is true but streaming config is missing"
318
+ )
319
+ return result
320
+
321
+ if streaming is None:
322
+ return result
323
+
324
+ # Validate streaming configuration
325
+ if isinstance(streaming, dict):
326
+ decoder = streaming.get("decoder")
327
+ event_map = streaming.get("event_map", [])
328
+ content_path = streaming.get("content_path")
329
+ tool_call_path = streaming.get("tool_call_path")
330
+ else:
331
+ decoder = getattr(streaming, "decoder", None)
332
+ event_map = getattr(streaming, "event_map", []) or []
333
+ content_path = getattr(streaming, "content_path", None)
334
+ tool_call_path = getattr(streaming, "tool_call_path", None)
335
+
336
+ # Check decoder
337
+ if strict:
338
+ if not decoder:
339
+ result.add_error(
340
+ "strict_streaming: streaming.decoder is required for streaming"
341
+ )
342
+ else:
343
+ # Check decoder format
344
+ if isinstance(decoder, dict):
345
+ format_val = decoder.get("format", "")
346
+ else:
347
+ format_val = getattr(decoder, "format", "")
348
+
349
+ if not format_val or not str(format_val).strip():
350
+ result.add_error(
351
+ "strict_streaming: streaming.decoder.format must be non-empty"
352
+ )
353
+
354
+ # Check event mapping paths when no explicit event_map
355
+ if not event_map:
356
+ if strict:
357
+ if not content_path or not str(content_path).strip():
358
+ result.add_error(
359
+ "strict_streaming: streaming.content_path is required "
360
+ "when streaming.event_map is empty"
361
+ )
362
+
363
+ # Check tool_call_path if tools are supported
364
+ supports_tools = False
365
+ if isinstance(capabilities, dict):
366
+ supports_tools = capabilities.get("tools", False)
367
+ elif capabilities is not None:
368
+ supports_tools = getattr(capabilities, "tools", False)
369
+
370
+ if tooling is not None:
371
+ supports_tools = True
372
+
373
+ if supports_tools and (
374
+ not tool_call_path or not str(tool_call_path).strip()
375
+ ):
376
+ result.add_error(
377
+ "strict_streaming: streaming.tool_call_path is required "
378
+ "for tools when streaming.event_map is empty"
379
+ )
380
+ else:
381
+ if not content_path:
382
+ result.add_warning(
383
+ "streaming.content_path is recommended when event_map is empty"
384
+ )
385
+
386
+ return result
387
+
388
+
389
+ def validate_manifest(
390
+ manifest: ProtocolManifest | dict[str, Any],
391
+ strict_streaming: bool | None = None,
392
+ check_version: bool = True,
393
+ ) -> ValidationResult:
394
+ """Comprehensive manifest validation.
395
+
396
+ Performs all validation checks including schema, version compatibility,
397
+ and streaming configuration.
398
+
399
+ Args:
400
+ manifest: Protocol manifest to validate
401
+ strict_streaming: Enable strict streaming validation
402
+ (uses AI_LIB_STRICT_STREAMING env var if None)
403
+ check_version: Whether to check protocol version compatibility
404
+
405
+ Returns:
406
+ Combined ValidationResult
407
+ """
408
+ result = ValidationResult()
409
+
410
+ # Determine strict_streaming setting
411
+ if strict_streaming is None:
412
+ strict_streaming = os.environ.get("AI_LIB_STRICT_STREAMING", "").lower() in (
413
+ "1",
414
+ "true",
415
+ "yes",
416
+ )
417
+
418
+ # 1. Validate protocol version
419
+ if check_version:
420
+ version_result = validate_protocol_version(manifest)
421
+ result.errors.extend(version_result.errors)
422
+ result.warnings.extend(version_result.warnings)
423
+ if not version_result.valid:
424
+ result.valid = False
425
+
426
+ # 2. Validate streaming configuration
427
+ streaming_result = validate_streaming_config(manifest, strict=strict_streaming)
428
+ result.errors.extend(streaming_result.errors)
429
+ result.warnings.extend(streaming_result.warnings)
430
+ if not streaming_result.valid:
431
+ result.valid = False
432
+
433
+ return result
434
+
435
+
436
+ def validate_manifest_or_raise(
437
+ manifest: ProtocolManifest | dict[str, Any],
438
+ strict_streaming: bool | None = None,
439
+ ) -> None:
440
+ """Validate manifest and raise on errors.
441
+
442
+ Args:
443
+ manifest: Protocol manifest to validate
444
+ strict_streaming: Enable strict streaming validation
445
+
446
+ Raises:
447
+ ProtocolError: If validation fails
448
+ """
449
+ result = validate_manifest(manifest, strict_streaming=strict_streaming)
450
+ if not result.valid:
451
+ # Get manifest ID for error context
452
+ if isinstance(manifest, dict):
453
+ manifest_id = manifest.get("id", "unknown")
454
+ else:
455
+ manifest_id = getattr(manifest, "id", "unknown")
456
+
457
+ raise ProtocolError(
458
+ f"Manifest validation failed: {'; '.join(result.errors)}",
459
+ protocol_path=str(manifest_id),
460
+ )
ai_lib_python/py.typed ADDED
@@ -0,0 +1 @@
1
+ # PEP 561 marker file - this package supports type checking
@@ -0,0 +1,102 @@
1
+ """
2
+ Resilience layer - Retry, rate limiting, circuit breaker, and backpressure.
3
+
4
+ This module provides production-ready resilience patterns:
5
+ - RetryPolicy: Exponential backoff with jitter
6
+ - RateLimiter: Token bucket algorithm with adaptive mode
7
+ - CircuitBreaker: Closed/Open/Half-Open state machine
8
+ - Backpressure: Semaphore-based concurrency control
9
+ - FallbackChain: Multi-model degradation
10
+ - ResilientExecutor: Unified executor combining all patterns
11
+ - SignalsSnapshot: Unified runtime state aggregation
12
+ - PreflightChecker: Unified request gating
13
+ """
14
+
15
+ from ai_lib_python.resilience.backpressure import Backpressure, BackpressureConfig
16
+ from ai_lib_python.resilience.circuit_breaker import (
17
+ CircuitBreaker,
18
+ CircuitBreakerConfig,
19
+ CircuitOpenError,
20
+ CircuitState,
21
+ CircuitStats,
22
+ )
23
+ from ai_lib_python.resilience.executor import (
24
+ ExecutionStats,
25
+ ResilientConfig,
26
+ ResilientExecutor,
27
+ )
28
+ from ai_lib_python.resilience.fallback import (
29
+ FallbackChain,
30
+ FallbackConfig,
31
+ FallbackResult,
32
+ FallbackTarget,
33
+ MultiFallback,
34
+ )
35
+ from ai_lib_python.resilience.preflight import (
36
+ PreflightChecker,
37
+ PreflightConfig,
38
+ PreflightContext,
39
+ PreflightError,
40
+ PreflightResult,
41
+ )
42
+ from ai_lib_python.resilience.rate_limiter import (
43
+ AdaptiveRateLimiter,
44
+ RateLimiter,
45
+ RateLimiterConfig,
46
+ )
47
+ from ai_lib_python.resilience.retry import (
48
+ JitterStrategy,
49
+ RetryConfig,
50
+ RetryPolicy,
51
+ RetryResult,
52
+ with_retry,
53
+ )
54
+ from ai_lib_python.resilience.signals import (
55
+ CircuitBreakerSnapshot,
56
+ InflightSnapshot,
57
+ RateLimiterSnapshot,
58
+ SignalsSnapshot,
59
+ )
60
+
61
+ __all__ = [
62
+ # Rate limiting
63
+ "AdaptiveRateLimiter",
64
+ # Backpressure
65
+ "Backpressure",
66
+ "BackpressureConfig",
67
+ # Circuit breaker
68
+ "CircuitBreaker",
69
+ "CircuitBreakerConfig",
70
+ "CircuitBreakerSnapshot",
71
+ "CircuitOpenError",
72
+ "CircuitState",
73
+ "CircuitStats",
74
+ # Executor
75
+ "ExecutionStats",
76
+ # Fallback
77
+ "FallbackChain",
78
+ "FallbackConfig",
79
+ "FallbackResult",
80
+ "FallbackTarget",
81
+ # Signals
82
+ "InflightSnapshot",
83
+ # Retry
84
+ "JitterStrategy",
85
+ "MultiFallback",
86
+ # Preflight
87
+ "PreflightChecker",
88
+ "PreflightConfig",
89
+ "PreflightContext",
90
+ "PreflightError",
91
+ "PreflightResult",
92
+ "RateLimiter",
93
+ "RateLimiterConfig",
94
+ "RateLimiterSnapshot",
95
+ "ResilientConfig",
96
+ "ResilientExecutor",
97
+ "RetryConfig",
98
+ "RetryPolicy",
99
+ "RetryResult",
100
+ "SignalsSnapshot",
101
+ "with_retry",
102
+ ]