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,607 @@
1
+ from typing import List, Union
2
+ from agentops.logging import logger
3
+ from agentops.semconv import (
4
+ SpanAttributes,
5
+ MessageAttributes,
6
+ )
7
+ from agentops.instrumentation.common.attributes import (
8
+ AttributeMap,
9
+ IndexedAttributeMap,
10
+ _extract_attributes_from_mapping,
11
+ _extract_attributes_from_mapping_with_index,
12
+ )
13
+
14
+ try:
15
+ from openai.types.responses import (
16
+ FunctionTool,
17
+ WebSearchTool,
18
+ FileSearchTool,
19
+ ComputerTool,
20
+ Response,
21
+ ResponseUsage,
22
+ ResponseReasoningItem,
23
+ ResponseOutputMessage,
24
+ ResponseOutputText,
25
+ ResponseFunctionToolCall,
26
+ ResponseFunctionWebSearch,
27
+ ResponseFileSearchToolCall,
28
+ ResponseComputerToolCall,
29
+ )
30
+ from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
31
+
32
+ ToolTypes = Union[
33
+ FunctionTool,
34
+ WebSearchTool,
35
+ FileSearchTool,
36
+ ]
37
+ ResponseOutputTypes = Union[
38
+ ResponseOutputMessage,
39
+ ResponseOutputText,
40
+ ResponseFunctionToolCall,
41
+ ResponseFunctionWebSearch,
42
+ ResponseComputerToolCall,
43
+ ResponseFileSearchToolCall,
44
+ ]
45
+ except ImportError as e:
46
+ logger.debug(f"[agentops.instrumentation.openai_agents] Could not import OpenAI Agents SDK types: {e}")
47
+
48
+
49
+ RESPONSE_ATTRIBUTES: AttributeMap = {
50
+ # Response(
51
+ # id='resp_67ddd0196a4c81929f7e3783a80f18110b486458d6766f93',
52
+ # created_at=1742589977.0,
53
+ # error=None,
54
+ # incomplete_details=None,
55
+ # instructions='You are a helpful assistant...',
56
+ # metadata={},
57
+ # model='gpt-4o-2024-08-06',
58
+ # object='response',
59
+ # output=[
60
+ # ...
61
+ # ],
62
+ # parallel_tool_calls=True,
63
+ # temperature=1.0,
64
+ # tool_choice='auto',
65
+ # tools=[
66
+ # ...)
67
+ # ],
68
+ # top_p=1.0,
69
+ # max_output_tokens=None,
70
+ # previous_response_id=None,
71
+ # reasoning=Reasoning(
72
+ # ...
73
+ # ),
74
+ # status='completed',
75
+ # text=ResponseTextConfig(format=ResponseFormatText(type='text')),
76
+ # truncation='disabled',
77
+ # usage=ResponseUsage(
78
+ # ...
79
+ # ),
80
+ # user=None,
81
+ # store=True
82
+ # )
83
+ SpanAttributes.LLM_RESPONSE_ID: "id",
84
+ SpanAttributes.LLM_REQUEST_MODEL: "model",
85
+ SpanAttributes.LLM_RESPONSE_MODEL: "model",
86
+ SpanAttributes.LLM_OPENAI_RESPONSE_INSTRUCTIONS: "instructions",
87
+ SpanAttributes.LLM_REQUEST_MAX_TOKENS: "max_output_tokens",
88
+ SpanAttributes.LLM_REQUEST_TEMPERATURE: "temperature",
89
+ SpanAttributes.LLM_REQUEST_TOP_P: "top_p",
90
+ }
91
+
92
+
93
+ RESPONSE_TOOL_ATTRIBUTES: IndexedAttributeMap = {
94
+ # FunctionTool(
95
+ # name='get_weather',
96
+ # parameters={'properties': {'location': {'title': 'Location', 'type': 'string'}}, 'required': ['location'], 'title': 'get_weather_args', 'type': 'object', 'additionalProperties': False},
97
+ # strict=True,
98
+ # type='function',
99
+ # description='Get the current weather for a location.'
100
+ # )
101
+ MessageAttributes.TOOL_CALL_TYPE: "type",
102
+ MessageAttributes.TOOL_CALL_NAME: "name",
103
+ MessageAttributes.TOOL_CALL_DESCRIPTION: "description",
104
+ MessageAttributes.TOOL_CALL_ARGUMENTS: "parameters",
105
+ # TODO `strict` is not converted
106
+ }
107
+
108
+
109
+ RESPONSE_TOOL_WEB_SEARCH_ATTRIBUTES: IndexedAttributeMap = {
110
+ # WebSearchTool(
111
+ # type='web_search_preview',
112
+ # search_context_size='medium',
113
+ # user_location=UserLocation(
114
+ # type='approximate',
115
+ # city=None,
116
+ # country='US',
117
+ # region=None,
118
+ # timezone=None
119
+ # )
120
+ # )
121
+ MessageAttributes.TOOL_CALL_NAME: "type",
122
+ # `parameters` is added by the `get_response_tool_web_search_attributes` function,
123
+ # which contains `search_context_size` and `user_location`.
124
+ MessageAttributes.TOOL_CALL_ARGUMENTS: "parameters",
125
+ }
126
+
127
+
128
+ RESPONSE_TOOL_FILE_SEARCH_ATTRIBUTES: IndexedAttributeMap = {
129
+ # FileSearchTool(
130
+ # type='file_search',
131
+ # vector_store_ids=['store_123', 'store_456'],
132
+ # filters=Filters(
133
+ # key='value'
134
+ # ),
135
+ # max_num_results=10,
136
+ # ranking_options=RankingOptions(
137
+ # ranker='default-2024-11-15',
138
+ # score_threshold=0.8
139
+ # )
140
+ # )
141
+ MessageAttributes.TOOL_CALL_TYPE: "type",
142
+ # `parameters` is added by the `get_response_tool_file_search_attributes` function,
143
+ # which contains `vector_store_ids`, `filters`, `max_num_results`, and `ranking_options`.
144
+ MessageAttributes.TOOL_CALL_ARGUMENTS: "parameters",
145
+ }
146
+
147
+
148
+ RESPONSE_TOOL_COMPUTER_ATTRIBUTES: IndexedAttributeMap = {
149
+ # ComputerTool(
150
+ # display_height=1080.0,
151
+ # display_width=1920.0,
152
+ # environment='mac',
153
+ # type='computer_use_preview'
154
+ # )
155
+ MessageAttributes.TOOL_CALL_TYPE: "type",
156
+ # `parameters` is added by the `get_response_tool_computer_attributes` function,
157
+ # which contains `display_height`, `display_width`, `environment`, etc.
158
+ MessageAttributes.TOOL_CALL_ARGUMENTS: "parameters",
159
+ }
160
+
161
+
162
+ RESPONSE_OUTPUT_MESSAGE_ATTRIBUTES: IndexedAttributeMap = {
163
+ # ResponseOutputMessage(
164
+ # id='msg_67ddcad3b6008192b521035d8b71fc570db7bfce93fd916a',
165
+ # content=[
166
+ # ...
167
+ # ],
168
+ # role='assistant',
169
+ # status='completed',
170
+ # type='message'
171
+ # )
172
+ MessageAttributes.COMPLETION_ID: "id",
173
+ MessageAttributes.COMPLETION_TYPE: "type",
174
+ MessageAttributes.COMPLETION_ROLE: "role",
175
+ MessageAttributes.COMPLETION_FINISH_REASON: "status",
176
+ }
177
+
178
+
179
+ RESPONSE_OUTPUT_TEXT_ATTRIBUTES: IndexedAttributeMap = {
180
+ # ResponseOutputText(
181
+ # annotations=[],
182
+ # text='Recursion is a programming technique ...',
183
+ # type='output_text'
184
+ # )
185
+ MessageAttributes.COMPLETION_TYPE: "type",
186
+ MessageAttributes.COMPLETION_CONTENT: "text",
187
+ # TODO `annotations` are not converted
188
+ }
189
+
190
+
191
+ RESPONSE_OUTPUT_REASONING_ATTRIBUTES: IndexedAttributeMap = {
192
+ # ResponseReasoningItem(
193
+ # id='reasoning_12345',
194
+ # summary=[
195
+ # Summary(
196
+ # text='The model used a step-by-step approach to solve the problem.',
197
+ # type='summary_text'
198
+ # )
199
+ # ],
200
+ # type='reasoning',
201
+ # status='completed'
202
+ # )
203
+ MessageAttributes.COMPLETION_ID: "id",
204
+ MessageAttributes.COMPLETION_TYPE: "type",
205
+ MessageAttributes.COMPLETION_FINISH_REASON: "status",
206
+ # TODO `summary` is not converted
207
+ }
208
+
209
+
210
+ RESPONSE_OUTPUT_TOOL_ATTRIBUTES: IndexedAttributeMap = {
211
+ # ResponseFunctionToolCall(
212
+ # id='ftc_67ddcad3b6008192b521035d8b71fc570db7bfce93fd916a',
213
+ # arguments='{"location": "New York"}',
214
+ # call_id='call_12345',
215
+ # name='get_weather',
216
+ # type='function_call',
217
+ # status='completed'
218
+ # )
219
+ MessageAttributes.COMPLETION_TOOL_CALL_ID: "id",
220
+ MessageAttributes.COMPLETION_TOOL_CALL_TYPE: "type",
221
+ MessageAttributes.COMPLETION_TOOL_CALL_NAME: "name",
222
+ MessageAttributes.COMPLETION_TOOL_CALL_ARGUMENTS: "arguments",
223
+ # TODO `status` & `call_id` are not converted
224
+ }
225
+
226
+
227
+ RESPONSE_OUTPUT_TOOL_WEB_SEARCH_ATTRIBUTES: IndexedAttributeMap = {
228
+ # ResponseFunctionWebSearch(
229
+ # id='ws_67eda37a5f18819280bf8b64f315bfa70091ec39ac46b411',
230
+ # status='completed',
231
+ # type='web_search_call'
232
+ # )
233
+ MessageAttributes.COMPLETION_TOOL_CALL_ID: "id",
234
+ MessageAttributes.COMPLETION_TOOL_CALL_TYPE: "type",
235
+ MessageAttributes.COMPLETION_TOOL_CALL_STATUS: "status",
236
+ }
237
+
238
+ RESPONSE_OUTPUT_TOOL_WEB_SEARCH_URL_ANNOTATIONS: IndexedAttributeMap = {
239
+ # AnnotationURLCitation(
240
+ # end_index=747,
241
+ # start_index=553,
242
+ # title="You can now play a real-time AI-rendered Quake II in your browser",
243
+ # type='url_citation',
244
+ # url='https://www.tomshardware.com/video-games/you-can-now-play-a-real-time-ai-rendered-quake-ii-in-your-browser-microsofts-whamm-offers-generative-ai-for-games?utm_source=openai'
245
+ # )
246
+ MessageAttributes.COMPLETION_ANNOTATION_END_INDEX: "end_index",
247
+ MessageAttributes.COMPLETION_ANNOTATION_START_INDEX: "start_index",
248
+ MessageAttributes.COMPLETION_ANNOTATION_TITLE: "title",
249
+ MessageAttributes.COMPLETION_ANNOTATION_TYPE: "type",
250
+ MessageAttributes.COMPLETION_ANNOTATION_URL: "url",
251
+ }
252
+
253
+
254
+ RESPONSE_OUTPUT_TOOL_COMPUTER_ATTRIBUTES: IndexedAttributeMap = {
255
+ # ResponseComputerToolCall(
256
+ # id='comp_12345',
257
+ # action=Action(
258
+ # type='click',
259
+ # target='button_submit'
260
+ # ),
261
+ # call_id='call_67890',
262
+ # pending_safety_checks=[
263
+ # PendingSafetyCheck(
264
+ # type='check_type',
265
+ # status='pending'
266
+ # )
267
+ # ],
268
+ # status='completed',
269
+ # type='computer_call'
270
+ # )
271
+ # TODO semantic conventions for `ResponseComputerToolCall` are not defined yet
272
+ }
273
+
274
+
275
+ RESPONSE_OUTPUT_TOOL_FILE_SEARCH_ATTRIBUTES: IndexedAttributeMap = {
276
+ # ResponseFileSearchToolCall(
277
+ # id='fsc_12345',
278
+ # queries=['example query'],
279
+ # status='completed',
280
+ # type='file_search_call',
281
+ # results=[
282
+ # Result(
283
+ # attributes={'key1': 'value1', 'key2': 42},
284
+ # file_id='file_67890',
285
+ # filename='example.txt',
286
+ # score=0.95,
287
+ # text='Example text retrieved from the file.'
288
+ # ),
289
+ # ...
290
+ # ]
291
+ # )
292
+ # TODO semantic conventions for `ResponseFileSearchToolCall` are not defined yet
293
+ }
294
+
295
+
296
+ RESPONSE_USAGE_ATTRIBUTES: AttributeMap = {
297
+ SpanAttributes.LLM_USAGE_COMPLETION_TOKENS: "output_tokens",
298
+ SpanAttributes.LLM_USAGE_PROMPT_TOKENS: "input_tokens",
299
+ SpanAttributes.LLM_USAGE_TOTAL_TOKENS: "total_tokens",
300
+ }
301
+
302
+
303
+ # usage attributes are shared with `input_details_tokens` and `output_details_tokens`
304
+ RESPONSE_USAGE_DETAILS_ATTRIBUTES: AttributeMap = {
305
+ SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS: "cached_tokens",
306
+ SpanAttributes.LLM_USAGE_REASONING_TOKENS: "reasoning_tokens",
307
+ }
308
+
309
+
310
+ RESPONSE_REASONING_ATTRIBUTES: AttributeMap = {
311
+ # Reasoning(
312
+ # effort='medium',
313
+ # generate_summary=None,
314
+ # )
315
+ # TODO `effort` and `generate_summary` need semantic conventions
316
+ }
317
+
318
+
319
+ def get_response_kwarg_attributes(kwargs: dict) -> AttributeMap:
320
+ """Handles interpretation of openai Responses.create method keyword arguments."""
321
+
322
+ # Just gather the attributes that are not present in the Response object
323
+ # TODO We could gather more here and have more context available in the
324
+ # event of an error during the request execution.
325
+
326
+ # Method signature for `Responses.create`:
327
+ # input: Union[str, ResponseInputParam],
328
+ # model: Union[str, ChatModel],
329
+ # include: Optional[List[ResponseIncludable]] | NotGiven = NOT_GIVEN,
330
+ # instructions: Optional[str] | NotGiven = NOT_GIVEN,
331
+ # max_output_tokens: Optional[int] | NotGiven = NOT_GIVEN,
332
+ # metadata: Optional[Metadata] | NotGiven = NOT_GIVEN,
333
+ # parallel_tool_calls: Optional[bool] | NotGiven = NOT_GIVEN,
334
+ # previous_response_id: Optional[str] | NotGiven = NOT_GIVEN,
335
+ # reasoning: Optional[Reasoning] | NotGiven = NOT_GIVEN,
336
+ # store: Optional[bool] | NotGiven = NOT_GIVEN,
337
+ # stream: Optional[Literal[False]] | NotGiven = NOT_GIVEN,
338
+ # temperature: Optional[float] | NotGiven = NOT_GIVEN,
339
+ # text: ResponseTextConfigParam | NotGiven = NOT_GIVEN,
340
+ # tool_choice: response_create_params.ToolChoice | NotGiven = NOT_GIVEN,
341
+ # tools: Iterable[ToolParam] | NotGiven = NOT_GIVEN,
342
+ # top_p: Optional[float] | NotGiven = NOT_GIVEN,
343
+ # truncation: Optional[Literal["auto", "disabled"]] | NotGiven = NOT_GIVEN,
344
+ # user: str | NotGiven = NOT_GIVEN,
345
+ # extra_headers: Headers | None = None,
346
+ # extra_query: Query | None = None,
347
+ # extra_body: Body | None = None,
348
+ # timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
349
+ attributes = {}
350
+
351
+ # `input` can either be a `str` or a list of many internal types, so we duck
352
+ # type our way into some usable common attributes
353
+ _input: Union[str, list, None] = kwargs.get("input")
354
+ if isinstance(_input, str):
355
+ attributes[MessageAttributes.PROMPT_ROLE.format(i=0)] = "user"
356
+ attributes[MessageAttributes.PROMPT_CONTENT.format(i=0)] = _input
357
+
358
+ elif isinstance(_input, list):
359
+ for i, prompt in enumerate(_input):
360
+ # Object type is pretty diverse, so we handle common attributes, but do so
361
+ # conditionally because not all attributes are guaranteed to exist
362
+ if hasattr(prompt, "type"):
363
+ attributes[MessageAttributes.PROMPT_TYPE.format(i=i)] = prompt.type
364
+ if hasattr(prompt, "role"):
365
+ attributes[MessageAttributes.PROMPT_ROLE.format(i=i)] = prompt.role
366
+ if hasattr(prompt, "content"):
367
+ attributes[MessageAttributes.PROMPT_CONTENT.format(i=i)] = prompt.content
368
+
369
+ else:
370
+ logger.debug(f"[agentops.instrumentation.openai.response] '{type(_input)}' is not a recognized input type.")
371
+
372
+ # `model` is always `str` (`ChatModel` type is just a string literal)
373
+ attributes[SpanAttributes.LLM_REQUEST_MODEL] = str(kwargs.get("model"))
374
+
375
+ return attributes
376
+
377
+
378
+ # We call this `response_response` because in OpenAI Agents the `Response` is
379
+ # a return type from the `responses` module
380
+ def get_response_response_attributes(response: "Response") -> AttributeMap:
381
+ """Handles interpretation of an openai Response object."""
382
+ attributes = _extract_attributes_from_mapping(response.__dict__, RESPONSE_ATTRIBUTES)
383
+
384
+ if response.output:
385
+ attributes.update(get_response_output_attributes(response.output))
386
+
387
+ if response.tools:
388
+ attributes.update(get_response_tools_attributes(response.tools))
389
+
390
+ if response.reasoning:
391
+ attributes.update(_extract_attributes_from_mapping(response.reasoning.__dict__, RESPONSE_REASONING_ATTRIBUTES))
392
+
393
+ if response.usage:
394
+ attributes.update(get_response_usage_attributes(response.usage))
395
+
396
+ return attributes
397
+
398
+
399
+ def get_response_output_attributes(output: List["ResponseOutputTypes"]) -> AttributeMap:
400
+ """Handles interpretation of an openai Response `output` list."""
401
+ attributes = {}
402
+
403
+ for i, output_item in enumerate(output):
404
+ if isinstance(output_item, ResponseOutputMessage):
405
+ attributes.update(get_response_output_message_attributes(i, output_item))
406
+
407
+ elif isinstance(output_item, ResponseReasoningItem):
408
+ attributes.update(
409
+ _extract_attributes_from_mapping_with_index(output_item, RESPONSE_OUTPUT_REASONING_ATTRIBUTES, i)
410
+ )
411
+
412
+ elif isinstance(output_item, ResponseFunctionToolCall):
413
+ attributes.update(
414
+ _extract_attributes_from_mapping_with_index(output_item, RESPONSE_OUTPUT_TOOL_ATTRIBUTES, i=i, j=0)
415
+ )
416
+
417
+ elif isinstance(output_item, ResponseFunctionWebSearch):
418
+ attributes.update(
419
+ _extract_attributes_from_mapping_with_index(
420
+ output_item, RESPONSE_OUTPUT_TOOL_WEB_SEARCH_ATTRIBUTES, i=i, j=0
421
+ )
422
+ )
423
+
424
+ elif isinstance(output_item, ResponseComputerToolCall):
425
+ attributes.update(
426
+ _extract_attributes_from_mapping_with_index(
427
+ output_item, RESPONSE_OUTPUT_TOOL_COMPUTER_ATTRIBUTES, i=i, j=0
428
+ )
429
+ )
430
+
431
+ elif isinstance(output_item, ResponseFileSearchToolCall):
432
+ attributes.update(
433
+ _extract_attributes_from_mapping_with_index(
434
+ output_item, RESPONSE_OUTPUT_TOOL_FILE_SEARCH_ATTRIBUTES, i=i, j=0
435
+ )
436
+ )
437
+
438
+ else:
439
+ logger.debug(f"[agentops.instrumentation.openai.response] '{output_item}' is not a recognized output type.")
440
+
441
+ return attributes
442
+
443
+
444
+ def get_response_output_text_attributes(output_text: "ResponseOutputText", index: int) -> AttributeMap:
445
+ """Handles interpretation of an openai ResponseOutputText object."""
446
+ # This function is a helper to handle the ResponseOutputText type specifically
447
+ attributes = _extract_attributes_from_mapping_with_index(output_text, RESPONSE_OUTPUT_TEXT_ATTRIBUTES, index)
448
+
449
+ if hasattr(output_text, "annotations"):
450
+ for j, output_text_annotation in enumerate(output_text.annotations):
451
+ attributes.update(
452
+ _extract_attributes_from_mapping_with_index(
453
+ output_text_annotation, RESPONSE_OUTPUT_TOOL_WEB_SEARCH_URL_ANNOTATIONS, i=index, j=j
454
+ )
455
+ )
456
+
457
+ return attributes
458
+
459
+
460
+ def get_response_output_message_attributes(index: int, message: "ResponseOutputMessage") -> AttributeMap:
461
+ """Handles interpretation of an openai ResponseOutputMessage object."""
462
+ attributes = _extract_attributes_from_mapping_with_index(message, RESPONSE_OUTPUT_MESSAGE_ATTRIBUTES, index)
463
+
464
+ if message.content:
465
+ for i, content in enumerate(message.content):
466
+ if isinstance(content, ResponseOutputText):
467
+ attributes.update(get_response_output_text_attributes(content, i))
468
+
469
+ else:
470
+ logger.debug(
471
+ f"[agentops.instrumentation.openai.response] '{content}' is not a recognized content type."
472
+ )
473
+
474
+ return attributes
475
+
476
+
477
+ def get_response_tools_attributes(tools: List["ToolTypes"]) -> AttributeMap:
478
+ """Handles interpretation of openai Response `tools` list."""
479
+ attributes = {}
480
+
481
+ for i, tool in enumerate(tools):
482
+ if isinstance(tool, FunctionTool):
483
+ attributes.update(_extract_attributes_from_mapping_with_index(tool, RESPONSE_TOOL_ATTRIBUTES, i))
484
+
485
+ elif isinstance(tool, WebSearchTool):
486
+ attributes.update(get_response_tool_web_search_attributes(tool, i))
487
+
488
+ elif isinstance(tool, FileSearchTool):
489
+ attributes.update(get_response_tool_file_search_attributes(tool, i))
490
+
491
+ elif isinstance(tool, ComputerTool):
492
+ attributes.update(get_response_tool_computer_attributes(tool, i))
493
+
494
+ else:
495
+ logger.debug(f"[agentops.instrumentation.openai.response] '{tool}' is not a recognized tool type.")
496
+
497
+ return attributes
498
+
499
+
500
+ def get_response_tool_web_search_attributes(tool: "WebSearchTool", index: int) -> AttributeMap:
501
+ """Handles interpretation of an openai WebSearchTool object."""
502
+ parameters = {}
503
+ if hasattr(tool, "search_context_size"):
504
+ parameters["search_context_size"] = tool.search_context_size
505
+
506
+ if hasattr(tool, "user_location"):
507
+ parameters["user_location"] = tool.user_location.__dict__
508
+
509
+ tool_data = tool.__dict__
510
+ if parameters:
511
+ # add parameters to the tool_data dict so we can format them with the other attributes
512
+ tool_data["parameters"] = parameters
513
+
514
+ return _extract_attributes_from_mapping_with_index(tool_data, RESPONSE_TOOL_WEB_SEARCH_ATTRIBUTES, index)
515
+
516
+
517
+ def get_response_tool_file_search_attributes(tool: "FileSearchTool", index: int) -> AttributeMap:
518
+ """Handles interpretation of an openai FileSearchTool object."""
519
+ parameters = {}
520
+
521
+ if hasattr(tool, "vector_store_ids"):
522
+ parameters["vector_store_ids"] = tool.vector_store_ids
523
+
524
+ if hasattr(tool, "filters"):
525
+ parameters["filters"] = tool.filters.__dict__
526
+
527
+ if hasattr(tool, "max_num_results"):
528
+ parameters["max_num_results"] = tool.max_num_results
529
+
530
+ if hasattr(tool, "ranking_options"):
531
+ parameters["ranking_options"] = tool.ranking_options.__dict__
532
+
533
+ tool_data = tool.__dict__
534
+ if parameters:
535
+ # add parameters to the tool_data dict so we can format them with the other attributes
536
+ tool_data["parameters"] = parameters
537
+
538
+ return _extract_attributes_from_mapping_with_index(tool_data, RESPONSE_TOOL_FILE_SEARCH_ATTRIBUTES, index)
539
+
540
+
541
+ def get_response_tool_computer_attributes(tool: "ComputerTool", index: int) -> AttributeMap:
542
+ """Handles interpretation of an openai ComputerTool object."""
543
+ parameters = {}
544
+
545
+ if hasattr(tool, "display_height"):
546
+ parameters["display_height"] = tool.display_height
547
+
548
+ if hasattr(tool, "display_width"):
549
+ parameters["display_width"] = tool.display_width
550
+
551
+ if hasattr(tool, "environment"):
552
+ parameters["environment"] = tool.environment
553
+
554
+ tool_data = tool.__dict__
555
+ if parameters:
556
+ # add parameters to the tool_data dict so we can format them with the other attributes
557
+ tool_data["parameters"] = parameters
558
+
559
+ return _extract_attributes_from_mapping_with_index(tool_data, RESPONSE_TOOL_COMPUTER_ATTRIBUTES, index)
560
+
561
+
562
+ def get_response_usage_attributes(usage: "ResponseUsage") -> AttributeMap:
563
+ """Handles interpretation of an openai ResponseUsage object."""
564
+ # ResponseUsage(
565
+ # input_tokens=0,
566
+ # output_tokens=0,
567
+ # output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
568
+ # total_tokens=0,
569
+ # input_tokens_details={'cached_tokens': 0}
570
+ # )
571
+ attributes = {}
572
+
573
+ attributes.update(_extract_attributes_from_mapping(usage.__dict__, RESPONSE_USAGE_ATTRIBUTES))
574
+
575
+ # input_tokens_details is an `InputTokensDetails` object or `dict` if it exists
576
+ if hasattr(usage, "input_tokens_details"):
577
+ input_details = usage.input_tokens_details
578
+ if input_details is None:
579
+ pass
580
+
581
+ elif isinstance(input_details, InputTokensDetails):
582
+ attributes.update(
583
+ _extract_attributes_from_mapping(input_details.__dict__, RESPONSE_USAGE_DETAILS_ATTRIBUTES)
584
+ )
585
+
586
+ elif isinstance(input_details, dict): # openai-agents often returns a dict for some reason.
587
+ attributes.update(_extract_attributes_from_mapping(input_details, RESPONSE_USAGE_DETAILS_ATTRIBUTES))
588
+
589
+ else:
590
+ logger.debug(
591
+ f"[agentops.instrumentation.openai.response] '{input_details}' is not a recognized input details type."
592
+ )
593
+
594
+ # output_tokens_details is an `OutputTokensDetails` object
595
+ output_details = usage.output_tokens_details
596
+ if output_details is None:
597
+ pass
598
+
599
+ elif isinstance(output_details, OutputTokensDetails):
600
+ attributes.update(_extract_attributes_from_mapping(output_details.__dict__, RESPONSE_USAGE_DETAILS_ATTRIBUTES))
601
+
602
+ else:
603
+ logger.debug(
604
+ f"[agentops.instrumentation.openai.response] '{output_details}' is not a recognized output details type."
605
+ )
606
+
607
+ return attributes
@@ -0,0 +1,36 @@
1
+ """Configuration for OpenAI instrumentation.
2
+
3
+ This module provides a global configuration object that can be used to customize
4
+ the behavior of OpenAI instrumentation across all components.
5
+ """
6
+
7
+ from typing import Callable, Optional, Dict
8
+ from typing_extensions import Protocol
9
+
10
+
11
+ class UploadImageCallable(Protocol):
12
+ """Protocol for the upload_base64_image function."""
13
+
14
+ async def __call__(self, trace_id: str, span_id: str, image_name: str, base64_string: str) -> str:
15
+ """Upload a base64 image and return the URL."""
16
+ ...
17
+
18
+
19
+ class Config:
20
+ """Global configuration for OpenAI instrumentation.
21
+
22
+ Attributes:
23
+ enrich_token_usage: Whether to calculate token usage for streaming responses
24
+ enrich_assistant: Whether to enrich assistant responses with additional data
25
+ exception_logger: Optional function to log exceptions
26
+ get_common_metrics_attributes: Function to get common attributes for metrics
27
+ upload_base64_image: Optional async function to upload base64 images
28
+ enable_trace_context_propagation: Whether to propagate trace context in headers
29
+ """
30
+
31
+ enrich_token_usage: bool = True
32
+ enrich_assistant: bool = True
33
+ exception_logger: Optional[Callable[[Exception], None]] = None
34
+ get_common_metrics_attributes: Callable[[], Dict[str, str]] = lambda: {}
35
+ upload_base64_image: Optional[UploadImageCallable] = None
36
+ enable_trace_context_propagation: bool = True