observra 1.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 (93) hide show
  1. observra/__init__.py +196 -0
  2. observra/_compat/__init__.py +0 -0
  3. observra/_compat/adk_telemetry/__init__.py +42 -0
  4. observra/adapters/__init__.py +5 -0
  5. observra/adapters/adk/__init__.py +14 -0
  6. observra/adapters/adk/plugin.py +781 -0
  7. observra/adapters/adk/pricing.json +42 -0
  8. observra/adapters/adk/tests/__init__.py +0 -0
  9. observra/adapters/adk/tests/test_adk_adapter.py +271 -0
  10. observra/adapters/claude/__init__.py +13 -0
  11. observra/adapters/claude/adapter.py +558 -0
  12. observra/adapters/claude/pricing.json +37 -0
  13. observra/adapters/claude/tests/__init__.py +0 -0
  14. observra/adapters/claude/tests/test_claude_adapter.py +530 -0
  15. observra/adapters/langchain/__init__.py +12 -0
  16. observra/adapters/langchain/adapter.py +507 -0
  17. observra/adapters/langchain/pricing.json +47 -0
  18. observra/adapters/langchain/tests/__init__.py +0 -0
  19. observra/adapters/langchain/tests/conftest.py +75 -0
  20. observra/adapters/langchain/tests/test_langchain_adapter.py +563 -0
  21. observra/adapters/openai/__init__.py +12 -0
  22. observra/adapters/openai/adapter.py +463 -0
  23. observra/adapters/openai/pricing.json +52 -0
  24. observra/adapters/openai/tests/__init__.py +0 -0
  25. observra/adapters/openai/tests/conftest.py +74 -0
  26. observra/adapters/openai/tests/test_openai_adapter.py +589 -0
  27. observra/adapters/pydantic_ai/__init__.py +12 -0
  28. observra/adapters/pydantic_ai/adapter.py +399 -0
  29. observra/adapters/pydantic_ai/pricing.json +16 -0
  30. observra/adapters/pydantic_ai/tests/__init__.py +0 -0
  31. observra/adapters/pydantic_ai/tests/conftest.py +51 -0
  32. observra/adapters/pydantic_ai/tests/test_pydantic_ai_adapter.py +508 -0
  33. observra/adapters/utils.py +287 -0
  34. observra/backends/__init__.py +21 -0
  35. observra/backends/jsonl.py +239 -0
  36. observra/backends/multi.py +215 -0
  37. observra/backends/otel.py +363 -0
  38. observra/backends/otel_log.py +439 -0
  39. observra/backends/tests/__init__.py +0 -0
  40. observra/backends/tests/conftest.py +336 -0
  41. observra/backends/tests/test_multi_backend.py +289 -0
  42. observra/backends/tests/test_otel_backend.py +282 -0
  43. observra/backends/tests/test_otel_log_backend.py +343 -0
  44. observra/backends/tests/test_webhook_backend.py +155 -0
  45. observra/backends/webhook.py +163 -0
  46. observra/core/__init__.py +60 -0
  47. observra/core/adapter.py +27 -0
  48. observra/core/atomic_write.py +209 -0
  49. observra/core/cim.py +302 -0
  50. observra/core/context.py +154 -0
  51. observra/core/cost.py +166 -0
  52. observra/core/decorators.py +315 -0
  53. observra/core/dedup.py +60 -0
  54. observra/core/deprecation.py +47 -0
  55. observra/core/detection.py +123 -0
  56. observra/core/encryption.py +217 -0
  57. observra/core/events.py +349 -0
  58. observra/core/host_context.py +130 -0
  59. observra/core/hot_cold.py +112 -0
  60. observra/core/injection.py +92 -0
  61. observra/core/logging_handler.py +73 -0
  62. observra/core/metrics.py +213 -0
  63. observra/core/patterns.py +83 -0
  64. observra/core/pool_writer.py +413 -0
  65. observra/core/queue.py +172 -0
  66. observra/core/redaction.py +97 -0
  67. observra/core/rules.py +126 -0
  68. observra/core/safe_regex.py +216 -0
  69. observra/core/sequences.py +103 -0
  70. observra/core/storage.py +143 -0
  71. observra/core/tests/__init__.py +0 -0
  72. observra/core/tests/test_atomic_write.py +57 -0
  73. observra/core/tests/test_cim.py +152 -0
  74. observra/core/tests/test_encryption.py +183 -0
  75. observra/core/tests/test_events_canonical.py +133 -0
  76. observra/core/tests/test_host_context.py +30 -0
  77. observra/core/tests/test_safe_regex.py +155 -0
  78. observra/core/types.py +18 -0
  79. observra/core/utils.py +26 -0
  80. observra/core/velocity.py +92 -0
  81. observra/core/worker.py +308 -0
  82. observra/log.py +468 -0
  83. observra/observability.py +42 -0
  84. observra/pricing/__init__.py +1 -0
  85. observra/pricing/default.json +42 -0
  86. observra/public.py +70 -0
  87. observra/senders/__init__.py +9 -0
  88. observra/senders/exabeam.py +198 -0
  89. observra-1.0.1.dist-info/METADATA +192 -0
  90. observra-1.0.1.dist-info/RECORD +93 -0
  91. observra-1.0.1.dist-info/WHEEL +5 -0
  92. observra-1.0.1.dist-info/licenses/LICENSE.md +190 -0
  93. observra-1.0.1.dist-info/top_level.txt +1 -0
observra/__init__.py ADDED
@@ -0,0 +1,196 @@
1
+ """Observra — Framework-agnostic agent behavior analytics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Optional
7
+
8
+ __version__ = "1.0.1"
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _worker: Optional["BackgroundWorker"] = None # noqa: F821
13
+ _queue: Optional["DropOldestQueue"] = None # noqa: F821
14
+ _cost_calculator = None
15
+ _cost_threshold = None
16
+ _max_sequence_length: int = 100
17
+
18
+
19
+ class _QueueProxy:
20
+ """Proxy that delegates to the current queue, surviving re-init.
21
+
22
+ Adapters hold a reference to this proxy. When initialize() is called
23
+ again, the proxy's target is swapped — existing adapters transparently
24
+ start writing to the new queue.
25
+ """
26
+
27
+ def __init__(self):
28
+ self._target = None
29
+
30
+ def set_target(self, queue):
31
+ self._target = queue
32
+
33
+ def put_nowait(self, item):
34
+ if self._target is not None:
35
+ self._target.put_nowait(item)
36
+
37
+ def get_stats(self):
38
+ if self._target is not None:
39
+ return self._target.get_stats()
40
+ return {"enqueued": 0, "dropped": 0, "depth": 0}
41
+
42
+
43
+ _queue_proxy = _QueueProxy()
44
+
45
+
46
+ def initialize(backend: str = "jsonl", **kwargs) -> None:
47
+ """Initialize the global telemetry pipeline with a named backend.
48
+
49
+ Tears down any existing pipeline and replaces it with a new one.
50
+ Existing adapters continue working via the queue proxy.
51
+
52
+ Args:
53
+ backend: Backend type name. Supported: "jsonl", "webhook", "otel",
54
+ "otel_log", "multi", "exabeam".
55
+ **kwargs: Arguments passed to the backend constructor.
56
+
57
+ Raises:
58
+ ValueError: If backend type is not recognized.
59
+ """
60
+ from observra.core.queue import DropOldestQueue
61
+ from observra.core.worker import BackgroundWorker
62
+
63
+ global _worker, _queue
64
+
65
+ storage = _create_backend(backend, **kwargs)
66
+
67
+ if _worker is not None:
68
+ _worker._shutdown()
69
+
70
+ event_queue = DropOldestQueue(maxsize=kwargs.pop("queue_size", 1000) if "queue_size" in kwargs else 1000)
71
+ worker = BackgroundWorker(event_queue=event_queue, storage_backend=storage)
72
+
73
+ _queue = event_queue
74
+ _worker = worker
75
+ _queue_proxy.set_target(event_queue)
76
+
77
+
78
+ def _create_backend(backend_type: str, **kwargs):
79
+ """Factory for creating backend instances by name."""
80
+ if backend_type == "jsonl":
81
+ from observra.backends.jsonl import JSONLBackend
82
+
83
+ return JSONLBackend(path=kwargs.get("path", "telemetry.jsonl"))
84
+ elif backend_type == "webhook":
85
+ from observra.backends.webhook import WebhookBackend
86
+
87
+ return WebhookBackend(**kwargs)
88
+ elif backend_type == "otel":
89
+ from observra.backends.otel import OTelExportBackend
90
+
91
+ return OTelExportBackend(**kwargs)
92
+ elif backend_type == "otel_log":
93
+ from observra.backends.otel_log import OTelLogBackend
94
+
95
+ return OTelLogBackend(**kwargs)
96
+ elif backend_type == "multi":
97
+ from observra.backends.multi import MultiBackend
98
+
99
+ return MultiBackend(**kwargs)
100
+ elif backend_type == "exabeam":
101
+ from observra.senders.exabeam import ExabeamSenderBackend
102
+
103
+ return ExabeamSenderBackend(**kwargs)
104
+ else:
105
+ raise ValueError(f"Unknown backend type: {backend_type!r}")
106
+
107
+
108
+ def create_plugin(framework: str = "adk", **kwargs):
109
+ """Create a framework adapter plugin connected to the global pipeline.
110
+
111
+ Args:
112
+ framework: One of "adk", "claude", "openai", "langchain", "pydantic-ai".
113
+ **kwargs: Additional arguments passed to the adapter constructor.
114
+
115
+ Returns:
116
+ The framework-specific adapter/plugin instance.
117
+
118
+ Raises:
119
+ ValueError: If framework is not recognized.
120
+ """
121
+ queue = _queue_proxy
122
+
123
+ if framework == "adk":
124
+ from observra.adapters.adk import TelemetryPlugin
125
+
126
+ return TelemetryPlugin(queue=queue, **kwargs)
127
+ elif framework == "claude":
128
+ from observra.adapters.claude import ClaudeAdapter
129
+
130
+ return ClaudeAdapter(queue=queue, **kwargs)
131
+ elif framework == "openai":
132
+ from observra.adapters.openai import OpenAIAdapter
133
+
134
+ return OpenAIAdapter(queue=queue, **kwargs)
135
+ elif framework == "langchain":
136
+ from observra.adapters.langchain import LangChainAdapter
137
+
138
+ return LangChainAdapter(queue=queue, **kwargs)
139
+ elif framework in ("pydantic-ai", "pydantic_ai"):
140
+ from observra.adapters.pydantic_ai import PydanticAIAdapter
141
+
142
+ return PydanticAIAdapter(queue=queue, **kwargs)
143
+ else:
144
+ raise ValueError(f"Unknown framework: {framework!r}. Supported: adk, claude, openai, langchain, pydantic-ai")
145
+
146
+
147
+ def create_logging_handler(level: int = logging.NOTSET):
148
+ """Create a TelemetryLoggingHandler bridging stdlib logging to telemetry.
149
+
150
+ Args:
151
+ level: Minimum log level to capture (default: NOTSET).
152
+
153
+ Returns:
154
+ A logging.Handler that converts log records to telemetry events.
155
+ """
156
+ from observra.core.logging_handler import TelemetryLoggingHandler
157
+
158
+ return TelemetryLoggingHandler(queue=_queue_proxy, level=level)
159
+
160
+
161
+ def get_stats() -> dict:
162
+ """Get pipeline statistics.
163
+
164
+ Returns a dict with queue and worker stats. Returns empty dict
165
+ if initialize() hasn't been called.
166
+ """
167
+ if _worker is None:
168
+ return {}
169
+
170
+ stats = _queue.get_stats()
171
+ stats.update(
172
+ {
173
+ "events_processed": _worker.events_processed,
174
+ "errors": _worker.errors,
175
+ }
176
+ )
177
+ return stats
178
+
179
+
180
+ from observra import observability # noqa: E402
181
+ from observra.core.events import TelemetryEvent # noqa: E402
182
+ from observra.core.storage import StorageBackend # noqa: E402
183
+
184
+ get_metrics = observability.get_metrics
185
+
186
+ __all__ = [
187
+ "__version__",
188
+ "initialize",
189
+ "create_plugin",
190
+ "create_logging_handler",
191
+ "get_stats",
192
+ "get_metrics",
193
+ "observability",
194
+ "TelemetryEvent",
195
+ "StorageBackend",
196
+ ]
File without changes
@@ -0,0 +1,42 @@
1
+ """Backward-compatible shim for adk_telemetry package.
2
+
3
+ This module allows `from adk_telemetry import X` to continue working
4
+ with a DeprecationWarning. Will be removed in 2.0.
5
+ """
6
+
7
+ import warnings
8
+
9
+ warnings.warn(
10
+ "adk_telemetry is deprecated; use observra instead. Will be removed in 2.0.",
11
+ DeprecationWarning,
12
+ stacklevel=2,
13
+ )
14
+
15
+ # Re-export all public symbols from the real package
16
+ from observra import ( # noqa: E402
17
+ FrameworkAdapter,
18
+ TelemetryEvent,
19
+ TelemetryLoggingHandler,
20
+ __version__,
21
+ create_event,
22
+ create_logging_handler,
23
+ create_plugin,
24
+ get_session_cost,
25
+ get_stats,
26
+ initialize,
27
+ )
28
+ from observra.adapters.adk.plugin import TelemetryPlugin # noqa: E402
29
+
30
+ __all__ = [
31
+ "TelemetryPlugin",
32
+ "TelemetryEvent",
33
+ "FrameworkAdapter",
34
+ "__version__",
35
+ "initialize",
36
+ "create_plugin",
37
+ "create_logging_handler",
38
+ "get_stats",
39
+ "get_session_cost",
40
+ "TelemetryLoggingHandler",
41
+ "create_event",
42
+ ]
@@ -0,0 +1,5 @@
1
+ """Framework adapters for agent telemetry.
2
+
3
+ Adapters are optional — each adapter requires its framework as an extra.
4
+ Example: pip install observra[adk]
5
+ """
@@ -0,0 +1,14 @@
1
+ """ADK framework adapter for agent telemetry.
2
+
3
+ Requires: pip install observra[adk]
4
+ """
5
+
6
+ try:
7
+ from .plugin import TelemetryPlugin
8
+
9
+ # ADKAdapter is an alias for TelemetryPlugin — the class name stays TelemetryPlugin
10
+ # to preserve all existing code; ADKAdapter is the public multi-framework API name.
11
+ ADKAdapter = TelemetryPlugin
12
+ __all__ = ["TelemetryPlugin", "ADKAdapter"]
13
+ except ImportError as e:
14
+ raise ImportError("ADK adapter requires google-adk. Install with: pip install observra[adk]") from e