mseep-agentops 0.4.18__py3-none-any.whl → 0.4.22__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.
Files changed (153) hide show
  1. agentops/__init__.py +0 -0
  2. agentops/client/api/base.py +28 -30
  3. agentops/client/api/versions/v3.py +29 -25
  4. agentops/client/api/versions/v4.py +87 -46
  5. agentops/client/client.py +98 -29
  6. agentops/client/http/README.md +87 -0
  7. agentops/client/http/http_client.py +126 -172
  8. agentops/config.py +8 -2
  9. agentops/instrumentation/OpenTelemetry.md +133 -0
  10. agentops/instrumentation/README.md +167 -0
  11. agentops/instrumentation/__init__.py +13 -1
  12. agentops/instrumentation/agentic/ag2/__init__.py +18 -0
  13. agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
  14. agentops/instrumentation/agentic/agno/__init__.py +19 -0
  15. agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
  16. agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
  17. agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
  18. agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
  19. agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
  20. agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
  21. agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
  22. agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
  23. agentops/instrumentation/agentic/crewai/LICENSE +201 -0
  24. agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
  25. agentops/instrumentation/agentic/crewai/__init__.py +6 -0
  26. agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
  27. agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
  28. agentops/instrumentation/agentic/crewai/version.py +1 -0
  29. agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
  30. agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
  31. agentops/instrumentation/agentic/google_adk/patch.py +767 -0
  32. agentops/instrumentation/agentic/haystack/__init__.py +1 -0
  33. agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
  34. agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
  35. agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
  36. agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
  37. agentops/instrumentation/agentic/langgraph/version.py +1 -0
  38. agentops/instrumentation/agentic/openai_agents/README.md +156 -0
  39. agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
  40. agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
  41. agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
  42. agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
  43. agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
  44. agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
  45. agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
  46. agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
  47. agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
  48. agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
  49. agentops/instrumentation/agentic/smolagents/README.md +88 -0
  50. agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
  51. agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
  52. agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
  53. agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
  54. agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
  55. agentops/instrumentation/agentic/xpander/__init__.py +15 -0
  56. agentops/instrumentation/agentic/xpander/context.py +112 -0
  57. agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
  58. agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
  59. agentops/instrumentation/agentic/xpander/version.py +3 -0
  60. agentops/instrumentation/common/README.md +65 -0
  61. agentops/instrumentation/common/attributes.py +1 -2
  62. agentops/instrumentation/providers/anthropic/__init__.py +24 -0
  63. agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
  64. agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
  65. agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
  66. agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
  67. agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
  68. agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
  69. agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
  70. agentops/instrumentation/providers/google_genai/README.md +33 -0
  71. agentops/instrumentation/providers/google_genai/__init__.py +24 -0
  72. agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
  73. agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
  74. agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
  75. agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
  76. agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
  77. agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
  78. agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
  79. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
  80. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
  81. agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
  82. agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
  83. agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
  84. agentops/instrumentation/providers/mem0/__init__.py +45 -0
  85. agentops/instrumentation/providers/mem0/common.py +377 -0
  86. agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
  87. agentops/instrumentation/providers/mem0/memory.py +430 -0
  88. agentops/instrumentation/providers/openai/__init__.py +21 -0
  89. agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
  90. agentops/instrumentation/providers/openai/attributes/common.py +55 -0
  91. agentops/instrumentation/providers/openai/attributes/response.py +607 -0
  92. agentops/instrumentation/providers/openai/config.py +36 -0
  93. agentops/instrumentation/providers/openai/instrumentor.py +312 -0
  94. agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
  95. agentops/instrumentation/providers/openai/utils.py +44 -0
  96. agentops/instrumentation/providers/openai/v0.py +176 -0
  97. agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
  98. agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
  99. agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
  100. agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
  101. agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
  102. agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
  103. agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
  104. agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
  105. agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
  106. agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
  107. agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
  108. agentops/integration/callbacks/dspy/__init__.py +11 -0
  109. agentops/integration/callbacks/dspy/callback.py +471 -0
  110. agentops/integration/callbacks/langchain/README.md +59 -0
  111. agentops/integration/callbacks/langchain/__init__.py +15 -0
  112. agentops/integration/callbacks/langchain/callback.py +791 -0
  113. agentops/integration/callbacks/langchain/utils.py +54 -0
  114. agentops/legacy/crewai.md +121 -0
  115. agentops/logging/instrument_logging.py +4 -0
  116. agentops/sdk/README.md +220 -0
  117. agentops/sdk/core.py +75 -32
  118. agentops/sdk/descriptors/classproperty.py +28 -0
  119. agentops/sdk/exporters.py +152 -33
  120. agentops/semconv/README.md +125 -0
  121. agentops/semconv/span_kinds.py +0 -2
  122. agentops/validation.py +102 -63
  123. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/METADATA +30 -40
  124. mseep_agentops-0.4.22.dist-info/RECORD +178 -0
  125. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/WHEEL +1 -2
  126. mseep_agentops-0.4.18.dist-info/RECORD +0 -94
  127. mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
  128. tests/conftest.py +0 -10
  129. tests/unit/client/__init__.py +0 -1
  130. tests/unit/client/test_http_adapter.py +0 -221
  131. tests/unit/client/test_http_client.py +0 -206
  132. tests/unit/conftest.py +0 -54
  133. tests/unit/sdk/__init__.py +0 -1
  134. tests/unit/sdk/instrumentation_tester.py +0 -207
  135. tests/unit/sdk/test_attributes.py +0 -392
  136. tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
  137. tests/unit/sdk/test_decorators.py +0 -763
  138. tests/unit/sdk/test_exporters.py +0 -241
  139. tests/unit/sdk/test_factory.py +0 -1188
  140. tests/unit/sdk/test_internal_span_processor.py +0 -397
  141. tests/unit/sdk/test_resource_attributes.py +0 -35
  142. tests/unit/test_config.py +0 -82
  143. tests/unit/test_context_manager.py +0 -777
  144. tests/unit/test_events.py +0 -27
  145. tests/unit/test_host_env.py +0 -54
  146. tests/unit/test_init_py.py +0 -501
  147. tests/unit/test_serialization.py +0 -433
  148. tests/unit/test_session.py +0 -676
  149. tests/unit/test_user_agent.py +0 -34
  150. tests/unit/test_validation.py +0 -405
  151. {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
  152. /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
  153. {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,195 @@
1
+ """Agno Team run attributes handler."""
2
+
3
+ from typing import Optional, Tuple, Dict, Any
4
+ import json
5
+ from agentops.instrumentation.common.attributes import AttributeMap
6
+ from agentops.semconv import SpanAttributes, WorkflowAttributes
7
+ from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
8
+
9
+
10
+ def get_team_run_attributes(
11
+ args: Optional[Tuple] = None,
12
+ kwargs: Optional[Dict] = None,
13
+ return_value: Optional[Any] = None,
14
+ ) -> AttributeMap:
15
+ """Extract span attributes for Team method calls (both internal and public).
16
+
17
+ Args:
18
+ args: Positional arguments passed to the Team method
19
+ kwargs: Keyword arguments passed to the Team method
20
+ return_value: The return value from the Team method
21
+
22
+ Returns:
23
+ A dictionary of span attributes to be set on the workflow span
24
+ """
25
+ attributes: AttributeMap = {}
26
+
27
+ # Base attributes
28
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.WORKFLOW
29
+ attributes[SpanAttributes.LLM_SYSTEM] = "agno"
30
+ attributes[WorkflowAttributes.WORKFLOW_TYPE] = "team_run"
31
+
32
+ # Extract team information from instance
33
+ if args and len(args) > 0:
34
+ team = args[0] # self (Team instance)
35
+
36
+ # Team identification
37
+ if hasattr(team, "name") and team.name:
38
+ attributes["team.name"] = str(team.name)
39
+ attributes["team.display_name"] = f"{team.name} (Team)"
40
+
41
+ if hasattr(team, "team_id") and team.team_id:
42
+ attributes["team.team_id"] = str(team.team_id)
43
+
44
+ if hasattr(team, "mode") and team.mode:
45
+ attributes["team.mode"] = str(team.mode)
46
+
47
+ if hasattr(team, "members") and team.members:
48
+ attributes["team.members_count"] = str(len(team.members))
49
+
50
+ # Add detailed member information
51
+ member_agents = []
52
+ for i, member in enumerate(team.members):
53
+ member_info = {}
54
+ if hasattr(member, "name") and member.name:
55
+ member_info["name"] = str(member.name)
56
+ if hasattr(member, "agent_id") and member.agent_id:
57
+ member_info["id"] = str(member.agent_id)
58
+ if hasattr(member, "role") and member.role:
59
+ member_info["role"] = str(member.role)
60
+ if hasattr(member, "model") and member.model:
61
+ if hasattr(member.model, "id"):
62
+ member_info["model"] = str(member.model.id)
63
+
64
+ # Add member info to list
65
+ if member_info:
66
+ member_agents.append(member_info)
67
+
68
+ # Also add individual member attributes
69
+ for key, value in member_info.items():
70
+ attributes[f"team.member.{i}.{key}"] = value
71
+
72
+ # Add aggregated member list
73
+ if member_agents:
74
+ try:
75
+ attributes["team.members"] = json.dumps(member_agents)
76
+ # Also add a simple list of member names
77
+ member_names = [m.get("name", "Unknown") for m in member_agents]
78
+ attributes["team.member_names"] = ", ".join(member_names)
79
+ except:
80
+ attributes["team.members"] = str(member_agents)
81
+
82
+ # Process input arguments - handle both internal and public method signatures
83
+ if args and len(args) >= 2:
84
+ input_arg = args[1]
85
+
86
+ # Check if it's internal method (has run_messages) or public method (direct message)
87
+ if hasattr(input_arg, "messages"):
88
+ # Internal method: args[1] is run_messages
89
+ run_messages = input_arg
90
+ # Get the user message for workflow input
91
+ user_messages = [msg for msg in run_messages.messages if hasattr(msg, "role") and msg.role == "user"]
92
+ if user_messages:
93
+ last_user_msg = user_messages[-1]
94
+ if hasattr(last_user_msg, "content"):
95
+ attributes[WorkflowAttributes.WORKFLOW_INPUT] = str(last_user_msg.content)
96
+ attributes[WorkflowAttributes.WORKFLOW_INPUT_TYPE] = "message"
97
+ # Count total messages
98
+ attributes["team.messages_count"] = str(len(run_messages.messages))
99
+ else:
100
+ # Public method: args[1] is the message directly
101
+ message = input_arg
102
+ if message is not None:
103
+ if isinstance(message, str):
104
+ message_content = message
105
+ elif hasattr(message, "content"):
106
+ message_content = str(message.content)
107
+ elif hasattr(message, "get_content_string"):
108
+ message_content = message.get_content_string()
109
+ else:
110
+ message_content = str(message)
111
+
112
+ attributes[WorkflowAttributes.WORKFLOW_INPUT] = message_content
113
+ attributes[WorkflowAttributes.WORKFLOW_INPUT_TYPE] = "message"
114
+
115
+ # Process keyword arguments
116
+ if kwargs:
117
+ if kwargs.get("user_id"):
118
+ attributes[SpanAttributes.LLM_USER] = kwargs["user_id"]
119
+
120
+ if kwargs.get("session_id"):
121
+ attributes["team.session_id"] = kwargs["session_id"]
122
+
123
+ if kwargs.get("response_format"):
124
+ attributes["team.response_format"] = str(type(kwargs["response_format"]).__name__)
125
+
126
+ if kwargs.get("stream"):
127
+ attributes["team.streaming"] = str(kwargs["stream"])
128
+
129
+ if kwargs.get("stream_intermediate_steps"):
130
+ attributes["team.stream_intermediate_steps"] = str(kwargs["stream_intermediate_steps"])
131
+
132
+ if kwargs.get("retries"):
133
+ attributes["team.retries"] = str(kwargs["retries"])
134
+
135
+ # Media attachments
136
+ if kwargs.get("audio"):
137
+ attributes["team.has_audio"] = "true"
138
+ if kwargs.get("images"):
139
+ attributes["team.has_images"] = "true"
140
+ if kwargs.get("videos"):
141
+ attributes["team.has_videos"] = "true"
142
+ if kwargs.get("files"):
143
+ attributes["team.has_files"] = "true"
144
+
145
+ if kwargs.get("knowledge_filters"):
146
+ attributes["team.has_knowledge_filters"] = "true"
147
+
148
+ # Process return value (TeamRunResponse or Iterator)
149
+ if return_value:
150
+ # Handle both single response and iterator
151
+ if hasattr(return_value, "__iter__") and not isinstance(return_value, str):
152
+ # It's an iterator for streaming
153
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_TYPE] = "team_run_response_stream"
154
+ attributes["team.is_streaming"] = "true"
155
+ else:
156
+ # Non-streaming response
157
+ if hasattr(return_value, "content"):
158
+ # It's a TeamRunResponse with content
159
+ content = str(return_value.content)
160
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT] = content
161
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_TYPE] = "team_run_response"
162
+ else:
163
+ # Unknown return type or response without content
164
+ output = str(return_value)
165
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT] = output
166
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_TYPE] = type(return_value).__name__
167
+
168
+ # Set additional team response attributes (for both streaming and non-streaming)
169
+ if hasattr(return_value, "run_id"):
170
+ attributes["team.run_id"] = str(return_value.run_id)
171
+
172
+ if hasattr(return_value, "session_id"):
173
+ attributes["team.response_session_id"] = str(return_value.session_id)
174
+
175
+ if hasattr(return_value, "team_id"):
176
+ attributes["team.response_team_id"] = str(return_value.team_id)
177
+
178
+ if hasattr(return_value, "model"):
179
+ attributes[SpanAttributes.LLM_RESPONSE_MODEL] = str(return_value.model)
180
+
181
+ if hasattr(return_value, "model_provider"):
182
+ attributes["team.model_provider"] = str(return_value.model_provider)
183
+
184
+ if hasattr(return_value, "event"):
185
+ attributes["team.event"] = str(return_value.event)
186
+
187
+ # Team-specific attributes
188
+ if hasattr(return_value, "content_type"):
189
+ attributes["team.response_content_type"] = str(return_value.content_type)
190
+
191
+ return attributes
192
+
193
+
194
+ # Keep the public function as an alias for backward compatibility
195
+ get_team_public_run_attributes = get_team_run_attributes
@@ -0,0 +1,210 @@
1
+ """Agno tool execution attributes handler."""
2
+
3
+ import json
4
+ from typing import Optional, Tuple, Dict, Any
5
+ import time
6
+ from agentops.instrumentation.common.attributes import AttributeMap
7
+ from agentops.semconv import SpanAttributes
8
+ from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
9
+ from agentops.semconv.tool import ToolAttributes
10
+
11
+
12
+ def get_tool_execution_attributes(
13
+ args: Optional[Tuple] = None,
14
+ kwargs: Optional[Dict] = None,
15
+ return_value: Optional[Any] = None,
16
+ ) -> AttributeMap:
17
+ """Extract span attributes for tool execution calls (FunctionCall.execute/aexecute).
18
+
19
+ Args:
20
+ args: Positional arguments passed to the execute method (self)
21
+ kwargs: Keyword arguments passed to the execute method
22
+ return_value: The return value from the execute method (FunctionExecutionResult)
23
+
24
+ Returns:
25
+ A dictionary of span attributes to be set on the tool execution span
26
+ """
27
+ attributes: AttributeMap = {}
28
+
29
+ # Base attributes
30
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.TOOL
31
+ attributes[SpanAttributes.LLM_SYSTEM] = "agno"
32
+
33
+ # AgentOps entity attributes
34
+ attributes[SpanAttributes.AGENTOPS_ENTITY_NAME] = "tool"
35
+
36
+ # Process the FunctionCall object (self in execute method)
37
+ if args and len(args) > 0:
38
+ function_call = args[0]
39
+
40
+ # Add detailed function call information
41
+ attributes["tool.function_call_type"] = str(type(function_call).__name__)
42
+
43
+ # Extract tool information
44
+ if hasattr(function_call, "function") and function_call.function:
45
+ function = function_call.function
46
+
47
+ # Get function name and add display name
48
+ if hasattr(function, "__name__"):
49
+ func_name = function.__name__
50
+ attributes["tool.function_name"] = func_name
51
+ attributes["tool.display_name"] = f"{func_name} (Tool)"
52
+
53
+ tool_name = getattr(function, "name", "unknown_tool")
54
+
55
+ # Set span attributes for the tool execution span
56
+ attributes[ToolAttributes.TOOL_NAME] = tool_name
57
+ attributes["tool.function_name"] = tool_name
58
+
59
+ # Function details and context
60
+ if hasattr(function, "description"):
61
+ description = getattr(function, "description", "")
62
+ if description:
63
+ attributes[ToolAttributes.TOOL_DESCRIPTION] = description
64
+ attributes["tool.function_description"] = description
65
+
66
+ # Function source information
67
+ if hasattr(function, "entrypoint") and function.entrypoint:
68
+ entrypoint = function.entrypoint
69
+ if hasattr(entrypoint, "__module__"):
70
+ attributes["tool.function_module"] = str(entrypoint.__module__)
71
+ if hasattr(entrypoint, "__name__"):
72
+ attributes["tool.function_method"] = str(entrypoint.__name__)
73
+ if hasattr(entrypoint, "__qualname__"):
74
+ attributes["tool.function_qualname"] = str(entrypoint.__qualname__)
75
+
76
+ # Tool capabilities
77
+ if hasattr(function, "requires_confirmation"):
78
+ attributes["tool.requires_confirmation"] = str(function.requires_confirmation)
79
+ if hasattr(function, "show_result"):
80
+ attributes["tool.show_result"] = str(function.show_result)
81
+ if hasattr(function, "stop_after_tool_call"):
82
+ attributes["tool.stop_after_tool_call"] = str(function.stop_after_tool_call)
83
+
84
+ # Extract tool arguments with better formatting
85
+ if hasattr(function_call, "arguments") and function_call.arguments:
86
+ try:
87
+ if isinstance(function_call.arguments, str):
88
+ args_dict = json.loads(function_call.arguments)
89
+ else:
90
+ args_dict = function_call.arguments
91
+
92
+ # Format arguments nicely
93
+ formatted_args = []
94
+ for key, value in args_dict.items():
95
+ value_str = str(value)
96
+ formatted_args.append(f"{key}={value_str}")
97
+
98
+ attributes[ToolAttributes.TOOL_PARAMETERS] = json.dumps(args_dict)
99
+ attributes["tool.formatted_args"] = ", ".join(formatted_args)
100
+ attributes["tool.args_count"] = str(len(args_dict))
101
+ except Exception as e:
102
+ attributes[ToolAttributes.TOOL_PARAMETERS] = str(function_call.arguments)
103
+ attributes["tool.args_parse_error"] = str(e)
104
+
105
+ # Extract call ID and metadata
106
+ if hasattr(function_call, "tool_call_id"):
107
+ attributes["tool.call_id"] = str(function_call.tool_call_id)
108
+
109
+ # Check for any agent context
110
+ if hasattr(function_call, "_agent") and function_call._agent:
111
+ agent = function_call._agent
112
+ if hasattr(agent, "name"):
113
+ attributes["tool.calling_agent_name"] = str(agent.name)
114
+ if hasattr(agent, "agent_id"):
115
+ attributes["tool.calling_agent_id"] = str(agent.agent_id)
116
+
117
+ # Process return value
118
+ if return_value is not None:
119
+ # Add timing information
120
+ attributes["tool.execution_timestamp"] = str(int(time.time() * 1000))
121
+
122
+ # Determine execution status and result information
123
+ if hasattr(return_value, "value"):
124
+ # FunctionExecutionResult with value
125
+ result_value = return_value.value
126
+ attributes["tool.execution_status"] = "success"
127
+ else:
128
+ # Direct return value
129
+ result_value = return_value
130
+ attributes["tool.execution_status"] = "success"
131
+
132
+ # Process result value
133
+ if result_value is not None:
134
+ result_type = type(result_value).__name__
135
+ attributes["tool.execution_result_status"] = str(result_type)
136
+
137
+ # Handle FunctionExecutionResult objects specifically
138
+ if hasattr(result_value, "status") and hasattr(result_value, "result"):
139
+ # This looks like a FunctionExecutionResult
140
+ status = getattr(result_value, "status", "unknown")
141
+ actual_result = getattr(result_value, "result", None)
142
+ error = getattr(result_value, "error", None)
143
+
144
+ attributes["tool.execution_result_status"] = str(status)
145
+ attributes[ToolAttributes.TOOL_STATUS] = str(status)
146
+
147
+ if error:
148
+ attributes["tool.execution_error"] = str(error)
149
+ attributes["tool.error"] = str(error)
150
+
151
+ if actual_result is not None:
152
+ actual_result_type = type(actual_result).__name__
153
+ attributes["tool.actual_result_type"] = actual_result_type
154
+
155
+ # Enhanced generator handling
156
+ if hasattr(actual_result, "__iter__") and hasattr(actual_result, "__next__"):
157
+ attributes["tool.result_is_generator"] = "true"
158
+
159
+ # Try to get more meaningful information about the generator
160
+ generator_info = []
161
+
162
+ # Get function name from the generator
163
+ if hasattr(actual_result, "gi_code"):
164
+ func_name = actual_result.gi_code.co_name
165
+ attributes["tool.generator_function"] = func_name
166
+ generator_info.append(f"function={func_name}")
167
+
168
+ if generator_info:
169
+ result_str = f"Generator<{actual_result_type}>({', '.join(generator_info)})"
170
+ else:
171
+ result_str = f"Generator<{actual_result_type}> - {str(actual_result)}"
172
+ else:
173
+ # Regular result
174
+ result_str = str(actual_result)
175
+ else:
176
+ result_str = str(status)
177
+ else:
178
+ # Not a FunctionExecutionResult, handle as direct result
179
+ if hasattr(result_value, "__iter__") and hasattr(result_value, "__next__"):
180
+ # It's a generator
181
+ attributes["tool.result_is_generator"] = "true"
182
+
183
+ if hasattr(result_value, "gi_code"):
184
+ func_name = result_value.gi_code.co_name
185
+ attributes["tool.generator_function"] = func_name
186
+ result_str = f"Generator<{result_type}> function={func_name} - {str(result_value)}"
187
+ else:
188
+ result_str = f"Generator<{result_type}> - {str(result_value)}"
189
+ else:
190
+ # Regular result
191
+ result_str = str(result_value)
192
+ else:
193
+ result_str = "None"
194
+
195
+ # Set the main result attribute
196
+ attributes[ToolAttributes.TOOL_RESULT] = result_str
197
+
198
+ # Add additional analysis attributes
199
+ attributes["tool.result_length"] = str(len(result_str))
200
+
201
+ # Set final execution status
202
+ if not attributes.get(ToolAttributes.TOOL_STATUS):
203
+ attributes[ToolAttributes.TOOL_STATUS] = "success"
204
+
205
+ # Add execution summary for debugging
206
+ tool_name = attributes.get(ToolAttributes.TOOL_NAME, "unknown")
207
+ call_type = attributes.get("tool.transfer_type", "unknown")
208
+ attributes["tool.execution_summary"] = f"Tool '{tool_name}' executed with type '{call_type}'"
209
+
210
+ return attributes
@@ -0,0 +1,254 @@
1
+ """Workflow attribute extraction for agno workflow instrumentation."""
2
+
3
+ from typing import Any, Dict, Optional, Tuple
4
+ from opentelemetry.util.types import AttributeValue
5
+ import json
6
+
7
+ from agentops.semconv.instrumentation import InstrumentationAttributes
8
+ from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
9
+ from agentops.semconv.workflow import WorkflowAttributes
10
+ from agentops.instrumentation.common.attributes import get_common_attributes
11
+
12
+
13
+ def get_workflow_run_attributes(
14
+ args: Tuple[Any, ...] = (),
15
+ kwargs: Optional[Dict[str, Any]] = None,
16
+ return_value: Optional[Any] = None,
17
+ ) -> Dict[str, AttributeValue]:
18
+ """Extract attributes from workflow run operations.
19
+
20
+ Args:
21
+ args: Positional arguments passed to the workflow run method
22
+ kwargs: Keyword arguments passed to the workflow run method
23
+ return_value: Return value from the workflow run method
24
+
25
+ Returns:
26
+ Dictionary of OpenTelemetry attributes for workflow runs
27
+ """
28
+ attributes = get_common_attributes()
29
+ kwargs = kwargs or {}
30
+
31
+ if args and len(args) > 0:
32
+ workflow = args[0]
33
+
34
+ # Core workflow attributes
35
+ if hasattr(workflow, "name") and workflow.name:
36
+ attributes[WorkflowAttributes.WORKFLOW_NAME] = str(workflow.name)
37
+ if hasattr(workflow, "workflow_id") and workflow.workflow_id:
38
+ attributes[WorkflowAttributes.WORKFLOW_ID] = str(workflow.workflow_id)
39
+ if hasattr(workflow, "description") and workflow.description:
40
+ attributes[WorkflowAttributes.WORKFLOW_DESCRIPTION] = str(workflow.description)
41
+ if hasattr(workflow, "app_id") and workflow.app_id:
42
+ attributes[WorkflowAttributes.WORKFLOW_APP_ID] = str(workflow.app_id)
43
+
44
+ # Set workflow type
45
+ attributes[WorkflowAttributes.WORKFLOW_TYPE] = "agno_workflow"
46
+
47
+ # Session and user attributes
48
+ if hasattr(workflow, "session_id") and workflow.session_id:
49
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_ID] = str(workflow.session_id)
50
+ if hasattr(workflow, "session_name") and workflow.session_name:
51
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_NAME] = str(workflow.session_name)
52
+ if hasattr(workflow, "user_id") and workflow.user_id:
53
+ attributes[WorkflowAttributes.WORKFLOW_USER_ID] = str(workflow.user_id)
54
+
55
+ # Run-specific attributes
56
+ if hasattr(workflow, "run_id") and workflow.run_id:
57
+ attributes[WorkflowAttributes.WORKFLOW_RUN_ID] = str(workflow.run_id)
58
+
59
+ # Configuration attributes
60
+ if hasattr(workflow, "debug_mode"):
61
+ attributes[WorkflowAttributes.WORKFLOW_DEBUG_MODE] = bool(workflow.debug_mode)
62
+ if hasattr(workflow, "monitoring"):
63
+ attributes[WorkflowAttributes.WORKFLOW_MONITORING] = bool(workflow.monitoring)
64
+ if hasattr(workflow, "telemetry"):
65
+ attributes[WorkflowAttributes.WORKFLOW_TELEMETRY] = bool(workflow.telemetry)
66
+
67
+ # Memory and storage attributes
68
+ if hasattr(workflow, "memory") and workflow.memory:
69
+ memory_type = type(workflow.memory).__name__
70
+ attributes[WorkflowAttributes.WORKFLOW_MEMORY_TYPE] = memory_type
71
+
72
+ if hasattr(workflow, "storage") and workflow.storage:
73
+ storage_type = type(workflow.storage).__name__
74
+ attributes[WorkflowAttributes.WORKFLOW_STORAGE_TYPE] = storage_type
75
+
76
+ # Input parameters from kwargs
77
+ if kwargs:
78
+ # Store workflow input
79
+ attributes[WorkflowAttributes.WORKFLOW_INPUT] = str(kwargs)
80
+
81
+ # Count and types of input parameters
82
+ attributes[WorkflowAttributes.WORKFLOW_INPUT_PARAMETER_COUNT] = len(kwargs)
83
+ param_types = list(set(type(v).__name__ for v in kwargs.values()))
84
+ if param_types:
85
+ attributes[WorkflowAttributes.WORKFLOW_INPUT_TYPE] = str(param_types)
86
+
87
+ # Store specific input keys (without values for privacy)
88
+ input_keys = list(kwargs.keys())
89
+ if input_keys:
90
+ attributes[WorkflowAttributes.WORKFLOW_INPUT_PARAMETER_KEYS] = str(input_keys)
91
+
92
+ # Workflow method parameters if available
93
+ if hasattr(workflow, "_run_parameters") and workflow._run_parameters:
94
+ param_count = len(workflow._run_parameters)
95
+ attributes[WorkflowAttributes.WORKFLOW_METHOD_PARAMETER_COUNT] = param_count
96
+
97
+ if hasattr(workflow, "_run_return_type") and workflow._run_return_type:
98
+ attributes[WorkflowAttributes.WORKFLOW_METHOD_RETURN_TYPE] = str(workflow._run_return_type)
99
+
100
+ # Process return value attributes
101
+ if return_value is not None:
102
+ return_type = type(return_value).__name__
103
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_TYPE] = return_type
104
+
105
+ # Handle RunResponse objects
106
+ if hasattr(return_value, "content"):
107
+ # Store workflow output
108
+ if return_value.content:
109
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT] = str(return_value.content)
110
+
111
+ if hasattr(return_value, "content_type"):
112
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_CONTENT_TYPE] = str(return_value.content_type)
113
+ if hasattr(return_value, "event"):
114
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_EVENT] = str(return_value.event)
115
+ if hasattr(return_value, "model"):
116
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_MODEL] = (
117
+ str(return_value.model) if return_value.model else ""
118
+ )
119
+ if hasattr(return_value, "model_provider"):
120
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_MODEL_PROVIDER] = (
121
+ str(return_value.model_provider) if return_value.model_provider else ""
122
+ )
123
+
124
+ # Count various response components
125
+ if hasattr(return_value, "messages") and return_value.messages:
126
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_MESSAGE_COUNT] = len(return_value.messages)
127
+ if hasattr(return_value, "tools") and return_value.tools:
128
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_TOOL_COUNT] = len(return_value.tools)
129
+ if hasattr(return_value, "images") and return_value.images:
130
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_IMAGE_COUNT] = len(return_value.images)
131
+ if hasattr(return_value, "videos") and return_value.videos:
132
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_VIDEO_COUNT] = len(return_value.videos)
133
+ if hasattr(return_value, "audio") and return_value.audio:
134
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_AUDIO_COUNT] = len(return_value.audio)
135
+
136
+ # Handle generators/iterators
137
+ elif hasattr(return_value, "__iter__") and not isinstance(return_value, (str, bytes)):
138
+ attributes[WorkflowAttributes.WORKFLOW_OUTPUT_IS_STREAMING] = True
139
+
140
+ # Set span kind - AgentOpsSpanKind.WORKFLOW is already a string
141
+ attributes[InstrumentationAttributes.INSTRUMENTATION_TYPE] = AgentOpsSpanKind.WORKFLOW
142
+
143
+ return attributes
144
+
145
+
146
+ def get_workflow_session_attributes(
147
+ args: Tuple[Any, ...] = (),
148
+ kwargs: Optional[Dict[str, Any]] = None,
149
+ return_value: Optional[Any] = None,
150
+ ) -> Dict[str, AttributeValue]:
151
+ """Extract attributes from workflow session operations.
152
+
153
+ Args:
154
+ args: Positional arguments passed to the session method
155
+ kwargs: Keyword arguments passed to the session method
156
+ return_value: Return value from the session method
157
+
158
+ Returns:
159
+ Dictionary of OpenTelemetry attributes for workflow sessions
160
+ """
161
+ attributes = get_common_attributes()
162
+ kwargs = kwargs or {}
163
+
164
+ if args and len(args) > 0:
165
+ workflow = args[0]
166
+
167
+ # Session attributes
168
+ if hasattr(workflow, "session_id") and workflow.session_id:
169
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_ID] = str(workflow.session_id)
170
+ if hasattr(workflow, "session_name") and workflow.session_name:
171
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_NAME] = str(workflow.session_name)
172
+ if hasattr(workflow, "workflow_id") and workflow.workflow_id:
173
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_WORKFLOW_ID] = str(workflow.workflow_id)
174
+ if hasattr(workflow, "user_id") and workflow.user_id:
175
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_USER_ID] = str(workflow.user_id)
176
+
177
+ # Session state attributes
178
+ if hasattr(workflow, "session_state") and workflow.session_state:
179
+ if isinstance(workflow.session_state, dict):
180
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_STATE_KEYS] = str(list(workflow.session_state.keys()))
181
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_STATE_SIZE] = len(workflow.session_state)
182
+
183
+ # Storage attributes
184
+ if hasattr(workflow, "storage") and workflow.storage:
185
+ storage_type = type(workflow.storage).__name__
186
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_STORAGE_TYPE] = storage_type
187
+
188
+ # Process session return value if it's a WorkflowSession
189
+ if return_value is not None and hasattr(return_value, "session_id"):
190
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_RETURNED_SESSION_ID] = str(return_value.session_id)
191
+ if hasattr(return_value, "created_at") and return_value.created_at:
192
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_CREATED_AT] = int(return_value.created_at)
193
+ if hasattr(return_value, "updated_at") and return_value.updated_at:
194
+ attributes[WorkflowAttributes.WORKFLOW_SESSION_UPDATED_AT] = int(return_value.updated_at)
195
+
196
+ # Set span kind - AgentOpsSpanKind.WORKFLOW is already a string
197
+ attributes[InstrumentationAttributes.INSTRUMENTATION_TYPE] = AgentOpsSpanKind.WORKFLOW
198
+
199
+ return attributes
200
+
201
+
202
+ def get_workflow_cache_attributes(
203
+ args: Tuple[Any, ...] = (),
204
+ kwargs: Optional[Dict[str, Any]] = None,
205
+ return_value: Optional[Any] = None,
206
+ ) -> Dict[str, AttributeValue]:
207
+ """Extract attributes from workflow cache operations.
208
+
209
+ Args:
210
+ args: Positional arguments passed to the cache method
211
+ kwargs: Keyword arguments passed to the cache method
212
+ return_value: Return value from the cache method
213
+
214
+ Returns:
215
+ Dictionary of OpenTelemetry attributes for cache operations
216
+ """
217
+ attributes = get_common_attributes()
218
+ kwargs = kwargs or {}
219
+
220
+ if args and len(args) > 0:
221
+ workflow = args[0]
222
+
223
+ # Get workflow information
224
+ if hasattr(workflow, "workflow_id") and workflow.workflow_id:
225
+ attributes["cache.workflow_id"] = str(workflow.workflow_id)
226
+ if hasattr(workflow, "session_id") and workflow.session_id:
227
+ attributes["cache.session_id"] = str(workflow.session_id)
228
+
229
+ # Get cache state
230
+ if hasattr(workflow, "session_state") and isinstance(workflow.session_state, dict):
231
+ attributes["cache.size"] = len(workflow.session_state)
232
+ attributes["cache.keys"] = json.dumps(list(workflow.session_state.keys()))
233
+
234
+ # Determine cache operation type and result
235
+ if len(args) > 1:
236
+ cache_key = str(args[1])
237
+ attributes["cache.key"] = cache_key
238
+
239
+ if return_value is not None:
240
+ attributes["cache.hit"] = True
241
+ attributes["cache.result"] = "hit"
242
+
243
+ # Add value info
244
+ if isinstance(return_value, str):
245
+ attributes["cache.value_size"] = len(return_value)
246
+ if len(return_value) <= 100:
247
+ attributes["cache.value"] = return_value
248
+ else:
249
+ attributes["cache.value_preview"] = return_value[:100] + "..."
250
+ else:
251
+ attributes["cache.hit"] = False
252
+ attributes["cache.result"] = "miss"
253
+
254
+ return attributes