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,683 @@
1
+ """
2
+ Metrics and observability hooks for Proxilion.
3
+
4
+ Provides hooks for metrics and observability without requiring external
5
+ dependencies. Allows integration with Prometheus, OpenTelemetry, StatsD, etc.
6
+
7
+ Quick Start:
8
+ >>> from proxilion.observability import (
9
+ ... ObservabilityHooks,
10
+ ... InMemoryMetricHook,
11
+ ... emit_counter,
12
+ ... emit_timing,
13
+ ... )
14
+ >>>
15
+ >>> # Set up metrics hook
16
+ >>> hooks = ObservabilityHooks.get_instance()
17
+ >>> memory_hook = InMemoryMetricHook()
18
+ >>> hooks.add_metric_hook(memory_hook)
19
+ >>>
20
+ >>> # Emit metrics
21
+ >>> emit_counter("proxilion.auth.requests", tags={"user": "alice"})
22
+ >>> emit_timing("proxilion.auth.latency_ms", 45.2, tags={"tool": "search"})
23
+ >>>
24
+ >>> # Check recorded metrics
25
+ >>> memory_hook.get_counter("proxilion.auth.requests", {"user": "alice"})
26
+ 1.0
27
+
28
+ Integration with Prometheus:
29
+ >>> from prometheus_client import Counter, Histogram
30
+ >>>
31
+ >>> class PrometheusMetricHook:
32
+ ... def __init__(self):
33
+ ... self.auth_requests = Counter(
34
+ ... "proxilion_auth_requests_total",
35
+ ... "Total authorization requests",
36
+ ... ["user", "tool"]
37
+ ... )
38
+ ... self.auth_latency = Histogram(
39
+ ... "proxilion_auth_latency_seconds",
40
+ ... "Authorization latency in seconds"
41
+ ... )
42
+ ...
43
+ ... def increment(self, name, value=1.0, tags=None):
44
+ ... if name == "proxilion.auth.requests":
45
+ ... self.auth_requests.labels(**(tags or {})).inc(value)
46
+ ...
47
+ ... def histogram(self, name, value, tags=None):
48
+ ... if name == "proxilion.auth.latency_ms":
49
+ ... self.auth_latency.observe(value / 1000) # Convert to seconds
50
+ ...
51
+ ... def gauge(self, name, value, tags=None): pass
52
+ ... def timing(self, name, duration_ms, tags=None):
53
+ ... self.histogram(name, duration_ms, tags)
54
+ >>>
55
+ >>> hooks = ObservabilityHooks.get_instance()
56
+ >>> hooks.add_metric_hook(PrometheusMetricHook())
57
+
58
+ Integration with OpenTelemetry:
59
+ >>> from opentelemetry import metrics
60
+ >>>
61
+ >>> class OpenTelemetryMetricHook:
62
+ ... def __init__(self):
63
+ ... meter = metrics.get_meter("proxilion")
64
+ ... self._counters = {}
65
+ ... self._histograms = {}
66
+ ... self._meter = meter
67
+ ...
68
+ ... def increment(self, name, value=1.0, tags=None):
69
+ ... if name not in self._counters:
70
+ ... self._counters[name] = self._meter.create_counter(name)
71
+ ... self._counters[name].add(value, tags or {})
72
+ ...
73
+ ... def histogram(self, name, value, tags=None):
74
+ ... if name not in self._histograms:
75
+ ... self._histograms[name] = self._meter.create_histogram(name)
76
+ ... self._histograms[name].record(value, tags or {})
77
+ ...
78
+ ... def gauge(self, name, value, tags=None): pass
79
+ ... def timing(self, name, duration_ms, tags=None):
80
+ ... self.histogram(name, duration_ms, tags)
81
+ """
82
+
83
+ from __future__ import annotations
84
+
85
+ import logging
86
+ from collections import defaultdict
87
+ from dataclasses import dataclass
88
+ from enum import Enum
89
+ from typing import Any, Protocol, runtime_checkable
90
+
91
+
92
+ class MetricType(Enum):
93
+ """Types of metrics that can be emitted."""
94
+
95
+ COUNTER = "counter"
96
+ """A monotonically increasing counter."""
97
+
98
+ GAUGE = "gauge"
99
+ """A value that can go up or down."""
100
+
101
+ HISTOGRAM = "histogram"
102
+ """Distribution of values."""
103
+
104
+ SUMMARY = "summary"
105
+ """Similar to histogram but calculates quantiles."""
106
+
107
+ TIMING = "timing"
108
+ """Duration measurement (usually milliseconds)."""
109
+
110
+
111
+ @runtime_checkable
112
+ class MetricHook(Protocol):
113
+ """
114
+ Protocol for metric backends (Prometheus, StatsD, OpenTelemetry, etc.).
115
+
116
+ Implement this protocol to integrate with your metrics infrastructure.
117
+ The hooks will be called by Proxilion whenever metrics are emitted.
118
+
119
+ Example:
120
+ >>> class MyMetricHook:
121
+ ... def increment(self, name, value=1.0, tags=None):
122
+ ... # Send to your metrics backend
123
+ ... pass
124
+ ...
125
+ ... def gauge(self, name, value, tags=None):
126
+ ... pass
127
+ ...
128
+ ... def histogram(self, name, value, tags=None):
129
+ ... pass
130
+ ...
131
+ ... def timing(self, name, duration_ms, tags=None):
132
+ ... pass
133
+ >>>
134
+ >>> from proxilion.observability import ObservabilityHooks
135
+ >>> hooks = ObservabilityHooks.get_instance()
136
+ >>> hooks.add_metric_hook(MyMetricHook())
137
+ """
138
+
139
+ def increment(self, name: str, value: float = 1.0, tags: dict[str, Any] | None = None) -> None:
140
+ """
141
+ Increment a counter metric.
142
+
143
+ Args:
144
+ name: The metric name (e.g., "proxilion.auth.requests").
145
+ value: The amount to increment by (default 1.0).
146
+ tags: Optional tags/labels for the metric.
147
+ """
148
+ ...
149
+
150
+ def gauge(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
151
+ """
152
+ Set a gauge metric.
153
+
154
+ Args:
155
+ name: The metric name.
156
+ value: The gauge value.
157
+ tags: Optional tags/labels for the metric.
158
+ """
159
+ ...
160
+
161
+ def histogram(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
162
+ """
163
+ Record a value in a histogram.
164
+
165
+ Args:
166
+ name: The metric name.
167
+ value: The value to record.
168
+ tags: Optional tags/labels for the metric.
169
+ """
170
+ ...
171
+
172
+ def timing(self, name: str, duration_ms: float, tags: dict[str, Any] | None = None) -> None:
173
+ """
174
+ Record a timing/duration measurement.
175
+
176
+ Args:
177
+ name: The metric name.
178
+ duration_ms: The duration in milliseconds.
179
+ tags: Optional tags/labels for the metric.
180
+ """
181
+ ...
182
+
183
+
184
+ class LoggingMetricHook:
185
+ """
186
+ Simple hook that logs metrics (for development and debugging).
187
+
188
+ Example:
189
+ >>> import logging
190
+ >>> logging.basicConfig(level=logging.DEBUG)
191
+ >>> hook = LoggingMetricHook()
192
+ >>> hook.increment("requests", 1.0, {"service": "api"})
193
+ DEBUG:proxilion.metrics:COUNTER requests=1.0 tags={'service': 'api'}
194
+ """
195
+
196
+ def __init__(self, logger: logging.Logger | None = None, level: int = logging.DEBUG):
197
+ """
198
+ Initialize the logging hook.
199
+
200
+ Args:
201
+ logger: Logger instance to use. Defaults to "proxilion.metrics".
202
+ level: Logging level for metric messages.
203
+ """
204
+ self.logger = logger or logging.getLogger("proxilion.metrics")
205
+ self.level = level
206
+
207
+ def increment(self, name: str, value: float = 1.0, tags: dict[str, Any] | None = None) -> None:
208
+ """Log a counter increment."""
209
+ self.logger.log(self.level, f"COUNTER {name}={value} tags={tags}")
210
+
211
+ def gauge(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
212
+ """Log a gauge value."""
213
+ self.logger.log(self.level, f"GAUGE {name}={value} tags={tags}")
214
+
215
+ def histogram(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
216
+ """Log a histogram value."""
217
+ self.logger.log(self.level, f"HISTOGRAM {name}={value} tags={tags}")
218
+
219
+ def timing(self, name: str, duration_ms: float, tags: dict[str, Any] | None = None) -> None:
220
+ """Log a timing value."""
221
+ self.logger.log(self.level, f"TIMING {name}={duration_ms}ms tags={tags}")
222
+
223
+
224
+ @dataclass
225
+ class HistogramStats:
226
+ """Statistics for a histogram metric."""
227
+
228
+ count: int
229
+ """Number of recorded values."""
230
+
231
+ total: float
232
+ """Sum of all values."""
233
+
234
+ min: float
235
+ """Minimum value."""
236
+
237
+ max: float
238
+ """Maximum value."""
239
+
240
+ avg: float
241
+ """Average value."""
242
+
243
+ def to_dict(self) -> dict[str, Any]:
244
+ """Convert to dictionary."""
245
+ return {
246
+ "count": self.count,
247
+ "total": self.total,
248
+ "min": self.min,
249
+ "max": self.max,
250
+ "avg": self.avg,
251
+ }
252
+
253
+
254
+ class InMemoryMetricHook:
255
+ """
256
+ In-memory metrics for testing and simple use cases.
257
+
258
+ Stores all metrics in memory and provides methods to query them.
259
+ Useful for testing that metrics are being emitted correctly.
260
+
261
+ Example:
262
+ >>> hook = InMemoryMetricHook()
263
+ >>> hook.increment("requests", tags={"user": "alice"})
264
+ >>> hook.increment("requests", tags={"user": "alice"})
265
+ >>> hook.increment("requests", tags={"user": "bob"})
266
+ >>>
267
+ >>> hook.get_counter("requests", {"user": "alice"})
268
+ 2.0
269
+ >>> hook.get_counter("requests", {"user": "bob"})
270
+ 1.0
271
+ >>>
272
+ >>> hook.histogram("latency", 100.0)
273
+ >>> hook.histogram("latency", 200.0)
274
+ >>> stats = hook.get_histogram_stats("latency")
275
+ >>> stats.avg
276
+ 150.0
277
+ """
278
+
279
+ def __init__(self):
280
+ """Initialize the in-memory hook."""
281
+ self.counters: dict[str, float] = defaultdict(float)
282
+ self.gauges: dict[str, float] = {}
283
+ self.histograms: dict[str, list[float]] = defaultdict(list)
284
+ self.timings: dict[str, list[float]] = defaultdict(list)
285
+
286
+ def _make_key(self, name: str, tags: dict[str, Any] | None) -> str:
287
+ """Create a unique key from metric name and tags."""
288
+ if not tags:
289
+ return name
290
+ sorted_tags = sorted(tags.items())
291
+ tag_str = ",".join(f"{k}={v}" for k, v in sorted_tags)
292
+ return f"{name}[{tag_str}]"
293
+
294
+ def increment(self, name: str, value: float = 1.0, tags: dict[str, Any] | None = None) -> None:
295
+ """Increment a counter."""
296
+ key = self._make_key(name, tags)
297
+ self.counters[key] += value
298
+
299
+ def gauge(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
300
+ """Set a gauge value."""
301
+ key = self._make_key(name, tags)
302
+ self.gauges[key] = value
303
+
304
+ def histogram(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
305
+ """Record a histogram value."""
306
+ key = self._make_key(name, tags)
307
+ self.histograms[key].append(value)
308
+
309
+ def timing(self, name: str, duration_ms: float, tags: dict[str, Any] | None = None) -> None:
310
+ """Record a timing value."""
311
+ key = self._make_key(name, tags)
312
+ self.timings[key].append(duration_ms)
313
+
314
+ def get_counter(self, name: str, tags: dict[str, Any] | None = None) -> float:
315
+ """
316
+ Get the current value of a counter.
317
+
318
+ Args:
319
+ name: The metric name.
320
+ tags: Optional tags/labels for the metric.
321
+
322
+ Returns:
323
+ The current counter value, or 0.0 if not found.
324
+ """
325
+ key = self._make_key(name, tags)
326
+ return self.counters[key]
327
+
328
+ def get_gauge(self, name: str, tags: dict[str, Any] | None = None) -> float | None:
329
+ """
330
+ Get the current value of a gauge.
331
+
332
+ Args:
333
+ name: The metric name.
334
+ tags: Optional tags/labels for the metric.
335
+
336
+ Returns:
337
+ The current gauge value, or None if not set.
338
+ """
339
+ key = self._make_key(name, tags)
340
+ return self.gauges.get(key)
341
+
342
+ def get_histogram_stats(
343
+ self, name: str, tags: dict[str, Any] | None = None
344
+ ) -> HistogramStats | None:
345
+ """
346
+ Get statistics for a histogram.
347
+
348
+ Args:
349
+ name: The metric name.
350
+ tags: Optional tags/labels for the metric.
351
+
352
+ Returns:
353
+ HistogramStats with count, sum, min, max, avg, or None if empty.
354
+ """
355
+ key = self._make_key(name, tags)
356
+ values = self.histograms.get(key, [])
357
+ if not values:
358
+ return None
359
+ return HistogramStats(
360
+ count=len(values),
361
+ total=sum(values),
362
+ min=min(values),
363
+ max=max(values),
364
+ avg=sum(values) / len(values),
365
+ )
366
+
367
+ def get_timing_stats(
368
+ self, name: str, tags: dict[str, Any] | None = None
369
+ ) -> HistogramStats | None:
370
+ """
371
+ Get statistics for timing measurements.
372
+
373
+ Args:
374
+ name: The metric name.
375
+ tags: Optional tags/labels for the metric.
376
+
377
+ Returns:
378
+ HistogramStats with count, sum, min, max, avg, or None if empty.
379
+ """
380
+ key = self._make_key(name, tags)
381
+ values = self.timings.get(key, [])
382
+ if not values:
383
+ return None
384
+ return HistogramStats(
385
+ count=len(values),
386
+ total=sum(values),
387
+ min=min(values),
388
+ max=max(values),
389
+ avg=sum(values) / len(values),
390
+ )
391
+
392
+ def get_all_counters(self) -> dict[str, float]:
393
+ """Get all counter values."""
394
+ return dict(self.counters)
395
+
396
+ def get_all_gauges(self) -> dict[str, float]:
397
+ """Get all gauge values."""
398
+ return dict(self.gauges)
399
+
400
+ def reset(self) -> None:
401
+ """Reset all metrics to initial state."""
402
+ self.counters.clear()
403
+ self.gauges.clear()
404
+ self.histograms.clear()
405
+ self.timings.clear()
406
+
407
+
408
+ class ObservabilityHooks:
409
+ """
410
+ Central registry for observability hooks.
411
+
412
+ This is a singleton that manages all metric hooks. Use `get_instance()`
413
+ to get the global instance.
414
+
415
+ Example:
416
+ >>> from proxilion.observability import ObservabilityHooks, InMemoryMetricHook
417
+ >>>
418
+ >>> hooks = ObservabilityHooks.get_instance()
419
+ >>> memory_hook = InMemoryMetricHook()
420
+ >>> hooks.add_metric_hook(memory_hook)
421
+ >>>
422
+ >>> # Emit metrics (these will be sent to all registered hooks)
423
+ >>> hooks.emit_counter("requests", tags={"service": "api"})
424
+ >>> hooks.emit_timing("latency", 45.2)
425
+ >>>
426
+ >>> # Check the recorded metrics
427
+ >>> memory_hook.get_counter("requests", {"service": "api"})
428
+ 1.0
429
+ """
430
+
431
+ _instance: ObservabilityHooks | None = None
432
+
433
+ def __init__(self):
434
+ """Initialize the observability hooks registry."""
435
+ self._metric_hooks: list[MetricHook] = []
436
+
437
+ @classmethod
438
+ def get_instance(cls) -> ObservabilityHooks:
439
+ """
440
+ Get the global ObservabilityHooks instance.
441
+
442
+ Returns:
443
+ The singleton instance.
444
+ """
445
+ if cls._instance is None:
446
+ cls._instance = cls()
447
+ return cls._instance
448
+
449
+ @classmethod
450
+ def reset_instance(cls) -> None:
451
+ """
452
+ Reset the global instance.
453
+
454
+ Useful for testing to ensure a clean state.
455
+ """
456
+ cls._instance = None
457
+
458
+ def add_metric_hook(self, hook: MetricHook) -> None:
459
+ """
460
+ Register a metric hook.
461
+
462
+ Args:
463
+ hook: The metric hook to register.
464
+ """
465
+ self._metric_hooks.append(hook)
466
+
467
+ def remove_metric_hook(self, hook: MetricHook) -> bool:
468
+ """
469
+ Remove a metric hook.
470
+
471
+ Args:
472
+ hook: The metric hook to remove.
473
+
474
+ Returns:
475
+ True if the hook was removed, False if not found.
476
+ """
477
+ try:
478
+ self._metric_hooks.remove(hook)
479
+ return True
480
+ except ValueError:
481
+ return False
482
+
483
+ def clear_metric_hooks(self) -> None:
484
+ """Remove all registered metric hooks."""
485
+ self._metric_hooks.clear()
486
+
487
+ @property
488
+ def metric_hooks(self) -> list[MetricHook]:
489
+ """Get a copy of the registered metric hooks."""
490
+ return list(self._metric_hooks)
491
+
492
+ def emit_metric(
493
+ self,
494
+ metric_type: MetricType,
495
+ name: str,
496
+ value: float,
497
+ tags: dict[str, Any] | None = None,
498
+ ) -> None:
499
+ """
500
+ Emit a metric to all registered hooks.
501
+
502
+ Args:
503
+ metric_type: The type of metric.
504
+ name: The metric name.
505
+ value: The metric value.
506
+ tags: Optional tags/labels for the metric.
507
+ """
508
+ for hook in self._metric_hooks:
509
+ if metric_type == MetricType.COUNTER:
510
+ hook.increment(name, value, tags)
511
+ elif metric_type == MetricType.GAUGE:
512
+ hook.gauge(name, value, tags)
513
+ elif metric_type == MetricType.HISTOGRAM:
514
+ hook.histogram(name, value, tags)
515
+ elif metric_type in (MetricType.TIMING, MetricType.SUMMARY):
516
+ hook.timing(name, value, tags)
517
+
518
+ def emit_counter(
519
+ self, name: str, value: float = 1.0, tags: dict[str, Any] | None = None
520
+ ) -> None:
521
+ """
522
+ Emit a counter metric.
523
+
524
+ Args:
525
+ name: The metric name.
526
+ value: The amount to increment by (default 1.0).
527
+ tags: Optional tags/labels for the metric.
528
+ """
529
+ self.emit_metric(MetricType.COUNTER, name, value, tags)
530
+
531
+ def emit_gauge(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
532
+ """
533
+ Emit a gauge metric.
534
+
535
+ Args:
536
+ name: The metric name.
537
+ value: The gauge value.
538
+ tags: Optional tags/labels for the metric.
539
+ """
540
+ self.emit_metric(MetricType.GAUGE, name, value, tags)
541
+
542
+ def emit_histogram(self, name: str, value: float, tags: dict[str, Any] | None = None) -> None:
543
+ """
544
+ Emit a histogram metric.
545
+
546
+ Args:
547
+ name: The metric name.
548
+ value: The value to record.
549
+ tags: Optional tags/labels for the metric.
550
+ """
551
+ self.emit_metric(MetricType.HISTOGRAM, name, value, tags)
552
+
553
+ def emit_timing(
554
+ self, name: str, duration_ms: float, tags: dict[str, Any] | None = None
555
+ ) -> None:
556
+ """
557
+ Emit a timing metric.
558
+
559
+ Args:
560
+ name: The metric name.
561
+ duration_ms: The duration in milliseconds.
562
+ tags: Optional tags/labels for the metric.
563
+ """
564
+ self.emit_metric(MetricType.TIMING, name, duration_ms, tags)
565
+
566
+
567
+ # Standard metric names emitted by Proxilion
568
+ # These are the metric names that Proxilion components emit
569
+
570
+ # Authorization metrics
571
+ METRIC_AUTH_REQUESTS = "proxilion.auth.requests"
572
+ """Counter: Total authorization requests."""
573
+
574
+ METRIC_AUTH_ALLOWED = "proxilion.auth.allowed"
575
+ """Counter: Authorization requests that were allowed."""
576
+
577
+ METRIC_AUTH_DENIED = "proxilion.auth.denied"
578
+ """Counter: Authorization requests that were denied."""
579
+
580
+ METRIC_AUTH_LATENCY = "proxilion.auth.latency_ms"
581
+ """Histogram: Authorization check latency in milliseconds."""
582
+
583
+ # Rate limiting metrics
584
+ METRIC_RATE_LIMIT_REQUESTS = "proxilion.rate_limit.requests"
585
+ """Counter: Total rate limit checks."""
586
+
587
+ METRIC_RATE_LIMIT_EXCEEDED = "proxilion.rate_limit.exceeded"
588
+ """Counter: Rate limit exceeded events."""
589
+
590
+ # Tool execution metrics
591
+ METRIC_TOOL_CALLS = "proxilion.tool.calls"
592
+ """Counter: Total tool call executions."""
593
+
594
+ METRIC_TOOL_LATENCY = "proxilion.tool.latency_ms"
595
+ """Histogram: Tool execution latency in milliseconds."""
596
+
597
+ METRIC_TOOL_ERRORS = "proxilion.tool.errors"
598
+ """Counter: Tool execution errors."""
599
+
600
+ # Cost metrics
601
+ METRIC_COST_USD = "proxilion.cost.usd"
602
+ """Counter: Total cost in USD."""
603
+
604
+ METRIC_TOKENS_INPUT = "proxilion.tokens.input"
605
+ """Counter: Total input tokens processed."""
606
+
607
+ METRIC_TOKENS_OUTPUT = "proxilion.tokens.output"
608
+ """Counter: Total output tokens generated."""
609
+
610
+ # Circuit breaker metrics
611
+ METRIC_CIRCUIT_BREAKER_OPEN = "proxilion.circuit_breaker.open"
612
+ """Counter: Circuit breaker open events."""
613
+
614
+ METRIC_CIRCUIT_BREAKER_HALF_OPEN = "proxilion.circuit_breaker.half_open"
615
+ """Counter: Circuit breaker half-open events."""
616
+
617
+ METRIC_CIRCUIT_BREAKER_CLOSED = "proxilion.circuit_breaker.closed"
618
+ """Counter: Circuit breaker closed events."""
619
+
620
+
621
+ # Convenience functions for emitting metrics
622
+ def emit_counter(name: str, value: float = 1.0, tags: dict[str, Any] | None = None) -> None:
623
+ """
624
+ Emit a counter metric to all registered hooks.
625
+
626
+ Args:
627
+ name: The metric name (e.g., "proxilion.auth.requests").
628
+ value: The amount to increment by (default 1.0).
629
+ tags: Optional tags/labels for the metric.
630
+
631
+ Example:
632
+ >>> from proxilion.observability import emit_counter
633
+ >>> emit_counter("proxilion.auth.requests", tags={"user": "alice"})
634
+ """
635
+ ObservabilityHooks.get_instance().emit_counter(name, value, tags)
636
+
637
+
638
+ def emit_gauge(name: str, value: float, tags: dict[str, Any] | None = None) -> None:
639
+ """
640
+ Emit a gauge metric to all registered hooks.
641
+
642
+ Args:
643
+ name: The metric name.
644
+ value: The gauge value.
645
+ tags: Optional tags/labels for the metric.
646
+
647
+ Example:
648
+ >>> from proxilion.observability import emit_gauge
649
+ >>> emit_gauge("proxilion.connections.active", 42)
650
+ """
651
+ ObservabilityHooks.get_instance().emit_gauge(name, value, tags)
652
+
653
+
654
+ def emit_histogram(name: str, value: float, tags: dict[str, Any] | None = None) -> None:
655
+ """
656
+ Emit a histogram metric to all registered hooks.
657
+
658
+ Args:
659
+ name: The metric name.
660
+ value: The value to record.
661
+ tags: Optional tags/labels for the metric.
662
+
663
+ Example:
664
+ >>> from proxilion.observability import emit_histogram
665
+ >>> emit_histogram("proxilion.response.size_bytes", 1024)
666
+ """
667
+ ObservabilityHooks.get_instance().emit_histogram(name, value, tags)
668
+
669
+
670
+ def emit_timing(name: str, duration_ms: float, tags: dict[str, Any] | None = None) -> None:
671
+ """
672
+ Emit a timing metric to all registered hooks.
673
+
674
+ Args:
675
+ name: The metric name.
676
+ duration_ms: The duration in milliseconds.
677
+ tags: Optional tags/labels for the metric.
678
+
679
+ Example:
680
+ >>> from proxilion.observability import emit_timing
681
+ >>> emit_timing("proxilion.auth.latency_ms", 45.2, tags={"tool": "search"})
682
+ """
683
+ ObservabilityHooks.get_instance().emit_timing(name, duration_ms, tags)