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.
- agentops/__init__.py +0 -0
- agentops/client/api/base.py +28 -30
- agentops/client/api/versions/v3.py +29 -25
- agentops/client/api/versions/v4.py +87 -46
- agentops/client/client.py +98 -29
- agentops/client/http/README.md +87 -0
- agentops/client/http/http_client.py +126 -172
- agentops/config.py +8 -2
- agentops/instrumentation/OpenTelemetry.md +133 -0
- agentops/instrumentation/README.md +167 -0
- agentops/instrumentation/__init__.py +13 -1
- agentops/instrumentation/agentic/ag2/__init__.py +18 -0
- agentops/instrumentation/agentic/ag2/instrumentor.py +922 -0
- agentops/instrumentation/agentic/agno/__init__.py +19 -0
- agentops/instrumentation/agentic/agno/attributes/__init__.py +20 -0
- agentops/instrumentation/agentic/agno/attributes/agent.py +250 -0
- agentops/instrumentation/agentic/agno/attributes/metrics.py +214 -0
- agentops/instrumentation/agentic/agno/attributes/storage.py +158 -0
- agentops/instrumentation/agentic/agno/attributes/team.py +195 -0
- agentops/instrumentation/agentic/agno/attributes/tool.py +210 -0
- agentops/instrumentation/agentic/agno/attributes/workflow.py +254 -0
- agentops/instrumentation/agentic/agno/instrumentor.py +1313 -0
- agentops/instrumentation/agentic/crewai/LICENSE +201 -0
- agentops/instrumentation/agentic/crewai/NOTICE.md +10 -0
- agentops/instrumentation/agentic/crewai/__init__.py +6 -0
- agentops/instrumentation/agentic/crewai/crewai_span_attributes.py +335 -0
- agentops/instrumentation/agentic/crewai/instrumentation.py +535 -0
- agentops/instrumentation/agentic/crewai/version.py +1 -0
- agentops/instrumentation/agentic/google_adk/__init__.py +19 -0
- agentops/instrumentation/agentic/google_adk/instrumentor.py +68 -0
- agentops/instrumentation/agentic/google_adk/patch.py +767 -0
- agentops/instrumentation/agentic/haystack/__init__.py +1 -0
- agentops/instrumentation/agentic/haystack/instrumentor.py +186 -0
- agentops/instrumentation/agentic/langgraph/__init__.py +3 -0
- agentops/instrumentation/agentic/langgraph/attributes.py +54 -0
- agentops/instrumentation/agentic/langgraph/instrumentation.py +598 -0
- agentops/instrumentation/agentic/langgraph/version.py +1 -0
- agentops/instrumentation/agentic/openai_agents/README.md +156 -0
- agentops/instrumentation/agentic/openai_agents/SPANS.md +145 -0
- agentops/instrumentation/agentic/openai_agents/TRACING_API.md +144 -0
- agentops/instrumentation/agentic/openai_agents/__init__.py +30 -0
- agentops/instrumentation/agentic/openai_agents/attributes/common.py +549 -0
- agentops/instrumentation/agentic/openai_agents/attributes/completion.py +172 -0
- agentops/instrumentation/agentic/openai_agents/attributes/model.py +58 -0
- agentops/instrumentation/agentic/openai_agents/attributes/tokens.py +275 -0
- agentops/instrumentation/agentic/openai_agents/exporter.py +469 -0
- agentops/instrumentation/agentic/openai_agents/instrumentor.py +107 -0
- agentops/instrumentation/agentic/openai_agents/processor.py +58 -0
- agentops/instrumentation/agentic/smolagents/README.md +88 -0
- agentops/instrumentation/agentic/smolagents/__init__.py +12 -0
- agentops/instrumentation/agentic/smolagents/attributes/agent.py +354 -0
- agentops/instrumentation/agentic/smolagents/attributes/model.py +205 -0
- agentops/instrumentation/agentic/smolagents/instrumentor.py +286 -0
- agentops/instrumentation/agentic/smolagents/stream_wrapper.py +258 -0
- agentops/instrumentation/agentic/xpander/__init__.py +15 -0
- agentops/instrumentation/agentic/xpander/context.py +112 -0
- agentops/instrumentation/agentic/xpander/instrumentor.py +877 -0
- agentops/instrumentation/agentic/xpander/trace_probe.py +86 -0
- agentops/instrumentation/agentic/xpander/version.py +3 -0
- agentops/instrumentation/common/README.md +65 -0
- agentops/instrumentation/common/attributes.py +1 -2
- agentops/instrumentation/providers/anthropic/__init__.py +24 -0
- agentops/instrumentation/providers/anthropic/attributes/__init__.py +23 -0
- agentops/instrumentation/providers/anthropic/attributes/common.py +64 -0
- agentops/instrumentation/providers/anthropic/attributes/message.py +541 -0
- agentops/instrumentation/providers/anthropic/attributes/tools.py +231 -0
- agentops/instrumentation/providers/anthropic/event_handler_wrapper.py +90 -0
- agentops/instrumentation/providers/anthropic/instrumentor.py +146 -0
- agentops/instrumentation/providers/anthropic/stream_wrapper.py +436 -0
- agentops/instrumentation/providers/google_genai/README.md +33 -0
- agentops/instrumentation/providers/google_genai/__init__.py +24 -0
- agentops/instrumentation/providers/google_genai/attributes/__init__.py +25 -0
- agentops/instrumentation/providers/google_genai/attributes/chat.py +125 -0
- agentops/instrumentation/providers/google_genai/attributes/common.py +88 -0
- agentops/instrumentation/providers/google_genai/attributes/model.py +284 -0
- agentops/instrumentation/providers/google_genai/instrumentor.py +170 -0
- agentops/instrumentation/providers/google_genai/stream_wrapper.py +238 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/__init__.py +28 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/__init__.py +27 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/attributes.py +277 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/attributes/common.py +104 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/instrumentor.py +162 -0
- agentops/instrumentation/providers/ibm_watsonx_ai/stream_wrapper.py +302 -0
- agentops/instrumentation/providers/mem0/__init__.py +45 -0
- agentops/instrumentation/providers/mem0/common.py +377 -0
- agentops/instrumentation/providers/mem0/instrumentor.py +270 -0
- agentops/instrumentation/providers/mem0/memory.py +430 -0
- agentops/instrumentation/providers/openai/__init__.py +21 -0
- agentops/instrumentation/providers/openai/attributes/__init__.py +7 -0
- agentops/instrumentation/providers/openai/attributes/common.py +55 -0
- agentops/instrumentation/providers/openai/attributes/response.py +607 -0
- agentops/instrumentation/providers/openai/config.py +36 -0
- agentops/instrumentation/providers/openai/instrumentor.py +312 -0
- agentops/instrumentation/providers/openai/stream_wrapper.py +941 -0
- agentops/instrumentation/providers/openai/utils.py +44 -0
- agentops/instrumentation/providers/openai/v0.py +176 -0
- agentops/instrumentation/providers/openai/v0_wrappers.py +483 -0
- agentops/instrumentation/providers/openai/wrappers/__init__.py +30 -0
- agentops/instrumentation/providers/openai/wrappers/assistant.py +277 -0
- agentops/instrumentation/providers/openai/wrappers/chat.py +259 -0
- agentops/instrumentation/providers/openai/wrappers/completion.py +109 -0
- agentops/instrumentation/providers/openai/wrappers/embeddings.py +94 -0
- agentops/instrumentation/providers/openai/wrappers/image_gen.py +75 -0
- agentops/instrumentation/providers/openai/wrappers/responses.py +191 -0
- agentops/instrumentation/providers/openai/wrappers/shared.py +81 -0
- agentops/instrumentation/utilities/concurrent_futures/__init__.py +10 -0
- agentops/instrumentation/utilities/concurrent_futures/instrumentation.py +206 -0
- agentops/integration/callbacks/dspy/__init__.py +11 -0
- agentops/integration/callbacks/dspy/callback.py +471 -0
- agentops/integration/callbacks/langchain/README.md +59 -0
- agentops/integration/callbacks/langchain/__init__.py +15 -0
- agentops/integration/callbacks/langchain/callback.py +791 -0
- agentops/integration/callbacks/langchain/utils.py +54 -0
- agentops/legacy/crewai.md +121 -0
- agentops/logging/instrument_logging.py +4 -0
- agentops/sdk/README.md +220 -0
- agentops/sdk/core.py +75 -32
- agentops/sdk/descriptors/classproperty.py +28 -0
- agentops/sdk/exporters.py +152 -33
- agentops/semconv/README.md +125 -0
- agentops/semconv/span_kinds.py +0 -2
- agentops/validation.py +102 -63
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/METADATA +30 -40
- mseep_agentops-0.4.22.dist-info/RECORD +178 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.22.dist-info}/WHEEL +1 -2
- mseep_agentops-0.4.18.dist-info/RECORD +0 -94
- mseep_agentops-0.4.18.dist-info/top_level.txt +0 -2
- tests/conftest.py +0 -10
- tests/unit/client/__init__.py +0 -1
- tests/unit/client/test_http_adapter.py +0 -221
- tests/unit/client/test_http_client.py +0 -206
- tests/unit/conftest.py +0 -54
- tests/unit/sdk/__init__.py +0 -1
- tests/unit/sdk/instrumentation_tester.py +0 -207
- tests/unit/sdk/test_attributes.py +0 -392
- tests/unit/sdk/test_concurrent_instrumentation.py +0 -468
- tests/unit/sdk/test_decorators.py +0 -763
- tests/unit/sdk/test_exporters.py +0 -241
- tests/unit/sdk/test_factory.py +0 -1188
- tests/unit/sdk/test_internal_span_processor.py +0 -397
- tests/unit/sdk/test_resource_attributes.py +0 -35
- tests/unit/test_config.py +0 -82
- tests/unit/test_context_manager.py +0 -777
- tests/unit/test_events.py +0 -27
- tests/unit/test_host_env.py +0 -54
- tests/unit/test_init_py.py +0 -501
- tests/unit/test_serialization.py +0 -433
- tests/unit/test_session.py +0 -676
- tests/unit/test_user_agent.py +0 -34
- tests/unit/test_validation.py +0 -405
- {tests → agentops/instrumentation/agentic/openai_agents/attributes}/__init__.py +0 -0
- /tests/unit/__init__.py → /agentops/instrumentation/providers/openai/attributes/tools.py +0 -0
- {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
|