openlit 1.34.29__py3-none-any.whl → 1.34.31__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 (168) hide show
  1. openlit/__helpers.py +235 -86
  2. openlit/__init__.py +16 -13
  3. openlit/_instrumentors.py +2 -1
  4. openlit/evals/all.py +50 -21
  5. openlit/evals/bias_detection.py +47 -20
  6. openlit/evals/hallucination.py +53 -22
  7. openlit/evals/toxicity.py +50 -21
  8. openlit/evals/utils.py +54 -30
  9. openlit/guard/all.py +61 -19
  10. openlit/guard/prompt_injection.py +34 -14
  11. openlit/guard/restrict_topic.py +46 -15
  12. openlit/guard/sensitive_topic.py +34 -14
  13. openlit/guard/utils.py +58 -22
  14. openlit/instrumentation/ag2/__init__.py +24 -8
  15. openlit/instrumentation/ag2/ag2.py +34 -13
  16. openlit/instrumentation/ag2/async_ag2.py +34 -13
  17. openlit/instrumentation/ag2/utils.py +133 -30
  18. openlit/instrumentation/ai21/__init__.py +43 -14
  19. openlit/instrumentation/ai21/ai21.py +47 -21
  20. openlit/instrumentation/ai21/async_ai21.py +47 -21
  21. openlit/instrumentation/ai21/utils.py +299 -78
  22. openlit/instrumentation/anthropic/__init__.py +21 -4
  23. openlit/instrumentation/anthropic/anthropic.py +28 -17
  24. openlit/instrumentation/anthropic/async_anthropic.py +28 -17
  25. openlit/instrumentation/anthropic/utils.py +145 -35
  26. openlit/instrumentation/assemblyai/__init__.py +11 -2
  27. openlit/instrumentation/assemblyai/assemblyai.py +15 -4
  28. openlit/instrumentation/assemblyai/utils.py +120 -25
  29. openlit/instrumentation/astra/__init__.py +43 -10
  30. openlit/instrumentation/astra/astra.py +28 -5
  31. openlit/instrumentation/astra/async_astra.py +28 -5
  32. openlit/instrumentation/astra/utils.py +151 -55
  33. openlit/instrumentation/azure_ai_inference/__init__.py +43 -10
  34. openlit/instrumentation/azure_ai_inference/async_azure_ai_inference.py +53 -21
  35. openlit/instrumentation/azure_ai_inference/azure_ai_inference.py +53 -21
  36. openlit/instrumentation/azure_ai_inference/utils.py +307 -83
  37. openlit/instrumentation/bedrock/__init__.py +21 -4
  38. openlit/instrumentation/bedrock/bedrock.py +63 -25
  39. openlit/instrumentation/bedrock/utils.py +139 -30
  40. openlit/instrumentation/chroma/__init__.py +89 -16
  41. openlit/instrumentation/chroma/chroma.py +28 -6
  42. openlit/instrumentation/chroma/utils.py +167 -51
  43. openlit/instrumentation/cohere/__init__.py +63 -18
  44. openlit/instrumentation/cohere/async_cohere.py +63 -24
  45. openlit/instrumentation/cohere/cohere.py +63 -24
  46. openlit/instrumentation/cohere/utils.py +286 -73
  47. openlit/instrumentation/controlflow/__init__.py +35 -9
  48. openlit/instrumentation/controlflow/controlflow.py +66 -33
  49. openlit/instrumentation/crawl4ai/__init__.py +25 -10
  50. openlit/instrumentation/crawl4ai/async_crawl4ai.py +78 -31
  51. openlit/instrumentation/crawl4ai/crawl4ai.py +78 -31
  52. openlit/instrumentation/crewai/__init__.py +111 -24
  53. openlit/instrumentation/crewai/async_crewai.py +114 -0
  54. openlit/instrumentation/crewai/crewai.py +104 -131
  55. openlit/instrumentation/crewai/utils.py +615 -0
  56. openlit/instrumentation/dynamiq/__init__.py +46 -12
  57. openlit/instrumentation/dynamiq/dynamiq.py +74 -33
  58. openlit/instrumentation/elevenlabs/__init__.py +23 -4
  59. openlit/instrumentation/elevenlabs/async_elevenlabs.py +16 -4
  60. openlit/instrumentation/elevenlabs/elevenlabs.py +16 -4
  61. openlit/instrumentation/elevenlabs/utils.py +128 -25
  62. openlit/instrumentation/embedchain/__init__.py +11 -2
  63. openlit/instrumentation/embedchain/embedchain.py +68 -35
  64. openlit/instrumentation/firecrawl/__init__.py +24 -7
  65. openlit/instrumentation/firecrawl/firecrawl.py +46 -20
  66. openlit/instrumentation/google_ai_studio/__init__.py +45 -10
  67. openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +67 -44
  68. openlit/instrumentation/google_ai_studio/google_ai_studio.py +67 -44
  69. openlit/instrumentation/google_ai_studio/utils.py +180 -67
  70. openlit/instrumentation/gpt4all/__init__.py +22 -7
  71. openlit/instrumentation/gpt4all/gpt4all.py +67 -29
  72. openlit/instrumentation/gpt4all/utils.py +285 -61
  73. openlit/instrumentation/gpu/__init__.py +128 -47
  74. openlit/instrumentation/groq/__init__.py +21 -4
  75. openlit/instrumentation/groq/async_groq.py +33 -21
  76. openlit/instrumentation/groq/groq.py +33 -21
  77. openlit/instrumentation/groq/utils.py +192 -55
  78. openlit/instrumentation/haystack/__init__.py +70 -24
  79. openlit/instrumentation/haystack/async_haystack.py +28 -6
  80. openlit/instrumentation/haystack/haystack.py +28 -6
  81. openlit/instrumentation/haystack/utils.py +196 -74
  82. openlit/instrumentation/julep/__init__.py +69 -19
  83. openlit/instrumentation/julep/async_julep.py +53 -27
  84. openlit/instrumentation/julep/julep.py +53 -28
  85. openlit/instrumentation/langchain/__init__.py +74 -63
  86. openlit/instrumentation/langchain/callback_handler.py +1100 -0
  87. openlit/instrumentation/langchain_community/__init__.py +13 -2
  88. openlit/instrumentation/langchain_community/async_langchain_community.py +23 -5
  89. openlit/instrumentation/langchain_community/langchain_community.py +23 -5
  90. openlit/instrumentation/langchain_community/utils.py +35 -9
  91. openlit/instrumentation/letta/__init__.py +68 -15
  92. openlit/instrumentation/letta/letta.py +99 -54
  93. openlit/instrumentation/litellm/__init__.py +43 -14
  94. openlit/instrumentation/litellm/async_litellm.py +51 -26
  95. openlit/instrumentation/litellm/litellm.py +51 -26
  96. openlit/instrumentation/litellm/utils.py +312 -101
  97. openlit/instrumentation/llamaindex/__init__.py +267 -90
  98. openlit/instrumentation/llamaindex/async_llamaindex.py +28 -6
  99. openlit/instrumentation/llamaindex/llamaindex.py +28 -6
  100. openlit/instrumentation/llamaindex/utils.py +204 -91
  101. openlit/instrumentation/mem0/__init__.py +11 -2
  102. openlit/instrumentation/mem0/mem0.py +50 -29
  103. openlit/instrumentation/milvus/__init__.py +10 -2
  104. openlit/instrumentation/milvus/milvus.py +31 -6
  105. openlit/instrumentation/milvus/utils.py +166 -67
  106. openlit/instrumentation/mistral/__init__.py +63 -18
  107. openlit/instrumentation/mistral/async_mistral.py +63 -24
  108. openlit/instrumentation/mistral/mistral.py +63 -24
  109. openlit/instrumentation/mistral/utils.py +277 -69
  110. openlit/instrumentation/multion/__init__.py +69 -19
  111. openlit/instrumentation/multion/async_multion.py +57 -26
  112. openlit/instrumentation/multion/multion.py +57 -26
  113. openlit/instrumentation/ollama/__init__.py +39 -18
  114. openlit/instrumentation/ollama/async_ollama.py +57 -26
  115. openlit/instrumentation/ollama/ollama.py +57 -26
  116. openlit/instrumentation/ollama/utils.py +226 -50
  117. openlit/instrumentation/openai/__init__.py +156 -32
  118. openlit/instrumentation/openai/async_openai.py +147 -67
  119. openlit/instrumentation/openai/openai.py +150 -67
  120. openlit/instrumentation/openai/utils.py +660 -186
  121. openlit/instrumentation/openai_agents/__init__.py +6 -2
  122. openlit/instrumentation/openai_agents/processor.py +409 -537
  123. openlit/instrumentation/phidata/__init__.py +13 -5
  124. openlit/instrumentation/phidata/phidata.py +67 -32
  125. openlit/instrumentation/pinecone/__init__.py +48 -9
  126. openlit/instrumentation/pinecone/async_pinecone.py +27 -5
  127. openlit/instrumentation/pinecone/pinecone.py +27 -5
  128. openlit/instrumentation/pinecone/utils.py +153 -47
  129. openlit/instrumentation/premai/__init__.py +22 -7
  130. openlit/instrumentation/premai/premai.py +51 -26
  131. openlit/instrumentation/premai/utils.py +246 -59
  132. openlit/instrumentation/pydantic_ai/__init__.py +49 -22
  133. openlit/instrumentation/pydantic_ai/pydantic_ai.py +69 -16
  134. openlit/instrumentation/pydantic_ai/utils.py +89 -24
  135. openlit/instrumentation/qdrant/__init__.py +19 -4
  136. openlit/instrumentation/qdrant/async_qdrant.py +33 -7
  137. openlit/instrumentation/qdrant/qdrant.py +33 -7
  138. openlit/instrumentation/qdrant/utils.py +228 -93
  139. openlit/instrumentation/reka/__init__.py +23 -10
  140. openlit/instrumentation/reka/async_reka.py +17 -11
  141. openlit/instrumentation/reka/reka.py +17 -11
  142. openlit/instrumentation/reka/utils.py +138 -36
  143. openlit/instrumentation/together/__init__.py +44 -12
  144. openlit/instrumentation/together/async_together.py +50 -27
  145. openlit/instrumentation/together/together.py +50 -27
  146. openlit/instrumentation/together/utils.py +301 -71
  147. openlit/instrumentation/transformers/__init__.py +2 -1
  148. openlit/instrumentation/transformers/transformers.py +13 -3
  149. openlit/instrumentation/transformers/utils.py +139 -36
  150. openlit/instrumentation/vertexai/__init__.py +81 -16
  151. openlit/instrumentation/vertexai/async_vertexai.py +33 -15
  152. openlit/instrumentation/vertexai/utils.py +123 -27
  153. openlit/instrumentation/vertexai/vertexai.py +33 -15
  154. openlit/instrumentation/vllm/__init__.py +12 -5
  155. openlit/instrumentation/vllm/utils.py +121 -31
  156. openlit/instrumentation/vllm/vllm.py +16 -10
  157. openlit/otel/events.py +35 -10
  158. openlit/otel/metrics.py +32 -24
  159. openlit/otel/tracing.py +24 -9
  160. openlit/semcov/__init__.py +101 -7
  161. {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/METADATA +2 -1
  162. openlit-1.34.31.dist-info/RECORD +166 -0
  163. openlit/instrumentation/langchain/async_langchain.py +0 -102
  164. openlit/instrumentation/langchain/langchain.py +0 -102
  165. openlit/instrumentation/langchain/utils.py +0 -252
  166. openlit-1.34.29.dist-info/RECORD +0 -166
  167. {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
  168. {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/WHEEL +0 -0
@@ -1,6 +1,7 @@
1
1
  """
2
2
  OpenAI OpenTelemetry instrumentation utility functions
3
3
  """
4
+
4
5
  import time
5
6
 
6
7
  from opentelemetry.trace import Status, StatusCode
@@ -22,14 +23,18 @@ from openlit.__helpers import (
22
23
  )
23
24
  from openlit.semcov import SemanticConvention
24
25
 
26
+
25
27
  def handle_not_given(value, default=None):
26
28
  """
27
- Handle OpenAI's NotGiven values by converting them to appropriate defaults.
29
+ Handle OpenAI's NotGiven values and None values by converting them to appropriate defaults.
28
30
  """
29
- if hasattr(value, '__class__') and value.__class__.__name__ == 'NotGiven':
31
+ if hasattr(value, "__class__") and value.__class__.__name__ == "NotGiven":
32
+ return default
33
+ if value is None:
30
34
  return default
31
35
  return value
32
36
 
37
+
33
38
  def format_content(messages):
34
39
  """
35
40
  Format the messages into a string for span events.
@@ -60,18 +65,19 @@ def format_content(messages):
60
65
  for item in content:
61
66
  # Chat completions format
62
67
  if item.get("type") == "text":
63
- content_str_list.append(f'text: {item.get("text", "")}')
64
- elif (item.get("type") == "image_url" and
65
- not item.get("image_url", {}).get("url", "").startswith("data:")):
66
- content_str_list.append(f'image_url: {item["image_url"]["url"]}')
68
+ content_str_list.append(f"text: {item.get('text', '')}")
69
+ elif item.get("type") == "image_url" and not item.get(
70
+ "image_url", {}
71
+ ).get("url", "").startswith("data:"):
72
+ content_str_list.append(f"image_url: {item['image_url']['url']}")
67
73
 
68
74
  # Responses API format
69
75
  elif item.get("type") == "input_text":
70
- content_str_list.append(f'text: {item.get("text", "")}')
76
+ content_str_list.append(f"text: {item.get('text', '')}")
71
77
  elif item.get("type") == "input_image":
72
78
  image_url = item.get("image_url", "")
73
79
  if image_url and not image_url.startswith("data:"):
74
- content_str_list.append(f'image_url: {image_url}')
80
+ content_str_list.append(f"image_url: {image_url}")
75
81
 
76
82
  content_str = ", ".join(content_str_list)
77
83
  formatted_messages.append(f"{role}: {content_str}")
@@ -80,6 +86,7 @@ def format_content(messages):
80
86
 
81
87
  return "\n".join(formatted_messages)
82
88
 
89
+
83
90
  def process_chat_chunk(scope, chunk):
84
91
  """
85
92
  Process a chunk of chat response data and update state.
@@ -94,9 +101,7 @@ def process_chat_chunk(scope, chunk):
94
101
  chunked = response_as_dict(chunk)
95
102
 
96
103
  # Extract content from chat completions
97
- if (len(chunked.get("choices", [])) > 0 and
98
- "delta" in chunked.get("choices")[0]):
99
-
104
+ if len(chunked.get("choices", [])) > 0 and "delta" in chunked.get("choices")[0]:
100
105
  delta = chunked.get("choices")[0]["delta"]
101
106
  content = delta.get("content")
102
107
  if content:
@@ -117,24 +122,36 @@ def process_chat_chunk(scope, chunk):
117
122
  func = tool.get("function", {})
118
123
  scope._tools[idx] = {
119
124
  "id": tool["id"],
120
- "function": {"name": func.get("name", ""), "arguments": func.get("arguments", "")},
121
- "type": tool.get("type", "function")
125
+ "function": {
126
+ "name": func.get("name", ""),
127
+ "arguments": func.get("arguments", ""),
128
+ },
129
+ "type": tool.get("type", "function"),
122
130
  }
123
- elif scope._tools[idx] and "function" in tool: # Append args (id is None)
124
- scope._tools[idx]["function"]["arguments"] += tool["function"].get("arguments", "")
131
+ elif (
132
+ scope._tools[idx] and "function" in tool
133
+ ): # Append args (id is None)
134
+ scope._tools[idx]["function"]["arguments"] += tool["function"].get(
135
+ "arguments", ""
136
+ )
125
137
 
126
138
  # Extract metadata
127
139
  scope._response_id = chunked.get("id") or scope._response_id
128
140
  scope._response_model = chunked.get("model") or scope._response_model
129
141
 
130
142
  try:
131
- scope._finish_reason = chunked.get("choices", [])[0].get("finish_reason") or scope._finish_reason
143
+ scope._finish_reason = (
144
+ chunked.get("choices", [])[0].get("finish_reason") or scope._finish_reason
145
+ )
132
146
  except (IndexError, AttributeError, TypeError):
133
147
  scope._finish_reason = "stop"
134
148
 
135
- scope._system_fingerprint = chunked.get("system_fingerprint") or scope._system_fingerprint
149
+ scope._system_fingerprint = (
150
+ chunked.get("system_fingerprint") or scope._system_fingerprint
151
+ )
136
152
  scope._service_tier = chunked.get("service_tier") or scope._service_tier
137
153
 
154
+
138
155
  def process_response_chunk(scope, chunk):
139
156
  """
140
157
  Process a chunk of response API data and update state.
@@ -160,14 +177,16 @@ def process_response_chunk(scope, chunk):
160
177
 
161
178
  item = chunked.get("item", {})
162
179
  if item.get("type") == "function_call":
163
- scope._response_tools.append({
164
- "id": item.get("id", ""),
165
- "call_id": item.get("call_id", ""),
166
- "name": item.get("name", ""),
167
- "type": item.get("type", "function_call"),
168
- "arguments": item.get("arguments", ""),
169
- "status": item.get("status", "in_progress")
170
- })
180
+ scope._response_tools.append(
181
+ {
182
+ "id": item.get("id", ""),
183
+ "call_id": item.get("call_id", ""),
184
+ "name": item.get("name", ""),
185
+ "type": item.get("type", "function_call"),
186
+ "arguments": item.get("arguments", ""),
187
+ "status": item.get("status", "in_progress"),
188
+ }
189
+ )
171
190
 
172
191
  elif chunked.get("type") == "response.function_call_arguments.delta":
173
192
  # Tool arguments being streamed
@@ -202,12 +221,16 @@ def process_response_chunk(scope, chunk):
202
221
  # Update the tool with final status and data
203
222
  for tool in scope._response_tools:
204
223
  if tool.get("id") == item_id:
205
- tool.update({
206
- "call_id": item.get("call_id", tool.get("call_id", "")),
207
- "name": item.get("name", tool.get("name", "")),
208
- "arguments": item.get("arguments", tool.get("arguments", "")),
209
- "status": item.get("status", "completed")
210
- })
224
+ tool.update(
225
+ {
226
+ "call_id": item.get("call_id", tool.get("call_id", "")),
227
+ "name": item.get("name", tool.get("name", "")),
228
+ "arguments": item.get(
229
+ "arguments", tool.get("arguments", "")
230
+ ),
231
+ "status": item.get("status", "completed"),
232
+ }
233
+ )
211
234
  break
212
235
 
213
236
  elif chunked.get("type") == "response.completed":
@@ -224,8 +247,18 @@ def process_response_chunk(scope, chunk):
224
247
  output_tokens_details = usage.get("output_tokens_details", {})
225
248
  scope._reasoning_tokens = output_tokens_details.get("reasoning_tokens", 0)
226
249
 
227
- def common_response_logic(scope, pricing_info, environment, application_name, metrics,
228
- capture_message_content, disable_metrics, version, is_stream):
250
+
251
+ def common_response_logic(
252
+ scope,
253
+ pricing_info,
254
+ environment,
255
+ application_name,
256
+ metrics,
257
+ capture_message_content,
258
+ disable_metrics,
259
+ version,
260
+ is_stream,
261
+ ):
229
262
  """
230
263
  Process responses API request and generate Telemetry
231
264
  """
@@ -250,66 +283,115 @@ def common_response_logic(scope, pricing_info, environment, application_name, me
250
283
  cost = get_chat_model_cost(request_model, pricing_info, input_tokens, output_tokens)
251
284
 
252
285
  # Common Span Attributes
253
- common_span_attributes(scope,
254
- SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
255
- scope._server_address, scope._server_port, request_model, scope._response_model,
256
- environment, application_name, is_stream, scope._tbt, scope._ttft, version)
286
+ common_span_attributes(
287
+ scope,
288
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
289
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
290
+ scope._server_address,
291
+ scope._server_port,
292
+ request_model,
293
+ scope._response_model,
294
+ environment,
295
+ application_name,
296
+ is_stream,
297
+ scope._tbt,
298
+ scope._ttft,
299
+ version,
300
+ )
257
301
 
258
302
  # Span Attributes for Request parameters specific to responses API
259
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, handle_not_given(scope._kwargs.get("temperature"), 1.0))
303
+ scope._span.set_attribute(
304
+ SemanticConvention.GEN_AI_REQUEST_TEMPERATURE,
305
+ handle_not_given(scope._kwargs.get("temperature"), 1.0),
306
+ )
260
307
  scope._span.set_attribute(
261
308
  SemanticConvention.GEN_AI_REQUEST_TOP_P,
262
- handle_not_given(scope._kwargs.get("top_p"), 1.0)
309
+ handle_not_given(scope._kwargs.get("top_p"), 1.0),
263
310
  )
264
311
  scope._span.set_attribute(
265
312
  SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS,
266
- handle_not_given(scope._kwargs.get("max_output_tokens"), -1)
313
+ handle_not_given(scope._kwargs.get("max_output_tokens"), -1),
267
314
  )
268
315
 
269
316
  # Reasoning parameters
270
317
  reasoning = scope._kwargs.get("reasoning", {})
271
318
  if reasoning:
272
319
  if reasoning.get("effort"):
273
- scope._span.set_attribute("gen_ai.request.reasoning_effort", reasoning.get("effort"))
320
+ scope._span.set_attribute(
321
+ "gen_ai.request.reasoning_effort", reasoning.get("effort")
322
+ )
274
323
 
275
324
  # Responses API specific attributes
276
325
  if hasattr(scope, "_service_tier"):
277
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER, scope._service_tier)
326
+ scope._span.set_attribute(
327
+ SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER, scope._service_tier
328
+ )
278
329
 
279
330
  # Span Attributes for Response parameters
280
331
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
281
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
332
+ scope._span.set_attribute(
333
+ SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason]
334
+ )
282
335
  scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE, "text")
283
336
 
284
337
  # Span Attributes for Tools (responses API structure) - optimized
285
338
  if hasattr(scope, "_response_tools") and scope._response_tools:
286
- tools = scope._response_tools if isinstance(scope._response_tools, list) else [scope._response_tools]
339
+ tools = (
340
+ scope._response_tools
341
+ if isinstance(scope._response_tools, list)
342
+ else [scope._response_tools]
343
+ )
287
344
 
288
- names, ids, args = zip(*[
289
- (t.get("name", ""),
290
- str(t.get("call_id", "")), # Use call_id for responses API
291
- str(t.get("arguments", "")))
292
- for t in tools if isinstance(t, dict) and t
293
- ]) if tools else ([], [], [])
345
+ names, ids, args = (
346
+ zip(
347
+ *[
348
+ (
349
+ t.get("name", ""),
350
+ str(t.get("call_id", "")), # Use call_id for responses API
351
+ str(t.get("arguments", "")),
352
+ )
353
+ for t in tools
354
+ if isinstance(t, dict) and t
355
+ ]
356
+ )
357
+ if tools
358
+ else ([], [], [])
359
+ )
294
360
 
295
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names)))
296
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids)))
297
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args)))
361
+ scope._span.set_attribute(
362
+ SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names))
363
+ )
364
+ scope._span.set_attribute(
365
+ SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids))
366
+ )
367
+ scope._span.set_attribute(
368
+ SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args))
369
+ )
298
370
 
299
371
  # Span Attributes for Cost and Tokens
300
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
301
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
302
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, input_tokens + output_tokens)
372
+ scope._span.set_attribute(
373
+ SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens
374
+ )
375
+ scope._span.set_attribute(
376
+ SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens
377
+ )
378
+ scope._span.set_attribute(
379
+ SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, input_tokens + output_tokens
380
+ )
303
381
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
304
382
 
305
383
  # Reasoning tokens
306
384
  if hasattr(scope, "_reasoning_tokens") and scope._reasoning_tokens > 0:
307
- scope._span.set_attribute("gen_ai.usage.reasoning_tokens", scope._reasoning_tokens)
385
+ scope._span.set_attribute(
386
+ "gen_ai.usage.reasoning_tokens", scope._reasoning_tokens
387
+ )
308
388
 
309
389
  # Span Attributes for Content
310
390
  if capture_message_content:
311
391
  scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
312
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
392
+ scope._span.set_attribute(
393
+ SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse
394
+ )
313
395
 
314
396
  # To be removed once the change to span_attributes (from span events) is complete
315
397
  scope._span.add_event(
@@ -329,23 +411,69 @@ def common_response_logic(scope, pricing_info, environment, application_name, me
329
411
 
330
412
  # Record metrics
331
413
  if not disable_metrics:
332
- record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
333
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
334
- application_name, scope._start_time, scope._end_time, input_tokens, output_tokens,
335
- cost, scope._tbt, scope._ttft)
414
+ record_completion_metrics(
415
+ metrics,
416
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
417
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
418
+ scope._server_address,
419
+ scope._server_port,
420
+ request_model,
421
+ scope._response_model,
422
+ environment,
423
+ application_name,
424
+ scope._start_time,
425
+ scope._end_time,
426
+ input_tokens,
427
+ output_tokens,
428
+ cost,
429
+ scope._tbt,
430
+ scope._ttft,
431
+ )
336
432
 
337
- def process_streaming_response_response(scope, pricing_info, environment, application_name, metrics,
338
- capture_message_content=False, disable_metrics=False, version=""):
433
+
434
+ def process_streaming_response_response(
435
+ scope,
436
+ pricing_info,
437
+ environment,
438
+ application_name,
439
+ metrics,
440
+ capture_message_content=False,
441
+ disable_metrics=False,
442
+ version="",
443
+ ):
339
444
  """
340
445
  Process streaming responses API response and generate telemetry.
341
446
  """
342
447
 
343
- common_response_logic(scope, pricing_info, environment, application_name, metrics,
344
- capture_message_content, disable_metrics, version, is_stream=True)
448
+ common_response_logic(
449
+ scope,
450
+ pricing_info,
451
+ environment,
452
+ application_name,
453
+ metrics,
454
+ capture_message_content,
455
+ disable_metrics,
456
+ version,
457
+ is_stream=True,
458
+ )
459
+
345
460
 
346
- def process_response_response(response, request_model, pricing_info, server_port, server_address,
347
- environment, application_name, metrics, start_time, span, capture_message_content=False,
348
- disable_metrics=False, version="1.0.0", **kwargs):
461
+ def process_response_response(
462
+ response,
463
+ request_model,
464
+ pricing_info,
465
+ server_port,
466
+ server_address,
467
+ environment,
468
+ application_name,
469
+ metrics,
470
+ start_time,
471
+ span,
472
+ capture_message_content=False,
473
+ disable_metrics=False,
474
+ version="1.0.0",
475
+ **kwargs,
476
+ ):
349
477
  """
350
478
  Process non-streaming responses API response and generate telemetry.
351
479
  """
@@ -371,14 +499,16 @@ def process_response_response(response, request_model, pricing_info, server_port
371
499
  break
372
500
  if item.get("type") == "function_call":
373
501
  # Handle tool call
374
- scope._response_tools = [{
375
- "id": item.get("id", ""),
376
- "call_id": item.get("call_id", ""),
377
- "name": item.get("name", ""),
378
- "type": item.get("type", "function_call"),
379
- "arguments": item.get("arguments", ""),
380
- "status": item.get("status", "")
381
- }]
502
+ scope._response_tools = [
503
+ {
504
+ "id": item.get("id", ""),
505
+ "call_id": item.get("call_id", ""),
506
+ "name": item.get("name", ""),
507
+ "type": item.get("type", "function_call"),
508
+ "arguments": item.get("arguments", ""),
509
+ "status": item.get("status", ""),
510
+ }
511
+ ]
382
512
 
383
513
  # Extract content from message item if found
384
514
  if message_item:
@@ -404,13 +534,32 @@ def process_response_response(response, request_model, pricing_info, server_port
404
534
  scope._service_tier = response_dict.get("service_tier", "default")
405
535
  scope._finish_reason = response_dict.get("status", "completed")
406
536
 
407
- common_response_logic(scope, pricing_info, environment, application_name, metrics,
408
- capture_message_content, disable_metrics, version, is_stream=False)
537
+ common_response_logic(
538
+ scope,
539
+ pricing_info,
540
+ environment,
541
+ application_name,
542
+ metrics,
543
+ capture_message_content,
544
+ disable_metrics,
545
+ version,
546
+ is_stream=False,
547
+ )
409
548
 
410
549
  return response
411
550
 
412
- def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
413
- capture_message_content, disable_metrics, version, is_stream):
551
+
552
+ def common_chat_logic(
553
+ scope,
554
+ pricing_info,
555
+ environment,
556
+ application_name,
557
+ metrics,
558
+ capture_message_content,
559
+ disable_metrics,
560
+ version,
561
+ is_stream,
562
+ ):
414
563
  """
415
564
  Process chat request and generate Telemetry
416
565
  """
@@ -445,63 +594,121 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
445
594
  scope,
446
595
  SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
447
596
  SemanticConvention.GEN_AI_SYSTEM_OPENAI,
448
- scope._server_address, scope._server_port, request_model,
449
- scope._response_model, environment, application_name,
450
- is_stream, scope._tbt, scope._ttft, version
597
+ scope._server_address,
598
+ scope._server_port,
599
+ request_model,
600
+ scope._response_model,
601
+ environment,
602
+ application_name,
603
+ is_stream,
604
+ scope._tbt,
605
+ scope._ttft,
606
+ version,
451
607
  )
452
608
 
453
609
  # Span Attributes for Request parameters
454
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SEED, str(handle_not_given(scope._kwargs.get("seed"), "")))
610
+ scope._span.set_attribute(
611
+ SemanticConvention.GEN_AI_REQUEST_SEED,
612
+ str(handle_not_given(scope._kwargs.get("seed"), "")),
613
+ )
455
614
  scope._span.set_attribute(
456
615
  SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY,
457
- handle_not_given(scope._kwargs.get("frequency_penalty"), 0.0)
616
+ handle_not_given(scope._kwargs.get("frequency_penalty"), 0.0),
617
+ )
618
+ scope._span.set_attribute(
619
+ SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS,
620
+ handle_not_given(scope._kwargs.get("max_tokens"), -1),
458
621
  )
459
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, handle_not_given(scope._kwargs.get("max_tokens"), -1))
460
622
  scope._span.set_attribute(
461
623
  SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY,
462
- handle_not_given(scope._kwargs.get("presence_penalty"), 0.0)
624
+ handle_not_given(scope._kwargs.get("presence_penalty"), 0.0),
625
+ )
626
+ scope._span.set_attribute(
627
+ SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES,
628
+ handle_not_given(scope._kwargs.get("stop"), []),
629
+ )
630
+ scope._span.set_attribute(
631
+ SemanticConvention.GEN_AI_REQUEST_TEMPERATURE,
632
+ handle_not_given(scope._kwargs.get("temperature"), 1.0),
633
+ )
634
+ scope._span.set_attribute(
635
+ SemanticConvention.GEN_AI_REQUEST_TOP_P,
636
+ handle_not_given(scope._kwargs.get("top_p"), 1.0),
637
+ )
638
+ scope._span.set_attribute(
639
+ SemanticConvention.GEN_AI_REQUEST_USER,
640
+ handle_not_given(scope._kwargs.get("user"), ""),
463
641
  )
464
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, handle_not_given(scope._kwargs.get("stop"), []))
465
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, handle_not_given(scope._kwargs.get("temperature"), 1.0))
466
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, handle_not_given(scope._kwargs.get("top_p"), 1.0))
467
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
468
642
 
469
643
  # Span Attributes for Response parameters
470
644
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
471
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
472
- scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE, "text" if isinstance(scope._llmresponse, str) else "json")
645
+ scope._span.set_attribute(
646
+ SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason]
647
+ )
648
+ scope._span.set_attribute(
649
+ SemanticConvention.GEN_AI_OUTPUT_TYPE,
650
+ "text" if isinstance(scope._llmresponse, str) else "json",
651
+ )
473
652
 
474
653
  # OpenAI-specific attributes
475
654
  if hasattr(scope, "_system_fingerprint") and scope._system_fingerprint:
476
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT, scope._system_fingerprint)
655
+ scope._span.set_attribute(
656
+ SemanticConvention.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT,
657
+ scope._system_fingerprint,
658
+ )
477
659
  if hasattr(scope, "_service_tier") and scope._service_tier:
478
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER, scope._service_tier)
660
+ scope._span.set_attribute(
661
+ SemanticConvention.GEN_AI_REQUEST_SERVICE_TIER, scope._service_tier
662
+ )
479
663
 
480
664
  # Span Attributes for Tools - optimized
481
665
  if hasattr(scope, "_tools") and scope._tools:
482
666
  tools = scope._tools if isinstance(scope._tools, list) else [scope._tools]
483
667
 
484
- names, ids, args = zip(*[
485
- (t.get("function", {}).get("name", ""),
486
- str(t.get("id", "")),
487
- str(t.get("function", {}).get("arguments", "")))
488
- for t in tools if isinstance(t, dict) and t
489
- ]) if tools else ([], [], [])
668
+ names, ids, args = (
669
+ zip(
670
+ *[
671
+ (
672
+ t.get("function", {}).get("name", ""),
673
+ str(t.get("id", "")),
674
+ str(t.get("function", {}).get("arguments", "")),
675
+ )
676
+ for t in tools
677
+ if isinstance(t, dict) and t
678
+ ]
679
+ )
680
+ if tools
681
+ else ([], [], [])
682
+ )
490
683
 
491
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names)))
492
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids)))
493
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args)))
684
+ scope._span.set_attribute(
685
+ SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names))
686
+ )
687
+ scope._span.set_attribute(
688
+ SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids))
689
+ )
690
+ scope._span.set_attribute(
691
+ SemanticConvention.GEN_AI_TOOL_ARGS, ", ".join(filter(None, args))
692
+ )
494
693
 
495
694
  # Span Attributes for Cost and Tokens
496
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
497
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
498
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, input_tokens + output_tokens)
695
+ scope._span.set_attribute(
696
+ SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens
697
+ )
698
+ scope._span.set_attribute(
699
+ SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens
700
+ )
701
+ scope._span.set_attribute(
702
+ SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, input_tokens + output_tokens
703
+ )
499
704
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
500
705
 
501
706
  # Span Attributes for Content
502
707
  if capture_message_content:
503
708
  scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
504
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse)
709
+ scope._span.set_attribute(
710
+ SemanticConvention.GEN_AI_CONTENT_COMPLETION, scope._llmresponse
711
+ )
505
712
 
506
713
  # To be removed once the change to span_attributes (from span events) is complete
507
714
  scope._span.add_event(
@@ -521,23 +728,69 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
521
728
 
522
729
  # Record metrics
523
730
  if not disable_metrics:
524
- record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
525
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
526
- application_name, scope._start_time, scope._end_time, input_tokens, output_tokens,
527
- cost, scope._tbt, scope._ttft)
731
+ record_completion_metrics(
732
+ metrics,
733
+ SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
734
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
735
+ scope._server_address,
736
+ scope._server_port,
737
+ request_model,
738
+ scope._response_model,
739
+ environment,
740
+ application_name,
741
+ scope._start_time,
742
+ scope._end_time,
743
+ input_tokens,
744
+ output_tokens,
745
+ cost,
746
+ scope._tbt,
747
+ scope._ttft,
748
+ )
749
+
528
750
 
529
- def process_streaming_chat_response(scope, pricing_info, environment, application_name, metrics,
530
- capture_message_content=False, disable_metrics=False, version=""):
751
+ def process_streaming_chat_response(
752
+ scope,
753
+ pricing_info,
754
+ environment,
755
+ application_name,
756
+ metrics,
757
+ capture_message_content=False,
758
+ disable_metrics=False,
759
+ version="",
760
+ ):
531
761
  """
532
762
  Process streaming chat response and generate telemetry.
533
763
  """
534
764
 
535
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
536
- capture_message_content, disable_metrics, version, is_stream=True)
765
+ common_chat_logic(
766
+ scope,
767
+ pricing_info,
768
+ environment,
769
+ application_name,
770
+ metrics,
771
+ capture_message_content,
772
+ disable_metrics,
773
+ version,
774
+ is_stream=True,
775
+ )
776
+
537
777
 
538
- def process_chat_response(response, request_model, pricing_info, server_port, server_address,
539
- environment, application_name, metrics, start_time, span, capture_message_content=False,
540
- disable_metrics=False, version="1.0.0", **kwargs):
778
+ def process_chat_response(
779
+ response,
780
+ request_model,
781
+ pricing_info,
782
+ server_port,
783
+ server_address,
784
+ environment,
785
+ application_name,
786
+ metrics,
787
+ start_time,
788
+ span,
789
+ capture_message_content=False,
790
+ disable_metrics=False,
791
+ version="1.0.0",
792
+ **kwargs,
793
+ ):
541
794
  """
542
795
  Process non-streaming chat response and generate telemetry.
543
796
  """
@@ -562,7 +815,11 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
562
815
  scope._kwargs = kwargs
563
816
  scope._system_fingerprint = response_dict.get("system_fingerprint", "")
564
817
  scope._service_tier = response_dict.get("service_tier", "auto")
565
- scope._finish_reason = str(response_dict.get("choices", [])[0].get("finish_reason", "")) if response_dict.get("choices") else ""
818
+ scope._finish_reason = (
819
+ str(response_dict.get("choices", [])[0].get("finish_reason", ""))
820
+ if response_dict.get("choices")
821
+ else ""
822
+ )
566
823
 
567
824
  # Handle operation type for responses API
568
825
  if kwargs.get("_operation_type") == "responses":
@@ -570,17 +827,38 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
570
827
 
571
828
  # Handle tool calls
572
829
  if kwargs.get("tools"):
573
- scope._tools = response_dict.get("choices", [{}])[0].get("message", {}).get("tool_calls")
830
+ scope._tools = (
831
+ response_dict.get("choices", [{}])[0].get("message", {}).get("tool_calls")
832
+ )
574
833
  else:
575
834
  scope._tools = None
576
835
 
577
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
578
- capture_message_content, disable_metrics, version, is_stream=False)
836
+ common_chat_logic(
837
+ scope,
838
+ pricing_info,
839
+ environment,
840
+ application_name,
841
+ metrics,
842
+ capture_message_content,
843
+ disable_metrics,
844
+ version,
845
+ is_stream=False,
846
+ )
579
847
 
580
848
  return response
581
849
 
582
- def common_embedding_logic(scope, request_model, pricing_info, environment, application_name,
583
- metrics, capture_message_content, disable_metrics, version):
850
+
851
+ def common_embedding_logic(
852
+ scope,
853
+ request_model,
854
+ pricing_info,
855
+ environment,
856
+ application_name,
857
+ metrics,
858
+ capture_message_content,
859
+ disable_metrics,
860
+ version,
861
+ ):
584
862
  """
585
863
  Common logic for processing embedding operations.
586
864
  """
@@ -589,61 +867,127 @@ def common_embedding_logic(scope, request_model, pricing_info, environment, appl
589
867
  cost = get_embed_model_cost(request_model, pricing_info, scope._input_tokens)
590
868
 
591
869
  # Common Span Attributes
592
- common_span_attributes(scope,
593
- SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
594
- scope._server_address, scope._server_port, request_model, request_model,
595
- environment, application_name, False, scope._tbt, scope._ttft, version)
870
+ common_span_attributes(
871
+ scope,
872
+ SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
873
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
874
+ scope._server_address,
875
+ scope._server_port,
876
+ request_model,
877
+ request_model,
878
+ environment,
879
+ application_name,
880
+ False,
881
+ scope._tbt,
882
+ scope._ttft,
883
+ version,
884
+ )
596
885
 
597
886
  # Span Attributes for Request parameters
598
887
  scope._span.set_attribute(
599
888
  SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS,
600
- [handle_not_given(scope._kwargs.get("encoding_format"), "float")]
889
+ [handle_not_given(scope._kwargs.get("encoding_format"), "float")],
890
+ )
891
+ scope._span.set_attribute(
892
+ SemanticConvention.GEN_AI_REQUEST_USER,
893
+ handle_not_given(scope._kwargs.get("user"), ""),
601
894
  )
602
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
603
895
 
604
896
  # Span Attributes for Cost and Tokens
605
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
606
- scope._span.set_attribute(SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens)
897
+ scope._span.set_attribute(
898
+ SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens
899
+ )
900
+ scope._span.set_attribute(
901
+ SemanticConvention.GEN_AI_CLIENT_TOKEN_USAGE, scope._input_tokens
902
+ )
607
903
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
608
904
 
609
905
  # Span Attributes for Content
610
906
  if capture_message_content:
611
907
  input_data = scope._kwargs.get("input", "")
612
- formatted_content = format_content(input_data) if isinstance(input_data, (list, dict)) else str(input_data)
613
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, formatted_content)
908
+ formatted_content = (
909
+ format_content(input_data)
910
+ if isinstance(input_data, (list, dict))
911
+ else str(input_data)
912
+ )
913
+ scope._span.set_attribute(
914
+ SemanticConvention.GEN_AI_CONTENT_PROMPT, formatted_content
915
+ )
614
916
 
615
917
  scope._span.set_status(Status(StatusCode.OK))
616
918
 
617
919
  # Record metrics
618
920
  if not disable_metrics:
619
- record_embedding_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
620
- scope._server_address, scope._server_port, request_model, request_model, environment,
621
- application_name, scope._start_time, scope._end_time, scope._input_tokens, cost)
921
+ record_embedding_metrics(
922
+ metrics,
923
+ SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING,
924
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
925
+ scope._server_address,
926
+ scope._server_port,
927
+ request_model,
928
+ request_model,
929
+ environment,
930
+ application_name,
931
+ scope._start_time,
932
+ scope._end_time,
933
+ scope._input_tokens,
934
+ cost,
935
+ )
622
936
 
623
- def common_image_logic(scope, request_model, pricing_info, environment, application_name,
624
- metrics, capture_message_content, disable_metrics, version):
937
+
938
+ def common_image_logic(
939
+ scope,
940
+ request_model,
941
+ pricing_info,
942
+ environment,
943
+ application_name,
944
+ metrics,
945
+ capture_message_content,
946
+ disable_metrics,
947
+ version,
948
+ ):
625
949
  """
626
950
  Common logic for processing image operations.
627
951
  """
628
952
 
629
953
  # Calculate cost
630
- cost = get_image_model_cost(request_model, pricing_info,
631
- scope._kwargs.get("size", "1024x1024"),
632
- scope._kwargs.get("quality", "standard"))
954
+ cost = get_image_model_cost(
955
+ request_model,
956
+ pricing_info,
957
+ scope._kwargs.get("size", "1024x1024"),
958
+ scope._kwargs.get("quality", "standard"),
959
+ )
633
960
 
634
961
  # Common Span Attributes
635
- common_span_attributes(scope,
636
- SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
637
- scope._server_address, scope._server_port, request_model, request_model,
638
- environment, application_name, False, scope._tbt, scope._ttft, version)
962
+ common_span_attributes(
963
+ scope,
964
+ SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE,
965
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
966
+ scope._server_address,
967
+ scope._server_port,
968
+ request_model,
969
+ request_model,
970
+ environment,
971
+ application_name,
972
+ False,
973
+ scope._tbt,
974
+ scope._ttft,
975
+ version,
976
+ )
639
977
 
640
978
  # Span Attributes for Request parameters
641
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_IMAGE_SIZE, handle_not_given(scope._kwargs.get("size"), "1024x1024"))
979
+ scope._span.set_attribute(
980
+ SemanticConvention.GEN_AI_REQUEST_IMAGE_SIZE,
981
+ handle_not_given(scope._kwargs.get("size"), "1024x1024"),
982
+ )
642
983
  scope._span.set_attribute(
643
984
  SemanticConvention.GEN_AI_REQUEST_IMAGE_QUALITY,
644
- handle_not_given(scope._kwargs.get("quality"), "standard")
985
+ handle_not_given(scope._kwargs.get("quality"), "standard"),
986
+ )
987
+ scope._span.set_attribute(
988
+ SemanticConvention.GEN_AI_REQUEST_USER,
989
+ handle_not_given(scope._kwargs.get("user"), ""),
645
990
  )
646
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
647
991
 
648
992
  # Extract response data
649
993
  response_dict = scope._response_dict
@@ -655,7 +999,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
655
999
 
656
1000
  # Span Attributes for Response
657
1001
  if response_created:
658
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, str(response_created))
1002
+ scope._span.set_attribute(
1003
+ SemanticConvention.GEN_AI_RESPONSE_ID, str(response_created)
1004
+ )
659
1005
 
660
1006
  # Process image data and collect URLs/base64 content
661
1007
  if images_data:
@@ -673,7 +1019,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
673
1019
 
674
1020
  # Set image response data using semantic conventions
675
1021
  if image_contents:
676
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_IMAGE, image_contents)
1022
+ scope._span.set_attribute(
1023
+ SemanticConvention.GEN_AI_RESPONSE_IMAGE, image_contents
1024
+ )
677
1025
 
678
1026
  # Response-level attributes if different from request
679
1027
  if response_size:
@@ -681,7 +1029,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
681
1029
  if response_quality:
682
1030
  scope._span.set_attribute("gen_ai.response.image_quality", response_quality)
683
1031
  if response_output_format:
684
- scope._span.set_attribute("gen_ai.response.output_format", response_output_format)
1032
+ scope._span.set_attribute(
1033
+ "gen_ai.response.output_format", response_output_format
1034
+ )
685
1035
 
686
1036
  # Span Attributes for Cost
687
1037
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
@@ -701,7 +1051,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
701
1051
 
702
1052
  # Set revised prompts as span attribute if any were found
703
1053
  if revised_prompts:
704
- scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT, revised_prompts)
1054
+ scope._span.set_attribute(
1055
+ SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT, revised_prompts
1056
+ )
705
1057
 
706
1058
  # Add revised prompt events for detailed tracking
707
1059
  for i, image in enumerate(images_data):
@@ -709,7 +1061,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
709
1061
  scope._span.add_event(
710
1062
  name=SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT,
711
1063
  attributes={
712
- SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT: image["revised_prompt"],
1064
+ SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT: image[
1065
+ "revised_prompt"
1066
+ ],
713
1067
  "image_index": i,
714
1068
  },
715
1069
  )
@@ -718,12 +1072,33 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
718
1072
 
719
1073
  # Record metrics
720
1074
  if not disable_metrics:
721
- record_image_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
722
- scope._server_address, scope._server_port, request_model, request_model, environment,
723
- application_name, scope._start_time, scope._end_time, cost)
1075
+ record_image_metrics(
1076
+ metrics,
1077
+ SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE,
1078
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
1079
+ scope._server_address,
1080
+ scope._server_port,
1081
+ request_model,
1082
+ request_model,
1083
+ environment,
1084
+ application_name,
1085
+ scope._start_time,
1086
+ scope._end_time,
1087
+ cost,
1088
+ )
724
1089
 
725
- def common_audio_logic(scope, request_model, pricing_info, environment, application_name,
726
- metrics, capture_message_content, disable_metrics, version):
1090
+
1091
+ def common_audio_logic(
1092
+ scope,
1093
+ request_model,
1094
+ pricing_info,
1095
+ environment,
1096
+ application_name,
1097
+ metrics,
1098
+ capture_message_content,
1099
+ disable_metrics,
1100
+ version,
1101
+ ):
727
1102
  """
728
1103
  Common logic for processing audio operations.
729
1104
  """
@@ -733,18 +1108,35 @@ def common_audio_logic(scope, request_model, pricing_info, environment, applicat
733
1108
  cost = get_audio_model_cost(request_model, pricing_info, input_text)
734
1109
 
735
1110
  # Common Span Attributes
736
- common_span_attributes(scope,
737
- SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
738
- scope._server_address, scope._server_port, request_model, request_model,
739
- environment, application_name, False, scope._tbt, scope._ttft, version)
1111
+ common_span_attributes(
1112
+ scope,
1113
+ SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO,
1114
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
1115
+ scope._server_address,
1116
+ scope._server_port,
1117
+ request_model,
1118
+ request_model,
1119
+ environment,
1120
+ application_name,
1121
+ False,
1122
+ scope._tbt,
1123
+ scope._ttft,
1124
+ version,
1125
+ )
740
1126
 
741
1127
  # Span Attributes for Request parameters
742
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_VOICE, handle_not_given(scope._kwargs.get("voice"), "alloy"))
1128
+ scope._span.set_attribute(
1129
+ SemanticConvention.GEN_AI_REQUEST_AUDIO_VOICE,
1130
+ handle_not_given(scope._kwargs.get("voice"), "alloy"),
1131
+ )
743
1132
  scope._span.set_attribute(
744
1133
  SemanticConvention.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT,
745
- handle_not_given(scope._kwargs.get("response_format"), "mp3")
1134
+ handle_not_given(scope._kwargs.get("response_format"), "mp3"),
1135
+ )
1136
+ scope._span.set_attribute(
1137
+ SemanticConvention.GEN_AI_REQUEST_AUDIO_SPEED,
1138
+ handle_not_given(scope._kwargs.get("speed"), 1.0),
746
1139
  )
747
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_SPEED, handle_not_given(scope._kwargs.get("speed"), 1.0))
748
1140
 
749
1141
  # Span Attributes for Cost
750
1142
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
@@ -758,13 +1150,39 @@ def common_audio_logic(scope, request_model, pricing_info, environment, applicat
758
1150
 
759
1151
  # Record metrics
760
1152
  if not disable_metrics:
761
- record_audio_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
762
- scope._server_address, scope._server_port, request_model, request_model, environment,
763
- application_name, scope._start_time, scope._end_time, cost)
1153
+ record_audio_metrics(
1154
+ metrics,
1155
+ SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO,
1156
+ SemanticConvention.GEN_AI_SYSTEM_OPENAI,
1157
+ scope._server_address,
1158
+ scope._server_port,
1159
+ request_model,
1160
+ request_model,
1161
+ environment,
1162
+ application_name,
1163
+ scope._start_time,
1164
+ scope._end_time,
1165
+ cost,
1166
+ )
1167
+
764
1168
 
765
- def process_audio_response(response, request_model, pricing_info, server_port, server_address,
766
- environment, application_name, metrics, start_time, end_time, span, capture_message_content=False,
767
- disable_metrics=False, version="1.0.0", **kwargs):
1169
+ def process_audio_response(
1170
+ response,
1171
+ request_model,
1172
+ pricing_info,
1173
+ server_port,
1174
+ server_address,
1175
+ environment,
1176
+ application_name,
1177
+ metrics,
1178
+ start_time,
1179
+ end_time,
1180
+ span,
1181
+ capture_message_content=False,
1182
+ disable_metrics=False,
1183
+ version="1.0.0",
1184
+ **kwargs,
1185
+ ):
768
1186
  """
769
1187
  Process audio generation response and generate telemetry.
770
1188
  """
@@ -779,14 +1197,37 @@ def process_audio_response(response, request_model, pricing_info, server_port, s
779
1197
  scope._server_address, scope._server_port = server_address, server_port
780
1198
  scope._kwargs = kwargs
781
1199
 
782
- common_audio_logic(scope, request_model, pricing_info, environment, application_name,
783
- metrics, capture_message_content, disable_metrics, version)
1200
+ common_audio_logic(
1201
+ scope,
1202
+ request_model,
1203
+ pricing_info,
1204
+ environment,
1205
+ application_name,
1206
+ metrics,
1207
+ capture_message_content,
1208
+ disable_metrics,
1209
+ version,
1210
+ )
784
1211
 
785
1212
  return response
786
1213
 
787
- def process_embedding_response(response, request_model, pricing_info, server_port, server_address,
788
- environment, application_name, metrics, start_time, span, capture_message_content=False,
789
- disable_metrics=False, version="1.0.0", **kwargs):
1214
+
1215
+ def process_embedding_response(
1216
+ response,
1217
+ request_model,
1218
+ pricing_info,
1219
+ server_port,
1220
+ server_address,
1221
+ environment,
1222
+ application_name,
1223
+ metrics,
1224
+ start_time,
1225
+ span,
1226
+ capture_message_content=False,
1227
+ disable_metrics=False,
1228
+ version="1.0.0",
1229
+ **kwargs,
1230
+ ):
790
1231
  """
791
1232
  Process embedding response and generate telemetry.
792
1233
  """
@@ -803,14 +1244,38 @@ def process_embedding_response(response, request_model, pricing_info, server_por
803
1244
  scope._server_address, scope._server_port = server_address, server_port
804
1245
  scope._kwargs = kwargs
805
1246
 
806
- common_embedding_logic(scope, request_model, pricing_info, environment, application_name,
807
- metrics, capture_message_content, disable_metrics, version)
1247
+ common_embedding_logic(
1248
+ scope,
1249
+ request_model,
1250
+ pricing_info,
1251
+ environment,
1252
+ application_name,
1253
+ metrics,
1254
+ capture_message_content,
1255
+ disable_metrics,
1256
+ version,
1257
+ )
808
1258
 
809
1259
  return response
810
1260
 
811
- def process_image_response(response, request_model, pricing_info, server_port, server_address,
812
- environment, application_name, metrics, start_time, end_time, span, capture_message_content=False,
813
- disable_metrics=False, version="1.0.0", **kwargs):
1261
+
1262
+ def process_image_response(
1263
+ response,
1264
+ request_model,
1265
+ pricing_info,
1266
+ server_port,
1267
+ server_address,
1268
+ environment,
1269
+ application_name,
1270
+ metrics,
1271
+ start_time,
1272
+ end_time,
1273
+ span,
1274
+ capture_message_content=False,
1275
+ disable_metrics=False,
1276
+ version="1.0.0",
1277
+ **kwargs,
1278
+ ):
814
1279
  """
815
1280
  Process image generation response and generate telemetry.
816
1281
  """
@@ -827,7 +1292,16 @@ def process_image_response(response, request_model, pricing_info, server_port, s
827
1292
  scope._kwargs = kwargs
828
1293
  scope._response_dict = response_dict
829
1294
 
830
- common_image_logic(scope, request_model, pricing_info, environment, application_name,
831
- metrics, capture_message_content, disable_metrics, version)
1295
+ common_image_logic(
1296
+ scope,
1297
+ request_model,
1298
+ pricing_info,
1299
+ environment,
1300
+ application_name,
1301
+ metrics,
1302
+ capture_message_content,
1303
+ disable_metrics,
1304
+ version,
1305
+ )
832
1306
 
833
1307
  return response