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,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")
|