ioa-observe-sdk 1.0.26__tar.gz → 1.0.28__tar.gz
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.
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/PKG-INFO +1 -1
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/client/client.py +13 -4
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/a2a.py +133 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/slim.py +9 -1
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/tracing.py +101 -22
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/PKG-INFO +1 -1
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/pyproject.toml +1 -1
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/README.md +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/client/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/client/http.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/config/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/connectors/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/connectors/slim.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/decorators/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/decorators/base.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/decorators/helpers.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/decorators/util.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/mcp.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/nats.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instruments.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/logging/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/logging/logging.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agent.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/agent_connections.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/availability.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/heuristics.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/recovery_tracker.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/tool_call_tracker.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/tracker.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/metrics.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/telemetry.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/content_allow_list.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/context_manager.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/context_utils.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/manual.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/transform_span.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/__init__.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/const.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/in_memory_span_exporter.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/json_encoder.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/package_check.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/version.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/SOURCES.txt +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/dependency_links.txt +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/requires.txt +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/top_level.txt +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/setup.cfg +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/tests/test_client.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/tests/test_instrumentor.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/tests/test_manual_instrumentation.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/tests/test_transform_span.py +0 -0
- {ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/tests/test_version.py +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
|
+
import threading
|
|
5
6
|
|
|
6
7
|
from .http import HTTPClient
|
|
7
8
|
from ioa_observe.sdk.version import __version__
|
|
@@ -51,21 +52,29 @@ class Client:
|
|
|
51
52
|
class KVStore(object):
|
|
52
53
|
"""
|
|
53
54
|
Key-Value Store for storing key-value pairs (Singleton).
|
|
55
|
+
Thread-safe implementation for concurrent access.
|
|
54
56
|
"""
|
|
55
57
|
|
|
56
58
|
_instance = None
|
|
59
|
+
_instance_lock = threading.Lock()
|
|
57
60
|
|
|
58
61
|
def __new__(cls):
|
|
59
62
|
if cls._instance is None:
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
with cls._instance_lock:
|
|
64
|
+
# Double-check locking pattern
|
|
65
|
+
if cls._instance is None:
|
|
66
|
+
cls._instance = super(KVStore, cls).__new__(cls)
|
|
67
|
+
cls._instance.store = {}
|
|
68
|
+
cls._instance._lock = threading.Lock()
|
|
62
69
|
return cls._instance
|
|
63
70
|
|
|
64
71
|
def set(self, key: str, value: str):
|
|
65
|
-
self.
|
|
72
|
+
with self._lock:
|
|
73
|
+
self.store[key] = value
|
|
66
74
|
|
|
67
75
|
def get(self, key: str):
|
|
68
|
-
|
|
76
|
+
with self._lock:
|
|
77
|
+
return self.store.get(key)
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
kv_store = KVStore()
|
|
@@ -204,6 +204,122 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
204
204
|
|
|
205
205
|
# original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
206
206
|
|
|
207
|
+
# Instrumentation for slima2a
|
|
208
|
+
if importlib.util.find_spec("slima2a"):
|
|
209
|
+
from slima2a.client_transport import SRPCTransport
|
|
210
|
+
|
|
211
|
+
# send_message
|
|
212
|
+
original_srpc_transport_send_message = SRPCTransport.send_message
|
|
213
|
+
|
|
214
|
+
@functools.wraps(original_srpc_transport_send_message)
|
|
215
|
+
async def instrumented_srpc_transport_send_message(
|
|
216
|
+
self, request, *args, **kwargs
|
|
217
|
+
):
|
|
218
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
219
|
+
with _global_tracer.start_as_current_span("slima2a.send_message"):
|
|
220
|
+
traceparent = get_current_traceparent()
|
|
221
|
+
session_id = None
|
|
222
|
+
if traceparent:
|
|
223
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
224
|
+
if not session_id:
|
|
225
|
+
session_id = get_value("session.id")
|
|
226
|
+
if session_id:
|
|
227
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
228
|
+
|
|
229
|
+
# Ensure metadata dict exists
|
|
230
|
+
try:
|
|
231
|
+
md = getattr(request.params, "metadata", None)
|
|
232
|
+
except AttributeError:
|
|
233
|
+
md = None
|
|
234
|
+
metadata = md if isinstance(md, dict) else {}
|
|
235
|
+
|
|
236
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
237
|
+
|
|
238
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
239
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
240
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
241
|
+
|
|
242
|
+
if traceparent:
|
|
243
|
+
observe_meta["traceparent"] = traceparent
|
|
244
|
+
if session_id:
|
|
245
|
+
observe_meta["session_id"] = session_id
|
|
246
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
247
|
+
|
|
248
|
+
metadata["observe"] = observe_meta
|
|
249
|
+
|
|
250
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
251
|
+
try:
|
|
252
|
+
request.metadata = metadata
|
|
253
|
+
except Exception:
|
|
254
|
+
# Fallback
|
|
255
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
256
|
+
|
|
257
|
+
# Call through without transport-specific kwargs
|
|
258
|
+
return await original_srpc_transport_send_message(
|
|
259
|
+
self, request, *args, **kwargs
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
SRPCTransport.send_message = instrumented_srpc_transport_send_message
|
|
263
|
+
|
|
264
|
+
# send_message_streaming
|
|
265
|
+
original_srpc_transport_send_message_streaming = (
|
|
266
|
+
SRPCTransport.send_message_streaming
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@functools.wraps(original_srpc_transport_send_message_streaming)
|
|
270
|
+
async def instrumented_srpc_transport_send_message_streaming(
|
|
271
|
+
self, request, *args, **kwargs
|
|
272
|
+
):
|
|
273
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
274
|
+
with _global_tracer.start_as_current_span(
|
|
275
|
+
"slima2a.send_message_streaming"
|
|
276
|
+
):
|
|
277
|
+
traceparent = get_current_traceparent()
|
|
278
|
+
session_id = None
|
|
279
|
+
if traceparent:
|
|
280
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
281
|
+
if not session_id:
|
|
282
|
+
session_id = get_value("session.id")
|
|
283
|
+
if session_id:
|
|
284
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
285
|
+
|
|
286
|
+
# Ensure metadata dict exists
|
|
287
|
+
try:
|
|
288
|
+
md = getattr(request.params, "metadata", None)
|
|
289
|
+
except AttributeError:
|
|
290
|
+
md = None
|
|
291
|
+
metadata = md if isinstance(md, dict) else {}
|
|
292
|
+
|
|
293
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
294
|
+
|
|
295
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
296
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
297
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
298
|
+
|
|
299
|
+
if traceparent:
|
|
300
|
+
observe_meta["traceparent"] = traceparent
|
|
301
|
+
if session_id:
|
|
302
|
+
observe_meta["session_id"] = session_id
|
|
303
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
304
|
+
|
|
305
|
+
metadata["observe"] = observe_meta
|
|
306
|
+
|
|
307
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
308
|
+
try:
|
|
309
|
+
request.metadata = metadata
|
|
310
|
+
except Exception:
|
|
311
|
+
# Fallback
|
|
312
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
313
|
+
|
|
314
|
+
# Call through without transport-specific kwargs
|
|
315
|
+
return await original_srpc_transport_send_message_streaming(
|
|
316
|
+
self, request, *args, **kwargs
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
SRPCTransport.send_message_streaming = (
|
|
320
|
+
instrumented_srpc_transport_send_message_streaming
|
|
321
|
+
)
|
|
322
|
+
|
|
207
323
|
def _uninstrument(self, **kwargs):
|
|
208
324
|
import importlib
|
|
209
325
|
|
|
@@ -227,3 +343,20 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
227
343
|
DefaultRequestHandler.on_message_send = (
|
|
228
344
|
DefaultRequestHandler.on_message_send.__wrapped__
|
|
229
345
|
)
|
|
346
|
+
|
|
347
|
+
# handle slima2a
|
|
348
|
+
if importlib.util.find_spec("slima2a"):
|
|
349
|
+
from slima2a.client_transport import SRPCTransport
|
|
350
|
+
|
|
351
|
+
# Uninstrument `send_message`
|
|
352
|
+
if hasattr(SRPCTransport, "send_message") and hasattr(
|
|
353
|
+
SRPCTransport.send_message, "__wrapped__"
|
|
354
|
+
):
|
|
355
|
+
SRPCTransport.send_message = SRPCTransport.send_message.__wrapped__
|
|
356
|
+
|
|
357
|
+
if hasattr(SRPCTransport, "send_message_streaming") and hasattr(
|
|
358
|
+
SRPCTransport.send_message_streaming, "__wrapped__"
|
|
359
|
+
):
|
|
360
|
+
SRPCTransport.send_message_streaming = (
|
|
361
|
+
SRPCTransport.send_message.__wrapped__
|
|
362
|
+
)
|
|
@@ -531,7 +531,15 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
531
531
|
if timeout is not None:
|
|
532
532
|
kwargs["timeout"] = timeout
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
if _global_tracer:
|
|
535
|
+
with _global_tracer.start_as_current_span(
|
|
536
|
+
"session.get_message"
|
|
537
|
+
) as span:
|
|
538
|
+
if hasattr(self, "id"):
|
|
539
|
+
span.set_attribute("slim.session.id", str(self.id))
|
|
540
|
+
result = await original_get_message(self, **kwargs)
|
|
541
|
+
else:
|
|
542
|
+
result = await original_get_message(self, **kwargs)
|
|
535
543
|
|
|
536
544
|
# Handle different return types from get_message
|
|
537
545
|
if result is None:
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
|
+
import threading
|
|
10
11
|
import time
|
|
11
12
|
import uuid
|
|
12
13
|
|
|
@@ -64,6 +65,10 @@ from typing import Callable, Dict, Optional, Set
|
|
|
64
65
|
TRACER_NAME = "ioa.observe.tracer"
|
|
65
66
|
APP_NAME = ""
|
|
66
67
|
|
|
68
|
+
SESSION_IDLE_TIMEOUT_SECONDS = 300 # e.g. 5 minutes
|
|
69
|
+
SESSION_WATCHER_INTERVAL_SECONDS = 30
|
|
70
|
+
MAX_PROCESSED_SPANS_SIZE = 100000 # Maximum size before cleanup
|
|
71
|
+
|
|
67
72
|
|
|
68
73
|
def determine_reliability_score(span):
|
|
69
74
|
if "observe.entity.output" in span.attributes:
|
|
@@ -132,11 +137,16 @@ class TracerWrapper(object):
|
|
|
132
137
|
if not TracerWrapper.endpoint:
|
|
133
138
|
return obj
|
|
134
139
|
|
|
140
|
+
# session activity tracking
|
|
141
|
+
obj._session_last_activity: dict[str, float] = {}
|
|
142
|
+
obj._session_lock = threading.Lock()
|
|
135
143
|
obj.__image_uploader = image_uploader
|
|
136
144
|
# {(agent_name): [success_count, total_count]}
|
|
137
145
|
obj._agent_execution_counts = {}
|
|
146
|
+
obj._agent_execution_counts_lock = threading.Lock()
|
|
138
147
|
# Track spans that have been processed to avoid duplicates
|
|
139
148
|
obj._processed_spans = set()
|
|
149
|
+
obj._processed_spans_lock = threading.Lock()
|
|
140
150
|
TracerWrapper.app_name = TracerWrapper.resource_attributes.get(
|
|
141
151
|
"service.name", "observe"
|
|
142
152
|
)
|
|
@@ -270,12 +280,74 @@ class TracerWrapper(object):
|
|
|
270
280
|
|
|
271
281
|
obj.__content_allow_list = ContentAllowList()
|
|
272
282
|
|
|
283
|
+
# start background watcher for session end
|
|
284
|
+
obj._start_session_watcher()
|
|
285
|
+
|
|
273
286
|
# Force flushes for debug environments (e.g. local development)
|
|
274
287
|
atexit.register(obj.exit_handler)
|
|
275
288
|
|
|
276
289
|
return cls.instance
|
|
277
290
|
|
|
291
|
+
def _start_session_watcher(self) -> None:
|
|
292
|
+
t = threading.Thread(
|
|
293
|
+
target=self._session_watcher_loop,
|
|
294
|
+
name="ioa-session-watcher",
|
|
295
|
+
daemon=True,
|
|
296
|
+
)
|
|
297
|
+
t.start()
|
|
298
|
+
|
|
299
|
+
def _session_watcher_loop(self) -> None:
|
|
300
|
+
while True:
|
|
301
|
+
time.sleep(SESSION_WATCHER_INTERVAL_SECONDS)
|
|
302
|
+
now = time.time()
|
|
303
|
+
expired: dict[str, float] = {}
|
|
304
|
+
|
|
305
|
+
# Find idle sessions and remove them from _session_last_activity
|
|
306
|
+
with self._session_lock:
|
|
307
|
+
for session_id, last_ts in list(self._session_last_activity.items()):
|
|
308
|
+
if now - last_ts > SESSION_IDLE_TIMEOUT_SECONDS:
|
|
309
|
+
expired[session_id] = last_ts
|
|
310
|
+
del self._session_last_activity[session_id]
|
|
311
|
+
|
|
312
|
+
# Periodic cleanup of processed spans to prevent unbounded memory growth
|
|
313
|
+
with self._processed_spans_lock:
|
|
314
|
+
if len(self._processed_spans) > MAX_PROCESSED_SPANS_SIZE:
|
|
315
|
+
self._processed_spans.clear()
|
|
316
|
+
|
|
317
|
+
if not expired:
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
tracer = self.get_tracer()
|
|
321
|
+
|
|
322
|
+
# Iterate over a snapshot and do *not* modify `expired` in the loop
|
|
323
|
+
for session_id, _last_ts in list(expired.items()):
|
|
324
|
+
with tracer.start_as_current_span("session.end") as span:
|
|
325
|
+
span.set_attribute("session.id", session_id)
|
|
326
|
+
workflow_name = get_value("workflow_name")
|
|
327
|
+
if workflow_name:
|
|
328
|
+
span.set_attribute(OBSERVE_WORKFLOW_NAME, workflow_name)
|
|
329
|
+
span.set_attribute("session.ended_at", _last_ts)
|
|
330
|
+
|
|
331
|
+
# ensure end spans are exported reasonably fast
|
|
332
|
+
self.flush()
|
|
333
|
+
|
|
278
334
|
def exit_handler(self):
|
|
335
|
+
# emit end spans for any sessions that never went idle
|
|
336
|
+
now = time.time()
|
|
337
|
+
tracer = self.get_tracer()
|
|
338
|
+
|
|
339
|
+
with self._session_lock:
|
|
340
|
+
remaining_ids = list(self._session_last_activity.keys())
|
|
341
|
+
self._session_last_activity.clear()
|
|
342
|
+
|
|
343
|
+
for session_id in remaining_ids:
|
|
344
|
+
with tracer.start_as_current_span("session.end") as span:
|
|
345
|
+
span.set_attribute("session.id", session_id)
|
|
346
|
+
workflow_name = get_value("workflow_name")
|
|
347
|
+
if workflow_name:
|
|
348
|
+
span.set_attribute(OBSERVE_WORKFLOW_NAME, workflow_name)
|
|
349
|
+
span.set_attribute("session.ended_at", now)
|
|
350
|
+
|
|
279
351
|
self.flush()
|
|
280
352
|
|
|
281
353
|
def _span_processor_on_start(self, span, parent_context):
|
|
@@ -355,12 +427,21 @@ class TracerWrapper(object):
|
|
|
355
427
|
# Added for avoid duplicate on_ending with manual triggers
|
|
356
428
|
# from decorators (@tool, @workflow) in base.py
|
|
357
429
|
span_id = span.context.span_id
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
430
|
+
with self._processed_spans_lock:
|
|
431
|
+
if span_id in self._processed_spans:
|
|
432
|
+
# This span was already processed, skip to avoid duplicates
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
# Mark this span as processed
|
|
436
|
+
self._processed_spans.add(span_id)
|
|
437
|
+
|
|
438
|
+
# update last activity per session
|
|
439
|
+
# Skip session.end spans to avoid infinite loop - these are cleanup spans
|
|
440
|
+
# that should not re-register the session as active
|
|
441
|
+
session_id = span.attributes.get("session.id")
|
|
442
|
+
if session_id and span.name != "session.end":
|
|
443
|
+
with self._session_lock:
|
|
444
|
+
self._session_last_activity[session_id] = time.time()
|
|
364
445
|
|
|
365
446
|
determine_reliability_score(span)
|
|
366
447
|
# start_time = span.attributes.get("ioa_start_time")
|
|
@@ -382,10 +463,6 @@ class TracerWrapper(object):
|
|
|
382
463
|
except Exception as e:
|
|
383
464
|
logging.error(f"Error applying span transformation: {e}")
|
|
384
465
|
|
|
385
|
-
# if start_time is not None:
|
|
386
|
-
# latency = (time.time() - start_time) * 1000
|
|
387
|
-
# self.response_latency_histogram.record(latency, attributes=span.attributes)
|
|
388
|
-
|
|
389
466
|
# Call original on_end method if it exists
|
|
390
467
|
if (
|
|
391
468
|
hasattr(self, "_TracerWrapper__spans_processor_original_on_end")
|
|
@@ -500,21 +577,23 @@ class TracerWrapper(object):
|
|
|
500
577
|
return self.__tracer_provider.get_tracer(TRACER_NAME)
|
|
501
578
|
|
|
502
579
|
def record_agent_execution(self, agent_name: str, success: bool):
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
580
|
+
with self._agent_execution_counts_lock:
|
|
581
|
+
counts = self._agent_execution_counts.setdefault(agent_name, [0, 0])
|
|
582
|
+
if success:
|
|
583
|
+
counts[0] += 1 # success count
|
|
584
|
+
counts[1] += 1 # total count
|
|
507
585
|
|
|
508
586
|
def _observe_agent_execution_success_rate(self, observer):
|
|
509
587
|
measurements = []
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
588
|
+
with self._agent_execution_counts_lock:
|
|
589
|
+
for agent_name, (
|
|
590
|
+
success_count,
|
|
591
|
+
total_count,
|
|
592
|
+
) in self._agent_execution_counts.items():
|
|
593
|
+
rate = (success_count / total_count) if total_count > 0 else 0.0
|
|
594
|
+
measurements.append(
|
|
595
|
+
Observation(value=rate, attributes={"agent_name": agent_name})
|
|
596
|
+
)
|
|
518
597
|
return measurements
|
|
519
598
|
|
|
520
599
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/instrumentations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/availability.py
RENAMED
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/heuristics.py
RENAMED
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/metrics/agents/recovery_tracker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/content_allow_list.py
RENAMED
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/tracing/context_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe/sdk/utils/in_memory_span_exporter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ioa_observe_sdk-1.0.26 → ioa_observe_sdk-1.0.28}/ioa_observe_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|