ioa-observe-sdk 1.0.28__py3-none-any.whl → 1.0.29__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.
- ioa_observe/sdk/instrumentations/a2a.py +431 -122
- ioa_observe/sdk/instrumentations/slim.py +9 -31
- ioa_observe/sdk/tracing/tracing.py +70 -13
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.29.dist-info}/METADATA +29 -29
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.29.dist-info}/RECORD +8 -8
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.29.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.29.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.28.dist-info → ioa_observe_sdk-1.0.29.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,72 @@ _instruments = ("a2a-sdk >= 0.3.0",)
|
|
|
19
19
|
_global_tracer = None
|
|
20
20
|
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
21
21
|
|
|
22
|
+
# Track original methods for uninstrumentation
|
|
23
|
+
_original_methods = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _inject_observe_metadata(request, span_name: str):
|
|
27
|
+
"""
|
|
28
|
+
Helper function to inject observability metadata into request.
|
|
29
|
+
This is shared between legacy and new client instrumentation.
|
|
30
|
+
"""
|
|
31
|
+
traceparent = get_current_traceparent()
|
|
32
|
+
session_id = None
|
|
33
|
+
if traceparent:
|
|
34
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
35
|
+
if not session_id:
|
|
36
|
+
session_id = get_value("session.id")
|
|
37
|
+
if session_id:
|
|
38
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
39
|
+
|
|
40
|
+
# Ensure metadata dict exists - handle both request.params.metadata and request.metadata
|
|
41
|
+
try:
|
|
42
|
+
if hasattr(request, "params") and hasattr(request.params, "metadata"):
|
|
43
|
+
md = getattr(request.params, "metadata", None)
|
|
44
|
+
else:
|
|
45
|
+
md = getattr(request, "metadata", None)
|
|
46
|
+
except AttributeError:
|
|
47
|
+
md = None
|
|
48
|
+
metadata = md if isinstance(md, dict) else {}
|
|
49
|
+
|
|
50
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
51
|
+
|
|
52
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
53
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
54
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
55
|
+
|
|
56
|
+
if traceparent:
|
|
57
|
+
observe_meta["traceparent"] = traceparent
|
|
58
|
+
if session_id:
|
|
59
|
+
observe_meta["session_id"] = session_id
|
|
60
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
61
|
+
|
|
62
|
+
metadata["observe"] = observe_meta
|
|
63
|
+
|
|
64
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
65
|
+
try:
|
|
66
|
+
if hasattr(request, "params") and hasattr(request.params, "metadata"):
|
|
67
|
+
request.params.metadata = metadata
|
|
68
|
+
else:
|
|
69
|
+
request.metadata = metadata
|
|
70
|
+
except Exception:
|
|
71
|
+
# Fallback - create a new request with updated metadata
|
|
72
|
+
try:
|
|
73
|
+
if hasattr(request, "params"):
|
|
74
|
+
request = request.model_copy(
|
|
75
|
+
update={
|
|
76
|
+
"params": request.params.model_copy(
|
|
77
|
+
update={"metadata": metadata}
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
83
|
+
except Exception:
|
|
84
|
+
pass # If all else fails, continue without metadata injection
|
|
85
|
+
|
|
86
|
+
return request
|
|
87
|
+
|
|
22
88
|
|
|
23
89
|
class A2AInstrumentor(BaseInstrumentor):
|
|
24
90
|
def __init__(self):
|
|
@@ -35,10 +101,105 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
35
101
|
if importlib.util.find_spec("a2a") is None:
|
|
36
102
|
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
37
103
|
|
|
38
|
-
# Instrument client
|
|
39
|
-
|
|
104
|
+
# Instrument new A2AClient (BaseClient) from a2a.client.base_client (v0.3.0+)
|
|
105
|
+
self._instrument_base_client()
|
|
106
|
+
|
|
107
|
+
# Instrument legacy A2AClient for backward compatibility
|
|
108
|
+
self._instrument_legacy_client()
|
|
109
|
+
|
|
110
|
+
# Instrument server handler
|
|
111
|
+
self._instrument_server_handler()
|
|
112
|
+
|
|
113
|
+
# Instrument slima2a if available
|
|
114
|
+
self._instrument_slima2a()
|
|
115
|
+
|
|
116
|
+
def _instrument_base_client(self):
|
|
117
|
+
"""Instrument the new A2AClient (BaseClient) pattern introduced in v0.3.0."""
|
|
118
|
+
import importlib
|
|
119
|
+
|
|
120
|
+
# Check if the new base_client module exists
|
|
121
|
+
if importlib.util.find_spec("a2a.client.base_client") is None:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
from a2a.client.base_client import A2AClient as BaseA2AClient
|
|
126
|
+
except ImportError:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Instrument send_message
|
|
130
|
+
if hasattr(BaseA2AClient, "send_message"):
|
|
131
|
+
original_send_message = BaseA2AClient.send_message
|
|
132
|
+
_original_methods["BaseA2AClient.send_message"] = original_send_message
|
|
133
|
+
|
|
134
|
+
@functools.wraps(original_send_message)
|
|
135
|
+
async def instrumented_send_message(self, request, *args, **kwargs):
|
|
136
|
+
nonlocal original_send_message
|
|
137
|
+
with _global_tracer.start_as_current_span("a2a.client.send_message"):
|
|
138
|
+
request = _inject_observe_metadata(
|
|
139
|
+
request, "a2a.client.send_message"
|
|
140
|
+
)
|
|
141
|
+
return await original_send_message(self, request, *args, **kwargs)
|
|
142
|
+
|
|
143
|
+
BaseA2AClient.send_message = instrumented_send_message
|
|
144
|
+
|
|
145
|
+
# Instrument send_message_streaming
|
|
146
|
+
if hasattr(BaseA2AClient, "send_message_streaming"):
|
|
147
|
+
original_send_message_streaming = BaseA2AClient.send_message_streaming
|
|
148
|
+
_original_methods["BaseA2AClient.send_message_streaming"] = (
|
|
149
|
+
original_send_message_streaming
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@functools.wraps(original_send_message_streaming)
|
|
153
|
+
async def instrumented_send_message_streaming(
|
|
154
|
+
self, request, *args, **kwargs
|
|
155
|
+
):
|
|
156
|
+
nonlocal original_send_message_streaming
|
|
157
|
+
with _global_tracer.start_as_current_span(
|
|
158
|
+
"a2a.client.send_message_streaming"
|
|
159
|
+
):
|
|
160
|
+
request = _inject_observe_metadata(
|
|
161
|
+
request, "a2a.client.send_message_streaming"
|
|
162
|
+
)
|
|
163
|
+
# This is an async generator, so we need to yield from it
|
|
164
|
+
async for response in original_send_message_streaming(
|
|
165
|
+
self, request, *args, **kwargs
|
|
166
|
+
):
|
|
167
|
+
yield response
|
|
168
|
+
|
|
169
|
+
BaseA2AClient.send_message_streaming = instrumented_send_message_streaming
|
|
170
|
+
|
|
171
|
+
def _instrument_legacy_client(self):
|
|
172
|
+
"""Instrument the legacy A2AClient for backward compatibility."""
|
|
173
|
+
|
|
174
|
+
# Try to import from legacy location first (v0.3.0+)
|
|
175
|
+
legacy_client = None
|
|
176
|
+
legacy_module_path = None
|
|
40
177
|
|
|
41
|
-
|
|
178
|
+
try:
|
|
179
|
+
from a2a.client.legacy import A2AClient as LegacyA2AClient
|
|
180
|
+
|
|
181
|
+
legacy_client = LegacyA2AClient
|
|
182
|
+
legacy_module_path = "a2a.client.legacy"
|
|
183
|
+
except ImportError:
|
|
184
|
+
# Fall back to old import path (pre-v0.3.0)
|
|
185
|
+
try:
|
|
186
|
+
from a2a.client import A2AClient as LegacyA2AClient
|
|
187
|
+
|
|
188
|
+
# Check if this is the legacy client (has send_message method directly)
|
|
189
|
+
if hasattr(LegacyA2AClient, "send_message"):
|
|
190
|
+
legacy_client = LegacyA2AClient
|
|
191
|
+
legacy_module_path = "a2a.client"
|
|
192
|
+
except ImportError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
if legacy_client is None:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
# Store original methods for uninstrumentation
|
|
199
|
+
original_send_message = legacy_client.send_message
|
|
200
|
+
_original_methods[f"{legacy_module_path}.A2AClient.send_message"] = (
|
|
201
|
+
original_send_message
|
|
202
|
+
)
|
|
42
203
|
|
|
43
204
|
@functools.wraps(original_send_message)
|
|
44
205
|
async def instrumented_send_message(self, request, *args, **kwargs):
|
|
@@ -90,11 +251,14 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
90
251
|
# Call through without transport-specific kwargs
|
|
91
252
|
return await original_send_message(self, request, *args, **kwargs)
|
|
92
253
|
|
|
93
|
-
|
|
254
|
+
legacy_client.send_message = instrumented_send_message
|
|
94
255
|
|
|
95
|
-
# Instrument broadcast_message
|
|
96
|
-
if hasattr(
|
|
97
|
-
original_broadcast_message =
|
|
256
|
+
# Instrument broadcast_message if it exists
|
|
257
|
+
if hasattr(legacy_client, "broadcast_message"):
|
|
258
|
+
original_broadcast_message = legacy_client.broadcast_message
|
|
259
|
+
_original_methods[f"{legacy_module_path}.A2AClient.broadcast_message"] = (
|
|
260
|
+
original_broadcast_message
|
|
261
|
+
)
|
|
98
262
|
|
|
99
263
|
@functools.wraps(original_broadcast_message)
|
|
100
264
|
async def instrumented_broadcast_message(self, request, *args, **kwargs):
|
|
@@ -146,12 +310,16 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
146
310
|
# Call through without transport-specific kwargs
|
|
147
311
|
return await original_broadcast_message(self, request, *args, **kwargs)
|
|
148
312
|
|
|
149
|
-
|
|
313
|
+
legacy_client.broadcast_message = instrumented_broadcast_message
|
|
150
314
|
|
|
151
|
-
|
|
315
|
+
def _instrument_server_handler(self):
|
|
316
|
+
"""Instrument the server-side request handlers."""
|
|
152
317
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
153
318
|
|
|
154
319
|
original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
320
|
+
_original_methods["DefaultRequestHandler.on_message_send"] = (
|
|
321
|
+
original_server_on_message_send
|
|
322
|
+
)
|
|
155
323
|
|
|
156
324
|
@functools.wraps(original_server_on_message_send)
|
|
157
325
|
async def instrumented_on_message_send(self, params, context):
|
|
@@ -190,173 +358,314 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
190
358
|
finally:
|
|
191
359
|
if token is not None:
|
|
192
360
|
try:
|
|
361
|
+
from opentelemetry import context as otel_ctx
|
|
362
|
+
|
|
193
363
|
otel_ctx.detach(token)
|
|
194
364
|
except Exception:
|
|
195
365
|
pass
|
|
196
366
|
|
|
197
367
|
DefaultRequestHandler.on_message_send = instrumented_on_message_send
|
|
198
368
|
|
|
199
|
-
#
|
|
369
|
+
# Instrument on_message_send_stream for streaming message reception
|
|
370
|
+
if hasattr(DefaultRequestHandler, "on_message_send_stream"):
|
|
371
|
+
original_on_message_send_stream = (
|
|
372
|
+
DefaultRequestHandler.on_message_send_stream
|
|
373
|
+
)
|
|
374
|
+
_original_methods["DefaultRequestHandler.on_message_send_stream"] = (
|
|
375
|
+
original_on_message_send_stream
|
|
376
|
+
)
|
|
200
377
|
|
|
201
|
-
|
|
378
|
+
@functools.wraps(original_on_message_send_stream)
|
|
379
|
+
async def instrumented_on_message_send_stream(self, params, context):
|
|
380
|
+
# Read context from A2A message metadata (transport-agnostic)
|
|
381
|
+
try:
|
|
382
|
+
metadata = getattr(params, "metadata", {}) or {}
|
|
383
|
+
except Exception:
|
|
384
|
+
metadata = {}
|
|
385
|
+
|
|
386
|
+
carrier = {}
|
|
387
|
+
observe_meta = metadata.get("observe", {}) or {}
|
|
388
|
+
for k in ("traceparent", "baggage", "session_id"):
|
|
389
|
+
if k in observe_meta:
|
|
390
|
+
carrier[k] = observe_meta[k]
|
|
391
|
+
|
|
392
|
+
token = None
|
|
393
|
+
if carrier.get("traceparent"):
|
|
394
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
395
|
+
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
396
|
+
try:
|
|
397
|
+
from opentelemetry import context as otel_ctx
|
|
202
398
|
|
|
203
|
-
|
|
399
|
+
token = otel_ctx.attach(ctx)
|
|
400
|
+
except Exception:
|
|
401
|
+
token = None
|
|
204
402
|
|
|
205
|
-
|
|
403
|
+
session_id = observe_meta.get("session_id")
|
|
404
|
+
if session_id and session_id != "None":
|
|
405
|
+
set_session_id(
|
|
406
|
+
session_id, traceparent=carrier.get("traceparent")
|
|
407
|
+
)
|
|
408
|
+
kv_store.set(
|
|
409
|
+
f"execution.{carrier.get('traceparent')}", session_id
|
|
410
|
+
)
|
|
206
411
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
412
|
+
try:
|
|
413
|
+
# This is an async generator
|
|
414
|
+
async for event in original_on_message_send_stream(
|
|
415
|
+
self, params, context
|
|
416
|
+
):
|
|
417
|
+
yield event
|
|
418
|
+
finally:
|
|
419
|
+
if token is not None:
|
|
420
|
+
try:
|
|
421
|
+
from opentelemetry import context as otel_ctx
|
|
422
|
+
|
|
423
|
+
otel_ctx.detach(token)
|
|
424
|
+
except Exception:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
DefaultRequestHandler.on_message_send_stream = (
|
|
428
|
+
instrumented_on_message_send_stream
|
|
429
|
+
)
|
|
210
430
|
|
|
211
|
-
|
|
212
|
-
|
|
431
|
+
def _instrument_slima2a(self):
|
|
432
|
+
"""Instrument slima2a transport if available."""
|
|
433
|
+
import importlib
|
|
213
434
|
|
|
214
|
-
|
|
215
|
-
|
|
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)
|
|
435
|
+
if importlib.util.find_spec("slima2a") is None:
|
|
436
|
+
return
|
|
228
437
|
|
|
229
|
-
|
|
230
|
-
try:
|
|
231
|
-
md = getattr(request.params, "metadata", None)
|
|
232
|
-
except AttributeError:
|
|
233
|
-
md = None
|
|
234
|
-
metadata = md if isinstance(md, dict) else {}
|
|
438
|
+
from slima2a.client_transport import SRPCTransport
|
|
235
439
|
|
|
236
|
-
|
|
440
|
+
# send_message
|
|
441
|
+
original_srpc_transport_send_message = SRPCTransport.send_message
|
|
442
|
+
_original_methods["SRPCTransport.send_message"] = (
|
|
443
|
+
original_srpc_transport_send_message
|
|
444
|
+
)
|
|
237
445
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
446
|
+
@functools.wraps(original_srpc_transport_send_message)
|
|
447
|
+
async def instrumented_srpc_transport_send_message(
|
|
448
|
+
self, request, *args, **kwargs
|
|
449
|
+
):
|
|
450
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
451
|
+
with _global_tracer.start_as_current_span("slima2a.send_message"):
|
|
452
|
+
traceparent = get_current_traceparent()
|
|
453
|
+
session_id = None
|
|
454
|
+
if traceparent:
|
|
455
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
456
|
+
if not session_id:
|
|
457
|
+
session_id = get_value("session.id")
|
|
458
|
+
if session_id:
|
|
459
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
241
460
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
461
|
+
# Ensure metadata dict exists
|
|
462
|
+
try:
|
|
463
|
+
md = getattr(request.params, "metadata", None)
|
|
464
|
+
except AttributeError:
|
|
465
|
+
md = None
|
|
466
|
+
metadata = md if isinstance(md, dict) else {}
|
|
247
467
|
|
|
248
|
-
|
|
468
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
249
469
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
except Exception:
|
|
254
|
-
# Fallback
|
|
255
|
-
request = request.model_copy(update={"metadata": metadata})
|
|
470
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
471
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
472
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
256
473
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
474
|
+
if traceparent:
|
|
475
|
+
observe_meta["traceparent"] = traceparent
|
|
476
|
+
if session_id:
|
|
477
|
+
observe_meta["session_id"] = session_id
|
|
478
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
261
479
|
|
|
262
|
-
|
|
480
|
+
metadata["observe"] = observe_meta
|
|
263
481
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
482
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
483
|
+
try:
|
|
484
|
+
request.metadata = metadata
|
|
485
|
+
except Exception:
|
|
486
|
+
# Fallback
|
|
487
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
268
488
|
|
|
269
|
-
|
|
270
|
-
|
|
489
|
+
# Call through without transport-specific kwargs
|
|
490
|
+
return await original_srpc_transport_send_message(
|
|
271
491
|
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)
|
|
492
|
+
)
|
|
285
493
|
|
|
286
|
-
|
|
287
|
-
try:
|
|
288
|
-
md = getattr(request.params, "metadata", None)
|
|
289
|
-
except AttributeError:
|
|
290
|
-
md = None
|
|
291
|
-
metadata = md if isinstance(md, dict) else {}
|
|
494
|
+
SRPCTransport.send_message = instrumented_srpc_transport_send_message
|
|
292
495
|
|
|
293
|
-
|
|
496
|
+
# send_message_streaming
|
|
497
|
+
original_srpc_transport_send_message_streaming = (
|
|
498
|
+
SRPCTransport.send_message_streaming
|
|
499
|
+
)
|
|
500
|
+
_original_methods["SRPCTransport.send_message_streaming"] = (
|
|
501
|
+
original_srpc_transport_send_message_streaming
|
|
502
|
+
)
|
|
294
503
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
504
|
+
@functools.wraps(original_srpc_transport_send_message_streaming)
|
|
505
|
+
async def instrumented_srpc_transport_send_message_streaming(
|
|
506
|
+
self, request, *args, **kwargs
|
|
507
|
+
):
|
|
508
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
509
|
+
with _global_tracer.start_as_current_span("slima2a.send_message_streaming"):
|
|
510
|
+
traceparent = get_current_traceparent()
|
|
511
|
+
session_id = None
|
|
512
|
+
if traceparent:
|
|
513
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
514
|
+
if not session_id:
|
|
515
|
+
session_id = get_value("session.id")
|
|
516
|
+
if session_id:
|
|
517
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
298
518
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
519
|
+
# Ensure metadata dict exists
|
|
520
|
+
try:
|
|
521
|
+
md = getattr(request.params, "metadata", None)
|
|
522
|
+
except AttributeError:
|
|
523
|
+
md = None
|
|
524
|
+
metadata = md if isinstance(md, dict) else {}
|
|
304
525
|
|
|
305
|
-
|
|
526
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
306
527
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
except Exception:
|
|
311
|
-
# Fallback
|
|
312
|
-
request = request.model_copy(update={"metadata": metadata})
|
|
528
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
529
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
530
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
313
531
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
532
|
+
if traceparent:
|
|
533
|
+
observe_meta["traceparent"] = traceparent
|
|
534
|
+
if session_id:
|
|
535
|
+
observe_meta["session_id"] = session_id
|
|
536
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
318
537
|
|
|
319
|
-
|
|
320
|
-
|
|
538
|
+
metadata["observe"] = observe_meta
|
|
539
|
+
|
|
540
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
541
|
+
try:
|
|
542
|
+
request.metadata = metadata
|
|
543
|
+
except Exception:
|
|
544
|
+
# Fallback
|
|
545
|
+
request = request.model_copy(update={"metadata": metadata})
|
|
546
|
+
|
|
547
|
+
# Call through without transport-specific kwargs
|
|
548
|
+
return await original_srpc_transport_send_message_streaming(
|
|
549
|
+
self, request, *args, **kwargs
|
|
321
550
|
)
|
|
322
551
|
|
|
552
|
+
SRPCTransport.send_message_streaming = (
|
|
553
|
+
instrumented_srpc_transport_send_message_streaming
|
|
554
|
+
)
|
|
555
|
+
|
|
323
556
|
def _uninstrument(self, **kwargs):
|
|
324
557
|
import importlib
|
|
325
558
|
|
|
326
559
|
if importlib.util.find_spec("a2a") is None:
|
|
327
560
|
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
328
561
|
|
|
329
|
-
# Uninstrument
|
|
330
|
-
|
|
562
|
+
# Uninstrument BaseA2AClient (new pattern v0.3.0+)
|
|
563
|
+
if importlib.util.find_spec("a2a.client.base_client") is not None:
|
|
564
|
+
try:
|
|
565
|
+
from a2a.client.base_client import A2AClient as BaseA2AClient
|
|
566
|
+
|
|
567
|
+
if "BaseA2AClient.send_message" in _original_methods:
|
|
568
|
+
BaseA2AClient.send_message = _original_methods[
|
|
569
|
+
"BaseA2AClient.send_message"
|
|
570
|
+
]
|
|
571
|
+
elif hasattr(BaseA2AClient.send_message, "__wrapped__"):
|
|
572
|
+
BaseA2AClient.send_message = BaseA2AClient.send_message.__wrapped__
|
|
573
|
+
|
|
574
|
+
if "BaseA2AClient.send_message_streaming" in _original_methods:
|
|
575
|
+
BaseA2AClient.send_message_streaming = _original_methods[
|
|
576
|
+
"BaseA2AClient.send_message_streaming"
|
|
577
|
+
]
|
|
578
|
+
elif hasattr(BaseA2AClient, "send_message_streaming") and hasattr(
|
|
579
|
+
BaseA2AClient.send_message_streaming, "__wrapped__"
|
|
580
|
+
):
|
|
581
|
+
BaseA2AClient.send_message_streaming = (
|
|
582
|
+
BaseA2AClient.send_message_streaming.__wrapped__
|
|
583
|
+
)
|
|
584
|
+
except ImportError:
|
|
585
|
+
pass
|
|
331
586
|
|
|
332
|
-
|
|
587
|
+
# Uninstrument legacy A2AClient
|
|
588
|
+
legacy_client = None
|
|
589
|
+
legacy_module_path = None
|
|
333
590
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
591
|
+
try:
|
|
592
|
+
from a2a.client.legacy import A2AClient as LegacyA2AClient
|
|
593
|
+
|
|
594
|
+
legacy_client = LegacyA2AClient
|
|
595
|
+
legacy_module_path = "a2a.client.legacy"
|
|
596
|
+
except ImportError:
|
|
597
|
+
try:
|
|
598
|
+
from a2a.client import A2AClient as LegacyA2AClient
|
|
599
|
+
|
|
600
|
+
if hasattr(LegacyA2AClient, "send_message"):
|
|
601
|
+
legacy_client = LegacyA2AClient
|
|
602
|
+
legacy_module_path = "a2a.client"
|
|
603
|
+
except ImportError:
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
if legacy_client is not None:
|
|
607
|
+
key = f"{legacy_module_path}.A2AClient.send_message"
|
|
608
|
+
if key in _original_methods:
|
|
609
|
+
legacy_client.send_message = _original_methods[key]
|
|
610
|
+
elif hasattr(legacy_client.send_message, "__wrapped__"):
|
|
611
|
+
legacy_client.send_message = legacy_client.send_message.__wrapped__
|
|
612
|
+
|
|
613
|
+
broadcast_key = f"{legacy_module_path}.A2AClient.broadcast_message"
|
|
614
|
+
if broadcast_key in _original_methods:
|
|
615
|
+
legacy_client.broadcast_message = _original_methods[broadcast_key]
|
|
616
|
+
elif hasattr(legacy_client, "broadcast_message") and hasattr(
|
|
617
|
+
legacy_client.broadcast_message, "__wrapped__"
|
|
618
|
+
):
|
|
619
|
+
legacy_client.broadcast_message = (
|
|
620
|
+
legacy_client.broadcast_message.__wrapped__
|
|
621
|
+
)
|
|
339
622
|
|
|
340
623
|
# Uninstrument server handler
|
|
341
624
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
342
625
|
|
|
343
|
-
DefaultRequestHandler.on_message_send
|
|
344
|
-
DefaultRequestHandler.on_message_send
|
|
345
|
-
|
|
626
|
+
if "DefaultRequestHandler.on_message_send" in _original_methods:
|
|
627
|
+
DefaultRequestHandler.on_message_send = _original_methods[
|
|
628
|
+
"DefaultRequestHandler.on_message_send"
|
|
629
|
+
]
|
|
630
|
+
elif hasattr(DefaultRequestHandler.on_message_send, "__wrapped__"):
|
|
631
|
+
DefaultRequestHandler.on_message_send = (
|
|
632
|
+
DefaultRequestHandler.on_message_send.__wrapped__
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
if "DefaultRequestHandler.on_message_send_stream" in _original_methods:
|
|
636
|
+
DefaultRequestHandler.on_message_send_stream = _original_methods[
|
|
637
|
+
"DefaultRequestHandler.on_message_send_stream"
|
|
638
|
+
]
|
|
639
|
+
elif hasattr(DefaultRequestHandler, "on_message_send_stream") and hasattr(
|
|
640
|
+
DefaultRequestHandler.on_message_send_stream, "__wrapped__"
|
|
641
|
+
):
|
|
642
|
+
DefaultRequestHandler.on_message_send_stream = (
|
|
643
|
+
DefaultRequestHandler.on_message_send_stream.__wrapped__
|
|
644
|
+
)
|
|
346
645
|
|
|
347
|
-
#
|
|
646
|
+
# Uninstrument slima2a
|
|
348
647
|
if importlib.util.find_spec("slima2a"):
|
|
349
648
|
from slima2a.client_transport import SRPCTransport
|
|
350
649
|
|
|
351
|
-
|
|
352
|
-
|
|
650
|
+
if "SRPCTransport.send_message" in _original_methods:
|
|
651
|
+
SRPCTransport.send_message = _original_methods[
|
|
652
|
+
"SRPCTransport.send_message"
|
|
653
|
+
]
|
|
654
|
+
elif hasattr(SRPCTransport, "send_message") and hasattr(
|
|
353
655
|
SRPCTransport.send_message, "__wrapped__"
|
|
354
656
|
):
|
|
355
657
|
SRPCTransport.send_message = SRPCTransport.send_message.__wrapped__
|
|
356
658
|
|
|
357
|
-
if
|
|
659
|
+
if "SRPCTransport.send_message_streaming" in _original_methods:
|
|
660
|
+
SRPCTransport.send_message_streaming = _original_methods[
|
|
661
|
+
"SRPCTransport.send_message_streaming"
|
|
662
|
+
]
|
|
663
|
+
elif hasattr(SRPCTransport, "send_message_streaming") and hasattr(
|
|
358
664
|
SRPCTransport.send_message_streaming, "__wrapped__"
|
|
359
665
|
):
|
|
360
666
|
SRPCTransport.send_message_streaming = (
|
|
361
|
-
SRPCTransport.
|
|
667
|
+
SRPCTransport.send_message_streaming.__wrapped__
|
|
362
668
|
)
|
|
669
|
+
|
|
670
|
+
# Clear stored original methods
|
|
671
|
+
_original_methods.clear()
|
|
@@ -7,16 +7,15 @@ import json
|
|
|
7
7
|
import base64
|
|
8
8
|
import threading
|
|
9
9
|
|
|
10
|
-
from opentelemetry.context import get_value
|
|
11
10
|
from opentelemetry import baggage, context
|
|
12
11
|
from opentelemetry.baggage.propagation import W3CBaggagePropagator
|
|
12
|
+
from opentelemetry.context import get_value
|
|
13
13
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
14
14
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
15
15
|
|
|
16
16
|
from ioa_observe.sdk import TracerWrapper
|
|
17
17
|
from ioa_observe.sdk.client import kv_store
|
|
18
18
|
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
19
|
-
from ioa_observe.sdk.tracing.context_manager import get_tracer
|
|
20
19
|
|
|
21
20
|
_instruments = ("slim-bindings >= 0.4.0",)
|
|
22
21
|
_global_tracer = None
|
|
@@ -81,10 +80,6 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
81
80
|
if traceparent:
|
|
82
81
|
with _kv_lock:
|
|
83
82
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
84
|
-
if not session_id:
|
|
85
|
-
session_id = get_value("session.id")
|
|
86
|
-
if session_id:
|
|
87
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
88
83
|
|
|
89
84
|
headers = {
|
|
90
85
|
"session_id": session_id if session_id else None,
|
|
@@ -142,10 +137,8 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
142
137
|
if traceparent:
|
|
143
138
|
with _kv_lock:
|
|
144
139
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
if session_id:
|
|
148
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
140
|
+
if session_id:
|
|
141
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
149
142
|
|
|
150
143
|
headers = {
|
|
151
144
|
"session_id": session_id if session_id else None,
|
|
@@ -207,10 +200,6 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
207
200
|
if traceparent:
|
|
208
201
|
with _kv_lock:
|
|
209
202
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
210
|
-
if not session_id:
|
|
211
|
-
session_id = get_value("session.id")
|
|
212
|
-
if session_id:
|
|
213
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
214
203
|
|
|
215
204
|
headers = {
|
|
216
205
|
"session_id": session_id if session_id else None,
|
|
@@ -531,15 +520,7 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
531
520
|
if timeout is not None:
|
|
532
521
|
kwargs["timeout"] = timeout
|
|
533
522
|
|
|
534
|
-
|
|
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)
|
|
523
|
+
result = await original_get_message(self, **kwargs)
|
|
543
524
|
|
|
544
525
|
# Handle different return types from get_message
|
|
545
526
|
if result is None:
|
|
@@ -688,8 +669,10 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
688
669
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
689
670
|
if not session_id:
|
|
690
671
|
session_id = get_value("session.id")
|
|
691
|
-
|
|
692
|
-
|
|
672
|
+
if session_id:
|
|
673
|
+
kv_store.set(
|
|
674
|
+
f"execution.{traceparent}", session_id
|
|
675
|
+
)
|
|
693
676
|
|
|
694
677
|
headers = {
|
|
695
678
|
"session_id": session_id if session_id else None,
|
|
@@ -800,8 +783,7 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
800
783
|
@functools.wraps(original_method)
|
|
801
784
|
async def instrumented_session_method(self, *args, **kwargs):
|
|
802
785
|
if _global_tracer:
|
|
803
|
-
|
|
804
|
-
with tracer.start_as_current_span(f"session.{method_name}"):
|
|
786
|
+
with _global_tracer.start_as_current_span(f"session.{method_name}"):
|
|
805
787
|
traceparent = get_current_traceparent()
|
|
806
788
|
|
|
807
789
|
# Handle message wrapping for publish methods
|
|
@@ -857,10 +839,6 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
857
839
|
if traceparent:
|
|
858
840
|
with _kv_lock:
|
|
859
841
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
860
|
-
if not session_id:
|
|
861
|
-
session_id = get_value("session.id")
|
|
862
|
-
if session_id:
|
|
863
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
864
842
|
|
|
865
843
|
if traceparent or session_id:
|
|
866
844
|
headers = {
|
|
@@ -30,8 +30,15 @@ from opentelemetry.sdk.trace.export import (
|
|
|
30
30
|
SimpleSpanProcessor,
|
|
31
31
|
BatchSpanProcessor,
|
|
32
32
|
)
|
|
33
|
-
from opentelemetry.trace import
|
|
33
|
+
from opentelemetry.trace import (
|
|
34
|
+
get_tracer_provider,
|
|
35
|
+
ProxyTracerProvider,
|
|
36
|
+
SpanContext,
|
|
37
|
+
TraceFlags,
|
|
38
|
+
NonRecordingSpan,
|
|
39
|
+
)
|
|
34
40
|
from opentelemetry.context import get_value, attach, set_value
|
|
41
|
+
from opentelemetry.trace import set_span_in_context
|
|
35
42
|
from opentelemetry.instrumentation.threading import ThreadingInstrumentor
|
|
36
43
|
from opentelemetry.metrics import get_meter
|
|
37
44
|
from ioa_observe.sdk.metrics.agents.agent_connections import connection_reliability
|
|
@@ -137,9 +144,12 @@ class TracerWrapper(object):
|
|
|
137
144
|
if not TracerWrapper.endpoint:
|
|
138
145
|
return obj
|
|
139
146
|
|
|
140
|
-
# session activity tracking
|
|
141
|
-
obj._session_last_activity: dict[str, float] = {}
|
|
147
|
+
# session activity tracking: {session_id: (last_activity_time, trace_id)}
|
|
148
|
+
obj._session_last_activity: dict[str, tuple[float, int]] = {}
|
|
142
149
|
obj._session_lock = threading.Lock()
|
|
150
|
+
# Track sessions that have already been ended to prevent duplicates
|
|
151
|
+
obj._ended_sessions: set[str] = set()
|
|
152
|
+
obj._ended_sessions_lock = threading.Lock()
|
|
143
153
|
obj.__image_uploader = image_uploader
|
|
144
154
|
# {(agent_name): [success_count, total_count]}
|
|
145
155
|
obj._agent_execution_counts = {}
|
|
@@ -300,13 +310,15 @@ class TracerWrapper(object):
|
|
|
300
310
|
while True:
|
|
301
311
|
time.sleep(SESSION_WATCHER_INTERVAL_SECONDS)
|
|
302
312
|
now = time.time()
|
|
303
|
-
expired: dict[str, float] = {}
|
|
313
|
+
expired: dict[str, tuple[float, int]] = {}
|
|
304
314
|
|
|
305
315
|
# Find idle sessions and remove them from _session_last_activity
|
|
306
316
|
with self._session_lock:
|
|
307
|
-
for session_id, last_ts in list(
|
|
317
|
+
for session_id, (last_ts, trace_id) in list(
|
|
318
|
+
self._session_last_activity.items()
|
|
319
|
+
):
|
|
308
320
|
if now - last_ts > SESSION_IDLE_TIMEOUT_SECONDS:
|
|
309
|
-
expired[session_id] = last_ts
|
|
321
|
+
expired[session_id] = (last_ts, trace_id)
|
|
310
322
|
del self._session_last_activity[session_id]
|
|
311
323
|
|
|
312
324
|
# Periodic cleanup of processed spans to prevent unbounded memory growth
|
|
@@ -314,19 +326,42 @@ class TracerWrapper(object):
|
|
|
314
326
|
if len(self._processed_spans) > MAX_PROCESSED_SPANS_SIZE:
|
|
315
327
|
self._processed_spans.clear()
|
|
316
328
|
|
|
329
|
+
# Periodic cleanup of ended sessions to prevent unbounded memory growth
|
|
330
|
+
with self._ended_sessions_lock:
|
|
331
|
+
if len(self._ended_sessions) > MAX_PROCESSED_SPANS_SIZE:
|
|
332
|
+
self._ended_sessions.clear()
|
|
333
|
+
|
|
317
334
|
if not expired:
|
|
318
335
|
continue
|
|
319
336
|
|
|
320
337
|
tracer = self.get_tracer()
|
|
321
338
|
|
|
322
339
|
# Iterate over a snapshot and do *not* modify `expired` in the loop
|
|
323
|
-
for session_id,
|
|
324
|
-
|
|
340
|
+
for session_id, (last_ts, trace_id) in list(expired.items()):
|
|
341
|
+
# Check if this session has already been ended to prevent duplicates
|
|
342
|
+
with self._ended_sessions_lock:
|
|
343
|
+
if session_id in self._ended_sessions:
|
|
344
|
+
continue
|
|
345
|
+
self._ended_sessions.add(session_id)
|
|
346
|
+
|
|
347
|
+
# Create a parent context from the stored trace_id to keep session.end under the same trace
|
|
348
|
+
parent_span_context = SpanContext(
|
|
349
|
+
trace_id=trace_id,
|
|
350
|
+
span_id=0, # Use 0 as we don't have a specific parent span
|
|
351
|
+
is_remote=False,
|
|
352
|
+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
353
|
+
)
|
|
354
|
+
parent_span = NonRecordingSpan(parent_span_context)
|
|
355
|
+
parent_context = set_span_in_context(parent_span)
|
|
356
|
+
|
|
357
|
+
with tracer.start_as_current_span(
|
|
358
|
+
"session.end", context=parent_context
|
|
359
|
+
) as span:
|
|
325
360
|
span.set_attribute("session.id", session_id)
|
|
326
361
|
workflow_name = get_value("workflow_name")
|
|
327
362
|
if workflow_name:
|
|
328
363
|
span.set_attribute(OBSERVE_WORKFLOW_NAME, workflow_name)
|
|
329
|
-
span.set_attribute("session.ended_at",
|
|
364
|
+
span.set_attribute("session.ended_at", last_ts)
|
|
330
365
|
|
|
331
366
|
# ensure end spans are exported reasonably fast
|
|
332
367
|
self.flush()
|
|
@@ -337,11 +372,29 @@ class TracerWrapper(object):
|
|
|
337
372
|
tracer = self.get_tracer()
|
|
338
373
|
|
|
339
374
|
with self._session_lock:
|
|
340
|
-
|
|
375
|
+
remaining_sessions = dict(self._session_last_activity)
|
|
341
376
|
self._session_last_activity.clear()
|
|
342
377
|
|
|
343
|
-
for session_id in
|
|
344
|
-
|
|
378
|
+
for session_id, (_, trace_id) in remaining_sessions.items():
|
|
379
|
+
# Check if this session has already been ended to prevent duplicates
|
|
380
|
+
with self._ended_sessions_lock:
|
|
381
|
+
if session_id in self._ended_sessions:
|
|
382
|
+
continue
|
|
383
|
+
self._ended_sessions.add(session_id)
|
|
384
|
+
|
|
385
|
+
# Create a parent context from the stored trace_id to keep session.end under the same trace
|
|
386
|
+
parent_span_context = SpanContext(
|
|
387
|
+
trace_id=trace_id,
|
|
388
|
+
span_id=0, # Use 0 as we don't have a specific parent span
|
|
389
|
+
is_remote=False,
|
|
390
|
+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
|
|
391
|
+
)
|
|
392
|
+
parent_span = NonRecordingSpan(parent_span_context)
|
|
393
|
+
parent_context = set_span_in_context(parent_span)
|
|
394
|
+
|
|
395
|
+
with tracer.start_as_current_span(
|
|
396
|
+
"session.end", context=parent_context
|
|
397
|
+
) as span:
|
|
345
398
|
span.set_attribute("session.id", session_id)
|
|
346
399
|
workflow_name = get_value("workflow_name")
|
|
347
400
|
if workflow_name:
|
|
@@ -441,7 +494,11 @@ class TracerWrapper(object):
|
|
|
441
494
|
session_id = span.attributes.get("session.id")
|
|
442
495
|
if session_id and span.name != "session.end":
|
|
443
496
|
with self._session_lock:
|
|
444
|
-
|
|
497
|
+
# Store both the last activity time and the trace_id for this session
|
|
498
|
+
self._session_last_activity[session_id] = (
|
|
499
|
+
time.time(),
|
|
500
|
+
span.context.trace_id,
|
|
501
|
+
)
|
|
445
502
|
|
|
446
503
|
determine_reliability_score(span)
|
|
447
504
|
# start_time = span.attributes.get("ioa_start_time")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.29
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -8,37 +8,37 @@ Description-Content-Type: text/markdown
|
|
|
8
8
|
License-File: LICENSE.md
|
|
9
9
|
Requires-Dist: colorama==0.4.6
|
|
10
10
|
Requires-Dist: requests>=2.32.3
|
|
11
|
-
Requires-Dist: opentelemetry-api==1.
|
|
11
|
+
Requires-Dist: opentelemetry-api==1.39.1
|
|
12
12
|
Requires-Dist: opentelemetry-distro
|
|
13
|
-
Requires-Dist: opentelemetry-exporter-otlp==1.
|
|
14
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.
|
|
15
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.
|
|
16
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.
|
|
13
|
+
Requires-Dist: opentelemetry-exporter-otlp==1.39.1
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.39.1
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.39.1
|
|
16
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.39.1
|
|
17
17
|
Requires-Dist: opentelemetry-instrumentation
|
|
18
|
-
Requires-Dist: opentelemetry-instrumentation-logging==0.
|
|
19
|
-
Requires-Dist: opentelemetry-instrumentation-openai==0.
|
|
20
|
-
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.
|
|
21
|
-
Requires-Dist: opentelemetry-instrumentation-ollama==0.
|
|
22
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic==0.
|
|
23
|
-
Requires-Dist: opentelemetry-instrumentation-langchain==0.
|
|
24
|
-
Requires-Dist: opentelemetry-instrumentation-bedrock==0.
|
|
25
|
-
Requires-Dist: opentelemetry-instrumentation-cohere==0.
|
|
26
|
-
Requires-Dist: opentelemetry-instrumentation-crewai==0.
|
|
27
|
-
Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.
|
|
28
|
-
Requires-Dist: opentelemetry-instrumentation-groq==0.
|
|
29
|
-
Requires-Dist: opentelemetry-instrumentation-mistralai==0.
|
|
30
|
-
Requires-Dist: opentelemetry-instrumentation-requests==0.
|
|
31
|
-
Requires-Dist: opentelemetry-instrumentation-sagemaker==0.
|
|
32
|
-
Requires-Dist: opentelemetry-instrumentation-threading==0.
|
|
33
|
-
Requires-Dist: opentelemetry-instrumentation-together==0.
|
|
34
|
-
Requires-Dist: opentelemetry-instrumentation-transformers==0.
|
|
35
|
-
Requires-Dist: opentelemetry-instrumentation-urllib3==0.
|
|
36
|
-
Requires-Dist: opentelemetry-instrumentation-vertexai==0.
|
|
37
|
-
Requires-Dist: opentelemetry-proto==1.
|
|
38
|
-
Requires-Dist: opentelemetry-sdk==1.
|
|
39
|
-
Requires-Dist: opentelemetry-semantic-conventions==0.
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-logging==0.60b1
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-openai==0.52.1
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.52.1
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-ollama==0.52.1
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic==0.52.1
|
|
23
|
+
Requires-Dist: opentelemetry-instrumentation-langchain==0.52.1
|
|
24
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock==0.52.1
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-cohere==0.52.1
|
|
26
|
+
Requires-Dist: opentelemetry-instrumentation-crewai==0.52.1
|
|
27
|
+
Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.52.1
|
|
28
|
+
Requires-Dist: opentelemetry-instrumentation-groq==0.52.1
|
|
29
|
+
Requires-Dist: opentelemetry-instrumentation-mistralai==0.52.1
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-requests==0.60b1
|
|
31
|
+
Requires-Dist: opentelemetry-instrumentation-sagemaker==0.52.1
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-threading==0.60b1
|
|
33
|
+
Requires-Dist: opentelemetry-instrumentation-together==0.52.1
|
|
34
|
+
Requires-Dist: opentelemetry-instrumentation-transformers==0.52.1
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3==0.60b1
|
|
36
|
+
Requires-Dist: opentelemetry-instrumentation-vertexai==0.52.1
|
|
37
|
+
Requires-Dist: opentelemetry-proto==1.39.1
|
|
38
|
+
Requires-Dist: opentelemetry-sdk==1.39.1
|
|
39
|
+
Requires-Dist: opentelemetry-semantic-conventions==0.60b1
|
|
40
40
|
Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.11
|
|
41
|
-
Requires-Dist: opentelemetry-util-http==0.
|
|
41
|
+
Requires-Dist: opentelemetry-util-http==0.60b1
|
|
42
42
|
Requires-Dist: langgraph>=0.3.2
|
|
43
43
|
Requires-Dist: langchain>=0.3.19
|
|
44
44
|
Requires-Dist: langchain-openai>=0.3.8
|
|
@@ -14,10 +14,10 @@ ioa_observe/sdk/decorators/base.py,sha256=gezoLMLOo-CWsxzsXbQMsj5HxmchUEmlhTspbq
|
|
|
14
14
|
ioa_observe/sdk/decorators/helpers.py,sha256=I9HXMBivkZpGDtPe9Ad_UU35p_m_wEPate4r_fU0oOA,2705
|
|
15
15
|
ioa_observe/sdk/decorators/util.py,sha256=IebvH9gwZN1en3LblYJUh4bAV2STl6xmp8WpZzBDH2g,30068
|
|
16
16
|
ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
ioa_observe/sdk/instrumentations/a2a.py,sha256=
|
|
17
|
+
ioa_observe/sdk/instrumentations/a2a.py,sha256=smPMHhFONi9B7r2qVHe-YWoFctjHWehO_MR0kY_DgVI,27897
|
|
18
18
|
ioa_observe/sdk/instrumentations/mcp.py,sha256=UG82fWitFT-K-ecnkTKZa-uFTDAs-j2HymvhUwCBRLY,18721
|
|
19
19
|
ioa_observe/sdk/instrumentations/nats.py,sha256=MEYCFqHyRK5HtkjrbGVV5qIWplZ1ZjJjFCB8vtQl6qE,14736
|
|
20
|
-
ioa_observe/sdk/instrumentations/slim.py,sha256=
|
|
20
|
+
ioa_observe/sdk/instrumentations/slim.py,sha256=pwKUnCvDmydysQ9VpqnREf4DyHjN2bZ-e54YyX-de1o,44500
|
|
21
21
|
ioa_observe/sdk/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
ioa_observe/sdk/logging/logging.py,sha256=HZxW9s8Due7jgiNkdI38cIjv5rC9D-Flta3RQMOnpow,2891
|
|
23
23
|
ioa_observe/sdk/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -35,15 +35,15 @@ ioa_observe/sdk/tracing/content_allow_list.py,sha256=1fAkpIwUQ7vDwCTkIVrqeltWQtr
|
|
|
35
35
|
ioa_observe/sdk/tracing/context_manager.py,sha256=O0JEXYa9h8anhW78R8KKBuqS0j4by1E1KXxNIMPnLr8,400
|
|
36
36
|
ioa_observe/sdk/tracing/context_utils.py,sha256=-sYS9vPLI87davV9ubneq5xqbV583CC_c0SmOQS1TAs,2933
|
|
37
37
|
ioa_observe/sdk/tracing/manual.py,sha256=KS6WN-zw9vAACzXYmnMoJm9d1fenYMfvzeK1GrGDPDE,1937
|
|
38
|
-
ioa_observe/sdk/tracing/tracing.py,sha256=
|
|
38
|
+
ioa_observe/sdk/tracing/tracing.py,sha256=pGiIa1S3562QRlTG5mANtU94wkFjKspnX21N_bJ7SfY,53359
|
|
39
39
|
ioa_observe/sdk/tracing/transform_span.py,sha256=XTApi_gJxum7ynvhtcoCfDyK8VVOj91Q1DT6hAeLHA8,8419
|
|
40
40
|
ioa_observe/sdk/utils/__init__.py,sha256=UPn182U-UblF_XwXaFpx8F-TmQTbm1LYf9y89uSp5Hw,704
|
|
41
41
|
ioa_observe/sdk/utils/const.py,sha256=d67dUTAH9UpWvUV9GLBUqn1Sc2knJ55dy-e6YoLrvSo,1318
|
|
42
42
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
43
43
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
44
44
|
ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
|
|
45
|
-
ioa_observe_sdk-1.0.
|
|
46
|
-
ioa_observe_sdk-1.0.
|
|
47
|
-
ioa_observe_sdk-1.0.
|
|
48
|
-
ioa_observe_sdk-1.0.
|
|
49
|
-
ioa_observe_sdk-1.0.
|
|
45
|
+
ioa_observe_sdk-1.0.29.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
46
|
+
ioa_observe_sdk-1.0.29.dist-info/METADATA,sha256=qksjp3ihfWLY1ihgNoZv13Q4mluzMOfTP2kP6Tud1GA,7997
|
|
47
|
+
ioa_observe_sdk-1.0.29.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
48
|
+
ioa_observe_sdk-1.0.29.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
49
|
+
ioa_observe_sdk-1.0.29.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|