ioa-observe-sdk 1.0.9__py3-none-any.whl → 1.0.11__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/__init__.py +22 -9
- ioa_observe/sdk/decorators/base.py +112 -51
- {ioa_observe_sdk-1.0.9.dist-info → ioa_observe_sdk-1.0.11.dist-info}/METADATA +2 -1
- {ioa_observe_sdk-1.0.9.dist-info → ioa_observe_sdk-1.0.11.dist-info}/RECORD +7 -7
- {ioa_observe_sdk-1.0.9.dist-info → ioa_observe_sdk-1.0.11.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.9.dist-info → ioa_observe_sdk-1.0.11.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.9.dist-info → ioa_observe_sdk-1.0.11.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
3
|
+
import inspect
|
|
4
4
|
from typing import Optional, Union, TypeVar, Callable, Awaitable
|
|
5
5
|
|
|
6
6
|
from typing_extensions import ParamSpec
|
|
@@ -50,18 +50,31 @@ def workflow(
|
|
|
50
50
|
Union[ObserveSpanKindValues, str]
|
|
51
51
|
] = ObserveSpanKindValues.WORKFLOW,
|
|
52
52
|
) -> Callable[[F], F]:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
def decorator(target):
|
|
54
|
+
# Check if target is a class
|
|
55
|
+
if inspect.isclass(target):
|
|
56
|
+
return entity_class(
|
|
57
|
+
name=name,
|
|
58
|
+
description=description,
|
|
59
|
+
version=version,
|
|
60
|
+
method_name=method_name,
|
|
61
|
+
tlp_span_kind=tlp_span_kind,
|
|
62
|
+
)(target)
|
|
63
|
+
else:
|
|
64
|
+
# Target is a function/method
|
|
65
|
+
return entity_method(
|
|
66
|
+
name=name,
|
|
67
|
+
description=description,
|
|
68
|
+
version=version,
|
|
69
|
+
tlp_span_kind=tlp_span_kind,
|
|
70
|
+
)(target)
|
|
71
|
+
|
|
72
|
+
return decorator
|
|
61
73
|
|
|
62
74
|
|
|
63
75
|
def graph(
|
|
64
76
|
name: Optional[str] = None,
|
|
77
|
+
description: Optional[str] = None,
|
|
65
78
|
version: Optional[int] = None,
|
|
66
79
|
method_name: Optional[str] = None,
|
|
67
80
|
) -> Callable[[F], F]:
|
|
@@ -43,7 +43,6 @@ from ioa_observe.sdk.utils.const import (
|
|
|
43
43
|
from ioa_observe.sdk.utils.json_encoder import JSONEncoder
|
|
44
44
|
from ioa_observe.sdk.metrics.agent import topology_dynamism, determinism_score
|
|
45
45
|
|
|
46
|
-
|
|
47
46
|
P = ParamSpec("P")
|
|
48
47
|
|
|
49
48
|
R = TypeVar("R")
|
|
@@ -158,15 +157,41 @@ def _handle_span_input(span, args, kwargs, cls=None):
|
|
|
158
157
|
"""Handles entity input logging in JSON for both sync and async functions"""
|
|
159
158
|
try:
|
|
160
159
|
if _should_send_prompts():
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
# Use a safer serialization approach to avoid recursion
|
|
161
|
+
safe_args = []
|
|
162
|
+
safe_kwargs = {}
|
|
163
|
+
|
|
164
|
+
# Safely convert args
|
|
165
|
+
for arg in args:
|
|
166
|
+
try:
|
|
167
|
+
# Test if the object can be JSON serialized
|
|
168
|
+
json.dumps(arg)
|
|
169
|
+
safe_args.append(arg)
|
|
170
|
+
except (TypeError, ValueError, PydanticSerializationError):
|
|
171
|
+
# If it can't be serialized, use string representation
|
|
172
|
+
safe_args.append(str(arg))
|
|
173
|
+
|
|
174
|
+
# Safely convert kwargs
|
|
175
|
+
for key, value in kwargs.items():
|
|
176
|
+
try:
|
|
177
|
+
# Test if the object can be JSON serialized
|
|
178
|
+
json.dumps(value)
|
|
179
|
+
safe_kwargs[key] = value
|
|
180
|
+
except (TypeError, ValueError, PydanticSerializationError):
|
|
181
|
+
# If it can't be serialized, use string representation
|
|
182
|
+
safe_kwargs[key] = str(value)
|
|
183
|
+
|
|
184
|
+
# Create the JSON without custom encoder to avoid recursion
|
|
185
|
+
json_input = json.dumps({"args": safe_args, "kwargs": safe_kwargs})
|
|
186
|
+
|
|
164
187
|
if _is_json_size_valid(json_input):
|
|
165
188
|
span.set_attribute(
|
|
166
189
|
OBSERVE_ENTITY_INPUT,
|
|
167
190
|
json_input,
|
|
168
191
|
)
|
|
169
|
-
except
|
|
192
|
+
except Exception as e:
|
|
193
|
+
# Log the exception but don't fail the actual function call
|
|
194
|
+
print(f"Warning: Failed to serialize input for span: {e}")
|
|
170
195
|
Telemetry().log_exception(e)
|
|
171
196
|
|
|
172
197
|
|
|
@@ -219,27 +244,32 @@ def _handle_span_output(span, tlp_span_kind, res, cls=None):
|
|
|
219
244
|
"gen_ai.ioa.agent.connection_reliability", reliability
|
|
220
245
|
)
|
|
221
246
|
|
|
222
|
-
|
|
223
|
-
try:
|
|
224
|
-
json_output = json.dumps(res, **({"cls": cls} if cls else {}))
|
|
225
|
-
except (TypeError, PydanticSerializationError):
|
|
226
|
-
# Fallback for objects that can't be directly serialized
|
|
247
|
+
if _should_send_prompts():
|
|
227
248
|
try:
|
|
228
|
-
# Try
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
# Try direct JSON serialization first (without custom encoder)
|
|
250
|
+
json_output = json.dumps(res)
|
|
251
|
+
except (TypeError, PydanticSerializationError, ValueError):
|
|
252
|
+
# Fallback for objects that can't be directly serialized
|
|
253
|
+
try:
|
|
254
|
+
# Try to serialize a string representation
|
|
255
|
+
safe_output = str(res)
|
|
256
|
+
json_output = json.dumps(
|
|
257
|
+
{"__str_representation__": safe_output}
|
|
258
|
+
)
|
|
259
|
+
except Exception:
|
|
260
|
+
# If all serialization fails, skip output attribute
|
|
261
|
+
json_output = None
|
|
262
|
+
|
|
263
|
+
if json_output and _is_json_size_valid(json_output):
|
|
264
|
+
span.set_attribute(
|
|
265
|
+
OBSERVE_ENTITY_OUTPUT,
|
|
266
|
+
json_output,
|
|
267
|
+
)
|
|
268
|
+
TracerWrapper().span_processor_on_ending(
|
|
269
|
+
span
|
|
270
|
+
) # record the response latency
|
|
271
|
+
except Exception as e:
|
|
272
|
+
print(f"Warning: Failed to serialize output for span: {e}")
|
|
243
273
|
Telemetry().log_exception(e)
|
|
244
274
|
|
|
245
275
|
|
|
@@ -286,7 +316,7 @@ def entity_method(
|
|
|
286
316
|
entity_name,
|
|
287
317
|
tlp_span_kind,
|
|
288
318
|
version,
|
|
289
|
-
description
|
|
319
|
+
description,
|
|
290
320
|
)
|
|
291
321
|
_handle_span_input(span, args, kwargs, cls=JSONEncoder)
|
|
292
322
|
|
|
@@ -307,8 +337,12 @@ def entity_method(
|
|
|
307
337
|
entity_name,
|
|
308
338
|
tlp_span_kind,
|
|
309
339
|
version,
|
|
310
|
-
description
|
|
340
|
+
description,
|
|
311
341
|
)
|
|
342
|
+
|
|
343
|
+
# Handle case where span setup failed
|
|
344
|
+
if span is None:
|
|
345
|
+
return fn(*args, **kwargs)
|
|
312
346
|
_handle_span_input(span, args, kwargs, cls=JSONEncoder)
|
|
313
347
|
success = False
|
|
314
348
|
try:
|
|
@@ -399,9 +433,13 @@ def entity_method(
|
|
|
399
433
|
entity_name,
|
|
400
434
|
tlp_span_kind,
|
|
401
435
|
version,
|
|
402
|
-
description
|
|
436
|
+
description,
|
|
403
437
|
)
|
|
404
438
|
|
|
439
|
+
# Handle case where span setup failed
|
|
440
|
+
if span is None:
|
|
441
|
+
return fn(*args, **kwargs)
|
|
442
|
+
|
|
405
443
|
_handle_span_input(span, args, kwargs, cls=JSONEncoder)
|
|
406
444
|
_handle_agent_span(span, entity_name, description, tlp_span_kind)
|
|
407
445
|
success = False
|
|
@@ -515,30 +553,53 @@ def entity_class(
|
|
|
515
553
|
# Specific method specified - existing behavior
|
|
516
554
|
methods_to_wrap = [method_name]
|
|
517
555
|
else:
|
|
518
|
-
# No method specified - wrap all public methods
|
|
556
|
+
# No method specified - wrap all public methods defined in this class
|
|
519
557
|
for attr_name in dir(cls):
|
|
520
558
|
if (
|
|
521
559
|
not attr_name.startswith("_") # Skip private/built-in methods
|
|
522
560
|
and attr_name != "mro" # Skip class method
|
|
523
561
|
and hasattr(cls, attr_name)
|
|
524
|
-
and callable(getattr(cls, attr_name))
|
|
525
|
-
and not isinstance(
|
|
526
|
-
getattr(cls, attr_name), (classmethod, staticmethod, property)
|
|
527
|
-
)
|
|
528
562
|
):
|
|
529
|
-
|
|
563
|
+
attr = getattr(cls, attr_name)
|
|
564
|
+
# Only wrap functions defined in this class (not inherited methods or built-ins)
|
|
565
|
+
if (
|
|
566
|
+
inspect.isfunction(attr) # Functions defined in the class
|
|
567
|
+
and not isinstance(attr, (classmethod, staticmethod, property))
|
|
568
|
+
and hasattr(attr, "__qualname__") # Has qualname attribute
|
|
569
|
+
and attr.__qualname__.startswith(
|
|
570
|
+
cls.__name__ + "."
|
|
571
|
+
) # Defined in this class
|
|
572
|
+
):
|
|
573
|
+
# Additional check: ensure the function has a proper signature with 'self' parameter
|
|
574
|
+
try:
|
|
575
|
+
sig = inspect.signature(attr)
|
|
576
|
+
params = list(sig.parameters.keys())
|
|
577
|
+
if params and params[0] == "self":
|
|
578
|
+
methods_to_wrap.append(attr_name)
|
|
579
|
+
except (ValueError, TypeError):
|
|
580
|
+
# Skip methods that can't be inspected
|
|
581
|
+
continue
|
|
530
582
|
|
|
531
583
|
# Wrap all detected methods
|
|
532
584
|
for method_to_wrap in methods_to_wrap:
|
|
533
585
|
if hasattr(cls, method_to_wrap):
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
586
|
+
original_method = getattr(cls, method_to_wrap)
|
|
587
|
+
# Only wrap actual functions defined in this class
|
|
588
|
+
if inspect.isfunction(original_method):
|
|
589
|
+
try:
|
|
590
|
+
# Verify the method has a proper signature
|
|
591
|
+
sig = inspect.signature(original_method)
|
|
592
|
+
wrapped_method = entity_method(
|
|
593
|
+
name=f"{task_name}.{method_to_wrap}",
|
|
594
|
+
description=description,
|
|
595
|
+
version=version,
|
|
596
|
+
tlp_span_kind=tlp_span_kind,
|
|
597
|
+
)(original_method)
|
|
598
|
+
# Set the wrapped method on the class
|
|
599
|
+
setattr(cls, method_to_wrap, wrapped_method)
|
|
600
|
+
except Exception:
|
|
601
|
+
# Don't wrap methods that can't be properly decorated
|
|
602
|
+
continue
|
|
542
603
|
|
|
543
604
|
return cls
|
|
544
605
|
|
|
@@ -571,15 +632,15 @@ def _handle_agent_failure_event(res, span, tlp_span_kind):
|
|
|
571
632
|
# Skip if span is already ended
|
|
572
633
|
return
|
|
573
634
|
if tlp_span_kind == ObserveSpanKindValues.AGENT:
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
)
|
|
635
|
+
attributes = {}
|
|
636
|
+
attributes["failure_reason"] = res
|
|
637
|
+
|
|
638
|
+
if "observe.workflow.name" in span.attributes:
|
|
639
|
+
attributes["agent_name"] = span.attributes["observe.workflow.name"]
|
|
640
|
+
elif "traceloop.workflow.name" in span.attributes:
|
|
641
|
+
attributes["agent_name"] = span.attributes["traceloop.workflow.name"]
|
|
642
|
+
|
|
643
|
+
TracerWrapper().failing_agents_counter.add(1, attributes=attributes)
|
|
583
644
|
|
|
584
645
|
|
|
585
646
|
def _handle_execution_result(span, success):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -44,6 +44,7 @@ Requires-Dist: opentelemetry-instrumentation-logging==0.54b1
|
|
|
44
44
|
Requires-Dist: opentelemetry-instrumentation-openai==0.40.8
|
|
45
45
|
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.40.8
|
|
46
46
|
Requires-Dist: opentelemetry-instrumentation-ollama==0.40.8
|
|
47
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic==0.40.8
|
|
47
48
|
Requires-Dist: opentelemetry-instrumentation-langchain==0.40.8
|
|
48
49
|
Requires-Dist: opentelemetry-instrumentation-threading==00.54b1
|
|
49
50
|
Requires-Dist: opentelemetry-instrumentation-urllib3==0.54b1
|
|
@@ -9,8 +9,8 @@ ioa_observe/sdk/client/http.py,sha256=LdLYSQPFIhKN5BTB-N78jLO7ITl7jGjA0-qpewEIvO
|
|
|
9
9
|
ioa_observe/sdk/config/__init__.py,sha256=8aVNaw0yRNLFPxlf97iOZLlJVcV81ivSDnudH2m1OIo,572
|
|
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
|
-
ioa_observe/sdk/decorators/__init__.py,sha256=
|
|
13
|
-
ioa_observe/sdk/decorators/base.py,sha256=
|
|
12
|
+
ioa_observe/sdk/decorators/__init__.py,sha256=Lv5EbouBazvWaYB0N82v26pqKtj2FAqlwfLKEh5e8Q0,3251
|
|
13
|
+
ioa_observe/sdk/decorators/base.py,sha256=kR_0KWMMbOdJ_t9m5EZ9ZUj3pmKWEgkj0-E1F3c-nIE,29969
|
|
14
14
|
ioa_observe/sdk/decorators/util.py,sha256=WMkzmwD7Js0g1BbId9_qR4pwhnaIJdW588zVc5dpqdQ,25399
|
|
15
15
|
ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
ioa_observe/sdk/instrumentations/a2a.py,sha256=ov_9ckkymf_qFXG0iXVWfxlW-3kFcP-knrM_t-Cf72w,4414
|
|
@@ -38,8 +38,8 @@ ioa_observe/sdk/utils/const.py,sha256=GwbHakKPjBL4wLqAVkDrSoKB-8p18EUrbaqPuRuV_x
|
|
|
38
38
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
39
39
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
40
40
|
ioa_observe/sdk/utils/package_check.py,sha256=1d1MjxhwoEZIx9dumirT2pRsEWgn-m-SI4npDeEalew,576
|
|
41
|
-
ioa_observe_sdk-1.0.
|
|
42
|
-
ioa_observe_sdk-1.0.
|
|
43
|
-
ioa_observe_sdk-1.0.
|
|
44
|
-
ioa_observe_sdk-1.0.
|
|
45
|
-
ioa_observe_sdk-1.0.
|
|
41
|
+
ioa_observe_sdk-1.0.11.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
42
|
+
ioa_observe_sdk-1.0.11.dist-info/METADATA,sha256=5YMHTWQw2c6VwUQG4K-15klct9thZBw6cJ0rO_Hs2zg,7810
|
|
43
|
+
ioa_observe_sdk-1.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
44
|
+
ioa_observe_sdk-1.0.11.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
45
|
+
ioa_observe_sdk-1.0.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|