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.
@@ -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
- # Always use entity_class for class decorators
54
- return entity_class(
55
- name=name,
56
- description=description,
57
- version=version,
58
- method_name=method_name,
59
- tlp_span_kind=tlp_span_kind,
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
- json_input = json.dumps(
162
- {"args": args, "kwargs": kwargs}, **({"cls": cls} if cls else {})
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 TypeError as e:
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
- if _should_send_prompts():
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 to serialize a string representation
229
- safe_output = str(res)
230
- json_output = json.dumps({"__str_representation__": safe_output})
231
- except Exception:
232
- # If all serialization fails, skip output attribute
233
- json_output = None
234
- if _is_json_size_valid(json_output):
235
- span.set_attribute(
236
- OBSERVE_ENTITY_OUTPUT,
237
- json_output,
238
- )
239
- TracerWrapper().span_processor_on_ending(
240
- span
241
- ) # record the response latency
242
- except TypeError as e:
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 if description else None,
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 if description else None,
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 if description else None,
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
- methods_to_wrap.append(attr_name)
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
- method = getattr(cls, method_to_wrap)
535
- wrapped_method = entity_method(
536
- name=f"{task_name}.{method_to_wrap}",
537
- description=description,
538
- version=version,
539
- tlp_span_kind=tlp_span_kind,
540
- )(method)
541
- setattr(cls, method_to_wrap, wrapped_method)
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
- TracerWrapper().failing_agents_counter.add(
575
- 1,
576
- attributes={
577
- "agent_name": span.attributes["observe.workflow.name"]
578
- if "observe.workflow.name" in span.attributes
579
- else span.attributes["traceloop.workflow.name"],
580
- "failure_reason": res, # or could be "none" if you don't want to specify
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.9
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=nopDcih_VW0GeDlgwA2y4gkHP9PZ2sQKdjfG6Iyn0LE,2805
13
- ioa_observe/sdk/decorators/base.py,sha256=4ytlog5Ub02BqmhWO5csXvplxp5k1AzheSDwRb7P2SA,26821
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.9.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
42
- ioa_observe_sdk-1.0.9.dist-info/METADATA,sha256=A1X5wIkGUPhTgxRUJZEXRd8G4bNr_phxw2tTa1SBVw4,7746
43
- ioa_observe_sdk-1.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
- ioa_observe_sdk-1.0.9.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
45
- ioa_observe_sdk-1.0.9.dist-info/RECORD,,
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,,