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