ioa-observe-sdk 1.0.12__py3-none-any.whl → 1.0.14__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 +13 -1
- ioa_observe/sdk/decorators/base.py +92 -80
- ioa_observe/sdk/decorators/helpers.py +66 -0
- ioa_observe/sdk/decorators/util.py +123 -0
- {ioa_observe_sdk-1.0.12.dist-info → ioa_observe_sdk-1.0.14.dist-info}/METADATA +7 -7
- {ioa_observe_sdk-1.0.12.dist-info → ioa_observe_sdk-1.0.14.dist-info}/RECORD +9 -8
- {ioa_observe_sdk-1.0.12.dist-info → ioa_observe_sdk-1.0.14.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.12.dist-info → ioa_observe_sdk-1.0.14.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.12.dist-info → ioa_observe_sdk-1.0.14.dist-info}/top_level.txt +0 -0
|
@@ -21,6 +21,7 @@ def task(
|
|
|
21
21
|
name: Optional[str] = None,
|
|
22
22
|
description: Optional[str] = None,
|
|
23
23
|
version: Optional[int] = None,
|
|
24
|
+
protocol: Optional[str] = None,
|
|
24
25
|
method_name: Optional[str] = None,
|
|
25
26
|
tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
|
|
26
27
|
) -> Callable[[F], F]:
|
|
@@ -29,6 +30,7 @@ def task(
|
|
|
29
30
|
name=name,
|
|
30
31
|
description=description,
|
|
31
32
|
version=version,
|
|
33
|
+
protocol=protocol,
|
|
32
34
|
tlp_span_kind=tlp_span_kind,
|
|
33
35
|
)
|
|
34
36
|
else:
|
|
@@ -36,6 +38,7 @@ def task(
|
|
|
36
38
|
name=name,
|
|
37
39
|
description=description,
|
|
38
40
|
version=version,
|
|
41
|
+
protocol=protocol,
|
|
39
42
|
method_name=method_name,
|
|
40
43
|
tlp_span_kind=tlp_span_kind,
|
|
41
44
|
)
|
|
@@ -45,6 +48,7 @@ def workflow(
|
|
|
45
48
|
name: Optional[str] = None,
|
|
46
49
|
description: Optional[str] = None,
|
|
47
50
|
version: Optional[int] = None,
|
|
51
|
+
protocol: Optional[str] = None,
|
|
48
52
|
method_name: Optional[str] = None,
|
|
49
53
|
tlp_span_kind: Optional[
|
|
50
54
|
Union[ObserveSpanKindValues, str]
|
|
@@ -57,6 +61,7 @@ def workflow(
|
|
|
57
61
|
name=name,
|
|
58
62
|
description=description,
|
|
59
63
|
version=version,
|
|
64
|
+
protocol=protocol,
|
|
60
65
|
method_name=method_name,
|
|
61
66
|
tlp_span_kind=tlp_span_kind,
|
|
62
67
|
)(target)
|
|
@@ -66,6 +71,7 @@ def workflow(
|
|
|
66
71
|
name=name,
|
|
67
72
|
description=description,
|
|
68
73
|
version=version,
|
|
74
|
+
protocol=protocol,
|
|
69
75
|
tlp_span_kind=tlp_span_kind,
|
|
70
76
|
)(target)
|
|
71
77
|
|
|
@@ -77,14 +83,18 @@ def graph(
|
|
|
77
83
|
description: Optional[str] = None,
|
|
78
84
|
version: Optional[int] = None,
|
|
79
85
|
method_name: Optional[str] = None,
|
|
86
|
+
protocol: Optional[str] = None,
|
|
80
87
|
) -> Callable[[F], F]:
|
|
81
88
|
if method_name is None:
|
|
82
|
-
return entity_method(
|
|
89
|
+
return entity_method(
|
|
90
|
+
name=name, version=version, protocol=protocol, tlp_span_kind="graph"
|
|
91
|
+
)
|
|
83
92
|
else:
|
|
84
93
|
return entity_class(
|
|
85
94
|
name=name,
|
|
86
95
|
version=version,
|
|
87
96
|
method_name=method_name,
|
|
97
|
+
protocol=protocol,
|
|
88
98
|
tlp_span_kind="graph",
|
|
89
99
|
)
|
|
90
100
|
|
|
@@ -93,12 +103,14 @@ def agent(
|
|
|
93
103
|
name: Optional[str] = None,
|
|
94
104
|
description: Optional[str] = None,
|
|
95
105
|
version: Optional[int] = None,
|
|
106
|
+
protocol: Optional[str] = None,
|
|
96
107
|
method_name: Optional[str] = None,
|
|
97
108
|
) -> Callable[[F], F]:
|
|
98
109
|
return workflow(
|
|
99
110
|
name=name,
|
|
100
111
|
description=description,
|
|
101
112
|
version=version,
|
|
113
|
+
protocol=protocol,
|
|
102
114
|
method_name=method_name,
|
|
103
115
|
tlp_span_kind=ObserveSpanKindValues.AGENT,
|
|
104
116
|
)
|
|
@@ -7,16 +7,23 @@ import traceback
|
|
|
7
7
|
from functools import wraps
|
|
8
8
|
import os
|
|
9
9
|
import types
|
|
10
|
-
from typing import Optional, TypeVar, Callable, Awaitable, Any,
|
|
10
|
+
from typing import Optional, TypeVar, Callable, Awaitable, Any, Union
|
|
11
11
|
import inspect
|
|
12
12
|
|
|
13
|
+
from ioa_observe.sdk.decorators.helpers import (
|
|
14
|
+
_is_async_method,
|
|
15
|
+
_get_original_function_name,
|
|
16
|
+
_is_async_generator,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
13
20
|
from langgraph.graph.state import CompiledStateGraph
|
|
14
21
|
from opentelemetry import trace
|
|
15
22
|
from opentelemetry import context as context_api
|
|
16
23
|
from pydantic_core import PydanticSerializationError
|
|
17
24
|
from typing_extensions import ParamSpec
|
|
18
25
|
|
|
19
|
-
from ioa_observe.sdk.decorators.util import determine_workflow_type
|
|
26
|
+
from ioa_observe.sdk.decorators.util import determine_workflow_type, _serialize_object
|
|
20
27
|
from ioa_observe.sdk.metrics.agents.availability import agent_availability
|
|
21
28
|
from ioa_observe.sdk.metrics.agents.recovery_tracker import agent_recovery_tracker
|
|
22
29
|
from ioa_observe.sdk.metrics.agents.tool_call_tracker import tool_call_tracker
|
|
@@ -79,14 +86,6 @@ def _should_send_prompts():
|
|
|
79
86
|
).lower() == "true" or context_api.get_value("override_enable_content_tracing")
|
|
80
87
|
|
|
81
88
|
|
|
82
|
-
# Unified Decorators : handles both sync and async operations
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _is_async_method(fn):
|
|
86
|
-
# check if co-routine function or async generator( example : using async & yield)
|
|
87
|
-
return inspect.iscoroutinefunction(fn) or inspect.isasyncgenfunction(fn)
|
|
88
|
-
|
|
89
|
-
|
|
90
89
|
def _setup_span(
|
|
91
90
|
entity_name,
|
|
92
91
|
tlp_span_kind: Optional[ObserveSpanKindValues] = None,
|
|
@@ -164,24 +163,24 @@ def _handle_span_input(span, args, kwargs, cls=None):
|
|
|
164
163
|
# Safely convert args
|
|
165
164
|
for arg in args:
|
|
166
165
|
try:
|
|
167
|
-
#
|
|
166
|
+
# Check if the object can be JSON serialized directly
|
|
168
167
|
json.dumps(arg)
|
|
169
168
|
safe_args.append(arg)
|
|
170
169
|
except (TypeError, ValueError, PydanticSerializationError):
|
|
171
|
-
#
|
|
172
|
-
safe_args.append(
|
|
170
|
+
# Use intelligent serialization
|
|
171
|
+
safe_args.append(_serialize_object(arg))
|
|
173
172
|
|
|
174
173
|
# Safely convert kwargs
|
|
175
174
|
for key, value in kwargs.items():
|
|
176
175
|
try:
|
|
177
|
-
# Test if the object can be JSON serialized
|
|
176
|
+
# Test if the object can be JSON serialized directly
|
|
178
177
|
json.dumps(value)
|
|
179
178
|
safe_kwargs[key] = value
|
|
180
179
|
except (TypeError, ValueError, PydanticSerializationError):
|
|
181
|
-
#
|
|
182
|
-
safe_kwargs[key] =
|
|
180
|
+
# Use intelligent serialization
|
|
181
|
+
safe_kwargs[key] = _serialize_object(value)
|
|
183
182
|
|
|
184
|
-
# Create the JSON
|
|
183
|
+
# Create the JSON
|
|
185
184
|
json_input = json.dumps({"args": safe_args, "kwargs": safe_kwargs})
|
|
186
185
|
|
|
187
186
|
if _is_json_size_valid(json_input):
|
|
@@ -212,62 +211,55 @@ def _handle_span_output(span, tlp_span_kind, res, cls=None):
|
|
|
212
211
|
# end_span.end() # end the span immediately
|
|
213
212
|
set_agent_id_event("") # reset the agent id event
|
|
214
213
|
# Add agent interpretation scoring
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
if (
|
|
215
|
+
tlp_span_kind == ObserveSpanKindValues.AGENT
|
|
216
|
+
or tlp_span_kind == ObserveSpanKindValues.WORKFLOW
|
|
217
|
+
):
|
|
218
|
+
current_agent = span.attributes.get("agent_id", "unknown")
|
|
219
|
+
|
|
220
|
+
# Determine next agent from response (if Command object with goto)
|
|
221
|
+
next_agent = None
|
|
222
|
+
if isinstance(res, dict) and "goto" in res:
|
|
223
|
+
next_agent = res["goto"]
|
|
224
|
+
# Check if there's an error flag in the response
|
|
225
|
+
success = not (res.get("error", False) or res.get("goto") == "__end__")
|
|
226
|
+
|
|
227
|
+
# If we have a chain of communication, compute interpretation score
|
|
228
|
+
if next_agent and next_agent != "__end__":
|
|
229
|
+
score = compute_agent_interpretation_score(
|
|
230
|
+
sender_agent=current_agent,
|
|
231
|
+
receiver_agent=next_agent,
|
|
232
|
+
data=res,
|
|
233
|
+
)
|
|
234
|
+
span.set_attribute("gen_ai.ioa.agent.interpretation_score", score)
|
|
235
|
+
reliability = connection_tracker.record_connection(
|
|
236
|
+
sender=current_agent, receiver=next_agent, success=success
|
|
237
|
+
)
|
|
238
|
+
span.set_attribute(
|
|
239
|
+
"gen_ai.ioa.agent.connection_reliability", reliability
|
|
228
240
|
)
|
|
229
241
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
span.set_attribute(
|
|
238
|
-
"gen_ai.ioa.agent.interpretation_score", score
|
|
239
|
-
)
|
|
240
|
-
reliability = connection_tracker.record_connection(
|
|
241
|
-
sender=current_agent, receiver=next_agent, success=success
|
|
242
|
-
)
|
|
243
|
-
span.set_attribute(
|
|
244
|
-
"gen_ai.ioa.agent.connection_reliability", reliability
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
if _should_send_prompts():
|
|
242
|
+
if _should_send_prompts():
|
|
243
|
+
try:
|
|
244
|
+
# Try direct JSON serialization first
|
|
245
|
+
json_output = json.dumps(res)
|
|
246
|
+
except (TypeError, PydanticSerializationError, ValueError):
|
|
247
|
+
# Use intelligent serialization for complex objects
|
|
248
248
|
try:
|
|
249
|
-
|
|
250
|
-
json_output = json.dumps(
|
|
251
|
-
except
|
|
252
|
-
#
|
|
253
|
-
|
|
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
|
|
249
|
+
serialized_res = _serialize_object(res)
|
|
250
|
+
json_output = json.dumps(serialized_res)
|
|
251
|
+
except Exception:
|
|
252
|
+
# If all serialization fails, skip output attribute
|
|
253
|
+
json_output = None
|
|
262
254
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
if json_output and _is_json_size_valid(json_output):
|
|
256
|
+
span.set_attribute(
|
|
257
|
+
OBSERVE_ENTITY_OUTPUT,
|
|
258
|
+
json_output,
|
|
259
|
+
)
|
|
260
|
+
TracerWrapper().span_processor_on_ending(
|
|
261
|
+
span
|
|
262
|
+
) # record the response latency
|
|
271
263
|
except Exception as e:
|
|
272
264
|
print(f"Warning: Failed to serialize output for span: {e}")
|
|
273
265
|
Telemetry().log_exception(e)
|
|
@@ -293,17 +285,27 @@ def _cleanup_span(span, ctx_token):
|
|
|
293
285
|
context_api.detach(ctx_token)
|
|
294
286
|
|
|
295
287
|
|
|
288
|
+
def _unwrap_structured_tool(fn):
|
|
289
|
+
# Unwraps StructuredTool or similar wrappers to get the underlying function
|
|
290
|
+
if hasattr(fn, "func") and callable(fn.func):
|
|
291
|
+
return fn.func
|
|
292
|
+
return fn
|
|
293
|
+
|
|
294
|
+
|
|
296
295
|
def entity_method(
|
|
297
296
|
name: Optional[str] = None,
|
|
298
297
|
description: Optional[str] = None,
|
|
299
298
|
version: Optional[int] = None,
|
|
299
|
+
protocol: Optional[str] = None,
|
|
300
300
|
tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
|
|
301
301
|
) -> Callable[[F], F]:
|
|
302
302
|
def decorate(fn: F) -> F:
|
|
303
|
+
# Unwrap StructuredTool if present
|
|
304
|
+
fn = _unwrap_structured_tool(fn)
|
|
303
305
|
is_async = _is_async_method(fn)
|
|
304
|
-
entity_name = name or fn
|
|
306
|
+
entity_name = name or _get_original_function_name(fn)
|
|
305
307
|
if is_async:
|
|
306
|
-
if
|
|
308
|
+
if _is_async_generator(fn):
|
|
307
309
|
|
|
308
310
|
@wraps(fn)
|
|
309
311
|
async def async_gen_wrap(*args: Any, **kwargs: Any) -> Any:
|
|
@@ -375,7 +377,7 @@ def entity_method(
|
|
|
375
377
|
agent_recovery_tracker.record_agent_recovery(
|
|
376
378
|
entity_name
|
|
377
379
|
)
|
|
378
|
-
_handle_graph_response(span, res, tlp_span_kind)
|
|
380
|
+
_handle_graph_response(span, res, protocol, tlp_span_kind)
|
|
379
381
|
# span will be ended in the generator
|
|
380
382
|
if isinstance(res, types.GeneratorType):
|
|
381
383
|
return _handle_generator(span, res)
|
|
@@ -421,7 +423,7 @@ def entity_method(
|
|
|
421
423
|
_cleanup_span(span, ctx_token)
|
|
422
424
|
return res
|
|
423
425
|
|
|
424
|
-
|
|
426
|
+
decorated = async_wrap
|
|
425
427
|
else:
|
|
426
428
|
|
|
427
429
|
@wraps(fn)
|
|
@@ -485,7 +487,7 @@ def entity_method(
|
|
|
485
487
|
agent_recovery_tracker.record_agent_recovery(
|
|
486
488
|
entity_name
|
|
487
489
|
)
|
|
488
|
-
_handle_graph_response(span, res, tlp_span_kind)
|
|
490
|
+
_handle_graph_response(span, res, protocol, tlp_span_kind)
|
|
489
491
|
|
|
490
492
|
# span will be ended in the generator
|
|
491
493
|
if isinstance(res, types.GeneratorType):
|
|
@@ -532,7 +534,12 @@ def entity_method(
|
|
|
532
534
|
_cleanup_span(span, ctx_token)
|
|
533
535
|
return res
|
|
534
536
|
|
|
535
|
-
|
|
537
|
+
decorated = sync_wrap
|
|
538
|
+
# # If the original fn was a StructuredTool, re-wrap
|
|
539
|
+
if hasattr(fn, "func") and callable(fn.func):
|
|
540
|
+
fn.func = decorated
|
|
541
|
+
return fn
|
|
542
|
+
return decorated
|
|
536
543
|
|
|
537
544
|
return decorate
|
|
538
545
|
|
|
@@ -541,6 +548,7 @@ def entity_class(
|
|
|
541
548
|
name: Optional[str],
|
|
542
549
|
description: Optional[str],
|
|
543
550
|
version: Optional[int],
|
|
551
|
+
protocol: Optional[str],
|
|
544
552
|
method_name: Optional[str],
|
|
545
553
|
tlp_span_kind: Optional[ObserveSpanKindValues] = ObserveSpanKindValues.TASK,
|
|
546
554
|
):
|
|
@@ -585,16 +593,18 @@ def entity_class(
|
|
|
585
593
|
if hasattr(cls, method_to_wrap):
|
|
586
594
|
original_method = getattr(cls, method_to_wrap)
|
|
587
595
|
# Only wrap actual functions defined in this class
|
|
588
|
-
|
|
596
|
+
unwrapped_method = _unwrap_structured_tool(original_method)
|
|
597
|
+
if inspect.isfunction(unwrapped_method):
|
|
589
598
|
try:
|
|
590
599
|
# Verify the method has a proper signature
|
|
591
|
-
sig = inspect.signature(
|
|
600
|
+
sig = inspect.signature(unwrapped_method)
|
|
592
601
|
wrapped_method = entity_method(
|
|
593
602
|
name=f"{task_name}.{method_to_wrap}",
|
|
594
603
|
description=description,
|
|
595
604
|
version=version,
|
|
605
|
+
protocol=protocol,
|
|
596
606
|
tlp_span_kind=tlp_span_kind,
|
|
597
|
-
)(
|
|
607
|
+
)(unwrapped_method)
|
|
598
608
|
# Set the wrapped method on the class
|
|
599
609
|
setattr(cls, method_to_wrap, wrapped_method)
|
|
600
610
|
except Exception:
|
|
@@ -654,15 +664,17 @@ def _handle_execution_result(span, success):
|
|
|
654
664
|
return
|
|
655
665
|
|
|
656
666
|
|
|
657
|
-
def _handle_graph_response(span, res, tlp_span_kind):
|
|
667
|
+
def _handle_graph_response(span, res, protocol, tlp_span_kind):
|
|
658
668
|
if tlp_span_kind == "graph":
|
|
669
|
+
if protocol:
|
|
670
|
+
protocol = protocol.upper()
|
|
671
|
+
span.set_attribute("gen_ai.ioa.graph.protocol", protocol)
|
|
659
672
|
# Check if the response is a Llama Index Workflow object
|
|
660
673
|
graph = determine_workflow_type(res)
|
|
661
674
|
if graph is not None:
|
|
662
675
|
# Convert the graph to JSON string
|
|
663
676
|
graph_json = json.dumps(graph, indent=2)
|
|
664
677
|
span.set_attribute("gen_ai.ioa.graph", graph_json)
|
|
665
|
-
|
|
666
678
|
# get graph dynamism
|
|
667
679
|
dynamism = topology_dynamism(graph)
|
|
668
680
|
span.set_attribute("gen_ai.ioa.graph_dynamism", dynamism)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _is_async_method(fn):
|
|
8
|
+
# check if co-routine function or async generator( example : using async & yield)
|
|
9
|
+
if inspect.iscoroutinefunction(fn) or inspect.isasyncgenfunction(fn):
|
|
10
|
+
return True
|
|
11
|
+
|
|
12
|
+
# Check if this is a wrapped function that might hide the original async nature
|
|
13
|
+
# Look for common wrapper attributes that might contain the original function
|
|
14
|
+
for attr_name in ["__wrapped__", "func", "_func", "function"]:
|
|
15
|
+
if hasattr(fn, attr_name):
|
|
16
|
+
wrapped_fn = getattr(fn, attr_name)
|
|
17
|
+
if wrapped_fn and callable(wrapped_fn):
|
|
18
|
+
if inspect.iscoroutinefunction(
|
|
19
|
+
wrapped_fn
|
|
20
|
+
) or inspect.isasyncgenfunction(wrapped_fn):
|
|
21
|
+
return True
|
|
22
|
+
# Recursively check in case of multiple levels of wrapping
|
|
23
|
+
if _is_async_method(wrapped_fn):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _is_async_generator(fn):
|
|
30
|
+
"""Check if function is an async generator, looking through wrapped functions"""
|
|
31
|
+
if inspect.isasyncgenfunction(fn):
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
# Check if this is a wrapped function that might hide the original async generator nature
|
|
35
|
+
for attr_name in ["__wrapped__", "func", "_func", "function"]:
|
|
36
|
+
if hasattr(fn, attr_name):
|
|
37
|
+
wrapped_fn = getattr(fn, attr_name)
|
|
38
|
+
if wrapped_fn and callable(wrapped_fn):
|
|
39
|
+
if inspect.isasyncgenfunction(wrapped_fn):
|
|
40
|
+
return True
|
|
41
|
+
# Recursively check in case of multiple levels of wrapping
|
|
42
|
+
if _is_async_generator(wrapped_fn):
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_original_function_name(fn):
|
|
49
|
+
"""Extract the original function name from potentially wrapped functions"""
|
|
50
|
+
if hasattr(fn, "__qualname__") and fn.__qualname__:
|
|
51
|
+
return fn.__qualname__
|
|
52
|
+
|
|
53
|
+
# Look for the original function in common wrapper attributes
|
|
54
|
+
for attr_name in ["__wrapped__", "func", "_func", "function"]:
|
|
55
|
+
if hasattr(fn, attr_name):
|
|
56
|
+
wrapped_fn = getattr(fn, attr_name)
|
|
57
|
+
if wrapped_fn and callable(wrapped_fn):
|
|
58
|
+
if hasattr(wrapped_fn, "__qualname__") and wrapped_fn.__qualname__:
|
|
59
|
+
return wrapped_fn.__qualname__
|
|
60
|
+
# Recursively check in case of multiple levels of wrapping
|
|
61
|
+
result = _get_original_function_name(wrapped_fn)
|
|
62
|
+
if result:
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
# Fallback to function name if qualname is not available
|
|
66
|
+
return getattr(fn, "__name__", "unknown_function")
|
|
@@ -13,6 +13,129 @@ from llama_index.core.workflow.utils import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def _serialize_object(obj, max_depth=3, current_depth=0):
|
|
17
|
+
"""
|
|
18
|
+
Intelligently serialize an object to a more meaningful representation
|
|
19
|
+
"""
|
|
20
|
+
if current_depth > max_depth:
|
|
21
|
+
return f"<{type(obj).__name__}:max_depth_reached>"
|
|
22
|
+
|
|
23
|
+
# Handle basic JSON-serializable types
|
|
24
|
+
if obj is None or isinstance(obj, (bool, int, float, str)):
|
|
25
|
+
return obj
|
|
26
|
+
|
|
27
|
+
# Handle lists and tuples
|
|
28
|
+
if isinstance(obj, (list, tuple)):
|
|
29
|
+
try:
|
|
30
|
+
return [
|
|
31
|
+
_serialize_object(item, max_depth, current_depth + 1)
|
|
32
|
+
for item in obj[:10]
|
|
33
|
+
] # Limit to first 10 items
|
|
34
|
+
except Exception:
|
|
35
|
+
return f"<{type(obj).__name__}:length={len(obj)}>"
|
|
36
|
+
|
|
37
|
+
# Handle dictionaries
|
|
38
|
+
if isinstance(obj, dict):
|
|
39
|
+
try:
|
|
40
|
+
serialized = {}
|
|
41
|
+
for key, value in list(obj.items())[:10]: # Limit to first 10 items
|
|
42
|
+
serialized[str(key)] = _serialize_object(
|
|
43
|
+
value, max_depth, current_depth + 1
|
|
44
|
+
)
|
|
45
|
+
return serialized
|
|
46
|
+
except Exception:
|
|
47
|
+
return f"<dict:keys={len(obj)}>"
|
|
48
|
+
|
|
49
|
+
# Handle common object types with meaningful attributes
|
|
50
|
+
try:
|
|
51
|
+
# Check class attributes first
|
|
52
|
+
class_attrs = {}
|
|
53
|
+
for attr_name in dir(type(obj)):
|
|
54
|
+
if (
|
|
55
|
+
not attr_name.startswith("_")
|
|
56
|
+
and not callable(getattr(type(obj), attr_name, None))
|
|
57
|
+
and hasattr(obj, attr_name)
|
|
58
|
+
):
|
|
59
|
+
try:
|
|
60
|
+
attr_value = getattr(obj, attr_name)
|
|
61
|
+
if not callable(attr_value):
|
|
62
|
+
class_attrs[attr_name] = _serialize_object(
|
|
63
|
+
attr_value, max_depth, current_depth + 1
|
|
64
|
+
)
|
|
65
|
+
if len(class_attrs) >= 5: # Limit attributes
|
|
66
|
+
break
|
|
67
|
+
except Exception:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
# Check if object has a __dict__ with interesting attributes
|
|
71
|
+
instance_attrs = {}
|
|
72
|
+
if hasattr(obj, "__dict__"):
|
|
73
|
+
obj_dict = obj.__dict__
|
|
74
|
+
if obj_dict:
|
|
75
|
+
# Extract meaningful attributes (skip private ones and callables)
|
|
76
|
+
for key, value in obj_dict.items():
|
|
77
|
+
if not key.startswith("_") and not callable(value):
|
|
78
|
+
try:
|
|
79
|
+
instance_attrs[key] = _serialize_object(
|
|
80
|
+
value, max_depth, current_depth + 1
|
|
81
|
+
)
|
|
82
|
+
if len(instance_attrs) >= 5: # Limit attributes
|
|
83
|
+
break
|
|
84
|
+
except Exception:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# Combine class and instance attributes
|
|
88
|
+
all_attrs = {**class_attrs, **instance_attrs}
|
|
89
|
+
|
|
90
|
+
if all_attrs:
|
|
91
|
+
return {
|
|
92
|
+
"__class__": type(obj).__name__,
|
|
93
|
+
"__module__": getattr(type(obj), "__module__", "unknown"),
|
|
94
|
+
"attributes": all_attrs,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Special handling for specific types
|
|
98
|
+
if hasattr(obj, "message") and hasattr(obj.message, "parts"):
|
|
99
|
+
# Handle RequestContext-like objects
|
|
100
|
+
try:
|
|
101
|
+
parts_content = []
|
|
102
|
+
for part in obj.message.parts:
|
|
103
|
+
if hasattr(part, "root") and hasattr(part.root, "text"):
|
|
104
|
+
parts_content.append(part.root.text)
|
|
105
|
+
return {
|
|
106
|
+
"__class__": type(obj).__name__,
|
|
107
|
+
"message_content": parts_content,
|
|
108
|
+
}
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
# Check for common readable attributes
|
|
113
|
+
for attr in ["name", "id", "type", "value", "content", "text", "data"]:
|
|
114
|
+
if hasattr(obj, attr):
|
|
115
|
+
try:
|
|
116
|
+
attr_value = getattr(obj, attr)
|
|
117
|
+
if not callable(attr_value):
|
|
118
|
+
return {
|
|
119
|
+
"__class__": type(obj).__name__,
|
|
120
|
+
attr: _serialize_object(
|
|
121
|
+
attr_value, max_depth, current_depth + 1
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
except Exception:
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
# Fallback to class information
|
|
128
|
+
return {
|
|
129
|
+
"__class__": type(obj).__name__,
|
|
130
|
+
"__module__": getattr(type(obj), "__module__", "unknown"),
|
|
131
|
+
"__repr__": str(obj)[:100] + ("..." if len(str(obj)) > 100 else ""),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
except Exception:
|
|
135
|
+
# Final fallback
|
|
136
|
+
return f"<{type(obj).__name__}:serialization_failed>"
|
|
137
|
+
|
|
138
|
+
|
|
16
139
|
def determine_workflow_type(workflow_obj: Any) -> Union[None, dict]:
|
|
17
140
|
"""Determines the workflow type and generates appropriate topology."""
|
|
18
141
|
# Check if it's a dict mapping agent roles to agent names
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ioa-observe-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.14
|
|
4
4
|
Summary: IOA Observability SDK
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -15,17 +15,17 @@ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc==1.33.1
|
|
|
15
15
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.33.1
|
|
16
16
|
Requires-Dist: opentelemetry-instrumentation
|
|
17
17
|
Requires-Dist: opentelemetry-instrumentation-logging==0.54b1
|
|
18
|
-
Requires-Dist: opentelemetry-instrumentation-openai==0.
|
|
19
|
-
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.
|
|
20
|
-
Requires-Dist: opentelemetry-instrumentation-ollama==0.
|
|
21
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic==0.
|
|
22
|
-
Requires-Dist: opentelemetry-instrumentation-langchain==0.
|
|
18
|
+
Requires-Dist: opentelemetry-instrumentation-openai==0.43.1
|
|
19
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex==0.43.1
|
|
20
|
+
Requires-Dist: opentelemetry-instrumentation-ollama==0.43.1
|
|
21
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic==0.43.1
|
|
22
|
+
Requires-Dist: opentelemetry-instrumentation-langchain==0.43.1
|
|
23
23
|
Requires-Dist: opentelemetry-instrumentation-threading==00.54b1
|
|
24
24
|
Requires-Dist: opentelemetry-instrumentation-urllib3==0.54b1
|
|
25
25
|
Requires-Dist: opentelemetry-proto==1.33.1
|
|
26
26
|
Requires-Dist: opentelemetry-sdk==1.33.1
|
|
27
27
|
Requires-Dist: opentelemetry-semantic-conventions==0.54b1
|
|
28
|
-
Requires-Dist: opentelemetry-semantic-conventions-ai
|
|
28
|
+
Requires-Dist: opentelemetry-semantic-conventions-ai>=0.4.11
|
|
29
29
|
Requires-Dist: opentelemetry-util-http==0.54b1
|
|
30
30
|
Requires-Dist: langgraph>=0.3.2
|
|
31
31
|
Requires-Dist: langchain>=0.3.19
|
|
@@ -9,9 +9,10 @@ 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=
|
|
14
|
-
ioa_observe/sdk/decorators/
|
|
12
|
+
ioa_observe/sdk/decorators/__init__.py,sha256=c8gzCG-CASNI61RiTpNiTvMfguWvn4zc25fhBBgbHFA,3626
|
|
13
|
+
ioa_observe/sdk/decorators/base.py,sha256=KbSxFUTP_qpZA0gzt_xe4pFTu9SLpAo1MbHskQ3BQ9A,30208
|
|
14
|
+
ioa_observe/sdk/decorators/helpers.py,sha256=I9HXMBivkZpGDtPe9Ad_UU35p_m_wEPate4r_fU0oOA,2705
|
|
15
|
+
ioa_observe/sdk/decorators/util.py,sha256=IebvH9gwZN1en3LblYJUh4bAV2STl6xmp8WpZzBDH2g,30068
|
|
15
16
|
ioa_observe/sdk/instrumentations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
17
|
ioa_observe/sdk/instrumentations/a2a.py,sha256=ov_9ckkymf_qFXG0iXVWfxlW-3kFcP-knrM_t-Cf72w,4414
|
|
17
18
|
ioa_observe/sdk/instrumentations/slim.py,sha256=J5e6XeshH55xXaUiT9_j4R_n6VQELzBjgRAU-AgZGOg,11435
|
|
@@ -38,8 +39,8 @@ ioa_observe/sdk/utils/const.py,sha256=GwbHakKPjBL4wLqAVkDrSoKB-8p18EUrbaqPuRuV_x
|
|
|
38
39
|
ioa_observe/sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
|
|
39
40
|
ioa_observe/sdk/utils/json_encoder.py,sha256=g4NQ0tTqgWssY6I1D7r4zo0G6PiUo61jhofTAw5-jno,639
|
|
40
41
|
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.
|
|
42
|
+
ioa_observe_sdk-1.0.14.dist-info/licenses/LICENSE.md,sha256=55VjUfgjWOS4vv3Cf55gfq-RxjPgRIO2vlgYPUuC5lA,11362
|
|
43
|
+
ioa_observe_sdk-1.0.14.dist-info/METADATA,sha256=SaSIS2l9wA9A4KRRazaD33ZmMPzLsjNKvqeFl50ko0Y,6110
|
|
44
|
+
ioa_observe_sdk-1.0.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
45
|
+
ioa_observe_sdk-1.0.14.dist-info/top_level.txt,sha256=Yt-6Y1olZEDqCs2REeqI30WjYx0pLGQSVqzYmDd67N8,12
|
|
46
|
+
ioa_observe_sdk-1.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|