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,19 @@
1
+ """Agno Agent instrumentation package."""
2
+
3
+ import logging
4
+ from agentops.instrumentation.common import LibraryInfo
5
+
6
+ from .instrumentor import AgnoInstrumentor
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Library information
11
+ _library_info = LibraryInfo(name="agno", default_version="1.5.8")
12
+ LIBRARY_NAME = _library_info.name
13
+ LIBRARY_VERSION = _library_info.version
14
+
15
+ __all__ = [
16
+ "AgnoInstrumentor",
17
+ "LIBRARY_NAME",
18
+ "LIBRARY_VERSION",
19
+ ]
@@ -0,0 +1,20 @@
1
+ """Agno instrumentation attribute handlers."""
2
+
3
+ from .agent import get_agent_run_attributes
4
+ from .metrics import get_metrics_attributes
5
+ from .team import get_team_run_attributes
6
+ from .tool import get_tool_execution_attributes
7
+ from .workflow import get_workflow_run_attributes, get_workflow_session_attributes, get_workflow_cache_attributes
8
+ from .storage import get_storage_read_attributes, get_storage_write_attributes
9
+
10
+ __all__ = [
11
+ "get_agent_run_attributes",
12
+ "get_metrics_attributes",
13
+ "get_team_run_attributes",
14
+ "get_tool_execution_attributes",
15
+ "get_workflow_run_attributes",
16
+ "get_workflow_session_attributes",
17
+ "get_workflow_cache_attributes",
18
+ "get_storage_read_attributes",
19
+ "get_storage_write_attributes",
20
+ ]
@@ -0,0 +1,250 @@
1
+ """Agno Agent run attributes handler."""
2
+
3
+ from typing import Optional, Tuple, Dict, Any
4
+ from agentops.instrumentation.common.attributes import AttributeMap
5
+ from agentops.semconv import SpanAttributes, AgentAttributes, ToolAttributes
6
+ from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
7
+ import json
8
+
9
+
10
+ def get_agent_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 Agent.run/arun calls.
16
+
17
+ Args:
18
+ args: Positional arguments passed to the run method (self, message, ...)
19
+ kwargs: Keyword arguments passed to the run method
20
+ return_value: The return value from the run method (RunResponse)
21
+
22
+ Returns:
23
+ A dictionary of span attributes to be set on the agent span
24
+ """
25
+ attributes: AttributeMap = {}
26
+
27
+ # Initialize variables to avoid UnboundLocalError
28
+ agent_name = None
29
+
30
+ # Base attributes
31
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.AGENT
32
+ attributes[SpanAttributes.LLM_SYSTEM] = "agno"
33
+
34
+ # AgentOps entity attributes
35
+ attributes[SpanAttributes.AGENTOPS_ENTITY_NAME] = "agent"
36
+
37
+ # Extract agent information from args[0] (self)
38
+ if args and len(args) >= 1:
39
+ agent = args[0]
40
+
41
+ # Core agent identification - set directly at root level
42
+ if hasattr(agent, "agent_id") and agent.agent_id:
43
+ agent_id = str(agent.agent_id)
44
+ attributes[AgentAttributes.AGENT_ID] = agent_id
45
+
46
+ if hasattr(agent, "name") and agent.name:
47
+ agent_name = str(agent.name)
48
+ attributes[AgentAttributes.AGENT_NAME] = agent_name
49
+
50
+ if hasattr(agent, "role") and agent.role:
51
+ agent_role = str(agent.role)
52
+ attributes[AgentAttributes.AGENT_ROLE] = agent_role
53
+
54
+ # Check if agent is part of a team
55
+ if hasattr(agent, "_team") and agent._team:
56
+ team = agent._team
57
+ if hasattr(team, "name") and team.name:
58
+ attributes["agent.parent_team"] = str(team.name)
59
+ attributes["agent.parent_team_display"] = f"Under {team.name}"
60
+ if hasattr(team, "team_id") and team.team_id:
61
+ attributes["agent.parent_team_id"] = str(team.team_id)
62
+
63
+ # Model information -
64
+ if hasattr(agent, "model") and agent.model:
65
+ model = agent.model
66
+ if hasattr(model, "id"):
67
+ model_id = str(model.id)
68
+ attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_id
69
+ attributes[SpanAttributes.LLM_RESPONSE_MODEL] = model_id
70
+
71
+ if hasattr(model, "provider"):
72
+ model_provider = str(model.provider)
73
+ attributes["agent.model_provider"] = model_provider
74
+
75
+ # Agent configuration details - set directly at root level
76
+ if hasattr(agent, "description") and agent.description:
77
+ attributes["agent.description"] = str(agent.description)
78
+ if hasattr(agent, "goal") and agent.goal:
79
+ attributes["agent.goal"] = str(agent.goal)
80
+
81
+ if hasattr(agent, "instructions") and agent.instructions:
82
+ if isinstance(agent.instructions, list):
83
+ attributes["agent.instruction"] = " | ".join(str(i) for i in agent.instructions)
84
+ else:
85
+ attributes["agent.instruction"] = str(agent.instructions)
86
+ if hasattr(agent, "expected_output") and agent.expected_output:
87
+ attributes["agent.expected_output"] = str(agent.expected_output)
88
+
89
+ if hasattr(agent, "markdown"):
90
+ attributes["agent.markdown"] = str(agent.markdown)
91
+
92
+ if hasattr(agent, "reasoning"):
93
+ attributes[AgentAttributes.AGENT_REASONING] = str(agent.reasoning)
94
+
95
+ if hasattr(agent, "stream"):
96
+ attributes["agent.stream"] = str(agent.stream)
97
+
98
+ if hasattr(agent, "show_tool_calls"):
99
+ attributes["agent.show_tool_calls"] = str(agent.show_tool_calls)
100
+
101
+ if hasattr(agent, "tool_call_limit") and agent.tool_call_limit:
102
+ attributes["agent.tool_call_limit"] = str(agent.tool_call_limit)
103
+
104
+ # Tools information
105
+ if hasattr(agent, "tools") and agent.tools:
106
+ # Set tool count based on actual number of tools
107
+ attributes["agent.tools_count"] = str(len(agent.tools))
108
+
109
+ # Collect all tool names for the AGENT_TOOLS attribute
110
+ tool_names = []
111
+
112
+ if len(agent.tools) == 1:
113
+ # Single tool - set directly at root level
114
+ tool = agent.tools[0]
115
+ tool_name = None
116
+ if hasattr(tool, "name"):
117
+ tool_name = str(tool.name)
118
+ attributes[ToolAttributes.TOOL_NAME] = tool_name
119
+ elif hasattr(tool, "__name__"):
120
+ tool_name = str(tool.__name__)
121
+ attributes[ToolAttributes.TOOL_NAME] = tool_name
122
+ elif callable(tool):
123
+ tool_name = getattr(tool, "__name__", "unknown_tool")
124
+ attributes[ToolAttributes.TOOL_NAME] = tool_name
125
+
126
+ if tool_name:
127
+ tool_names.append(tool_name)
128
+
129
+ if hasattr(tool, "description") and tool.description:
130
+ attributes[ToolAttributes.TOOL_DESCRIPTION] = str(tool.description)
131
+ elif hasattr(tool, "__doc__") and tool.__doc__:
132
+ # Fallback to docstring if no description attribute
133
+ attributes[ToolAttributes.TOOL_DESCRIPTION] = str(tool.__doc__).strip()
134
+ else:
135
+ # Multiple tools - use indexed format
136
+ for i, tool in enumerate(agent.tools):
137
+ tool_name = None
138
+ if hasattr(tool, "name"):
139
+ tool_name = str(tool.name)
140
+ attributes[f"tool.{i}.name"] = tool_name
141
+ elif hasattr(tool, "__name__"):
142
+ tool_name = str(tool.__name__)
143
+ attributes[f"tool.{i}.name"] = tool_name
144
+ elif callable(tool):
145
+ tool_name = getattr(tool, "__name__", "unknown_tool")
146
+ attributes[f"tool.{i}.name"] = tool_name
147
+
148
+ if tool_name:
149
+ tool_names.append(tool_name)
150
+
151
+ if hasattr(tool, "description") and tool.description:
152
+ attributes[f"tool.{i}.description"] = str(tool.description)
153
+ elif hasattr(tool, "__doc__") and tool.__doc__:
154
+ # Fallback to docstring if no description attribute
155
+ attributes[f"tool.{i}.description"] = str(tool.__doc__).strip()
156
+
157
+ # Set the AGENT_TOOLS attribute with all tool names
158
+ if tool_names:
159
+ attributes[AgentAttributes.AGENT_TOOLS] = json.dumps(tool_names)
160
+
161
+ if hasattr(agent, "knowledge") and agent.knowledge:
162
+ knowledge_type = type(agent.knowledge).__name__
163
+ attributes["agent.knowledge_type"] = knowledge_type
164
+
165
+ if hasattr(agent, "storage") and agent.storage:
166
+ storage_type = type(agent.storage).__name__
167
+ attributes["agent.storage_type"] = storage_type
168
+
169
+ # Session information
170
+ if hasattr(agent, "session_id") and agent.session_id:
171
+ session_id = str(agent.session_id)
172
+ attributes["agent.session_id"] = session_id
173
+
174
+ if hasattr(agent, "user_id") and agent.user_id:
175
+ user_id = str(agent.user_id)
176
+ attributes["agent.user_id"] = user_id
177
+
178
+ # Output key if present
179
+ if hasattr(agent, "output_key") and agent.output_key:
180
+ attributes["agent.output_key"] = str(agent.output_key)
181
+
182
+ # Extract run input information
183
+ if args and len(args) >= 2:
184
+ message = args[1] # The message argument
185
+ if message:
186
+ message_str = str(message)
187
+ attributes["agent.input"] = message_str
188
+ # AgentOps entity input
189
+ attributes[SpanAttributes.AGENTOPS_ENTITY_INPUT] = message_str
190
+
191
+ # Extract kwargs information
192
+ if kwargs:
193
+ if kwargs.get("stream") is not None:
194
+ attributes[SpanAttributes.LLM_REQUEST_STREAMING] = str(kwargs["stream"])
195
+
196
+ if kwargs.get("session_id"):
197
+ attributes["agent.run_session_id"] = str(kwargs["session_id"])
198
+
199
+ if kwargs.get("user_id"):
200
+ attributes["agent.run_user_id"] = str(kwargs["user_id"])
201
+
202
+ # Extract return value information
203
+ if return_value:
204
+ if hasattr(return_value, "run_id") and return_value.run_id:
205
+ run_id = str(return_value.run_id)
206
+ attributes["agent.run_id"] = run_id
207
+
208
+ if hasattr(return_value, "content") and return_value.content:
209
+ content = str(return_value.content)
210
+ attributes["agent.output"] = content
211
+
212
+ if hasattr(return_value, "event") and return_value.event:
213
+ event = str(return_value.event)
214
+ attributes["agent.event"] = event
215
+
216
+ # Tool executions from the response
217
+ if hasattr(return_value, "tools") and return_value.tools:
218
+ # Track the number of tool executions
219
+ attributes["agent.tool_executions_count"] = str(len(return_value.tools))
220
+
221
+ for i, tool_exec in enumerate(return_value.tools): # No limit - show all tools
222
+ if hasattr(tool_exec, "tool_name") and tool_exec.tool_name:
223
+ attributes[f"tool.{i}.name"] = str(tool_exec.tool_name)
224
+
225
+ if hasattr(tool_exec, "tool_args") and tool_exec.tool_args:
226
+ try:
227
+ args_str = json.dumps(tool_exec.tool_args)
228
+ attributes[f"tool.{i}.parameters"] = args_str
229
+ except:
230
+ attributes[f"tool.{i}.parameters"] = str(tool_exec.tool_args)
231
+
232
+ if hasattr(tool_exec, "result") and tool_exec.result:
233
+ result_str = str(tool_exec.result)
234
+ attributes[f"tool.{i}.result"] = result_str
235
+
236
+ if hasattr(tool_exec, "tool_call_error") and tool_exec.tool_call_error:
237
+ attributes[f"tool.{i}.error"] = str(tool_exec.tool_call_error)
238
+
239
+ attributes[f"tool.{i}.status"] = "success" # Default to success
240
+
241
+ # Add display name for better UI visualization
242
+ if agent_name:
243
+ # Check if we have parent team info
244
+ parent_team = attributes.get("agent.parent_team")
245
+ if parent_team:
246
+ attributes["agent.display_name"] = f"{agent_name} (Agent under {parent_team})"
247
+ else:
248
+ attributes["agent.display_name"] = f"{agent_name}"
249
+
250
+ return attributes
@@ -0,0 +1,214 @@
1
+ """Agno Agent session metrics attributes handler."""
2
+
3
+ from typing import Optional, Tuple, Dict, Any
4
+
5
+ from agentops.instrumentation.common.attributes import AttributeMap
6
+ from agentops.semconv import SpanAttributes
7
+
8
+
9
+ def get_metrics_attributes(
10
+ args: Optional[Tuple] = None,
11
+ kwargs: Optional[Dict] = None,
12
+ return_value: Optional[Any] = None,
13
+ ) -> AttributeMap:
14
+ """Extract span attributes for Agent._set_session_metrics calls.
15
+
16
+ Args:
17
+ args: Positional arguments passed to the _set_session_metrics method (self, run_messages)
18
+ kwargs: Keyword arguments passed to the _set_session_metrics method
19
+ return_value: The return value from the _set_session_metrics method
20
+
21
+ Returns:
22
+ A dictionary of span attributes to be set on the metrics span
23
+ """
24
+ attributes: AttributeMap = {}
25
+
26
+ # Base attributes
27
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = "llm"
28
+ attributes[SpanAttributes.LLM_SYSTEM] = "agno"
29
+ attributes[SpanAttributes.AGENTOPS_ENTITY_NAME] = "LLM"
30
+
31
+ # Initialize usage tracking variables
32
+ usage_data = {}
33
+
34
+ # Initialize counters for indexed messages
35
+ prompt_count = 0
36
+ completion_count = 0
37
+
38
+ # Extract agent and run_messages from args (self, run_messages)
39
+ if args and len(args) >= 2:
40
+ agent = args[0] # self (Agent instance)
41
+ run_messages = args[1] # RunMessages object
42
+
43
+ # Add agent display name for LLM calls
44
+ if hasattr(agent, "name") and agent.name:
45
+ attributes["agno.llm.display_name"] = f"{agent.name} → LLM"
46
+
47
+ # Model information - get additional request parameters if available
48
+ if hasattr(agent, "model") and agent.model:
49
+ model = agent.model
50
+ # Set model ID first
51
+ if hasattr(model, "id"):
52
+ attributes[SpanAttributes.LLM_REQUEST_MODEL] = str(model.id)
53
+ attributes[SpanAttributes.LLM_RESPONSE_MODEL] = str(model.id)
54
+ # Additional model parameters
55
+ if hasattr(model, "temperature") and model.temperature is not None:
56
+ attributes[SpanAttributes.LLM_REQUEST_TEMPERATURE] = str(model.temperature)
57
+ if hasattr(model, "max_tokens") and model.max_tokens is not None:
58
+ attributes[SpanAttributes.LLM_REQUEST_MAX_TOKENS] = str(model.max_tokens)
59
+ if hasattr(model, "top_p") and model.top_p is not None:
60
+ attributes[SpanAttributes.LLM_REQUEST_TOP_P] = str(model.top_p)
61
+ if hasattr(model, "provider"):
62
+ attributes["agno.model.provider"] = str(model.provider)
63
+
64
+ # Add model class name for better identification (with null check)
65
+ if hasattr(model, "__class__") and hasattr(model.__class__, "__name__"):
66
+ model_class = model.__class__.__name__
67
+ attributes["agno.model.class"] = model_class
68
+
69
+ if hasattr(run_messages, "messages") and run_messages.messages:
70
+ messages = run_messages.messages
71
+
72
+ # Initialize token tracking
73
+ total_prompt_tokens = 0
74
+ total_completion_tokens = 0
75
+ total_output_tokens = 0
76
+ total_input_tokens = 0
77
+ total_tokens = 0
78
+ total_time = 0.0
79
+
80
+ # Process messages to create individual indexed gen_ai.prompt.{i} and gen_ai.completion.{i} attributes
81
+ for i, msg in enumerate(messages):
82
+ # Extract message content for prompts/completions
83
+ if hasattr(msg, "role") and hasattr(msg, "content"):
84
+ # Only process messages with actual content
85
+ if msg.content is not None and str(msg.content).strip() != "" and str(msg.content) != "None":
86
+ content = str(msg.content)
87
+ # No truncation - keep full content for observability
88
+
89
+ if msg.role == "user":
90
+ attributes[f"{SpanAttributes.LLM_PROMPTS}.{prompt_count}.role"] = "user"
91
+ attributes[f"{SpanAttributes.LLM_PROMPTS}.{prompt_count}.content"] = content
92
+ prompt_count += 1
93
+ elif msg.role == "assistant":
94
+ attributes[f"{SpanAttributes.LLM_COMPLETIONS}.{completion_count}.role"] = "assistant"
95
+ attributes[f"{SpanAttributes.LLM_COMPLETIONS}.{completion_count}.content"] = content
96
+ completion_count += 1
97
+ elif msg.role == "system":
98
+ attributes[f"{SpanAttributes.LLM_PROMPTS}.{prompt_count}.role"] = "system"
99
+ attributes[f"{SpanAttributes.LLM_PROMPTS}.{prompt_count}.content"] = content
100
+ prompt_count += 1
101
+
102
+ # Extract token metrics from message
103
+ if hasattr(msg, "metrics") and msg.metrics:
104
+ metrics = msg.metrics
105
+
106
+ # Handle different token metric patterns
107
+ if hasattr(metrics, "prompt_tokens") and metrics.prompt_tokens > 0:
108
+ total_prompt_tokens += metrics.prompt_tokens
109
+ if hasattr(metrics, "completion_tokens") and metrics.completion_tokens > 0:
110
+ total_completion_tokens += metrics.completion_tokens
111
+ if hasattr(metrics, "total_tokens") and metrics.total_tokens > 0:
112
+ total_tokens += metrics.total_tokens
113
+ # For messages that only have output_tokens
114
+ if hasattr(metrics, "output_tokens") and metrics.output_tokens > 0:
115
+ total_output_tokens += metrics.output_tokens
116
+ if hasattr(metrics, "input_tokens") and metrics.input_tokens > 0:
117
+ total_input_tokens += metrics.input_tokens
118
+ if hasattr(metrics, "time") and metrics.time:
119
+ total_time += metrics.time
120
+
121
+ # Token metrics from agent session metrics
122
+ if hasattr(agent, "session_metrics") and agent.session_metrics:
123
+ session_metrics = agent.session_metrics
124
+
125
+ # Try to get model name from session metrics if not already set
126
+ if SpanAttributes.LLM_REQUEST_MODEL not in attributes:
127
+ if hasattr(session_metrics, "model") and session_metrics.model:
128
+ model_id = str(session_metrics.model)
129
+ attributes[SpanAttributes.LLM_REQUEST_MODEL] = model_id
130
+ attributes[SpanAttributes.LLM_RESPONSE_MODEL] = model_id
131
+
132
+ # Only set token variables if the attributes actually exist
133
+ session_prompt_tokens = None
134
+ session_completion_tokens = None
135
+ session_output_tokens = None
136
+ session_input_tokens = None
137
+ session_total_tokens = None
138
+
139
+ if hasattr(session_metrics, "prompt_tokens"):
140
+ session_prompt_tokens = session_metrics.prompt_tokens
141
+
142
+ if hasattr(session_metrics, "completion_tokens"):
143
+ session_completion_tokens = session_metrics.completion_tokens
144
+
145
+ if hasattr(session_metrics, "output_tokens"):
146
+ session_output_tokens = session_metrics.output_tokens
147
+
148
+ if hasattr(session_metrics, "input_tokens"):
149
+ session_input_tokens = session_metrics.input_tokens
150
+
151
+ if hasattr(session_metrics, "total_tokens"):
152
+ session_total_tokens = session_metrics.total_tokens
153
+
154
+ # For Anthropic, output_tokens represents completion tokens
155
+ if session_output_tokens is not None and session_output_tokens > 0:
156
+ if session_completion_tokens is None or session_completion_tokens == 0:
157
+ session_completion_tokens = session_output_tokens
158
+
159
+ # For some providers, input_tokens represents prompt tokens
160
+ if session_input_tokens is not None and session_input_tokens > 0:
161
+ if session_prompt_tokens is None or session_prompt_tokens == 0:
162
+ session_prompt_tokens = session_input_tokens
163
+
164
+ # Only set token attributes if we have actual values
165
+ if session_total_tokens is not None and session_total_tokens > 0:
166
+ usage_data["total_tokens"] = session_total_tokens
167
+
168
+ # Set breakdown if available
169
+ if session_prompt_tokens is not None and session_prompt_tokens > 0:
170
+ usage_data["prompt_tokens"] = session_prompt_tokens
171
+ if session_completion_tokens is not None and session_completion_tokens > 0:
172
+ usage_data["completion_tokens"] = session_completion_tokens
173
+
174
+ # Additional token types from session metrics - only set if present
175
+ if hasattr(session_metrics, "cached_tokens") and session_metrics.cached_tokens > 0:
176
+ usage_data["cache_read_input_tokens"] = session_metrics.cached_tokens
177
+ if hasattr(session_metrics, "reasoning_tokens") and session_metrics.reasoning_tokens > 0:
178
+ usage_data["reasoning_tokens"] = session_metrics.reasoning_tokens
179
+
180
+ # If we don't have token data from session metrics, try message aggregation
181
+ if "total_tokens" not in usage_data:
182
+ # Set aggregated token usage from messages
183
+ if total_prompt_tokens > 0 or total_input_tokens > 0:
184
+ usage_data["prompt_tokens"] = total_prompt_tokens or total_input_tokens
185
+ if total_completion_tokens > 0 or total_output_tokens > 0:
186
+ usage_data["completion_tokens"] = total_completion_tokens or total_output_tokens
187
+ if total_tokens > 0:
188
+ usage_data["total_tokens"] = total_tokens
189
+
190
+ # Extract user message info if available
191
+ if hasattr(run_messages, "user_message") and run_messages.user_message:
192
+ user_msg = run_messages.user_message
193
+ if hasattr(user_msg, "content"):
194
+ content = str(user_msg.content)
195
+ attributes["agno.metrics.user_input"] = content
196
+
197
+ # Set individual LLM usage attributes only for values we actually have
198
+ if "prompt_tokens" in usage_data:
199
+ attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS] = usage_data["prompt_tokens"]
200
+ if "completion_tokens" in usage_data:
201
+ attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS] = usage_data["completion_tokens"]
202
+ if "total_tokens" in usage_data:
203
+ attributes[SpanAttributes.LLM_USAGE_TOTAL_TOKENS] = usage_data["total_tokens"]
204
+ if "cache_read_input_tokens" in usage_data:
205
+ attributes[SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS] = usage_data["cache_read_input_tokens"]
206
+ if "reasoning_tokens" in usage_data:
207
+ attributes[SpanAttributes.LLM_USAGE_REASONING_TOKENS] = usage_data["reasoning_tokens"]
208
+
209
+ # But only if we have any usage data
210
+ if usage_data:
211
+ for key, value in usage_data.items():
212
+ attributes[f"gen_ai.usage.{key}"] = value
213
+
214
+ return attributes
@@ -0,0 +1,158 @@
1
+ """Storage operation attribute handlers for Agno workflow instrumentation."""
2
+
3
+ import json
4
+ from typing import Any, Dict, Optional, Tuple
5
+ from opentelemetry.util.types import AttributeValue
6
+
7
+ from agentops.semconv.span_attributes import SpanAttributes
8
+ from agentops.semconv.span_kinds import SpanKind as AgentOpsSpanKind
9
+ from agentops.instrumentation.common.attributes import get_common_attributes
10
+
11
+
12
+ def get_storage_read_attributes(
13
+ args: Tuple[Any, ...] = (),
14
+ kwargs: Optional[Dict[str, Any]] = None,
15
+ return_value: Optional[Any] = None,
16
+ ) -> Dict[str, AttributeValue]:
17
+ """Extract attributes from storage read operations.
18
+
19
+ Args:
20
+ args: Positional arguments passed to read_from_storage
21
+ kwargs: Keyword arguments passed to read_from_storage
22
+ return_value: Return value from read_from_storage (the cached data or None)
23
+
24
+ Returns:
25
+ Dictionary of OpenTelemetry attributes for storage read operations
26
+ """
27
+ attributes = get_common_attributes()
28
+ kwargs = kwargs or {}
29
+
30
+ # Mark this as a storage operation within workflow context
31
+ attributes["storage.operation"] = "read"
32
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.WORKFLOW
33
+
34
+ if args and len(args) > 0:
35
+ workflow = args[0]
36
+
37
+ # Get workflow information
38
+ if hasattr(workflow, "workflow_id") and workflow.workflow_id:
39
+ attributes["storage.workflow_id"] = str(workflow.workflow_id)
40
+ if hasattr(workflow, "session_id") and workflow.session_id:
41
+ attributes["storage.session_id"] = str(workflow.session_id)
42
+
43
+ # Get storage type
44
+ if hasattr(workflow, "storage") and workflow.storage:
45
+ storage_type = type(workflow.storage).__name__
46
+ attributes["storage.backend"] = storage_type
47
+
48
+ # Get session state info for context
49
+ if hasattr(workflow, "session_state") and isinstance(workflow.session_state, dict):
50
+ # Get all cache keys
51
+ cache_keys = list(workflow.session_state.keys())
52
+ attributes["storage.cache_size"] = len(cache_keys)
53
+ if cache_keys:
54
+ attributes["storage.cache_keys"] = json.dumps(cache_keys)
55
+
56
+ # Analyze the return value to determine cache hit/miss
57
+ if return_value is not None:
58
+ # Cache hit
59
+ attributes["storage.cache_hit"] = True
60
+ attributes["storage.result"] = "hit"
61
+
62
+ # Get data type and size
63
+ data_type = type(return_value).__name__
64
+ attributes["storage.data_type"] = data_type
65
+
66
+ # For dict/list, show structure
67
+ if isinstance(return_value, dict):
68
+ attributes["storage.data_keys"] = json.dumps(list(return_value.keys()))
69
+ attributes["storage.data_size"] = len(return_value)
70
+ elif isinstance(return_value, (list, tuple)):
71
+ attributes["storage.data_size"] = len(return_value)
72
+ elif isinstance(return_value, str):
73
+ attributes["storage.data_size"] = len(return_value)
74
+ # Show full string data without truncation
75
+ attributes["storage.data_preview"] = return_value
76
+ else:
77
+ # Cache miss
78
+ attributes["storage.cache_hit"] = False
79
+ attributes["storage.result"] = "miss"
80
+
81
+ return attributes
82
+
83
+
84
+ def get_storage_write_attributes(
85
+ args: Tuple[Any, ...] = (),
86
+ kwargs: Optional[Dict[str, Any]] = None,
87
+ return_value: Optional[Any] = None,
88
+ ) -> Dict[str, AttributeValue]:
89
+ """Extract attributes from storage write operations.
90
+
91
+ Args:
92
+ args: Positional arguments passed to write_to_storage
93
+ kwargs: Keyword arguments passed to write_to_storage
94
+ return_value: Return value from write_to_storage (usually None or success indicator)
95
+
96
+ Returns:
97
+ Dictionary of OpenTelemetry attributes for storage write operations
98
+ """
99
+ attributes = get_common_attributes()
100
+ kwargs = kwargs or {}
101
+
102
+ # Mark this as a storage operation within workflow context
103
+ attributes["storage.operation"] = "write"
104
+ attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKind.WORKFLOW
105
+
106
+ if args and len(args) > 0:
107
+ workflow = args[0]
108
+
109
+ # Get workflow information
110
+ if hasattr(workflow, "workflow_id") and workflow.workflow_id:
111
+ attributes["storage.workflow_id"] = str(workflow.workflow_id)
112
+ if hasattr(workflow, "session_id") and workflow.session_id:
113
+ attributes["storage.session_id"] = str(workflow.session_id)
114
+
115
+ # Get storage type
116
+ if hasattr(workflow, "storage") and workflow.storage:
117
+ storage_type = type(workflow.storage).__name__
118
+ attributes["storage.backend"] = storage_type
119
+
120
+ # Get session state info to see what's being written
121
+ if hasattr(workflow, "session_state") and isinstance(workflow.session_state, dict):
122
+ # Get cache state after write
123
+ cache_keys = list(workflow.session_state.keys())
124
+ attributes["storage.cache_size"] = len(cache_keys)
125
+ if cache_keys:
126
+ attributes["storage.cache_keys"] = json.dumps(cache_keys)
127
+
128
+ # Try to identify what was written (the newest/changed data)
129
+ # This is a heuristic - in practice you might need to track state changes
130
+ if cache_keys:
131
+ # Show the last key as likely the one just written
132
+ last_key = cache_keys[-1]
133
+ attributes["storage.written_key"] = last_key
134
+
135
+ # Get value preview
136
+ value = workflow.session_state.get(last_key)
137
+ if value is not None:
138
+ value_type = type(value).__name__
139
+ attributes["storage.written_value_type"] = value_type
140
+
141
+ if isinstance(value, str):
142
+ if len(value) > 100:
143
+ attributes["storage.written_value_preview"] = value[:100] + "..."
144
+ else:
145
+ attributes["storage.written_value_preview"] = value
146
+ attributes["storage.written_value_size"] = len(value)
147
+ elif isinstance(value, (dict, list)):
148
+ attributes["storage.written_value_size"] = len(value)
149
+ attributes["storage.written_value_preview"] = f"{value_type} with {len(value)} items"
150
+
151
+ # Check write result
152
+ if return_value is not None:
153
+ attributes["storage.write_success"] = True
154
+ else:
155
+ # Most storage writes return None on success, so this is normal
156
+ attributes["storage.write_success"] = True
157
+
158
+ return attributes