proxilion 0.0.1__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 (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,55 @@
1
+ """
2
+ Timeout and deadline management for AI agent operations.
3
+
4
+ Provides configurable timeout handling for tool calls, LLM operations,
5
+ and overall request budgets. Essential for WebSocket/real-time applications
6
+ where responsiveness is critical.
7
+
8
+ Example:
9
+ >>> from proxilion.timeouts import (
10
+ ... TimeoutManager, TimeoutConfig, DeadlineContext,
11
+ ... with_timeout, with_deadline,
12
+ ... )
13
+ >>>
14
+ >>> # Configure timeouts
15
+ >>> config = TimeoutConfig(
16
+ ... default_timeout=30.0,
17
+ ... tool_timeouts={"web_search": 60.0, "database_query": 10.0},
18
+ ... llm_timeout=120.0,
19
+ ... )
20
+ >>> manager = TimeoutManager(config)
21
+ >>>
22
+ >>> # Use deadline context for request budget
23
+ >>> async with DeadlineContext(timeout=60.0) as deadline:
24
+ ... result1 = await tool1(timeout=deadline.remaining())
25
+ ... result2 = await tool2(timeout=deadline.remaining())
26
+ >>>
27
+ >>> # Use decorator for individual functions
28
+ >>> @with_timeout(10.0)
29
+ ... async def quick_operation():
30
+ ... return await some_api_call()
31
+ """
32
+
33
+ from proxilion.timeouts.decorators import (
34
+ with_deadline,
35
+ with_timeout,
36
+ )
37
+ from proxilion.timeouts.manager import (
38
+ DeadlineContext,
39
+ TimeoutConfig,
40
+ TimeoutManager,
41
+ )
42
+ from proxilion.timeouts.manager import (
43
+ TimeoutError as ProxilionTimeoutError,
44
+ )
45
+
46
+ __all__ = [
47
+ # Manager classes
48
+ "DeadlineContext",
49
+ "TimeoutConfig",
50
+ "TimeoutManager",
51
+ "ProxilionTimeoutError",
52
+ # Decorators
53
+ "with_deadline",
54
+ "with_timeout",
55
+ ]
@@ -0,0 +1,477 @@
1
+ """
2
+ Timeout decorators for AI agent operations.
3
+
4
+ Provides decorators for applying timeouts to sync and async functions,
5
+ with support for both explicit timeouts and deadline contexts.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import concurrent.futures
12
+ import functools
13
+ import inspect
14
+ from collections.abc import Callable, Coroutine
15
+ from typing import Any, ParamSpec, TypeVar
16
+
17
+ from proxilion.timeouts.manager import (
18
+ DeadlineContext,
19
+ TimeoutError,
20
+ TimeoutManager,
21
+ get_current_deadline,
22
+ get_default_manager,
23
+ )
24
+
25
+ P = ParamSpec("P")
26
+ T = TypeVar("T")
27
+
28
+
29
+ def with_timeout(
30
+ timeout: float | None = None,
31
+ timeout_manager: TimeoutManager | None = None,
32
+ operation_name: str | None = None,
33
+ use_deadline: bool = True,
34
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
35
+ """
36
+ Decorator that applies a timeout to a sync or async function.
37
+
38
+ For async functions, uses asyncio.wait_for.
39
+ For sync functions, uses ThreadPoolExecutor.
40
+
41
+ Args:
42
+ timeout: Explicit timeout in seconds. If None, uses manager's default.
43
+ timeout_manager: TimeoutManager to use for configuration.
44
+ operation_name: Name of operation (defaults to function name).
45
+ use_deadline: If True, respects active deadline context.
46
+
47
+ Returns:
48
+ Decorator function.
49
+
50
+ Example:
51
+ >>> @with_timeout(10.0)
52
+ ... async def slow_api_call():
53
+ ... await asyncio.sleep(20) # Will raise TimeoutError
54
+
55
+ >>> @with_timeout(5.0)
56
+ ... def sync_operation():
57
+ ... time.sleep(10) # Will raise TimeoutError
58
+ """
59
+ def decorator(func: Callable[P, T]) -> Callable[P, T]:
60
+ nonlocal operation_name
61
+ if operation_name is None:
62
+ operation_name = func.__name__
63
+
64
+ manager = timeout_manager or get_default_manager()
65
+
66
+ if inspect.iscoroutinefunction(func):
67
+ @functools.wraps(func)
68
+ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
69
+ effective_timeout = _get_effective_timeout(
70
+ timeout, manager, operation_name, use_deadline
71
+ )
72
+
73
+ if effective_timeout <= 0:
74
+ raise TimeoutError(
75
+ message="Deadline already exceeded",
76
+ operation=operation_name,
77
+ timeout=effective_timeout,
78
+ )
79
+
80
+ try:
81
+ return await asyncio.wait_for(
82
+ func(*args, **kwargs),
83
+ timeout=effective_timeout,
84
+ )
85
+ except asyncio.TimeoutError as e:
86
+ raise TimeoutError(
87
+ message="Operation timed out",
88
+ operation=operation_name,
89
+ timeout=effective_timeout,
90
+ ) from e
91
+
92
+ return async_wrapper # type: ignore
93
+ else:
94
+ @functools.wraps(func)
95
+ def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
96
+ effective_timeout = _get_effective_timeout(
97
+ timeout, manager, operation_name, use_deadline
98
+ )
99
+
100
+ if effective_timeout <= 0:
101
+ raise TimeoutError(
102
+ message="Deadline already exceeded",
103
+ operation=operation_name,
104
+ timeout=effective_timeout,
105
+ )
106
+
107
+ return _run_with_timeout_sync(
108
+ func, args, kwargs, effective_timeout, operation_name
109
+ )
110
+
111
+ return sync_wrapper # type: ignore
112
+
113
+ return decorator
114
+
115
+
116
+ def with_deadline(
117
+ timeout: float,
118
+ operation_name: str | None = None,
119
+ raise_on_expire: bool = True,
120
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
121
+ """
122
+ Decorator that creates a deadline context for the function.
123
+
124
+ The entire function execution is wrapped in a DeadlineContext,
125
+ allowing nested calls to check remaining time.
126
+
127
+ Args:
128
+ timeout: Total timeout budget for the function.
129
+ operation_name: Name of operation (defaults to function name).
130
+ raise_on_expire: Whether to raise TimeoutError on expiration.
131
+
132
+ Returns:
133
+ Decorator function.
134
+
135
+ Example:
136
+ >>> @with_deadline(30.0)
137
+ ... async def complex_operation():
138
+ ... # Nested calls can use get_current_deadline()
139
+ ... deadline = get_current_deadline()
140
+ ... await call_api(timeout=deadline.remaining())
141
+ """
142
+ def decorator(func: Callable[P, T]) -> Callable[P, T]:
143
+ nonlocal operation_name
144
+ if operation_name is None:
145
+ operation_name = func.__name__
146
+
147
+ if inspect.iscoroutinefunction(func):
148
+ @functools.wraps(func)
149
+ async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
150
+ async with DeadlineContext(
151
+ timeout=timeout,
152
+ operation=operation_name,
153
+ raise_on_expire=raise_on_expire,
154
+ ) as deadline:
155
+ try:
156
+ return await asyncio.wait_for(
157
+ func(*args, **kwargs),
158
+ timeout=deadline.remaining(),
159
+ )
160
+ except asyncio.TimeoutError as e:
161
+ raise TimeoutError(
162
+ message="Deadline exceeded",
163
+ operation=operation_name,
164
+ timeout=timeout,
165
+ elapsed=deadline.elapsed(),
166
+ ) from e
167
+
168
+ return async_wrapper # type: ignore
169
+ else:
170
+ @functools.wraps(func)
171
+ def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
172
+ with DeadlineContext(
173
+ timeout=timeout,
174
+ operation=operation_name,
175
+ raise_on_expire=raise_on_expire,
176
+ ) as deadline:
177
+ return _run_with_timeout_sync(
178
+ func, args, kwargs, deadline.remaining(), operation_name
179
+ )
180
+
181
+ return sync_wrapper # type: ignore
182
+
183
+ return decorator
184
+
185
+
186
+ def _get_effective_timeout(
187
+ explicit_timeout: float | None,
188
+ manager: TimeoutManager,
189
+ operation_name: str,
190
+ use_deadline: bool,
191
+ ) -> float:
192
+ """
193
+ Get effective timeout considering explicit, configured, and deadline.
194
+
195
+ Args:
196
+ explicit_timeout: Explicitly specified timeout.
197
+ manager: TimeoutManager for configuration.
198
+ operation_name: Name of the operation.
199
+ use_deadline: Whether to consider active deadline.
200
+
201
+ Returns:
202
+ Effective timeout in seconds.
203
+ """
204
+ # Start with explicit or configured timeout
205
+ if explicit_timeout is not None:
206
+ timeout = explicit_timeout
207
+ else:
208
+ timeout = manager.get_timeout(operation_name)
209
+
210
+ # Consider active deadline if enabled
211
+ if use_deadline:
212
+ current_deadline = get_current_deadline()
213
+ if current_deadline is not None:
214
+ try:
215
+ remaining = current_deadline.remaining()
216
+ timeout = min(timeout, remaining)
217
+ except TimeoutError:
218
+ return 0.0
219
+
220
+ return timeout
221
+
222
+
223
+ def _run_with_timeout_sync(
224
+ func: Callable[..., T],
225
+ args: tuple,
226
+ kwargs: dict,
227
+ timeout: float,
228
+ operation_name: str,
229
+ ) -> T:
230
+ """
231
+ Run a synchronous function with a timeout.
232
+
233
+ Uses ThreadPoolExecutor for cross-platform compatibility.
234
+
235
+ Args:
236
+ func: Function to run.
237
+ args: Positional arguments.
238
+ kwargs: Keyword arguments.
239
+ timeout: Timeout in seconds.
240
+ operation_name: Name of operation for error messages.
241
+
242
+ Returns:
243
+ Function result.
244
+
245
+ Raises:
246
+ TimeoutError: If function exceeds timeout.
247
+ """
248
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
249
+ future = executor.submit(func, *args, **kwargs)
250
+ try:
251
+ return future.result(timeout=timeout)
252
+ except concurrent.futures.TimeoutError as e:
253
+ # Cancel the future (may not actually stop the thread)
254
+ future.cancel()
255
+ raise TimeoutError(
256
+ message="Operation timed out",
257
+ operation=operation_name,
258
+ timeout=timeout,
259
+ ) from e
260
+
261
+
262
+ async def run_with_timeout(
263
+ coro: Coroutine[Any, Any, T],
264
+ timeout: float,
265
+ operation_name: str | None = None,
266
+ ) -> T:
267
+ """
268
+ Run a coroutine with a timeout.
269
+
270
+ Convenience function for applying timeout to a coroutine
271
+ without using a decorator.
272
+
273
+ Args:
274
+ coro: Coroutine to run.
275
+ timeout: Timeout in seconds.
276
+ operation_name: Name of operation for error messages.
277
+
278
+ Returns:
279
+ Coroutine result.
280
+
281
+ Raises:
282
+ TimeoutError: If coroutine exceeds timeout.
283
+
284
+ Example:
285
+ >>> result = await run_with_timeout(
286
+ ... fetch_data(),
287
+ ... timeout=10.0,
288
+ ... operation_name="fetch_data"
289
+ ... )
290
+ """
291
+ try:
292
+ return await asyncio.wait_for(coro, timeout=timeout)
293
+ except asyncio.TimeoutError as e:
294
+ raise TimeoutError(
295
+ message="Operation timed out",
296
+ operation=operation_name,
297
+ timeout=timeout,
298
+ ) from e
299
+
300
+
301
+ async def run_with_deadline(
302
+ coro: Coroutine[Any, Any, T],
303
+ deadline: DeadlineContext,
304
+ operation_name: str | None = None,
305
+ ) -> T:
306
+ """
307
+ Run a coroutine respecting an existing deadline context.
308
+
309
+ Args:
310
+ coro: Coroutine to run.
311
+ deadline: Active deadline context.
312
+ operation_name: Name of operation for error messages.
313
+
314
+ Returns:
315
+ Coroutine result.
316
+
317
+ Raises:
318
+ TimeoutError: If deadline is exceeded.
319
+
320
+ Example:
321
+ >>> async with DeadlineContext(30.0) as deadline:
322
+ ... result1 = await run_with_deadline(call1(), deadline)
323
+ ... result2 = await run_with_deadline(call2(), deadline)
324
+ """
325
+ remaining = deadline.remaining() # May raise TimeoutError
326
+
327
+ try:
328
+ return await asyncio.wait_for(coro, timeout=remaining)
329
+ except asyncio.TimeoutError as e:
330
+ raise TimeoutError(
331
+ message="Deadline exceeded during operation",
332
+ operation=operation_name,
333
+ timeout=deadline.timeout,
334
+ elapsed=deadline.elapsed(),
335
+ ) from e
336
+
337
+
338
+ class TimeoutScope:
339
+ """
340
+ Structured timeout scope for multiple operations.
341
+
342
+ Similar to DeadlineContext but designed for grouping
343
+ multiple operations with named checkpoints.
344
+
345
+ Example:
346
+ >>> async with TimeoutScope(30.0) as scope:
347
+ ... result1 = await scope.run("fetch", fetch_data())
348
+ ... result2 = await scope.run("process", process_data(result1))
349
+ ... result3 = await scope.run("save", save_result(result2))
350
+ """
351
+
352
+ def __init__(
353
+ self,
354
+ timeout: float,
355
+ operation_name: str = "scope",
356
+ ) -> None:
357
+ """
358
+ Initialize timeout scope.
359
+
360
+ Args:
361
+ timeout: Total timeout budget.
362
+ operation_name: Name of the scope.
363
+ """
364
+ self.timeout = timeout
365
+ self.operation_name = operation_name
366
+ self._deadline: DeadlineContext | None = None
367
+ self._checkpoints: list[tuple[str, float]] = []
368
+
369
+ async def __aenter__(self) -> TimeoutScope:
370
+ """Enter the scope."""
371
+ self._deadline = DeadlineContext(
372
+ timeout=self.timeout,
373
+ operation=self.operation_name,
374
+ )
375
+ await self._deadline.__aenter__()
376
+ return self
377
+
378
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
379
+ """Exit the scope."""
380
+ if self._deadline is not None:
381
+ await self._deadline.__aexit__(exc_type, exc_val, exc_tb)
382
+
383
+ def __enter__(self) -> TimeoutScope:
384
+ """Enter the scope (sync)."""
385
+ self._deadline = DeadlineContext(
386
+ timeout=self.timeout,
387
+ operation=self.operation_name,
388
+ )
389
+ self._deadline.__enter__()
390
+ return self
391
+
392
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
393
+ """Exit the scope (sync)."""
394
+ if self._deadline is not None:
395
+ self._deadline.__exit__(exc_type, exc_val, exc_tb)
396
+
397
+ def remaining(self) -> float:
398
+ """Get remaining time."""
399
+ if self._deadline is None:
400
+ raise RuntimeError("TimeoutScope not started")
401
+ return self._deadline.remaining()
402
+
403
+ def elapsed(self) -> float:
404
+ """Get elapsed time."""
405
+ if self._deadline is None:
406
+ return 0.0
407
+ return self._deadline.elapsed()
408
+
409
+ def checkpoint(self, name: str) -> None:
410
+ """
411
+ Record a checkpoint.
412
+
413
+ Args:
414
+ name: Name of the checkpoint.
415
+ """
416
+ self._checkpoints.append((name, self.elapsed()))
417
+
418
+ def get_checkpoints(self) -> list[tuple[str, float]]:
419
+ """Get all recorded checkpoints."""
420
+ return list(self._checkpoints)
421
+
422
+ async def run(
423
+ self,
424
+ name: str,
425
+ coro: Coroutine[Any, Any, T],
426
+ ) -> T:
427
+ """
428
+ Run a coroutine with remaining timeout.
429
+
430
+ Args:
431
+ name: Name of the operation (for checkpointing).
432
+ coro: Coroutine to run.
433
+
434
+ Returns:
435
+ Coroutine result.
436
+
437
+ Raises:
438
+ TimeoutError: If deadline is exceeded.
439
+ """
440
+ if self._deadline is None:
441
+ raise RuntimeError("TimeoutScope not started")
442
+
443
+ self.checkpoint(f"{name}_start")
444
+ try:
445
+ result = await run_with_deadline(coro, self._deadline, name)
446
+ self.checkpoint(f"{name}_end")
447
+ return result
448
+ except TimeoutError:
449
+ self.checkpoint(f"{name}_timeout")
450
+ raise
451
+
452
+ def run_sync(self, name: str, func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
453
+ """
454
+ Run a sync function with remaining timeout.
455
+
456
+ Args:
457
+ name: Name of the operation.
458
+ func: Function to run.
459
+ *args: Positional arguments.
460
+ **kwargs: Keyword arguments.
461
+
462
+ Returns:
463
+ Function result.
464
+ """
465
+ if self._deadline is None:
466
+ raise RuntimeError("TimeoutScope not started")
467
+
468
+ self.checkpoint(f"{name}_start")
469
+ try:
470
+ result = _run_with_timeout_sync(
471
+ func, args, kwargs, self._deadline.remaining(), name
472
+ )
473
+ self.checkpoint(f"{name}_end")
474
+ return result
475
+ except TimeoutError:
476
+ self.checkpoint(f"{name}_timeout")
477
+ raise