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,535 @@
1
+ import os
2
+ import time
3
+ import logging
4
+ from typing import Dict, Any
5
+ from contextlib import contextmanager
6
+
7
+ from opentelemetry.trace import SpanKind, get_current_span
8
+ from opentelemetry.metrics import Meter
9
+ from opentelemetry.instrumentation.utils import unwrap
10
+
11
+ from agentops.instrumentation.common import (
12
+ CommonInstrumentor,
13
+ InstrumentorConfig,
14
+ StandardMetrics,
15
+ create_wrapper_factory,
16
+ create_span,
17
+ SpanAttributeManager,
18
+ safe_set_attribute,
19
+ set_token_usage_attributes,
20
+ TokenUsageExtractor,
21
+ )
22
+ from agentops.instrumentation.agentic.crewai.version import __version__
23
+ from agentops.semconv import SpanAttributes, AgentOpsSpanKindValues, ToolAttributes, MessageAttributes
24
+ from agentops.semconv.core import CoreAttributes
25
+ from agentops.instrumentation.agentic.crewai.crewai_span_attributes import CrewAISpanAttributes, set_span_attribute
26
+ from agentops import get_client
27
+
28
+ # Initialize logger
29
+ logger = logging.getLogger(__name__)
30
+
31
+ _instruments = ("crewai >= 0.70.0",)
32
+
33
+ # Global context to store tool executions by parent span ID
34
+ _tool_executions_by_agent = {}
35
+
36
+
37
+ @contextmanager
38
+ def store_tool_execution():
39
+ """Context manager to store tool execution details for later attachment to agent spans."""
40
+ parent_span = get_current_span()
41
+ parent_span_id = getattr(parent_span.get_span_context(), "span_id", None)
42
+
43
+ if parent_span_id:
44
+ if parent_span_id not in _tool_executions_by_agent:
45
+ _tool_executions_by_agent[parent_span_id] = []
46
+
47
+ tool_details = {}
48
+
49
+ try:
50
+ yield tool_details
51
+
52
+ if tool_details:
53
+ _tool_executions_by_agent[parent_span_id].append(tool_details)
54
+ finally:
55
+ pass
56
+
57
+
58
+ def attach_tool_executions_to_agent_span(span):
59
+ """Attach stored tool executions to the agent span."""
60
+ span_id = getattr(span.get_span_context(), "span_id", None)
61
+
62
+ if span_id and span_id in _tool_executions_by_agent:
63
+ for idx, tool_execution in enumerate(_tool_executions_by_agent[span_id]):
64
+ for key, value in tool_execution.items():
65
+ if value is not None:
66
+ span.set_attribute(f"crewai.agent.tool_execution.{idx}.{key}", str(value))
67
+
68
+ del _tool_executions_by_agent[span_id]
69
+
70
+
71
+ class CrewaiInstrumentor(CommonInstrumentor):
72
+ """Instrumentor for CrewAI framework."""
73
+
74
+ def __init__(self):
75
+ config = InstrumentorConfig(
76
+ library_name="crewai",
77
+ library_version=__version__,
78
+ wrapped_methods=[], # We'll use custom wrapping for CrewAI
79
+ metrics_enabled=is_metrics_enabled(),
80
+ dependencies=_instruments,
81
+ )
82
+ super().__init__(config)
83
+ self._attribute_manager = None
84
+
85
+ def _initialize(self, **kwargs):
86
+ """Initialize attribute manager."""
87
+ application_name = kwargs.get("application_name", "default_application")
88
+ environment = kwargs.get("environment", "default_environment")
89
+ self._attribute_manager = SpanAttributeManager(
90
+ service_name=application_name, deployment_environment=environment
91
+ )
92
+
93
+ def _create_metrics(self, meter: Meter) -> Dict[str, Any]:
94
+ """Create metrics for CrewAI instrumentation."""
95
+ return StandardMetrics.create_standard_metrics(meter)
96
+
97
+ def _custom_wrap(self, **kwargs):
98
+ """Perform custom wrapping for CrewAI methods."""
99
+ from wrapt import wrap_function_wrapper
100
+
101
+ # Get attribute manager for all wrappers
102
+ attr_manager = self._attribute_manager
103
+
104
+ # Define wrappers using the new create_wrapper_factory
105
+ wrap_function_wrapper(
106
+ "crewai.crew",
107
+ "Crew.kickoff",
108
+ create_wrapper_factory(wrap_kickoff_impl, self._metrics, attr_manager)(self._tracer),
109
+ )
110
+
111
+ wrap_function_wrapper(
112
+ "crewai.agent",
113
+ "Agent.execute_task",
114
+ create_wrapper_factory(wrap_agent_execute_task_impl, self._metrics, attr_manager)(self._tracer),
115
+ )
116
+
117
+ wrap_function_wrapper(
118
+ "crewai.task",
119
+ "Task.execute_sync",
120
+ create_wrapper_factory(wrap_task_execute_impl, self._metrics, attr_manager)(self._tracer),
121
+ )
122
+
123
+ wrap_function_wrapper(
124
+ "crewai.llm",
125
+ "LLM.call",
126
+ create_wrapper_factory(wrap_llm_call_impl, self._metrics, attr_manager)(self._tracer),
127
+ )
128
+
129
+ wrap_function_wrapper(
130
+ "crewai.utilities.tool_utils",
131
+ "execute_tool_and_check_finality",
132
+ create_wrapper_factory(wrap_tool_execution_impl, self._metrics, attr_manager)(self._tracer),
133
+ )
134
+
135
+ wrap_function_wrapper(
136
+ "crewai.tools.tool_usage",
137
+ "ToolUsage.use",
138
+ create_wrapper_factory(wrap_tool_usage_impl, self._metrics, attr_manager)(self._tracer),
139
+ )
140
+
141
+ def _custom_unwrap(self, **kwargs):
142
+ """Perform custom unwrapping for CrewAI methods."""
143
+ unwrap("crewai.crew", "Crew.kickoff")
144
+ unwrap("crewai.agent", "Agent.execute_task")
145
+ unwrap("crewai.task", "Task.execute_sync")
146
+ unwrap("crewai.llm", "LLM.call")
147
+ unwrap("crewai.utilities.tool_utils", "execute_tool_and_check_finality")
148
+ unwrap("crewai.tools.tool_usage", "ToolUsage.use")
149
+
150
+
151
+ # Implementation functions for wrappers
152
+ def wrap_kickoff_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
153
+ """Implementation of kickoff wrapper."""
154
+ logger.debug(
155
+ f"CrewAI: Starting workflow instrumentation for Crew with {len(getattr(instance, 'agents', []))} agents"
156
+ )
157
+
158
+ config = get_client().config
159
+ attributes = {
160
+ SpanAttributes.LLM_SYSTEM: "crewai",
161
+ }
162
+
163
+ if config.default_tags and len(config.default_tags) > 0:
164
+ tag_list = list(config.default_tags)
165
+ attributes[CoreAttributes.TAGS] = tag_list
166
+
167
+ # Use trace_name from config if available, otherwise default to "crewai.workflow"
168
+ span_name = config.trace_name if config.trace_name else "crewai.workflow"
169
+
170
+ with create_span(
171
+ tracer, span_name, kind=SpanKind.INTERNAL, attributes=attributes, attribute_manager=attr_manager
172
+ ) as span:
173
+ logger.debug("CrewAI: Processing crew instance attributes")
174
+
175
+ # First set general crew attributes but skip agent processing
176
+ crew_attrs = CrewAISpanAttributes(span=span, instance=instance, skip_agent_processing=True)
177
+
178
+ # Prioritize agent processing before task execution
179
+ if hasattr(instance, "agents") and instance.agents:
180
+ logger.debug(f"CrewAI: Explicitly processing {len(instance.agents)} agents before task execution")
181
+ crew_attrs._parse_agents(instance.agents)
182
+
183
+ logger.debug("CrewAI: Executing wrapped crew kickoff function")
184
+ result = wrapped(*args, **kwargs)
185
+
186
+ if result:
187
+ class_name = instance.__class__.__name__
188
+ span.set_attribute(f"crewai.{class_name.lower()}.result", str(result))
189
+
190
+ if class_name == "Crew":
191
+ _process_crew_result(span, instance, result)
192
+
193
+ # Set token usage using common utilities
194
+ set_token_usage_attributes(span, result)
195
+ _calculate_efficiency_metrics(span, result)
196
+
197
+ return result
198
+
199
+
200
+ def _process_crew_result(span, instance, result):
201
+ """Process crew execution result."""
202
+ if hasattr(result, "usage_metrics"):
203
+ span.set_attribute("crewai.crew.usage_metrics", str(getattr(result, "usage_metrics")))
204
+
205
+ if hasattr(result, "tasks_output") and result.tasks_output:
206
+ span.set_attribute("crewai.crew.tasks_output", str(result.tasks_output))
207
+
208
+ try:
209
+ task_details_by_description = _build_task_details_map(instance)
210
+ _process_task_outputs(span, result.tasks_output, task_details_by_description)
211
+ except Exception as ex:
212
+ logger.warning(f"Failed to parse task outputs: {ex}")
213
+
214
+
215
+ def _build_task_details_map(instance):
216
+ """Build a map of task descriptions to task details."""
217
+ task_details_by_description = {}
218
+ if hasattr(instance, "tasks"):
219
+ for task in instance.tasks:
220
+ if task is not None:
221
+ agent_id = ""
222
+ agent_role = ""
223
+ if hasattr(task, "agent") and task.agent:
224
+ agent_id = str(getattr(task.agent, "id", ""))
225
+ agent_role = getattr(task.agent, "role", "")
226
+
227
+ tools = []
228
+ if hasattr(task, "tools") and task.tools:
229
+ for tool in task.tools:
230
+ tool_info = {}
231
+ if hasattr(tool, "name"):
232
+ tool_info["name"] = tool.name
233
+ if hasattr(tool, "description"):
234
+ tool_info["description"] = tool.description
235
+ if tool_info:
236
+ tools.append(tool_info)
237
+
238
+ task_details_by_description[task.description] = {
239
+ "agent_id": agent_id,
240
+ "agent_role": agent_role,
241
+ "async_execution": getattr(task, "async_execution", False),
242
+ "human_input": getattr(task, "human_input", False),
243
+ "output_file": getattr(task, "output_file", ""),
244
+ "tools": tools,
245
+ }
246
+ return task_details_by_description
247
+
248
+
249
+ def _process_task_outputs(span, tasks_output, task_details_by_description):
250
+ """Process task outputs and set attributes."""
251
+ for idx, task_output in enumerate(tasks_output):
252
+ task_prefix = f"crewai.crew.tasks.{idx}"
253
+
254
+ task_attrs = {
255
+ "description": getattr(task_output, "description", ""),
256
+ "name": getattr(task_output, "name", ""),
257
+ "expected_output": getattr(task_output, "expected_output", ""),
258
+ "summary": getattr(task_output, "summary", ""),
259
+ "raw": getattr(task_output, "raw", ""),
260
+ "agent": getattr(task_output, "agent", ""),
261
+ "output_format": str(getattr(task_output, "output_format", "")),
262
+ }
263
+
264
+ for attr_name, attr_value in task_attrs.items():
265
+ if attr_value:
266
+ safe_set_attribute(span, f"{task_prefix}.{attr_name}", attr_value, max_length=1000)
267
+
268
+ span.set_attribute(f"{task_prefix}.status", "completed")
269
+ span.set_attribute(f"{task_prefix}.id", str(idx))
270
+
271
+ description = task_attrs.get("description", "")
272
+ if description and description in task_details_by_description:
273
+ details = task_details_by_description[description]
274
+
275
+ span.set_attribute(f"{task_prefix}.agent_id", details["agent_id"])
276
+ span.set_attribute(f"{task_prefix}.async_execution", str(details["async_execution"]))
277
+ span.set_attribute(f"{task_prefix}.human_input", str(details["human_input"]))
278
+
279
+ if details["output_file"]:
280
+ span.set_attribute(f"{task_prefix}.output_file", details["output_file"])
281
+
282
+ for tool_idx, tool in enumerate(details["tools"]):
283
+ for tool_key, tool_value in tool.items():
284
+ span.set_attribute(f"{task_prefix}.tools.{tool_idx}.{tool_key}", str(tool_value))
285
+
286
+
287
+ def _calculate_efficiency_metrics(span, result):
288
+ """Calculate and set efficiency metrics."""
289
+ if hasattr(result, "token_usage"):
290
+ try:
291
+ usage = TokenUsageExtractor.extract_from_response(result)
292
+
293
+ # Calculate efficiency
294
+ if usage.prompt_tokens and usage.completion_tokens and usage.prompt_tokens > 0:
295
+ efficiency = usage.completion_tokens / usage.prompt_tokens
296
+ span.set_attribute("crewai.crew.token_efficiency", f"{efficiency:.4f}")
297
+
298
+ # Calculate cache efficiency
299
+ if usage.cached_prompt_tokens and usage.prompt_tokens and usage.prompt_tokens > 0:
300
+ cache_ratio = usage.cached_prompt_tokens / usage.prompt_tokens
301
+ span.set_attribute("crewai.crew.cache_efficiency", f"{cache_ratio:.4f}")
302
+
303
+ except Exception as ex:
304
+ logger.warning(f"Failed to calculate efficiency metrics: {ex}")
305
+
306
+
307
+ def wrap_agent_execute_task_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
308
+ """Implementation of agent execute task wrapper."""
309
+ agent_name = instance.role if hasattr(instance, "role") else "agent"
310
+
311
+ with create_span(
312
+ tracer,
313
+ f"{agent_name}.agent",
314
+ kind=SpanKind.CLIENT,
315
+ attributes={
316
+ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.AGENT.value,
317
+ },
318
+ attribute_manager=attr_manager,
319
+ ) as span:
320
+ CrewAISpanAttributes(span=span, instance=instance)
321
+
322
+ result = wrapped(*args, **kwargs)
323
+
324
+ attach_tool_executions_to_agent_span(span)
325
+
326
+ # Record token metrics if available
327
+ if metrics.get("token_histogram") and hasattr(instance, "_token_process"):
328
+ token_process = instance._token_process.get_summary()
329
+ if hasattr(token_process, "prompt_tokens"):
330
+ metrics["token_histogram"].record(
331
+ token_process.prompt_tokens,
332
+ attributes={
333
+ SpanAttributes.LLM_SYSTEM: "crewai",
334
+ SpanAttributes.LLM_TOKEN_TYPE: "input",
335
+ SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model),
336
+ },
337
+ )
338
+ if hasattr(token_process, "completion_tokens"):
339
+ metrics["token_histogram"].record(
340
+ token_process.completion_tokens,
341
+ attributes={
342
+ SpanAttributes.LLM_SYSTEM: "crewai",
343
+ SpanAttributes.LLM_TOKEN_TYPE: "output",
344
+ SpanAttributes.LLM_RESPONSE_MODEL: str(instance.llm.model),
345
+ },
346
+ )
347
+
348
+ if hasattr(instance, "llm") and hasattr(instance.llm, "model"):
349
+ set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, str(instance.llm.model))
350
+ set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, str(instance.llm.model))
351
+
352
+ return result
353
+
354
+
355
+ def wrap_task_execute_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
356
+ """Implementation of task execute wrapper."""
357
+ task_name = instance.description if hasattr(instance, "description") else "task"
358
+
359
+ config = get_client().config
360
+ attributes = {
361
+ SpanAttributes.AGENTOPS_SPAN_KIND: AgentOpsSpanKindValues.TASK.value,
362
+ }
363
+
364
+ if config.default_tags and len(config.default_tags) > 0:
365
+ tag_list = list(config.default_tags)
366
+ attributes[CoreAttributes.TAGS] = tag_list
367
+
368
+ with create_span(
369
+ tracer, f"{task_name}.task", kind=SpanKind.CLIENT, attributes=attributes, attribute_manager=attr_manager
370
+ ) as span:
371
+ CrewAISpanAttributes(span=span, instance=instance)
372
+
373
+ result = wrapped(*args, **kwargs)
374
+
375
+ set_span_attribute(span, SpanAttributes.AGENTOPS_ENTITY_OUTPUT, str(result))
376
+ return result
377
+
378
+
379
+ def wrap_llm_call_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
380
+ """Implementation of LLM call wrapper."""
381
+ llm = instance.model if hasattr(instance, "model") else "llm"
382
+ start_time = time.time()
383
+
384
+ with create_span(tracer, f"{llm}.llm", kind=SpanKind.CLIENT, attribute_manager=attr_manager) as span:
385
+ CrewAISpanAttributes(span=span, instance=instance)
386
+
387
+ result = wrapped(*args, **kwargs)
388
+
389
+ # Set prompt attributes from args
390
+ if args and isinstance(args[0], list):
391
+ for i, message in enumerate(args[0]):
392
+ if isinstance(message, dict):
393
+ if "role" in message:
394
+ span.set_attribute(MessageAttributes.PROMPT_ROLE.format(i=i), message["role"])
395
+ if "content" in message:
396
+ span.set_attribute(MessageAttributes.PROMPT_CONTENT.format(i=i), message["content"])
397
+
398
+ # Set completion attributes from result
399
+ if result:
400
+ span.set_attribute(MessageAttributes.COMPLETION_CONTENT.format(i=0), str(result))
401
+ span.set_attribute(MessageAttributes.COMPLETION_ROLE.format(i=0), "assistant")
402
+
403
+ # Set token usage attributes from callbacks
404
+ if "callbacks" in kwargs and kwargs["callbacks"] and hasattr(kwargs["callbacks"][0], "token_cost_process"):
405
+ token_process = kwargs["callbacks"][0].token_cost_process
406
+ if hasattr(token_process, "completion_tokens"):
407
+ span.set_attribute(SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, token_process.completion_tokens)
408
+ if hasattr(token_process, "prompt_tokens"):
409
+ span.set_attribute(SpanAttributes.LLM_USAGE_PROMPT_TOKENS, token_process.prompt_tokens)
410
+ if hasattr(token_process, "total_tokens"):
411
+ span.set_attribute(SpanAttributes.LLM_USAGE_TOTAL_TOKENS, token_process.total_tokens)
412
+
413
+ # Record duration metric
414
+ if metrics.get("duration_histogram"):
415
+ duration = time.time() - start_time
416
+ metrics["duration_histogram"].record(
417
+ duration,
418
+ attributes={
419
+ SpanAttributes.LLM_SYSTEM: "crewai",
420
+ SpanAttributes.LLM_RESPONSE_MODEL: str(instance.model),
421
+ },
422
+ )
423
+
424
+ return result
425
+
426
+
427
+ def wrap_tool_execution_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
428
+ """Implementation of tool execution wrapper."""
429
+ agent_action = args[0] if args else None
430
+ tools = args[1] if len(args) > 1 else []
431
+
432
+ if not agent_action:
433
+ return wrapped(*args, **kwargs)
434
+
435
+ tool_name = getattr(agent_action, "tool", "unknown_tool")
436
+ tool_input = getattr(agent_action, "tool_input", "")
437
+
438
+ with store_tool_execution() as tool_details:
439
+ tool_details["name"] = tool_name
440
+ tool_details["parameters"] = str(tool_input)
441
+
442
+ matching_tool = next((tool for tool in tools if hasattr(tool, "name") and tool.name == tool_name), None)
443
+ if matching_tool and hasattr(matching_tool, "description"):
444
+ tool_details["description"] = str(matching_tool.description)
445
+
446
+ start_time = time.time()
447
+
448
+ with create_span(
449
+ tracer,
450
+ f"{tool_name}.tool",
451
+ kind=SpanKind.CLIENT,
452
+ attributes={
453
+ SpanAttributes.AGENTOPS_SPAN_KIND: "tool",
454
+ ToolAttributes.TOOL_NAME: tool_name,
455
+ ToolAttributes.TOOL_PARAMETERS: str(tool_input),
456
+ },
457
+ attribute_manager=attr_manager,
458
+ ) as span:
459
+ if matching_tool and hasattr(matching_tool, "description"):
460
+ span.set_attribute(ToolAttributes.TOOL_DESCRIPTION, str(matching_tool.description))
461
+
462
+ result = wrapped(*args, **kwargs)
463
+
464
+ # Record duration metric
465
+ if metrics.get("duration_histogram"):
466
+ duration = time.time() - start_time
467
+ metrics["duration_histogram"].record(
468
+ duration,
469
+ attributes={
470
+ SpanAttributes.LLM_SYSTEM: "crewai",
471
+ ToolAttributes.TOOL_NAME: tool_name,
472
+ },
473
+ )
474
+
475
+ if hasattr(result, "result"):
476
+ tool_result = str(result.result)
477
+ span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result)
478
+ tool_details["result"] = tool_result
479
+
480
+ tool_status = "success" if not hasattr(result, "error") or not result.error else "error"
481
+ span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status)
482
+ tool_details["status"] = tool_status
483
+
484
+ if hasattr(result, "error") and result.error:
485
+ tool_details["error"] = str(result.error)
486
+
487
+ duration = time.time() - start_time
488
+ tool_details["duration"] = f"{duration:.3f}"
489
+
490
+ return result
491
+
492
+
493
+ def wrap_tool_usage_impl(tracer, metrics, attr_manager, wrapped, instance, args, kwargs):
494
+ """Implementation of tool usage wrapper."""
495
+ calling = args[0] if args else None
496
+
497
+ if not calling:
498
+ return wrapped(*args, **kwargs)
499
+
500
+ tool_name = getattr(calling, "tool_name", "unknown_tool")
501
+
502
+ with store_tool_execution() as tool_details:
503
+ tool_details["name"] = tool_name
504
+
505
+ if hasattr(calling, "arguments") and calling.arguments:
506
+ tool_details["parameters"] = str(calling.arguments)
507
+
508
+ with create_span(
509
+ tracer,
510
+ f"{tool_name}.tool_usage",
511
+ kind=SpanKind.INTERNAL,
512
+ attributes={
513
+ SpanAttributes.AGENTOPS_SPAN_KIND: "tool.usage",
514
+ ToolAttributes.TOOL_NAME: tool_name,
515
+ },
516
+ attribute_manager=attr_manager,
517
+ ) as span:
518
+ if hasattr(calling, "arguments") and calling.arguments:
519
+ span.set_attribute(ToolAttributes.TOOL_PARAMETERS, str(calling.arguments))
520
+
521
+ result = wrapped(*args, **kwargs)
522
+
523
+ tool_result = str(result)
524
+ span.set_attribute(ToolAttributes.TOOL_RESULT, tool_result)
525
+ tool_details["result"] = tool_result
526
+
527
+ tool_status = "success"
528
+ span.set_attribute(ToolAttributes.TOOL_STATUS, tool_status)
529
+ tool_details["status"] = tool_status
530
+
531
+ return result
532
+
533
+
534
+ def is_metrics_enabled() -> bool:
535
+ return (os.getenv("AGENTOPS_METRICS_ENABLED") or "true").lower() == "true"
@@ -0,0 +1 @@
1
+ __version__ = "0.36.0"
@@ -0,0 +1,19 @@
1
+ """Google ADK Instrumentation for AgentOps
2
+
3
+ This module provides instrumentation for Google's Agent Development Kit (ADK),
4
+ capturing agent execution, LLM calls, tool calls, and other ADK-specific events.
5
+ """
6
+
7
+ from agentops.instrumentation.common import LibraryInfo
8
+
9
+ # Library information
10
+ _library_info = LibraryInfo(
11
+ name="agentops.instrumentation.google_adk", package_name="google-adk", default_version="0.0.0"
12
+ )
13
+ LIBRARY_NAME = _library_info.name
14
+ LIBRARY_VERSION = _library_info.version
15
+
16
+ from agentops.instrumentation.agentic.google_adk.instrumentor import GooogleAdkInstrumentor # noqa: E402
17
+ from agentops.instrumentation.agentic.google_adk import patch # noqa: E402
18
+
19
+ __all__ = ["LIBRARY_NAME", "LIBRARY_VERSION", "GooogleAdkInstrumentor", "patch"]
@@ -0,0 +1,68 @@
1
+ """Google ADK Instrumentation for AgentOps
2
+
3
+ This module provides instrumentation for Google's Agent Development Kit (ADK).
4
+ It uses a patching approach to:
5
+ 1. Disable ADK's built-in telemetry to prevent duplicate spans
6
+ 2. Create AgentOps spans that mirror ADK's telemetry structure
7
+ 3. Extract and properly index LLM messages and tool calls
8
+ """
9
+
10
+ from typing import Dict, Any
11
+
12
+ from agentops.logging import logger
13
+ from opentelemetry.metrics import Meter
14
+ from agentops.instrumentation.common import CommonInstrumentor, StandardMetrics, InstrumentorConfig
15
+ from agentops.instrumentation.agentic.google_adk.patch import patch_adk, unpatch_adk
16
+
17
+ # Library info for tracer/meter
18
+ LIBRARY_NAME = "agentops.instrumentation.google_adk"
19
+ LIBRARY_VERSION = "0.1.0"
20
+
21
+
22
+ class GooogleAdkInstrumentor(CommonInstrumentor):
23
+ """An instrumentor for Google Agent Development Kit (ADK).
24
+
25
+ This instrumentor patches Google ADK to:
26
+ - Prevent ADK from creating its own telemetry spans
27
+ - Create AgentOps spans for agent runs, LLM calls, and tool calls
28
+ - Properly extract and index message content and tool interactions
29
+ """
30
+
31
+ def __init__(self):
32
+ """Initialize the Google ADK instrumentor."""
33
+ # Create instrumentor config
34
+ config = InstrumentorConfig(
35
+ library_name=LIBRARY_NAME,
36
+ library_version=LIBRARY_VERSION,
37
+ wrapped_methods=[], # We use patching instead of wrapping
38
+ metrics_enabled=True,
39
+ dependencies=["google-adk >= 0.1.0"],
40
+ )
41
+
42
+ super().__init__(config)
43
+
44
+ def _create_metrics(self, meter: Meter) -> Dict[str, Any]:
45
+ """Create metrics for the instrumentor.
46
+
47
+ Returns a dictionary of metric name to metric instance.
48
+ """
49
+ # Create standard metrics for LLM operations
50
+ return StandardMetrics.create_standard_metrics(meter)
51
+
52
+ def _custom_wrap(self, **kwargs):
53
+ """Apply custom patching for Google ADK.
54
+
55
+ This is called after normal wrapping, but we use it for patching
56
+ since we don't have normal wrapped methods.
57
+ """
58
+ # Apply patches with our tracer
59
+ patch_adk(self._tracer)
60
+ logger.info("Google ADK instrumentation enabled")
61
+
62
+ def _custom_unwrap(self, **kwargs):
63
+ """Remove custom patching from Google ADK.
64
+
65
+ This method removes all patches and restores ADK's original behavior.
66
+ """
67
+ unpatch_adk()
68
+ logger.info("Google ADK instrumentation disabled")