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.
- ai_lib_python/__init__.py +43 -0
- ai_lib_python/batch/__init__.py +15 -0
- ai_lib_python/batch/collector.py +244 -0
- ai_lib_python/batch/executor.py +224 -0
- ai_lib_python/cache/__init__.py +26 -0
- ai_lib_python/cache/backends.py +380 -0
- ai_lib_python/cache/key.py +237 -0
- ai_lib_python/cache/manager.py +332 -0
- ai_lib_python/client/__init__.py +37 -0
- ai_lib_python/client/builder.py +528 -0
- ai_lib_python/client/cancel.py +368 -0
- ai_lib_python/client/core.py +433 -0
- ai_lib_python/client/response.py +134 -0
- ai_lib_python/embeddings/__init__.py +36 -0
- ai_lib_python/embeddings/client.py +339 -0
- ai_lib_python/embeddings/types.py +234 -0
- ai_lib_python/embeddings/vectors.py +246 -0
- ai_lib_python/errors/__init__.py +41 -0
- ai_lib_python/errors/base.py +316 -0
- ai_lib_python/errors/classification.py +210 -0
- ai_lib_python/guardrails/__init__.py +35 -0
- ai_lib_python/guardrails/base.py +336 -0
- ai_lib_python/guardrails/filters.py +583 -0
- ai_lib_python/guardrails/validators.py +475 -0
- ai_lib_python/pipeline/__init__.py +55 -0
- ai_lib_python/pipeline/accumulate.py +248 -0
- ai_lib_python/pipeline/base.py +240 -0
- ai_lib_python/pipeline/decode.py +281 -0
- ai_lib_python/pipeline/event_map.py +506 -0
- ai_lib_python/pipeline/fan_out.py +284 -0
- ai_lib_python/pipeline/select.py +297 -0
- ai_lib_python/plugins/__init__.py +32 -0
- ai_lib_python/plugins/base.py +294 -0
- ai_lib_python/plugins/hooks.py +296 -0
- ai_lib_python/plugins/middleware.py +285 -0
- ai_lib_python/plugins/registry.py +294 -0
- ai_lib_python/protocol/__init__.py +71 -0
- ai_lib_python/protocol/loader.py +317 -0
- ai_lib_python/protocol/manifest.py +385 -0
- ai_lib_python/protocol/validator.py +460 -0
- ai_lib_python/py.typed +1 -0
- ai_lib_python/resilience/__init__.py +102 -0
- ai_lib_python/resilience/backpressure.py +225 -0
- ai_lib_python/resilience/circuit_breaker.py +318 -0
- ai_lib_python/resilience/executor.py +343 -0
- ai_lib_python/resilience/fallback.py +341 -0
- ai_lib_python/resilience/preflight.py +413 -0
- ai_lib_python/resilience/rate_limiter.py +291 -0
- ai_lib_python/resilience/retry.py +299 -0
- ai_lib_python/resilience/signals.py +283 -0
- ai_lib_python/routing/__init__.py +118 -0
- ai_lib_python/routing/manager.py +593 -0
- ai_lib_python/routing/strategy.py +345 -0
- ai_lib_python/routing/types.py +397 -0
- ai_lib_python/structured/__init__.py +33 -0
- ai_lib_python/structured/json_mode.py +281 -0
- ai_lib_python/structured/schema.py +316 -0
- ai_lib_python/structured/validator.py +334 -0
- ai_lib_python/telemetry/__init__.py +127 -0
- ai_lib_python/telemetry/exporters/__init__.py +9 -0
- ai_lib_python/telemetry/exporters/prometheus.py +111 -0
- ai_lib_python/telemetry/feedback.py +446 -0
- ai_lib_python/telemetry/health.py +409 -0
- ai_lib_python/telemetry/logger.py +389 -0
- ai_lib_python/telemetry/metrics.py +496 -0
- ai_lib_python/telemetry/tracer.py +473 -0
- ai_lib_python/tokens/__init__.py +25 -0
- ai_lib_python/tokens/counter.py +282 -0
- ai_lib_python/tokens/estimator.py +286 -0
- ai_lib_python/transport/__init__.py +34 -0
- ai_lib_python/transport/auth.py +141 -0
- ai_lib_python/transport/http.py +364 -0
- ai_lib_python/transport/pool.py +425 -0
- ai_lib_python/types/__init__.py +41 -0
- ai_lib_python/types/events.py +343 -0
- ai_lib_python/types/message.py +332 -0
- ai_lib_python/types/tool.py +191 -0
- ai_lib_python/utils/__init__.py +21 -0
- ai_lib_python/utils/tool_call_assembler.py +317 -0
- ai_lib_python-0.5.0.dist-info/METADATA +837 -0
- ai_lib_python-0.5.0.dist-info/RECORD +84 -0
- ai_lib_python-0.5.0.dist-info/WHEEL +4 -0
- ai_lib_python-0.5.0.dist-info/licenses/LICENSE-APACHE +201 -0
- 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
|
+
]
|