ioa-observe-sdk 1.0.19__py3-none-any.whl → 1.0.21__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/decorators/base.py +43 -4
- ioa_observe/sdk/instrumentations/slim.py +512 -68
- {ioa_observe_sdk-1.0.19.dist-info → ioa_observe_sdk-1.0.21.dist-info}/METADATA +15 -14
- {ioa_observe_sdk-1.0.19.dist-info → ioa_observe_sdk-1.0.21.dist-info}/RECORD +7 -7
- {ioa_observe_sdk-1.0.19.dist-info → ioa_observe_sdk-1.0.21.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.19.dist-info → ioa_observe_sdk-1.0.21.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.19.dist-info → ioa_observe_sdk-1.0.21.dist-info}/top_level.txt +0 -0
|
@@ -16,10 +16,10 @@ from ioa_observe.sdk.decorators.helpers import (
|
|
|
16
16
|
_is_async_generator,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
from langgraph.graph.state import CompiledStateGraph
|
|
21
20
|
from opentelemetry import trace
|
|
22
21
|
from opentelemetry import context as context_api
|
|
22
|
+
from opentelemetry.context import get_value, attach, set_value
|
|
23
23
|
from pydantic_core import PydanticSerializationError
|
|
24
24
|
from typing_extensions import ParamSpec
|
|
25
25
|
|
|
@@ -113,7 +113,49 @@ def _setup_span(
|
|
|
113
113
|
with get_tracer() as tracer:
|
|
114
114
|
span = tracer.start_span(span_name)
|
|
115
115
|
ctx = trace.set_span_in_context(span)
|
|
116
|
+
|
|
117
|
+
# Preserve existing context values before attaching new context
|
|
118
|
+
session_id = get_value("session.id")
|
|
119
|
+
current_traceparent = get_value("current_traceparent")
|
|
120
|
+
agent_id = get_value("agent_id")
|
|
121
|
+
application_id_ctx = get_value("application_id")
|
|
122
|
+
association_properties = get_value("association_properties")
|
|
123
|
+
managed_prompt = get_value("managed_prompt")
|
|
124
|
+
prompt_key = get_value("prompt_key")
|
|
125
|
+
prompt_version = get_value("prompt_version")
|
|
126
|
+
prompt_version_name = get_value("prompt_version_name")
|
|
127
|
+
prompt_version_hash = get_value("prompt_version_hash")
|
|
128
|
+
prompt_template = get_value("prompt_template")
|
|
129
|
+
prompt_template_variables = get_value("prompt_template_variables")
|
|
130
|
+
|
|
116
131
|
ctx_token = context_api.attach(ctx)
|
|
132
|
+
|
|
133
|
+
# Re-attach preserved context values to the new context
|
|
134
|
+
if session_id is not None:
|
|
135
|
+
attach(set_value("session.id", session_id))
|
|
136
|
+
if current_traceparent is not None:
|
|
137
|
+
attach(set_value("current_traceparent", current_traceparent))
|
|
138
|
+
if agent_id is not None:
|
|
139
|
+
attach(set_value("agent_id", agent_id))
|
|
140
|
+
if application_id_ctx is not None:
|
|
141
|
+
attach(set_value("application_id", application_id_ctx))
|
|
142
|
+
if association_properties is not None:
|
|
143
|
+
attach(set_value("association_properties", association_properties))
|
|
144
|
+
if managed_prompt is not None:
|
|
145
|
+
attach(set_value("managed_prompt", managed_prompt))
|
|
146
|
+
if prompt_key is not None:
|
|
147
|
+
attach(set_value("prompt_key", prompt_key))
|
|
148
|
+
if prompt_version is not None:
|
|
149
|
+
attach(set_value("prompt_version", prompt_version))
|
|
150
|
+
if prompt_version_name is not None:
|
|
151
|
+
attach(set_value("prompt_version_name", prompt_version_name))
|
|
152
|
+
if prompt_version_hash is not None:
|
|
153
|
+
attach(set_value("prompt_version_hash", prompt_version_hash))
|
|
154
|
+
if prompt_template is not None:
|
|
155
|
+
attach(set_value("prompt_template", prompt_template))
|
|
156
|
+
if prompt_template_variables is not None:
|
|
157
|
+
attach(set_value("prompt_template_variables", prompt_template_variables))
|
|
158
|
+
|
|
117
159
|
if tlp_span_kind == ObserveSpanKindValues.AGENT:
|
|
118
160
|
with trace.get_tracer(__name__).start_span(
|
|
119
161
|
"agent_start_event", context=trace.set_span_in_context(span)
|
|
@@ -127,9 +169,6 @@ def _setup_span(
|
|
|
127
169
|
},
|
|
128
170
|
)
|
|
129
171
|
# start_span.end() # end the span immediately
|
|
130
|
-
# session_id = get_value("session.id")
|
|
131
|
-
# if session_id is not None:
|
|
132
|
-
# span.set_attribute("session.id", session_id)
|
|
133
172
|
if tlp_span_kind in [
|
|
134
173
|
ObserveSpanKindValues.TASK,
|
|
135
174
|
ObserveSpanKindValues.TOOL,
|
|
@@ -16,7 +16,7 @@ from ioa_observe.sdk import TracerWrapper
|
|
|
16
16
|
from ioa_observe.sdk.client import kv_store
|
|
17
17
|
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
18
18
|
|
|
19
|
-
_instruments = ("slim-bindings >= 0.4",)
|
|
19
|
+
_instruments = ("slim-bindings >= 0.4.0",)
|
|
20
20
|
_global_tracer = None
|
|
21
21
|
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
22
22
|
|
|
@@ -39,7 +39,9 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
# Instrument `publish` method - handles multiple signatures
|
|
42
|
+
# In v0.6.0+, publish moved from Slim class to Session objects
|
|
42
43
|
if hasattr(slim_bindings.Slim, "publish"):
|
|
44
|
+
# Legacy v0.5.x app-level publish method
|
|
43
45
|
original_publish = slim_bindings.Slim.publish
|
|
44
46
|
|
|
45
47
|
@functools.wraps(original_publish)
|
|
@@ -166,7 +168,7 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
166
168
|
|
|
167
169
|
slim_bindings.Slim.publish_to = instrumented_publish_to
|
|
168
170
|
|
|
169
|
-
# Instrument `request_reply` (
|
|
171
|
+
# Instrument `request_reply` (v0.4.0+ to v0.5.x method, removed in v0.6.0)
|
|
170
172
|
if hasattr(slim_bindings.Slim, "request_reply"):
|
|
171
173
|
original_request_reply = slim_bindings.Slim.request_reply
|
|
172
174
|
|
|
@@ -290,35 +292,261 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
290
292
|
|
|
291
293
|
slim_bindings.Slim.set_route = instrumented_set_route
|
|
292
294
|
|
|
293
|
-
# Instrument `receive`
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
295
|
+
# Instrument `receive` - only if it exists (removed in v0.6.0)
|
|
296
|
+
if hasattr(slim_bindings.Slim, "receive"):
|
|
297
|
+
original_receive = slim_bindings.Slim.receive
|
|
298
|
+
|
|
299
|
+
@functools.wraps(original_receive)
|
|
300
|
+
async def instrumented_receive(
|
|
301
|
+
self, session=None, timeout=None, *args, **kwargs
|
|
302
|
+
):
|
|
303
|
+
# Handle both old and new API patterns
|
|
304
|
+
if session is not None or timeout is not None:
|
|
305
|
+
# New API pattern with session parameter
|
|
306
|
+
kwargs_with_params = kwargs.copy()
|
|
307
|
+
if session is not None:
|
|
308
|
+
kwargs_with_params["session"] = session
|
|
309
|
+
if timeout is not None:
|
|
310
|
+
kwargs_with_params["timeout"] = timeout
|
|
311
|
+
recv_session, raw_message = await original_receive(
|
|
312
|
+
self, **kwargs_with_params
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
# Legacy API pattern
|
|
316
|
+
recv_session, raw_message = await original_receive(
|
|
317
|
+
self, *args, **kwargs
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if raw_message is None:
|
|
321
|
+
return recv_session, raw_message
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
message_dict = json.loads(raw_message.decode())
|
|
325
|
+
headers = message_dict.get("headers", {})
|
|
326
|
+
|
|
327
|
+
# Extract traceparent and session info from headers
|
|
328
|
+
traceparent = headers.get("traceparent")
|
|
329
|
+
session_id = headers.get("session_id")
|
|
330
|
+
|
|
331
|
+
# Create carrier for context propagation
|
|
332
|
+
carrier = {}
|
|
333
|
+
for key in ["traceparent", "Traceparent", "baggage", "Baggage"]:
|
|
334
|
+
if key.lower() in [k.lower() for k in headers.keys()]:
|
|
335
|
+
for k in headers.keys():
|
|
336
|
+
if k.lower() == key.lower():
|
|
337
|
+
carrier[key.lower()] = headers[k]
|
|
338
|
+
|
|
339
|
+
# Restore trace context
|
|
340
|
+
if carrier and traceparent:
|
|
341
|
+
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
342
|
+
ctx = W3CBaggagePropagator().extract(
|
|
343
|
+
carrier=carrier, context=ctx
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Activate the restored context
|
|
347
|
+
token = context.attach(ctx)
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
# Set execution ID with the restored context
|
|
351
|
+
if session_id and session_id != "None":
|
|
352
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
353
|
+
|
|
354
|
+
# Store in kv_store with thread safety
|
|
355
|
+
with _kv_lock:
|
|
356
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
357
|
+
|
|
358
|
+
# DON'T detach the context yet - we need it to persist for the callback
|
|
359
|
+
# The context will be cleaned up later or by the garbage collector
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
# Only detach on error
|
|
363
|
+
context.detach(token)
|
|
364
|
+
raise e
|
|
365
|
+
elif traceparent and session_id and session_id != "None":
|
|
366
|
+
# Even without carrier context, set session ID if we have the data
|
|
367
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
368
|
+
|
|
369
|
+
# Fallback: check stored execution ID if not found in headers
|
|
370
|
+
if traceparent and (not session_id or session_id == "None"):
|
|
371
|
+
with _kv_lock:
|
|
372
|
+
stored_session_id = kv_store.get(f"execution.{traceparent}")
|
|
373
|
+
if stored_session_id:
|
|
374
|
+
session_id = stored_session_id
|
|
375
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
376
|
+
|
|
377
|
+
# Process and clean the message
|
|
378
|
+
message_to_return = message_dict.copy()
|
|
379
|
+
if "headers" in message_to_return:
|
|
380
|
+
headers_copy = message_to_return["headers"].copy()
|
|
381
|
+
# Remove tracing-specific headers but keep other headers
|
|
382
|
+
headers_copy.pop("traceparent", None)
|
|
383
|
+
headers_copy.pop("session_id", None)
|
|
384
|
+
headers_copy.pop("slim_session_id", None)
|
|
385
|
+
if headers_copy:
|
|
386
|
+
message_to_return["headers"] = headers_copy
|
|
387
|
+
else:
|
|
388
|
+
message_to_return.pop("headers", None)
|
|
389
|
+
|
|
390
|
+
# Return processed message
|
|
391
|
+
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
392
|
+
payload = message_to_return["payload"]
|
|
393
|
+
if isinstance(payload, str):
|
|
394
|
+
try:
|
|
395
|
+
payload_dict = json.loads(payload)
|
|
396
|
+
return recv_session, json.dumps(payload_dict).encode(
|
|
397
|
+
"utf-8"
|
|
398
|
+
)
|
|
399
|
+
except json.JSONDecodeError:
|
|
400
|
+
return recv_session, payload.encode(
|
|
401
|
+
"utf-8"
|
|
402
|
+
) if isinstance(payload, str) else payload
|
|
403
|
+
return recv_session, json.dumps(payload).encode(
|
|
404
|
+
"utf-8"
|
|
405
|
+
) if isinstance(payload, (dict, list)) else payload
|
|
406
|
+
else:
|
|
407
|
+
return recv_session, json.dumps(message_to_return).encode(
|
|
408
|
+
"utf-8"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
print(f"Error processing message: {e}")
|
|
413
|
+
return recv_session, raw_message
|
|
414
|
+
|
|
415
|
+
slim_bindings.Slim.receive = instrumented_receive
|
|
416
|
+
|
|
417
|
+
# Instrument `connect` - only if it exists
|
|
418
|
+
if hasattr(slim_bindings.Slim, "connect"):
|
|
419
|
+
original_connect = slim_bindings.Slim.connect
|
|
420
|
+
|
|
421
|
+
@functools.wraps(original_connect)
|
|
422
|
+
async def instrumented_connect(self, *args, **kwargs):
|
|
423
|
+
if _global_tracer:
|
|
424
|
+
with _global_tracer.start_as_current_span("slim.connect"):
|
|
425
|
+
return await original_connect(self, *args, **kwargs)
|
|
426
|
+
else:
|
|
427
|
+
return await original_connect(self, *args, **kwargs)
|
|
428
|
+
|
|
429
|
+
slim_bindings.Slim.connect = instrumented_connect
|
|
430
|
+
|
|
431
|
+
# Instrument `create_session` (new v0.4.0+ method)
|
|
432
|
+
if hasattr(slim_bindings.Slim, "create_session"):
|
|
433
|
+
original_create_session = slim_bindings.Slim.create_session
|
|
434
|
+
|
|
435
|
+
@functools.wraps(original_create_session)
|
|
436
|
+
async def instrumented_create_session(self, config, *args, **kwargs):
|
|
437
|
+
if _global_tracer:
|
|
438
|
+
with _global_tracer.start_as_current_span(
|
|
439
|
+
"slim.create_session"
|
|
440
|
+
) as span:
|
|
441
|
+
session_info = await original_create_session(
|
|
442
|
+
self, config, *args, **kwargs
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Add session attributes to span
|
|
446
|
+
if hasattr(session_info, "id"):
|
|
447
|
+
span.set_attribute("slim.session.id", str(session_info.id))
|
|
448
|
+
|
|
449
|
+
return session_info
|
|
450
|
+
else:
|
|
451
|
+
return await original_create_session(self, config, *args, **kwargs)
|
|
452
|
+
|
|
453
|
+
slim_bindings.Slim.create_session = instrumented_create_session
|
|
454
|
+
|
|
455
|
+
# Instrument new v0.6.0+ session-level methods
|
|
456
|
+
# These methods are available on Session objects, not the Slim app
|
|
457
|
+
self._instrument_session_methods(slim_bindings)
|
|
458
|
+
|
|
459
|
+
# Instrument new v0.6.0+ app-level methods
|
|
460
|
+
# listen_for_session replaces app.receive() for new sessions in v0.6.0+
|
|
461
|
+
if hasattr(slim_bindings.Slim, "listen_for_session"):
|
|
462
|
+
original_listen_for_session = slim_bindings.Slim.listen_for_session
|
|
463
|
+
|
|
464
|
+
@functools.wraps(original_listen_for_session)
|
|
465
|
+
async def instrumented_listen_for_session(self, *args, **kwargs):
|
|
466
|
+
if _global_tracer:
|
|
467
|
+
with _global_tracer.start_as_current_span(
|
|
468
|
+
"slim.listen_for_session"
|
|
469
|
+
):
|
|
470
|
+
session = await original_listen_for_session(
|
|
471
|
+
self, *args, **kwargs
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
return session
|
|
475
|
+
else:
|
|
476
|
+
return await original_listen_for_session(self, *args, **kwargs)
|
|
477
|
+
|
|
478
|
+
slim_bindings.Slim.listen_for_session = instrumented_listen_for_session
|
|
479
|
+
|
|
480
|
+
def _instrument_session_methods(self, slim_bindings):
|
|
481
|
+
# In v0.6.0+, we need to instrument session classes dynamically
|
|
482
|
+
# Try to find session-related classes in the slim_bindings module
|
|
483
|
+
session_classes = []
|
|
484
|
+
|
|
485
|
+
# Look for common session class names
|
|
486
|
+
for attr_name in ["Session", "P2PSession", "GroupSession"]:
|
|
487
|
+
if hasattr(slim_bindings, attr_name):
|
|
488
|
+
session_class = getattr(slim_bindings, attr_name)
|
|
489
|
+
session_classes.append((attr_name, session_class))
|
|
490
|
+
|
|
491
|
+
# Also look for any class that has session-like methods
|
|
492
|
+
for attr_name in dir(slim_bindings):
|
|
493
|
+
attr = getattr(slim_bindings, attr_name)
|
|
494
|
+
if isinstance(attr, type) and (
|
|
495
|
+
hasattr(attr, "get_message") or hasattr(attr, "publish")
|
|
496
|
+
):
|
|
497
|
+
session_classes.append((attr_name, attr))
|
|
498
|
+
|
|
499
|
+
# Instrument session methods for found classes
|
|
500
|
+
for _, session_class in session_classes:
|
|
501
|
+
# Instrument get_message (v0.6.0+ replacement for receive)
|
|
502
|
+
if hasattr(session_class, "get_message"):
|
|
503
|
+
self._instrument_session_get_message(session_class)
|
|
504
|
+
|
|
505
|
+
# Instrument session publish methods
|
|
506
|
+
if hasattr(session_class, "publish"):
|
|
507
|
+
self._instrument_session_publish(session_class, "publish")
|
|
508
|
+
|
|
509
|
+
if hasattr(session_class, "publish_to"):
|
|
510
|
+
self._instrument_session_publish(session_class, "publish_to")
|
|
511
|
+
|
|
512
|
+
def _instrument_session_get_message(self, session_class):
|
|
513
|
+
"""Instrument session.get_message method (v0.6.0+)"""
|
|
514
|
+
original_get_message = session_class.get_message
|
|
515
|
+
|
|
516
|
+
@functools.wraps(original_get_message)
|
|
517
|
+
async def instrumented_get_message(self, timeout=None, *args, **kwargs):
|
|
518
|
+
# Handle the message reception similar to the old receive method
|
|
519
|
+
if timeout is not None:
|
|
520
|
+
kwargs["timeout"] = timeout
|
|
521
|
+
|
|
522
|
+
result = await original_get_message(self, **kwargs)
|
|
523
|
+
|
|
524
|
+
# Handle different return types from get_message
|
|
525
|
+
if result is None:
|
|
526
|
+
return result
|
|
527
|
+
|
|
528
|
+
# Check if get_message returns a tuple (context, message) or just message
|
|
529
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
530
|
+
message_context, raw_message = result
|
|
311
531
|
else:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
self, *args, **kwargs
|
|
315
|
-
)
|
|
532
|
+
raw_message = result
|
|
533
|
+
message_context = None
|
|
316
534
|
|
|
317
535
|
if raw_message is None:
|
|
318
|
-
return
|
|
536
|
+
return result
|
|
319
537
|
|
|
320
538
|
try:
|
|
321
|
-
|
|
539
|
+
# Handle different message types
|
|
540
|
+
if isinstance(raw_message, bytes):
|
|
541
|
+
message_dict = json.loads(raw_message.decode())
|
|
542
|
+
elif isinstance(raw_message, str):
|
|
543
|
+
message_dict = json.loads(raw_message)
|
|
544
|
+
elif isinstance(raw_message, dict):
|
|
545
|
+
message_dict = raw_message
|
|
546
|
+
else:
|
|
547
|
+
# Unknown type, return as-is
|
|
548
|
+
return result
|
|
549
|
+
|
|
322
550
|
headers = message_dict.get("headers", {})
|
|
323
551
|
|
|
324
552
|
# Extract traceparent and session info from headers
|
|
@@ -349,8 +577,16 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
349
577
|
# Store in kv_store with thread safety
|
|
350
578
|
with _kv_lock:
|
|
351
579
|
kv_store.set(f"execution.{traceparent}", session_id)
|
|
352
|
-
|
|
580
|
+
|
|
581
|
+
# DON'T detach the context yet - we need it to persist for the callback
|
|
582
|
+
|
|
583
|
+
except Exception as e:
|
|
584
|
+
# Only detach on error
|
|
353
585
|
context.detach(token)
|
|
586
|
+
raise e
|
|
587
|
+
elif traceparent and session_id and session_id != "None":
|
|
588
|
+
# Even without carrier context, set session ID if we have the data
|
|
589
|
+
set_session_id(session_id, traceparent=traceparent)
|
|
354
590
|
|
|
355
591
|
# Fallback: check stored execution ID if not found in headers
|
|
356
592
|
if traceparent and (not session_id or session_id == "None"):
|
|
@@ -373,68 +609,257 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
373
609
|
else:
|
|
374
610
|
message_to_return.pop("headers", None)
|
|
375
611
|
|
|
376
|
-
# Return processed message
|
|
612
|
+
# Return processed message, maintaining original return format
|
|
377
613
|
if len(message_to_return) == 1 and "payload" in message_to_return:
|
|
378
614
|
payload = message_to_return["payload"]
|
|
379
615
|
if isinstance(payload, str):
|
|
380
616
|
try:
|
|
381
617
|
payload_dict = json.loads(payload)
|
|
382
|
-
|
|
383
|
-
"utf-8"
|
|
384
|
-
)
|
|
618
|
+
processed_message = json.dumps(payload_dict).encode("utf-8")
|
|
385
619
|
except json.JSONDecodeError:
|
|
386
|
-
|
|
387
|
-
payload
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
620
|
+
processed_message = (
|
|
621
|
+
payload.encode("utf-8")
|
|
622
|
+
if isinstance(payload, str)
|
|
623
|
+
else payload
|
|
624
|
+
)
|
|
625
|
+
else:
|
|
626
|
+
processed_message = (
|
|
627
|
+
json.dumps(payload).encode("utf-8")
|
|
628
|
+
if isinstance(payload, (dict, list))
|
|
629
|
+
else payload
|
|
630
|
+
)
|
|
631
|
+
else:
|
|
632
|
+
processed_message = json.dumps(message_to_return).encode("utf-8")
|
|
633
|
+
|
|
634
|
+
# Return in the same format as received
|
|
635
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
636
|
+
return (message_context, processed_message)
|
|
392
637
|
else:
|
|
393
|
-
return
|
|
638
|
+
return processed_message
|
|
394
639
|
|
|
395
640
|
except Exception as e:
|
|
396
641
|
print(f"Error processing message: {e}")
|
|
397
|
-
return
|
|
642
|
+
return result
|
|
398
643
|
|
|
399
|
-
|
|
644
|
+
session_class.get_message = instrumented_get_message
|
|
400
645
|
|
|
401
|
-
|
|
402
|
-
|
|
646
|
+
def _instrument_session_publish(self, session_class, method_name):
|
|
647
|
+
"""Instrument session publish methods"""
|
|
648
|
+
original_method = getattr(session_class, method_name)
|
|
403
649
|
|
|
404
|
-
@functools.wraps(
|
|
405
|
-
async def
|
|
650
|
+
@functools.wraps(original_method)
|
|
651
|
+
async def instrumented_session_publish(self, *args, **kwargs):
|
|
406
652
|
if _global_tracer:
|
|
407
|
-
with _global_tracer.start_as_current_span(
|
|
408
|
-
|
|
653
|
+
with _global_tracer.start_as_current_span(
|
|
654
|
+
f"session.{method_name}"
|
|
655
|
+
) as span:
|
|
656
|
+
traceparent = get_current_traceparent()
|
|
657
|
+
|
|
658
|
+
# Add session context to span
|
|
659
|
+
if hasattr(self, "id"):
|
|
660
|
+
span.set_attribute("slim.session.id", str(self.id))
|
|
661
|
+
|
|
662
|
+
# Handle message wrapping
|
|
663
|
+
if args:
|
|
664
|
+
# Thread-safe access to kv_store
|
|
665
|
+
session_id = None
|
|
666
|
+
if traceparent:
|
|
667
|
+
with _kv_lock:
|
|
668
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
669
|
+
if session_id:
|
|
670
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
671
|
+
|
|
672
|
+
headers = {
|
|
673
|
+
"session_id": session_id if session_id else None,
|
|
674
|
+
"traceparent": traceparent,
|
|
675
|
+
"slim_session_id": str(self.id)
|
|
676
|
+
if hasattr(self, "id")
|
|
677
|
+
else None,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Set baggage context
|
|
681
|
+
if traceparent and session_id:
|
|
682
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
683
|
+
|
|
684
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
685
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
686
|
+
if len(args) > message_idx:
|
|
687
|
+
args_list = list(args)
|
|
688
|
+
message = args_list[message_idx]
|
|
689
|
+
wrapped_message = (
|
|
690
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
691
|
+
None, message, headers
|
|
692
|
+
)
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Convert wrapped message back to bytes if needed
|
|
696
|
+
if isinstance(wrapped_message, dict):
|
|
697
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
698
|
+
"utf-8"
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
message_to_send = wrapped_message
|
|
702
|
+
|
|
703
|
+
args_list[message_idx] = message_to_send
|
|
704
|
+
args = tuple(args_list)
|
|
705
|
+
|
|
706
|
+
return await original_method(self, *args, **kwargs)
|
|
409
707
|
else:
|
|
410
|
-
|
|
708
|
+
# Handle message wrapping even without tracing
|
|
709
|
+
if args:
|
|
710
|
+
traceparent = get_current_traceparent()
|
|
711
|
+
session_id = None
|
|
712
|
+
if traceparent:
|
|
713
|
+
with _kv_lock:
|
|
714
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
715
|
+
|
|
716
|
+
if traceparent or session_id:
|
|
717
|
+
headers = {
|
|
718
|
+
"session_id": session_id if session_id else None,
|
|
719
|
+
"traceparent": traceparent,
|
|
720
|
+
"slim_session_id": str(self.id)
|
|
721
|
+
if hasattr(self, "id")
|
|
722
|
+
else None,
|
|
723
|
+
}
|
|
411
724
|
|
|
412
|
-
|
|
725
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
726
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
727
|
+
if len(args) > message_idx:
|
|
728
|
+
args_list = list(args)
|
|
729
|
+
message = args_list[message_idx]
|
|
730
|
+
wrapped_message = (
|
|
731
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
732
|
+
None, message, headers
|
|
733
|
+
)
|
|
734
|
+
)
|
|
413
735
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
736
|
+
if isinstance(wrapped_message, dict):
|
|
737
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
738
|
+
"utf-8"
|
|
739
|
+
)
|
|
740
|
+
else:
|
|
741
|
+
message_to_send = wrapped_message
|
|
417
742
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if _global_tracer:
|
|
421
|
-
with _global_tracer.start_as_current_span(
|
|
422
|
-
"slim.create_session"
|
|
423
|
-
) as span:
|
|
424
|
-
session_info = await original_create_session(
|
|
425
|
-
self, config, *args, **kwargs
|
|
426
|
-
)
|
|
743
|
+
args_list[message_idx] = message_to_send
|
|
744
|
+
args = tuple(args_list)
|
|
427
745
|
|
|
428
|
-
|
|
429
|
-
if hasattr(session_info, "id"):
|
|
430
|
-
span.set_attribute("slim.session.id", str(session_info.id))
|
|
746
|
+
return await original_method(self, *args, **kwargs)
|
|
431
747
|
|
|
432
|
-
|
|
433
|
-
else:
|
|
434
|
-
return await original_create_session(self, config, *args, **kwargs)
|
|
748
|
+
setattr(session_class, method_name, instrumented_session_publish)
|
|
435
749
|
|
|
436
|
-
|
|
750
|
+
def _instrument_session_method_if_exists(self, slim_bindings, method_name):
|
|
751
|
+
"""Helper to instrument a session method if it exists"""
|
|
752
|
+
|
|
753
|
+
# Look for session classes that might have this method
|
|
754
|
+
for attr_name in dir(slim_bindings):
|
|
755
|
+
attr = getattr(slim_bindings, attr_name)
|
|
756
|
+
if hasattr(attr, method_name):
|
|
757
|
+
original_method = getattr(attr, method_name)
|
|
758
|
+
|
|
759
|
+
if callable(original_method):
|
|
760
|
+
instrumented_method = self._create_session_method_wrapper(
|
|
761
|
+
method_name, original_method
|
|
762
|
+
)
|
|
763
|
+
setattr(attr, method_name, instrumented_method)
|
|
764
|
+
|
|
765
|
+
def _create_session_method_wrapper(self, method_name, original_method):
|
|
766
|
+
"""Create an instrumented wrapper for session methods"""
|
|
767
|
+
|
|
768
|
+
@functools.wraps(original_method)
|
|
769
|
+
async def instrumented_session_method(self, *args, **kwargs):
|
|
770
|
+
if _global_tracer:
|
|
771
|
+
with _global_tracer.start_as_current_span(f"session.{method_name}"):
|
|
772
|
+
traceparent = get_current_traceparent()
|
|
773
|
+
|
|
774
|
+
# Handle message wrapping for publish methods
|
|
775
|
+
if method_name in ["publish", "publish_to"] and args:
|
|
776
|
+
# Thread-safe access to kv_store
|
|
777
|
+
session_id = None
|
|
778
|
+
if traceparent:
|
|
779
|
+
with _kv_lock:
|
|
780
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
781
|
+
if session_id:
|
|
782
|
+
kv_store.set(f"execution.{traceparent}", session_id)
|
|
783
|
+
|
|
784
|
+
headers = {
|
|
785
|
+
"session_id": session_id if session_id else None,
|
|
786
|
+
"traceparent": traceparent,
|
|
787
|
+
"slim_session_id": str(self.id)
|
|
788
|
+
if hasattr(self, "id")
|
|
789
|
+
else None,
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
# Set baggage context
|
|
793
|
+
if traceparent and session_id:
|
|
794
|
+
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
795
|
+
|
|
796
|
+
# Wrap the message (first argument for publish, second for publish_to)
|
|
797
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
798
|
+
if len(args) > message_idx:
|
|
799
|
+
args_list = list(args)
|
|
800
|
+
message = args_list[message_idx]
|
|
801
|
+
wrapped_message = SLIMInstrumentor._wrap_message_with_headers(
|
|
802
|
+
None,
|
|
803
|
+
message,
|
|
804
|
+
headers, # Pass None for self since it's a static method
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Convert wrapped message back to bytes if needed
|
|
808
|
+
if isinstance(wrapped_message, dict):
|
|
809
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
810
|
+
"utf-8"
|
|
811
|
+
)
|
|
812
|
+
else:
|
|
813
|
+
message_to_send = wrapped_message
|
|
814
|
+
|
|
815
|
+
args_list[message_idx] = message_to_send
|
|
816
|
+
args = tuple(args_list)
|
|
437
817
|
|
|
818
|
+
return await original_method(self, *args, **kwargs)
|
|
819
|
+
else:
|
|
820
|
+
# Handle message wrapping even without tracing
|
|
821
|
+
if method_name in ["publish", "publish_to"] and args:
|
|
822
|
+
traceparent = get_current_traceparent()
|
|
823
|
+
session_id = None
|
|
824
|
+
if traceparent:
|
|
825
|
+
with _kv_lock:
|
|
826
|
+
session_id = kv_store.get(f"execution.{traceparent}")
|
|
827
|
+
|
|
828
|
+
if traceparent or session_id:
|
|
829
|
+
headers = {
|
|
830
|
+
"session_id": session_id if session_id else None,
|
|
831
|
+
"traceparent": traceparent,
|
|
832
|
+
"slim_session_id": str(self.id)
|
|
833
|
+
if hasattr(self, "id")
|
|
834
|
+
else None,
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
# Wrap the message
|
|
838
|
+
message_idx = 1 if method_name == "publish_to" else 0
|
|
839
|
+
if len(args) > message_idx:
|
|
840
|
+
args_list = list(args)
|
|
841
|
+
message = args_list[message_idx]
|
|
842
|
+
wrapped_message = (
|
|
843
|
+
SLIMInstrumentor._wrap_message_with_headers(
|
|
844
|
+
None, message, headers
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
if isinstance(wrapped_message, dict):
|
|
849
|
+
message_to_send = json.dumps(wrapped_message).encode(
|
|
850
|
+
"utf-8"
|
|
851
|
+
)
|
|
852
|
+
else:
|
|
853
|
+
message_to_send = wrapped_message
|
|
854
|
+
|
|
855
|
+
args_list[message_idx] = message_to_send
|
|
856
|
+
args = tuple(args_list)
|
|
857
|
+
|
|
858
|
+
return await original_method(self, *args, **kwargs)
|
|
859
|
+
|
|
860
|
+
return instrumented_session_method
|
|
861
|
+
|
|
862
|
+
@staticmethod
|
|
438
863
|
def _wrap_message_with_headers(self, message, headers):
|
|
439
864
|
"""Helper method to wrap messages with headers consistently"""
|
|
440
865
|
if isinstance(message, bytes):
|
|
@@ -497,12 +922,13 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
497
922
|
methods_to_restore = [
|
|
498
923
|
"publish",
|
|
499
924
|
"publish_to",
|
|
500
|
-
"request_reply",
|
|
925
|
+
"request_reply", # v0.4.0-v0.5.x only
|
|
501
926
|
"receive",
|
|
502
927
|
"connect",
|
|
503
928
|
"create_session",
|
|
504
929
|
"invite",
|
|
505
930
|
"set_route",
|
|
931
|
+
"listen_for_session", # v0.6.0+
|
|
506
932
|
]
|
|
507
933
|
|
|
508
934
|
for method_name in methods_to_restore:
|
|
@@ -512,3 +938,21 @@ class SLIMInstrumentor(BaseInstrumentor):
|
|
|
512
938
|
setattr(
|
|
513
939
|
slim_bindings.Slim, method_name, original_method.__wrapped__
|
|
514
940
|
)
|
|
941
|
+
|
|
942
|
+
# Also try to restore session-level methods (v0.6.0+)
|
|
943
|
+
# This is best-effort since session classes may vary
|
|
944
|
+
session_methods_to_restore = [
|
|
945
|
+
"publish",
|
|
946
|
+
"publish_to",
|
|
947
|
+
"get_message",
|
|
948
|
+
"invite",
|
|
949
|
+
"remove",
|
|
950
|
+
]
|
|
951
|
+
|
|
952
|
+
for attr_name in dir(slim_bindings):
|
|
953
|
+
attr = getattr(slim_bindings, attr_name)
|
|
954
|
+
for method_name in session_methods_to_restore:
|
|
955
|
+
if hasattr(attr, method_name):
|
|
956
|
+
original_method = getattr(attr, method_name)
|
|
957
|
+
if hasattr(original_method, "__wrapped__"):
|
|
958
|
+
setattr(attr, method_name, original_method.__wrapped__)
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.21
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
|
+
License-Expression: Apache-2.0
|
|
5
6
|
Requires-Python: >=3.10
|
|
6
7
|
Description-Content-Type: text/markdown
|
|
7
8
|
License-File: LICENSE.md
|
|
8
9
|
Requires-Dist: colorama==0.4.6
|
|
9
10
|
Requires-Dist: requests>=2.32.3
|
|
10
|
-
Requires-Dist: opentelemetry-api==1.
|
|
11
|
+
Requires-Dist: opentelemetry-api==1.37.0
|
|
11
12
|
Requires-Dist: opentelemetry-distro
|
|
12
|
-
Requires-Dist: opentelemetry-exporter-otlp==1.
|
|
13
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.
|
|
14
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.
|
|
15
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.
|
|
13
|
+
Requires-Dist: opentelemetry-exporter-otlp==1.37.0
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.37.0
|
|
15
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.37.0
|
|
16
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.37.0
|
|
16
17
|
Requires-Dist: opentelemetry-instrumentation
|
|
17
|
-
Requires-Dist: opentelemetry-instrumentation-logging==0.
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-logging==0.58b0
|
|
18
19
|
Requires-Dist: opentelemetry-instrumentation-openai==0.43.1
|
|
19
20
|
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.43.1
|
|
20
21
|
Requires-Dist: opentelemetry-instrumentation-ollama==0.43.1
|
|
@@ -26,18 +27,18 @@ Requires-Dist: opentelemetry-instrumentation-crewai==0.43.1
|
|
|
26
27
|
Requires-Dist: opentelemetry-instrumentation-google-generativeai==0.43.1
|
|
27
28
|
Requires-Dist: opentelemetry-instrumentation-groq==0.43.1
|
|
28
29
|
Requires-Dist: opentelemetry-instrumentation-mistralai==0.43.1
|
|
29
|
-
Requires-Dist: opentelemetry-instrumentation-requests==0.
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-requests==0.58b0
|
|
30
31
|
Requires-Dist: opentelemetry-instrumentation-sagemaker==0.43.1
|
|
31
|
-
Requires-Dist: opentelemetry-instrumentation-threading==0.
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-threading==0.58b0
|
|
32
33
|
Requires-Dist: opentelemetry-instrumentation-together==0.43.1
|
|
33
34
|
Requires-Dist: opentelemetry-instrumentation-transformers==0.43.1
|
|
34
|
-
Requires-Dist: opentelemetry-instrumentation-urllib3==0.
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3==0.58b0
|
|
35
36
|
Requires-Dist: opentelemetry-instrumentation-vertexai==0.43.1
|
|
36
|
-
Requires-Dist: opentelemetry-proto==1.
|
|
37
|
-
Requires-Dist: opentelemetry-sdk==1.
|
|
38
|
-
Requires-Dist: opentelemetry-semantic-conventions==0.
|
|
37
|
+
Requires-Dist: opentelemetry-proto==1.37.0
|
|
38
|
+
Requires-Dist: opentelemetry-sdk==1.37.0
|
|
39
|
+
Requires-Dist: opentelemetry-semantic-conventions==0.58b0
|
|
39
40
|
Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.11
|
|
40
|
-
Requires-Dist: opentelemetry-util-http==0.
|
|
41
|
+
Requires-Dist: opentelemetry-util-http==0.58b0
|
|
41
42
|
Requires-Dist: langgraph>=0.3.2
|
|
42
43
|
Requires-Dist: langchain>=0.3.19
|
|
43
44
|
Requires-Dist: langchain-openai>=0.3.8
|
|
@@ -10,13 +10,13 @@ ioa_observe/sdk/config/__init__.py,sha256=8aVNaw0yRNLFPxlf97iOZLlJVcV81ivSDnudH2
|
|
|
10
10
|
ioa_observe/sdk/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
ioa_observe/sdk/connectors/slim.py,sha256=NwbKEV7d5NIOqmG8zKqtgGigSJl7kf3QJ65z2gxpsY8,8498
|
|
12
12
|
ioa_observe/sdk/decorators/__init__.py,sha256=qCpJAv98eLKs3I5EMXJVTV0s49Nc6QDSOHNh5rW5vLg,4268
|
|
13
|
-
ioa_observe/sdk/decorators/base.py,sha256=
|
|
13
|
+
ioa_observe/sdk/decorators/base.py,sha256=pnoj73UrpvaZmOPbcpGRRYCwg7LBAgO8PiBDh5ntn0c,32694
|
|
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
17
|
ioa_observe/sdk/instrumentations/a2a.py,sha256=ZpqvPl4u-yheQzSdBfxnZhWFZ8ntbKni_uaW3IDyjqw,6309
|
|
18
18
|
ioa_observe/sdk/instrumentations/mcp.py,sha256=vRM3ofnn7AMmry2RrfyZnZVPEutLWiDMghx2TSnm0Wk,18569
|
|
19
|
-
ioa_observe/sdk/instrumentations/slim.py,sha256=
|
|
19
|
+
ioa_observe/sdk/instrumentations/slim.py,sha256=r_vIuYUo-IhSRuYWymqfS26swDZ8_CFDH6p_QbduLKU,43757
|
|
20
20
|
ioa_observe/sdk/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
ioa_observe/sdk/logging/logging.py,sha256=HZxW9s8Due7jgiNkdI38cIjv5rC9D-Flta3RQMOnpow,2891
|
|
22
22
|
ioa_observe/sdk/metrics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -41,8 +41,8 @@ ioa_observe/sdk/utils/const.py,sha256=d67dUTAH9UpWvUV9GLBUqn1Sc2knJ55dy-e6YoLrvS
|
|
|
41
41
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
42
42
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
43
43
|
ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
|
|
44
|
-
ioa_observe_sdk-1.0.
|
|
45
|
-
ioa_observe_sdk-1.0.
|
|
46
|
-
ioa_observe_sdk-1.0.
|
|
47
|
-
ioa_observe_sdk-1.0.
|
|
48
|
-
ioa_observe_sdk-1.0.
|
|
44
|
+
ioa_observe_sdk-1.0.21.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
45
|
+
ioa_observe_sdk-1.0.21.dist-info/METADATA,sha256=M6yTeHdBIWvp72-hLTpv8AztbhAxUwA8hVoduyRqxHU,7058
|
|
46
|
+
ioa_observe_sdk-1.0.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
ioa_observe_sdk-1.0.21.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
48
|
+
ioa_observe_sdk-1.0.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|