mseep-agentops 0.4.18__py3-none-any.whl → 0.4.23__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.23.dist-info}/METADATA +30 -40
- mseep_agentops-0.4.23.dist-info/RECORD +178 -0
- {mseep_agentops-0.4.18.dist-info → mseep_agentops-0.4.23.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.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,549 @@
|
|
1
|
+
"""Common utilities and constants for attribute processing.
|
2
|
+
|
3
|
+
This module contains shared constants, attribute mappings, and utility functions for processing
|
4
|
+
trace and span attributes in OpenAI Agents instrumentation. It provides the core functionality
|
5
|
+
for extracting and formatting attributes according to OpenTelemetry semantic conventions.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, List, Dict, Optional
|
9
|
+
from agentops.logging import logger
|
10
|
+
from agentops.semconv import (
|
11
|
+
AgentAttributes,
|
12
|
+
WorkflowAttributes,
|
13
|
+
SpanAttributes,
|
14
|
+
InstrumentationAttributes,
|
15
|
+
ToolAttributes,
|
16
|
+
AgentOpsSpanKindValues,
|
17
|
+
ToolStatus,
|
18
|
+
)
|
19
|
+
from agentops.helpers import safe_serialize # Import safe_serialize
|
20
|
+
|
21
|
+
from agentops.instrumentation.common import AttributeMap, _extract_attributes_from_mapping
|
22
|
+
from agentops.instrumentation.common.attributes import get_common_attributes
|
23
|
+
from agentops.instrumentation.common.objects import get_uploaded_object_attributes
|
24
|
+
from agentops.instrumentation.providers.openai.attributes.response import get_response_response_attributes
|
25
|
+
from agentops.instrumentation.agentic.openai_agents import LIBRARY_NAME, LIBRARY_VERSION
|
26
|
+
|
27
|
+
from agentops.instrumentation.agentic.openai_agents.attributes.model import (
|
28
|
+
get_model_attributes,
|
29
|
+
get_model_config_attributes,
|
30
|
+
)
|
31
|
+
from agentops.instrumentation.agentic.openai_agents.attributes.completion import get_generation_output_attributes
|
32
|
+
|
33
|
+
|
34
|
+
# Attribute mapping for AgentSpanData
|
35
|
+
AGENT_SPAN_ATTRIBUTES: AttributeMap = {
|
36
|
+
AgentAttributes.AGENT_NAME: "name",
|
37
|
+
AgentAttributes.AGENT_TOOLS: "tools",
|
38
|
+
AgentAttributes.HANDOFFS: "handoffs",
|
39
|
+
WorkflowAttributes.WORKFLOW_INPUT: "input",
|
40
|
+
WorkflowAttributes.WORKFLOW_OUTPUT: "output",
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
# Attribute mapping for FunctionSpanData
|
45
|
+
FUNCTION_SPAN_ATTRIBUTES: AttributeMap = {
|
46
|
+
ToolAttributes.TOOL_NAME: "name",
|
47
|
+
ToolAttributes.TOOL_PARAMETERS: "input",
|
48
|
+
ToolAttributes.TOOL_RESULT: "output",
|
49
|
+
# AgentAttributes.AGENT_NAME: "name",
|
50
|
+
AgentAttributes.FROM_AGENT: "from_agent",
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
# Attribute mapping for HandoffSpanData
|
55
|
+
HANDOFF_SPAN_ATTRIBUTES: AttributeMap = {
|
56
|
+
AgentAttributes.FROM_AGENT: "from_agent",
|
57
|
+
AgentAttributes.TO_AGENT: "to_agent",
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
# Attribute mapping for GenerationSpanData
|
62
|
+
GENERATION_SPAN_ATTRIBUTES: AttributeMap = {
|
63
|
+
SpanAttributes.LLM_PROMPTS: "input",
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
# Attribute mapping for ResponseSpanData
|
68
|
+
RESPONSE_SPAN_ATTRIBUTES: AttributeMap = {
|
69
|
+
# Don't map input here as it causes double serialization
|
70
|
+
# We handle prompts manually in get_response_span_attributes
|
71
|
+
SpanAttributes.LLM_RESPONSE_MODEL: "model",
|
72
|
+
}
|
73
|
+
|
74
|
+
|
75
|
+
# Attribute mapping for TranscriptionSpanData
|
76
|
+
TRANSCRIPTION_SPAN_ATTRIBUTES: AttributeMap = {
|
77
|
+
# `input` and `input_format` are handled below
|
78
|
+
WorkflowAttributes.WORKFLOW_OUTPUT: "output",
|
79
|
+
}
|
80
|
+
|
81
|
+
|
82
|
+
# Attribute mapping for SpeechSpanData
|
83
|
+
SPEECH_SPAN_ATTRIBUTES: AttributeMap = {
|
84
|
+
WorkflowAttributes.WORKFLOW_INPUT: "input",
|
85
|
+
# `output` and `output_format` are handled below
|
86
|
+
# TODO `first_content_at` is not converted
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
# Attribute mapping for SpeechGroupSpanData
|
91
|
+
SPEECH_GROUP_SPAN_ATTRIBUTES: AttributeMap = {
|
92
|
+
WorkflowAttributes.WORKFLOW_INPUT: "input",
|
93
|
+
}
|
94
|
+
|
95
|
+
|
96
|
+
def _get_llm_messages_attributes(messages: Optional[List[Dict]], attribute_base: str) -> AttributeMap:
|
97
|
+
"""
|
98
|
+
Extracts attributes from a list of message dictionaries (e.g., prompts or completions).
|
99
|
+
Uses the attribute_base to format the specific attribute keys.
|
100
|
+
"""
|
101
|
+
attributes: AttributeMap = {}
|
102
|
+
if not messages:
|
103
|
+
return attributes
|
104
|
+
if not isinstance(messages, list):
|
105
|
+
logger.warning(
|
106
|
+
f"[_get_llm_messages_attributes] Expected a list of messages for base '{attribute_base}', got {type(messages)}. Value: {safe_serialize(messages)}. Returning empty."
|
107
|
+
)
|
108
|
+
return attributes
|
109
|
+
|
110
|
+
for i, msg_dict in enumerate(messages):
|
111
|
+
if isinstance(msg_dict, dict):
|
112
|
+
role = msg_dict.get("role")
|
113
|
+
content = msg_dict.get("content")
|
114
|
+
name = msg_dict.get("name")
|
115
|
+
tool_calls = msg_dict.get("tool_calls")
|
116
|
+
tool_call_id = msg_dict.get("tool_call_id")
|
117
|
+
|
118
|
+
# Common role and content
|
119
|
+
if role:
|
120
|
+
attributes[f"{attribute_base}.{i}.role"] = str(role)
|
121
|
+
if content is not None:
|
122
|
+
attributes[f"{attribute_base}.{i}.content"] = safe_serialize(content)
|
123
|
+
|
124
|
+
# Optional name for some roles
|
125
|
+
if name:
|
126
|
+
attributes[f"{attribute_base}.{i}.name"] = str(name)
|
127
|
+
|
128
|
+
# Tool calls (specific to assistant messages)
|
129
|
+
if tool_calls and isinstance(tool_calls, list):
|
130
|
+
for tc_idx, tc_dict in enumerate(tool_calls):
|
131
|
+
if isinstance(tc_dict, dict):
|
132
|
+
tc_id = tc_dict.get("id")
|
133
|
+
tc_type = tc_dict.get("type")
|
134
|
+
tc_function_data = tc_dict.get("function")
|
135
|
+
|
136
|
+
if tc_function_data and isinstance(tc_function_data, dict):
|
137
|
+
tc_func_name = tc_function_data.get("name")
|
138
|
+
tc_func_args = tc_function_data.get("arguments")
|
139
|
+
|
140
|
+
base_tool_call_key_formatted = f"{attribute_base}.{i}.tool_calls.{tc_idx}"
|
141
|
+
if tc_id:
|
142
|
+
attributes[f"{base_tool_call_key_formatted}.id"] = str(tc_id)
|
143
|
+
if tc_type:
|
144
|
+
attributes[f"{base_tool_call_key_formatted}.type"] = str(tc_type)
|
145
|
+
if tc_func_name:
|
146
|
+
attributes[f"{base_tool_call_key_formatted}.function.name"] = str(tc_func_name)
|
147
|
+
if tc_func_args is not None:
|
148
|
+
attributes[f"{base_tool_call_key_formatted}.function.arguments"] = safe_serialize(
|
149
|
+
tc_func_args
|
150
|
+
)
|
151
|
+
|
152
|
+
# Tool call ID (specific to tool_call_output messages)
|
153
|
+
if tool_call_id:
|
154
|
+
attributes[f"{attribute_base}.{i}.tool_call_id"] = str(tool_call_id)
|
155
|
+
else:
|
156
|
+
# If a message is not a dict, serialize its representation
|
157
|
+
attributes[f"{attribute_base}.{i}.content"] = safe_serialize(msg_dict)
|
158
|
+
|
159
|
+
return attributes
|
160
|
+
|
161
|
+
|
162
|
+
def get_common_instrumentation_attributes() -> AttributeMap:
|
163
|
+
"""Get common instrumentation attributes for the OpenAI Agents instrumentation.
|
164
|
+
|
165
|
+
This combines the generic AgentOps attributes with OpenAI Agents specific library attributes.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Dictionary of common instrumentation attributes
|
169
|
+
"""
|
170
|
+
attributes = get_common_attributes()
|
171
|
+
attributes.update(
|
172
|
+
{
|
173
|
+
InstrumentationAttributes.LIBRARY_NAME: LIBRARY_NAME,
|
174
|
+
InstrumentationAttributes.LIBRARY_VERSION: LIBRARY_VERSION,
|
175
|
+
}
|
176
|
+
)
|
177
|
+
return attributes
|
178
|
+
|
179
|
+
|
180
|
+
def get_agent_span_attributes(span_data: Any) -> AttributeMap:
|
181
|
+
"""Extract attributes from an AgentSpanData object.
|
182
|
+
|
183
|
+
Agents are requests made to the `openai.agents` endpoint.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
span_data: The AgentSpanData object
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
Dictionary of attributes for agent span
|
190
|
+
"""
|
191
|
+
attributes = {}
|
192
|
+
attributes.update(get_common_attributes())
|
193
|
+
|
194
|
+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKindValues.AGENT.value
|
195
|
+
|
196
|
+
# Get agent name directly from span_data
|
197
|
+
if hasattr(span_data, "name") and span_data.name:
|
198
|
+
attributes[AgentAttributes.AGENT_NAME] = str(span_data.name)
|
199
|
+
|
200
|
+
# Get handoffs directly from span_data
|
201
|
+
if hasattr(span_data, "handoffs") and span_data.handoffs:
|
202
|
+
attributes[AgentAttributes.HANDOFFS] = safe_serialize(span_data.handoffs)
|
203
|
+
|
204
|
+
if hasattr(span_data, "tools") and span_data.tools:
|
205
|
+
attributes[AgentAttributes.AGENT_TOOLS] = safe_serialize([str(getattr(t, "name", t)) for t in span_data.tools])
|
206
|
+
|
207
|
+
return attributes
|
208
|
+
|
209
|
+
|
210
|
+
def get_function_span_attributes(span_data: Any) -> AttributeMap:
|
211
|
+
"""Extract attributes from a FunctionSpanData object.
|
212
|
+
|
213
|
+
Functions are requests made to the `openai.functions` endpoint.
|
214
|
+
|
215
|
+
Args:
|
216
|
+
span_data: The FunctionSpanData object
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
Dictionary of attributes for function span
|
220
|
+
"""
|
221
|
+
attributes = _extract_attributes_from_mapping(span_data, FUNCTION_SPAN_ATTRIBUTES)
|
222
|
+
attributes.update(get_common_attributes())
|
223
|
+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKindValues.TOOL.value
|
224
|
+
|
225
|
+
# Determine tool status based on presence of error
|
226
|
+
if hasattr(span_data, "error") and span_data.error:
|
227
|
+
attributes[ToolAttributes.TOOL_STATUS] = ToolStatus.FAILED.value
|
228
|
+
else:
|
229
|
+
if hasattr(span_data, "output") and span_data.output is not None:
|
230
|
+
attributes[ToolAttributes.TOOL_STATUS] = ToolStatus.SUCCEEDED.value
|
231
|
+
else:
|
232
|
+
# Status will be set by exporter based on span lifecycle
|
233
|
+
pass
|
234
|
+
|
235
|
+
if hasattr(span_data, "from_agent") and span_data.from_agent:
|
236
|
+
attributes["agent.calling_tool.name"] = str(span_data.from_agent)
|
237
|
+
|
238
|
+
return attributes
|
239
|
+
|
240
|
+
|
241
|
+
def get_handoff_span_attributes(span_data: Any) -> AttributeMap:
|
242
|
+
"""Extract attributes from a HandoffSpanData object.
|
243
|
+
|
244
|
+
Handoffs are requests made to the `openai.handoffs` endpoint.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
span_data: The HandoffSpanData object
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
Dictionary of attributes for handoff span
|
251
|
+
"""
|
252
|
+
attributes = _extract_attributes_from_mapping(span_data, HANDOFF_SPAN_ATTRIBUTES)
|
253
|
+
attributes.update(get_common_attributes())
|
254
|
+
|
255
|
+
return attributes
|
256
|
+
|
257
|
+
|
258
|
+
def _extract_text_from_content(content: Any) -> Optional[str]:
|
259
|
+
"""Extract text from various content formats used in the Responses API.
|
260
|
+
|
261
|
+
Args:
|
262
|
+
content: Content in various formats (str, dict, list)
|
263
|
+
|
264
|
+
Returns:
|
265
|
+
Extracted text or None if no text found
|
266
|
+
"""
|
267
|
+
if isinstance(content, str):
|
268
|
+
return content
|
269
|
+
|
270
|
+
if isinstance(content, dict):
|
271
|
+
# Direct text field
|
272
|
+
if "text" in content:
|
273
|
+
return content["text"]
|
274
|
+
# Output text type
|
275
|
+
if content.get("type") == "output_text":
|
276
|
+
return content.get("text", "")
|
277
|
+
|
278
|
+
if isinstance(content, list):
|
279
|
+
text_parts = []
|
280
|
+
for item in content:
|
281
|
+
extracted = _extract_text_from_content(item)
|
282
|
+
if extracted:
|
283
|
+
text_parts.append(extracted)
|
284
|
+
return " ".join(text_parts) if text_parts else None
|
285
|
+
|
286
|
+
return None
|
287
|
+
|
288
|
+
|
289
|
+
def _build_prompt_messages_from_input(input_data: Any) -> List[Dict[str, Any]]:
|
290
|
+
"""Build prompt messages from various input formats.
|
291
|
+
|
292
|
+
Args:
|
293
|
+
input_data: Input data from span_data.input
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
List of message dictionaries with role and content
|
297
|
+
"""
|
298
|
+
messages = []
|
299
|
+
|
300
|
+
if isinstance(input_data, str):
|
301
|
+
# Single string input - assume it's a user message
|
302
|
+
messages.append({"role": "user", "content": input_data})
|
303
|
+
|
304
|
+
elif isinstance(input_data, list):
|
305
|
+
for msg in input_data:
|
306
|
+
if isinstance(msg, dict):
|
307
|
+
role = msg.get("role")
|
308
|
+
content = msg.get("content")
|
309
|
+
|
310
|
+
if role and content is not None:
|
311
|
+
extracted_text = _extract_text_from_content(content)
|
312
|
+
if extracted_text:
|
313
|
+
messages.append({"role": role, "content": extracted_text})
|
314
|
+
|
315
|
+
return messages
|
316
|
+
|
317
|
+
|
318
|
+
def get_response_span_attributes(span_data: Any) -> AttributeMap:
|
319
|
+
"""Extract attributes from a ResponseSpanData object with full LLM response processing.
|
320
|
+
|
321
|
+
Responses are requests made to the `openai.responses` endpoint.
|
322
|
+
|
323
|
+
This function extracts not just the basic input/response mapping but also processes
|
324
|
+
the rich response object to extract LLM-specific attributes like token usage,
|
325
|
+
model information, content, etc.
|
326
|
+
|
327
|
+
TODO tool calls arrive from this span type; need to figure out why that is.
|
328
|
+
|
329
|
+
Args:
|
330
|
+
span_data: The ResponseSpanData object
|
331
|
+
|
332
|
+
Returns:
|
333
|
+
Dictionary of attributes for response span
|
334
|
+
"""
|
335
|
+
# Get basic attributes from mapping
|
336
|
+
attributes = _extract_attributes_from_mapping(span_data, RESPONSE_SPAN_ATTRIBUTES)
|
337
|
+
attributes.update(get_common_attributes())
|
338
|
+
|
339
|
+
# Process response attributes first to get all response data including instructions
|
340
|
+
if span_data.response:
|
341
|
+
response_attrs = get_response_response_attributes(span_data.response)
|
342
|
+
|
343
|
+
# Extract system prompt if present
|
344
|
+
system_prompt = response_attrs.get(SpanAttributes.LLM_OPENAI_RESPONSE_INSTRUCTIONS)
|
345
|
+
|
346
|
+
prompt_messages = []
|
347
|
+
# Add system prompt as first message if available
|
348
|
+
if system_prompt:
|
349
|
+
prompt_messages.append({"role": "system", "content": system_prompt})
|
350
|
+
# Remove from response attrs to avoid duplication
|
351
|
+
response_attrs.pop(SpanAttributes.LLM_OPENAI_RESPONSE_INSTRUCTIONS, None)
|
352
|
+
|
353
|
+
# Add conversation history from input
|
354
|
+
if hasattr(span_data, "input") and span_data.input:
|
355
|
+
prompt_messages.extend(_build_prompt_messages_from_input(span_data.input))
|
356
|
+
|
357
|
+
# Format prompts using existing function
|
358
|
+
if prompt_messages:
|
359
|
+
attributes.update(_get_llm_messages_attributes(prompt_messages, "gen_ai.prompt"))
|
360
|
+
|
361
|
+
# Remove any prompt-related attributes that might have been set by response processing
|
362
|
+
response_attrs = {
|
363
|
+
k: v for k, v in response_attrs.items() if not k.startswith("gen_ai.prompt") and k != "gen_ai.request.tools"
|
364
|
+
}
|
365
|
+
|
366
|
+
# Add remaining response attributes
|
367
|
+
attributes.update(response_attrs)
|
368
|
+
else:
|
369
|
+
# No response object, just process input as prompts
|
370
|
+
if hasattr(span_data, "input") and span_data.input:
|
371
|
+
prompt_messages = _build_prompt_messages_from_input(span_data.input)
|
372
|
+
if prompt_messages:
|
373
|
+
attributes.update(_get_llm_messages_attributes(prompt_messages, "gen_ai.prompt"))
|
374
|
+
|
375
|
+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKindValues.LLM.value
|
376
|
+
|
377
|
+
return attributes
|
378
|
+
|
379
|
+
|
380
|
+
def get_generation_span_attributes(span_data: Any) -> AttributeMap:
|
381
|
+
"""Extract attributes from a GenerationSpanData object.
|
382
|
+
|
383
|
+
Generations are requests made to the `openai.completions` endpoint.
|
384
|
+
|
385
|
+
Args:
|
386
|
+
span_data: The GenerationSpanData object
|
387
|
+
|
388
|
+
Returns:
|
389
|
+
Dictionary of attributes for generation span
|
390
|
+
"""
|
391
|
+
attributes = _extract_attributes_from_mapping(span_data, GENERATION_SPAN_ATTRIBUTES)
|
392
|
+
attributes.update(get_common_attributes())
|
393
|
+
|
394
|
+
if SpanAttributes.LLM_PROMPTS in attributes:
|
395
|
+
raw_prompt_input = attributes.pop(SpanAttributes.LLM_PROMPTS)
|
396
|
+
formatted_prompt_for_llm = []
|
397
|
+
if isinstance(raw_prompt_input, str):
|
398
|
+
formatted_prompt_for_llm.append({"role": "user", "content": raw_prompt_input})
|
399
|
+
elif isinstance(raw_prompt_input, list):
|
400
|
+
temp_formatted_list = []
|
401
|
+
all_strings_or_dicts = True
|
402
|
+
for item in raw_prompt_input:
|
403
|
+
if isinstance(item, str):
|
404
|
+
temp_formatted_list.append({"role": "user", "content": item})
|
405
|
+
elif isinstance(item, dict):
|
406
|
+
temp_formatted_list.append(item)
|
407
|
+
else:
|
408
|
+
all_strings_or_dicts = False
|
409
|
+
break
|
410
|
+
if all_strings_or_dicts:
|
411
|
+
formatted_prompt_for_llm = temp_formatted_list
|
412
|
+
else:
|
413
|
+
logger.warning(
|
414
|
+
f"[get_generation_span_attributes] span_data.input was a list with mixed/unexpected content: {safe_serialize(raw_prompt_input)}"
|
415
|
+
)
|
416
|
+
|
417
|
+
if formatted_prompt_for_llm:
|
418
|
+
attributes.update(_get_llm_messages_attributes(formatted_prompt_for_llm, "gen_ai.prompt"))
|
419
|
+
|
420
|
+
if span_data.model:
|
421
|
+
attributes.update(get_model_attributes(span_data.model))
|
422
|
+
|
423
|
+
if span_data.output:
|
424
|
+
attributes.update(get_generation_output_attributes(span_data.output))
|
425
|
+
|
426
|
+
if span_data.model_config:
|
427
|
+
attributes.update(get_model_config_attributes(span_data.model_config))
|
428
|
+
|
429
|
+
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = AgentOpsSpanKindValues.LLM.value
|
430
|
+
return attributes
|
431
|
+
|
432
|
+
|
433
|
+
def get_transcription_span_attributes(span_data: Any) -> AttributeMap:
|
434
|
+
"""Extract attributes from a TranscriptionSpanData object.
|
435
|
+
|
436
|
+
This represents a conversion from audio to text.
|
437
|
+
|
438
|
+
Args:
|
439
|
+
span_data: The TranscriptionSpanData object
|
440
|
+
Returns:
|
441
|
+
Dictionary of attributes for transcription span
|
442
|
+
"""
|
443
|
+
from agentops import get_client
|
444
|
+
from agentops.client.api.types import UploadedObjectResponse
|
445
|
+
|
446
|
+
client = get_client()
|
447
|
+
|
448
|
+
attributes = _extract_attributes_from_mapping(span_data, TRANSCRIPTION_SPAN_ATTRIBUTES)
|
449
|
+
attributes.update(get_common_attributes())
|
450
|
+
|
451
|
+
if span_data.input:
|
452
|
+
prefix = WorkflowAttributes.WORKFLOW_INPUT
|
453
|
+
uploaded_object: UploadedObjectResponse = client.api.v4.upload_object(span_data.input)
|
454
|
+
attributes.update(get_uploaded_object_attributes(uploaded_object, prefix))
|
455
|
+
|
456
|
+
if span_data.model:
|
457
|
+
attributes.update(get_model_attributes(span_data.model))
|
458
|
+
|
459
|
+
if span_data.model_config:
|
460
|
+
attributes.update(get_model_config_attributes(span_data.model_config))
|
461
|
+
|
462
|
+
return attributes
|
463
|
+
|
464
|
+
|
465
|
+
def get_speech_span_attributes(span_data: Any) -> AttributeMap:
|
466
|
+
"""Extract attributes from a SpeechSpanData object.
|
467
|
+
|
468
|
+
This represents a conversion from audio to text.
|
469
|
+
|
470
|
+
Args:
|
471
|
+
span_data: The SpeechSpanData object
|
472
|
+
|
473
|
+
Returns:
|
474
|
+
Dictionary of attributes for speech span
|
475
|
+
"""
|
476
|
+
from agentops import get_client
|
477
|
+
from agentops.client.api.types import UploadedObjectResponse
|
478
|
+
|
479
|
+
client = get_client()
|
480
|
+
|
481
|
+
attributes = _extract_attributes_from_mapping(span_data, SPEECH_SPAN_ATTRIBUTES)
|
482
|
+
attributes.update(get_common_attributes())
|
483
|
+
|
484
|
+
if span_data.output:
|
485
|
+
prefix = WorkflowAttributes.WORKFLOW_OUTPUT
|
486
|
+
uploaded_object: UploadedObjectResponse = client.api.v4.upload_object(span_data.output)
|
487
|
+
attributes.update(get_uploaded_object_attributes(uploaded_object, prefix))
|
488
|
+
|
489
|
+
if span_data.model:
|
490
|
+
attributes.update(get_model_attributes(span_data.model))
|
491
|
+
|
492
|
+
if span_data.model_config:
|
493
|
+
attributes.update(get_model_config_attributes(span_data.model_config))
|
494
|
+
|
495
|
+
return attributes
|
496
|
+
|
497
|
+
|
498
|
+
def get_speech_group_span_attributes(span_data: Any) -> AttributeMap:
|
499
|
+
"""Extract attributes from a SpeechGroupSpanData object.
|
500
|
+
|
501
|
+
This represents a conversion from audio to text.
|
502
|
+
|
503
|
+
Args:
|
504
|
+
span_data: The SpeechGroupSpanData object
|
505
|
+
|
506
|
+
Returns:
|
507
|
+
Dictionary of attributes for speech group span
|
508
|
+
"""
|
509
|
+
attributes = _extract_attributes_from_mapping(span_data, SPEECH_GROUP_SPAN_ATTRIBUTES)
|
510
|
+
attributes.update(get_common_attributes())
|
511
|
+
|
512
|
+
return attributes
|
513
|
+
|
514
|
+
|
515
|
+
def get_span_attributes(span_data: Any) -> AttributeMap:
|
516
|
+
"""Get attributes for a span based on its type.
|
517
|
+
|
518
|
+
This function centralizes attribute extraction by delegating to type-specific
|
519
|
+
getter functions.
|
520
|
+
|
521
|
+
Args:
|
522
|
+
span_data: The span data object
|
523
|
+
|
524
|
+
Returns:
|
525
|
+
Dictionary of attributes for the span
|
526
|
+
"""
|
527
|
+
span_type = span_data.__class__.__name__
|
528
|
+
|
529
|
+
if span_type == "AgentSpanData":
|
530
|
+
attributes = get_agent_span_attributes(span_data)
|
531
|
+
elif span_type == "FunctionSpanData":
|
532
|
+
attributes = get_function_span_attributes(span_data)
|
533
|
+
elif span_type == "GenerationSpanData":
|
534
|
+
attributes = get_generation_span_attributes(span_data)
|
535
|
+
elif span_type == "HandoffSpanData":
|
536
|
+
attributes = get_handoff_span_attributes(span_data)
|
537
|
+
elif span_type == "ResponseSpanData":
|
538
|
+
attributes = get_response_span_attributes(span_data)
|
539
|
+
elif span_type == "TranscriptionSpanData":
|
540
|
+
attributes = get_transcription_span_attributes(span_data)
|
541
|
+
elif span_type == "SpeechSpanData":
|
542
|
+
attributes = get_speech_span_attributes(span_data)
|
543
|
+
elif span_type == "SpeechGroupSpanData":
|
544
|
+
attributes = get_speech_group_span_attributes(span_data)
|
545
|
+
else:
|
546
|
+
logger.debug(f"[agentops.instrumentation.openai_agents.attributes] Unknown span type: {span_type}")
|
547
|
+
attributes = {}
|
548
|
+
|
549
|
+
return attributes
|