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.
- observra/__init__.py +196 -0
- observra/_compat/__init__.py +0 -0
- observra/_compat/adk_telemetry/__init__.py +42 -0
- observra/adapters/__init__.py +5 -0
- observra/adapters/adk/__init__.py +14 -0
- observra/adapters/adk/plugin.py +781 -0
- observra/adapters/adk/pricing.json +42 -0
- observra/adapters/adk/tests/__init__.py +0 -0
- observra/adapters/adk/tests/test_adk_adapter.py +271 -0
- observra/adapters/claude/__init__.py +13 -0
- observra/adapters/claude/adapter.py +558 -0
- observra/adapters/claude/pricing.json +37 -0
- observra/adapters/claude/tests/__init__.py +0 -0
- observra/adapters/claude/tests/test_claude_adapter.py +530 -0
- observra/adapters/langchain/__init__.py +12 -0
- observra/adapters/langchain/adapter.py +507 -0
- observra/adapters/langchain/pricing.json +47 -0
- observra/adapters/langchain/tests/__init__.py +0 -0
- observra/adapters/langchain/tests/conftest.py +75 -0
- observra/adapters/langchain/tests/test_langchain_adapter.py +563 -0
- observra/adapters/openai/__init__.py +12 -0
- observra/adapters/openai/adapter.py +463 -0
- observra/adapters/openai/pricing.json +52 -0
- observra/adapters/openai/tests/__init__.py +0 -0
- observra/adapters/openai/tests/conftest.py +74 -0
- observra/adapters/openai/tests/test_openai_adapter.py +589 -0
- observra/adapters/pydantic_ai/__init__.py +12 -0
- observra/adapters/pydantic_ai/adapter.py +399 -0
- observra/adapters/pydantic_ai/pricing.json +16 -0
- observra/adapters/pydantic_ai/tests/__init__.py +0 -0
- observra/adapters/pydantic_ai/tests/conftest.py +51 -0
- observra/adapters/pydantic_ai/tests/test_pydantic_ai_adapter.py +508 -0
- observra/adapters/utils.py +287 -0
- observra/backends/__init__.py +21 -0
- observra/backends/jsonl.py +239 -0
- observra/backends/multi.py +215 -0
- observra/backends/otel.py +363 -0
- observra/backends/otel_log.py +439 -0
- observra/backends/tests/__init__.py +0 -0
- observra/backends/tests/conftest.py +336 -0
- observra/backends/tests/test_multi_backend.py +289 -0
- observra/backends/tests/test_otel_backend.py +282 -0
- observra/backends/tests/test_otel_log_backend.py +343 -0
- observra/backends/tests/test_webhook_backend.py +155 -0
- observra/backends/webhook.py +163 -0
- observra/core/__init__.py +60 -0
- observra/core/adapter.py +27 -0
- observra/core/atomic_write.py +209 -0
- observra/core/cim.py +302 -0
- observra/core/context.py +154 -0
- observra/core/cost.py +166 -0
- observra/core/decorators.py +315 -0
- observra/core/dedup.py +60 -0
- observra/core/deprecation.py +47 -0
- observra/core/detection.py +123 -0
- observra/core/encryption.py +217 -0
- observra/core/events.py +349 -0
- observra/core/host_context.py +130 -0
- observra/core/hot_cold.py +112 -0
- observra/core/injection.py +92 -0
- observra/core/logging_handler.py +73 -0
- observra/core/metrics.py +213 -0
- observra/core/patterns.py +83 -0
- observra/core/pool_writer.py +413 -0
- observra/core/queue.py +172 -0
- observra/core/redaction.py +97 -0
- observra/core/rules.py +126 -0
- observra/core/safe_regex.py +216 -0
- observra/core/sequences.py +103 -0
- observra/core/storage.py +143 -0
- observra/core/tests/__init__.py +0 -0
- observra/core/tests/test_atomic_write.py +57 -0
- observra/core/tests/test_cim.py +152 -0
- observra/core/tests/test_encryption.py +183 -0
- observra/core/tests/test_events_canonical.py +133 -0
- observra/core/tests/test_host_context.py +30 -0
- observra/core/tests/test_safe_regex.py +155 -0
- observra/core/types.py +18 -0
- observra/core/utils.py +26 -0
- observra/core/velocity.py +92 -0
- observra/core/worker.py +308 -0
- observra/log.py +468 -0
- observra/observability.py +42 -0
- observra/pricing/__init__.py +1 -0
- observra/pricing/default.json +42 -0
- observra/public.py +70 -0
- observra/senders/__init__.py +9 -0
- observra/senders/exabeam.py +198 -0
- observra-1.0.1.dist-info/METADATA +192 -0
- observra-1.0.1.dist-info/RECORD +93 -0
- observra-1.0.1.dist-info/WHEEL +5 -0
- observra-1.0.1.dist-info/licenses/LICENSE.md +190 -0
- 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,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
|