openlit 1.34.30__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 +40 -15
  53. openlit/instrumentation/crewai/async_crewai.py +32 -7
  54. openlit/instrumentation/crewai/crewai.py +32 -7
  55. openlit/instrumentation/crewai/utils.py +159 -56
  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 +304 -102
  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 +657 -185
  121. openlit/instrumentation/openai_agents/__init__.py +5 -1
  122. openlit/instrumentation/openai_agents/processor.py +110 -90
  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 +72 -6
  161. {openlit-1.34.30.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.30.dist-info/RECORD +0 -168
  167. {openlit-1.34.30.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
  168. {openlit-1.34.30.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,16 +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
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":
30
32
  return default
31
33
  if value is None:
32
34
  return default
33
35
  return value
34
36
 
37
+
35
38
  def format_content(messages):
36
39
  """
37
40
  Format the messages into a string for span events.
@@ -62,18 +65,19 @@ def format_content(messages):
62
65
  for item in content:
63
66
  # Chat completions format
64
67
  if item.get("type") == "text":
65
- content_str_list.append(f'text: {item.get("text", "")}')
66
- elif (item.get("type") == "image_url" and
67
- not item.get("image_url", {}).get("url", "").startswith("data:")):
68
- 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']}")
69
73
 
70
74
  # Responses API format
71
75
  elif item.get("type") == "input_text":
72
- content_str_list.append(f'text: {item.get("text", "")}')
76
+ content_str_list.append(f"text: {item.get('text', '')}")
73
77
  elif item.get("type") == "input_image":
74
78
  image_url = item.get("image_url", "")
75
79
  if image_url and not image_url.startswith("data:"):
76
- content_str_list.append(f'image_url: {image_url}')
80
+ content_str_list.append(f"image_url: {image_url}")
77
81
 
78
82
  content_str = ", ".join(content_str_list)
79
83
  formatted_messages.append(f"{role}: {content_str}")
@@ -82,6 +86,7 @@ def format_content(messages):
82
86
 
83
87
  return "\n".join(formatted_messages)
84
88
 
89
+
85
90
  def process_chat_chunk(scope, chunk):
86
91
  """
87
92
  Process a chunk of chat response data and update state.
@@ -96,9 +101,7 @@ def process_chat_chunk(scope, chunk):
96
101
  chunked = response_as_dict(chunk)
97
102
 
98
103
  # Extract content from chat completions
99
- if (len(chunked.get("choices", [])) > 0 and
100
- "delta" in chunked.get("choices")[0]):
101
-
104
+ if len(chunked.get("choices", [])) > 0 and "delta" in chunked.get("choices")[0]:
102
105
  delta = chunked.get("choices")[0]["delta"]
103
106
  content = delta.get("content")
104
107
  if content:
@@ -119,24 +122,36 @@ def process_chat_chunk(scope, chunk):
119
122
  func = tool.get("function", {})
120
123
  scope._tools[idx] = {
121
124
  "id": tool["id"],
122
- "function": {"name": func.get("name", ""), "arguments": func.get("arguments", "")},
123
- "type": tool.get("type", "function")
125
+ "function": {
126
+ "name": func.get("name", ""),
127
+ "arguments": func.get("arguments", ""),
128
+ },
129
+ "type": tool.get("type", "function"),
124
130
  }
125
- elif scope._tools[idx] and "function" in tool: # Append args (id is None)
126
- 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
+ )
127
137
 
128
138
  # Extract metadata
129
139
  scope._response_id = chunked.get("id") or scope._response_id
130
140
  scope._response_model = chunked.get("model") or scope._response_model
131
141
 
132
142
  try:
133
- 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
+ )
134
146
  except (IndexError, AttributeError, TypeError):
135
147
  scope._finish_reason = "stop"
136
148
 
137
- 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
+ )
138
152
  scope._service_tier = chunked.get("service_tier") or scope._service_tier
139
153
 
154
+
140
155
  def process_response_chunk(scope, chunk):
141
156
  """
142
157
  Process a chunk of response API data and update state.
@@ -162,14 +177,16 @@ def process_response_chunk(scope, chunk):
162
177
 
163
178
  item = chunked.get("item", {})
164
179
  if item.get("type") == "function_call":
165
- scope._response_tools.append({
166
- "id": item.get("id", ""),
167
- "call_id": item.get("call_id", ""),
168
- "name": item.get("name", ""),
169
- "type": item.get("type", "function_call"),
170
- "arguments": item.get("arguments", ""),
171
- "status": item.get("status", "in_progress")
172
- })
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
+ )
173
190
 
174
191
  elif chunked.get("type") == "response.function_call_arguments.delta":
175
192
  # Tool arguments being streamed
@@ -204,12 +221,16 @@ def process_response_chunk(scope, chunk):
204
221
  # Update the tool with final status and data
205
222
  for tool in scope._response_tools:
206
223
  if tool.get("id") == item_id:
207
- tool.update({
208
- "call_id": item.get("call_id", tool.get("call_id", "")),
209
- "name": item.get("name", tool.get("name", "")),
210
- "arguments": item.get("arguments", tool.get("arguments", "")),
211
- "status": item.get("status", "completed")
212
- })
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
+ )
213
234
  break
214
235
 
215
236
  elif chunked.get("type") == "response.completed":
@@ -226,8 +247,18 @@ def process_response_chunk(scope, chunk):
226
247
  output_tokens_details = usage.get("output_tokens_details", {})
227
248
  scope._reasoning_tokens = output_tokens_details.get("reasoning_tokens", 0)
228
249
 
229
- def common_response_logic(scope, pricing_info, environment, application_name, metrics,
230
- 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
+ ):
231
262
  """
232
263
  Process responses API request and generate Telemetry
233
264
  """
@@ -252,66 +283,115 @@ def common_response_logic(scope, pricing_info, environment, application_name, me
252
283
  cost = get_chat_model_cost(request_model, pricing_info, input_tokens, output_tokens)
253
284
 
254
285
  # Common Span Attributes
255
- common_span_attributes(scope,
256
- SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
257
- scope._server_address, scope._server_port, request_model, scope._response_model,
258
- 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
+ )
259
301
 
260
302
  # Span Attributes for Request parameters specific to responses API
261
- 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
+ )
262
307
  scope._span.set_attribute(
263
308
  SemanticConvention.GEN_AI_REQUEST_TOP_P,
264
- handle_not_given(scope._kwargs.get("top_p"), 1.0)
309
+ handle_not_given(scope._kwargs.get("top_p"), 1.0),
265
310
  )
266
311
  scope._span.set_attribute(
267
312
  SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS,
268
- handle_not_given(scope._kwargs.get("max_output_tokens"), -1)
313
+ handle_not_given(scope._kwargs.get("max_output_tokens"), -1),
269
314
  )
270
315
 
271
316
  # Reasoning parameters
272
317
  reasoning = scope._kwargs.get("reasoning", {})
273
318
  if reasoning:
274
319
  if reasoning.get("effort"):
275
- 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
+ )
276
323
 
277
324
  # Responses API specific attributes
278
325
  if hasattr(scope, "_service_tier"):
279
- 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
+ )
280
329
 
281
330
  # Span Attributes for Response parameters
282
331
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
283
- 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
+ )
284
335
  scope._span.set_attribute(SemanticConvention.GEN_AI_OUTPUT_TYPE, "text")
285
336
 
286
337
  # Span Attributes for Tools (responses API structure) - optimized
287
338
  if hasattr(scope, "_response_tools") and scope._response_tools:
288
- 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
+ )
289
344
 
290
- names, ids, args = zip(*[
291
- (t.get("name", ""),
292
- str(t.get("call_id", "")), # Use call_id for responses API
293
- str(t.get("arguments", "")))
294
- for t in tools if isinstance(t, dict) and t
295
- ]) 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
+ )
296
360
 
297
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names)))
298
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids)))
299
- 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
+ )
300
370
 
301
371
  # Span Attributes for Cost and Tokens
302
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
303
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
304
- 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
+ )
305
381
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
306
382
 
307
383
  # Reasoning tokens
308
384
  if hasattr(scope, "_reasoning_tokens") and scope._reasoning_tokens > 0:
309
- 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
+ )
310
388
 
311
389
  # Span Attributes for Content
312
390
  if capture_message_content:
313
391
  scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
314
- 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
+ )
315
395
 
316
396
  # To be removed once the change to span_attributes (from span events) is complete
317
397
  scope._span.add_event(
@@ -331,23 +411,69 @@ def common_response_logic(scope, pricing_info, environment, application_name, me
331
411
 
332
412
  # Record metrics
333
413
  if not disable_metrics:
334
- record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
335
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
336
- application_name, scope._start_time, scope._end_time, input_tokens, output_tokens,
337
- 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
+ )
432
+
338
433
 
339
- def process_streaming_response_response(scope, pricing_info, environment, application_name, metrics,
340
- capture_message_content=False, disable_metrics=False, version=""):
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
+ ):
341
444
  """
342
445
  Process streaming responses API response and generate telemetry.
343
446
  """
344
447
 
345
- common_response_logic(scope, pricing_info, environment, application_name, metrics,
346
- 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
+
347
460
 
348
- def process_response_response(response, request_model, pricing_info, server_port, server_address,
349
- environment, application_name, metrics, start_time, span, capture_message_content=False,
350
- 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
+ ):
351
477
  """
352
478
  Process non-streaming responses API response and generate telemetry.
353
479
  """
@@ -373,14 +499,16 @@ def process_response_response(response, request_model, pricing_info, server_port
373
499
  break
374
500
  if item.get("type") == "function_call":
375
501
  # Handle tool call
376
- scope._response_tools = [{
377
- "id": item.get("id", ""),
378
- "call_id": item.get("call_id", ""),
379
- "name": item.get("name", ""),
380
- "type": item.get("type", "function_call"),
381
- "arguments": item.get("arguments", ""),
382
- "status": item.get("status", "")
383
- }]
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
+ ]
384
512
 
385
513
  # Extract content from message item if found
386
514
  if message_item:
@@ -406,13 +534,32 @@ def process_response_response(response, request_model, pricing_info, server_port
406
534
  scope._service_tier = response_dict.get("service_tier", "default")
407
535
  scope._finish_reason = response_dict.get("status", "completed")
408
536
 
409
- common_response_logic(scope, pricing_info, environment, application_name, metrics,
410
- 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
+ )
411
548
 
412
549
  return response
413
550
 
414
- def common_chat_logic(scope, pricing_info, environment, application_name, metrics,
415
- 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
+ ):
416
563
  """
417
564
  Process chat request and generate Telemetry
418
565
  """
@@ -447,63 +594,121 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
447
594
  scope,
448
595
  SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT,
449
596
  SemanticConvention.GEN_AI_SYSTEM_OPENAI,
450
- scope._server_address, scope._server_port, request_model,
451
- scope._response_model, environment, application_name,
452
- 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,
453
607
  )
454
608
 
455
609
  # Span Attributes for Request parameters
456
- 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
+ )
457
614
  scope._span.set_attribute(
458
615
  SemanticConvention.GEN_AI_REQUEST_FREQUENCY_PENALTY,
459
- 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),
460
621
  )
461
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_MAX_TOKENS, handle_not_given(scope._kwargs.get("max_tokens"), -1))
462
622
  scope._span.set_attribute(
463
623
  SemanticConvention.GEN_AI_REQUEST_PRESENCE_PENALTY,
464
- 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"), ""),
465
641
  )
466
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_STOP_SEQUENCES, handle_not_given(scope._kwargs.get("stop"), []))
467
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TEMPERATURE, handle_not_given(scope._kwargs.get("temperature"), 1.0))
468
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_TOP_P, handle_not_given(scope._kwargs.get("top_p"), 1.0))
469
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
470
642
 
471
643
  # Span Attributes for Response parameters
472
644
  scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_ID, scope._response_id)
473
- scope._span.set_attribute(SemanticConvention.GEN_AI_RESPONSE_FINISH_REASON, [scope._finish_reason])
474
- 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
+ )
475
652
 
476
653
  # OpenAI-specific attributes
477
654
  if hasattr(scope, "_system_fingerprint") and scope._system_fingerprint:
478
- 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
+ )
479
659
  if hasattr(scope, "_service_tier") and scope._service_tier:
480
- 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
+ )
481
663
 
482
664
  # Span Attributes for Tools - optimized
483
665
  if hasattr(scope, "_tools") and scope._tools:
484
666
  tools = scope._tools if isinstance(scope._tools, list) else [scope._tools]
485
667
 
486
- names, ids, args = zip(*[
487
- (t.get("function", {}).get("name", ""),
488
- str(t.get("id", "")),
489
- str(t.get("function", {}).get("arguments", "")))
490
- for t in tools if isinstance(t, dict) and t
491
- ]) 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
+ )
492
683
 
493
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_NAME, ", ".join(filter(None, names)))
494
- scope._span.set_attribute(SemanticConvention.GEN_AI_TOOL_CALL_ID, ", ".join(filter(None, ids)))
495
- 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
+ )
496
693
 
497
694
  # Span Attributes for Cost and Tokens
498
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
499
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
500
- 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
+ )
501
704
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
502
705
 
503
706
  # Span Attributes for Content
504
707
  if capture_message_content:
505
708
  scope._span.set_attribute(SemanticConvention.GEN_AI_CONTENT_PROMPT, prompt)
506
- 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
+ )
507
712
 
508
713
  # To be removed once the change to span_attributes (from span events) is complete
509
714
  scope._span.add_event(
@@ -523,23 +728,69 @@ def common_chat_logic(scope, pricing_info, environment, application_name, metric
523
728
 
524
729
  # Record metrics
525
730
  if not disable_metrics:
526
- record_completion_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_CHAT, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
527
- scope._server_address, scope._server_port, request_model, scope._response_model, environment,
528
- application_name, scope._start_time, scope._end_time, input_tokens, output_tokens,
529
- 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
+
530
750
 
531
- def process_streaming_chat_response(scope, pricing_info, environment, application_name, metrics,
532
- 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
+ ):
533
761
  """
534
762
  Process streaming chat response and generate telemetry.
535
763
  """
536
764
 
537
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
538
- 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
+
539
777
 
540
- def process_chat_response(response, request_model, pricing_info, server_port, server_address,
541
- environment, application_name, metrics, start_time, span, capture_message_content=False,
542
- 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
+ ):
543
794
  """
544
795
  Process non-streaming chat response and generate telemetry.
545
796
  """
@@ -564,7 +815,11 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
564
815
  scope._kwargs = kwargs
565
816
  scope._system_fingerprint = response_dict.get("system_fingerprint", "")
566
817
  scope._service_tier = response_dict.get("service_tier", "auto")
567
- 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
+ )
568
823
 
569
824
  # Handle operation type for responses API
570
825
  if kwargs.get("_operation_type") == "responses":
@@ -572,17 +827,38 @@ def process_chat_response(response, request_model, pricing_info, server_port, se
572
827
 
573
828
  # Handle tool calls
574
829
  if kwargs.get("tools"):
575
- 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
+ )
576
833
  else:
577
834
  scope._tools = None
578
835
 
579
- common_chat_logic(scope, pricing_info, environment, application_name, metrics,
580
- 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
+ )
581
847
 
582
848
  return response
583
849
 
584
- def common_embedding_logic(scope, request_model, pricing_info, environment, application_name,
585
- 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
+ ):
586
862
  """
587
863
  Common logic for processing embedding operations.
588
864
  """
@@ -591,61 +867,127 @@ def common_embedding_logic(scope, request_model, pricing_info, environment, appl
591
867
  cost = get_embed_model_cost(request_model, pricing_info, scope._input_tokens)
592
868
 
593
869
  # Common Span Attributes
594
- common_span_attributes(scope,
595
- SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
596
- scope._server_address, scope._server_port, request_model, request_model,
597
- 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
+ )
598
885
 
599
886
  # Span Attributes for Request parameters
600
887
  scope._span.set_attribute(
601
888
  SemanticConvention.GEN_AI_REQUEST_ENCODING_FORMATS,
602
- [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"), ""),
603
894
  )
604
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
605
895
 
606
896
  # Span Attributes for Cost and Tokens
607
- scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_INPUT_TOKENS, scope._input_tokens)
608
- 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
+ )
609
903
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
610
904
 
611
905
  # Span Attributes for Content
612
906
  if capture_message_content:
613
907
  input_data = scope._kwargs.get("input", "")
614
- formatted_content = format_content(input_data) if isinstance(input_data, (list, dict)) else str(input_data)
615
- 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
+ )
616
916
 
617
917
  scope._span.set_status(Status(StatusCode.OK))
618
918
 
619
919
  # Record metrics
620
920
  if not disable_metrics:
621
- record_embedding_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_EMBEDDING, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
622
- scope._server_address, scope._server_port, request_model, request_model, environment,
623
- 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
+ )
624
936
 
625
- def common_image_logic(scope, request_model, pricing_info, environment, application_name,
626
- 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
+ ):
627
949
  """
628
950
  Common logic for processing image operations.
629
951
  """
630
952
 
631
953
  # Calculate cost
632
- cost = get_image_model_cost(request_model, pricing_info,
633
- scope._kwargs.get("size", "1024x1024"),
634
- 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
+ )
635
960
 
636
961
  # Common Span Attributes
637
- common_span_attributes(scope,
638
- SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
639
- scope._server_address, scope._server_port, request_model, request_model,
640
- 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
+ )
641
977
 
642
978
  # Span Attributes for Request parameters
643
- 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
+ )
644
983
  scope._span.set_attribute(
645
984
  SemanticConvention.GEN_AI_REQUEST_IMAGE_QUALITY,
646
- 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"), ""),
647
990
  )
648
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_USER, handle_not_given(scope._kwargs.get("user"), ""))
649
991
 
650
992
  # Extract response data
651
993
  response_dict = scope._response_dict
@@ -657,7 +999,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
657
999
 
658
1000
  # Span Attributes for Response
659
1001
  if response_created:
660
- 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
+ )
661
1005
 
662
1006
  # Process image data and collect URLs/base64 content
663
1007
  if images_data:
@@ -675,7 +1019,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
675
1019
 
676
1020
  # Set image response data using semantic conventions
677
1021
  if image_contents:
678
- 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
+ )
679
1025
 
680
1026
  # Response-level attributes if different from request
681
1027
  if response_size:
@@ -683,7 +1029,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
683
1029
  if response_quality:
684
1030
  scope._span.set_attribute("gen_ai.response.image_quality", response_quality)
685
1031
  if response_output_format:
686
- 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
+ )
687
1035
 
688
1036
  # Span Attributes for Cost
689
1037
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
@@ -703,7 +1051,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
703
1051
 
704
1052
  # Set revised prompts as span attribute if any were found
705
1053
  if revised_prompts:
706
- 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
+ )
707
1057
 
708
1058
  # Add revised prompt events for detailed tracking
709
1059
  for i, image in enumerate(images_data):
@@ -711,7 +1061,9 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
711
1061
  scope._span.add_event(
712
1062
  name=SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT,
713
1063
  attributes={
714
- SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT: image["revised_prompt"],
1064
+ SemanticConvention.GEN_AI_CONTENT_REVISED_PROMPT: image[
1065
+ "revised_prompt"
1066
+ ],
715
1067
  "image_index": i,
716
1068
  },
717
1069
  )
@@ -720,12 +1072,33 @@ def common_image_logic(scope, request_model, pricing_info, environment, applicat
720
1072
 
721
1073
  # Record metrics
722
1074
  if not disable_metrics:
723
- record_image_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_IMAGE, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
724
- scope._server_address, scope._server_port, request_model, request_model, environment,
725
- 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
+ )
726
1089
 
727
- def common_audio_logic(scope, request_model, pricing_info, environment, application_name,
728
- 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
+ ):
729
1102
  """
730
1103
  Common logic for processing audio operations.
731
1104
  """
@@ -735,18 +1108,35 @@ def common_audio_logic(scope, request_model, pricing_info, environment, applicat
735
1108
  cost = get_audio_model_cost(request_model, pricing_info, input_text)
736
1109
 
737
1110
  # Common Span Attributes
738
- common_span_attributes(scope,
739
- SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
740
- scope._server_address, scope._server_port, request_model, request_model,
741
- 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
+ )
742
1126
 
743
1127
  # Span Attributes for Request parameters
744
- 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
+ )
745
1132
  scope._span.set_attribute(
746
1133
  SemanticConvention.GEN_AI_REQUEST_AUDIO_RESPONSE_FORMAT,
747
- 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),
748
1139
  )
749
- scope._span.set_attribute(SemanticConvention.GEN_AI_REQUEST_AUDIO_SPEED, handle_not_given(scope._kwargs.get("speed"), 1.0))
750
1140
 
751
1141
  # Span Attributes for Cost
752
1142
  scope._span.set_attribute(SemanticConvention.GEN_AI_USAGE_COST, cost)
@@ -760,13 +1150,39 @@ def common_audio_logic(scope, request_model, pricing_info, environment, applicat
760
1150
 
761
1151
  # Record metrics
762
1152
  if not disable_metrics:
763
- record_audio_metrics(metrics, SemanticConvention.GEN_AI_OPERATION_TYPE_AUDIO, SemanticConvention.GEN_AI_SYSTEM_OPENAI,
764
- scope._server_address, scope._server_port, request_model, request_model, environment,
765
- 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
+
766
1168
 
767
- def process_audio_response(response, request_model, pricing_info, server_port, server_address,
768
- environment, application_name, metrics, start_time, end_time, span, capture_message_content=False,
769
- 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
+ ):
770
1186
  """
771
1187
  Process audio generation response and generate telemetry.
772
1188
  """
@@ -781,14 +1197,37 @@ def process_audio_response(response, request_model, pricing_info, server_port, s
781
1197
  scope._server_address, scope._server_port = server_address, server_port
782
1198
  scope._kwargs = kwargs
783
1199
 
784
- common_audio_logic(scope, request_model, pricing_info, environment, application_name,
785
- 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
+ )
786
1211
 
787
1212
  return response
788
1213
 
789
- def process_embedding_response(response, request_model, pricing_info, server_port, server_address,
790
- environment, application_name, metrics, start_time, span, capture_message_content=False,
791
- 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
+ ):
792
1231
  """
793
1232
  Process embedding response and generate telemetry.
794
1233
  """
@@ -805,14 +1244,38 @@ def process_embedding_response(response, request_model, pricing_info, server_por
805
1244
  scope._server_address, scope._server_port = server_address, server_port
806
1245
  scope._kwargs = kwargs
807
1246
 
808
- common_embedding_logic(scope, request_model, pricing_info, environment, application_name,
809
- 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
+ )
810
1258
 
811
1259
  return response
812
1260
 
813
- def process_image_response(response, request_model, pricing_info, server_port, server_address,
814
- environment, application_name, metrics, start_time, end_time, span, capture_message_content=False,
815
- 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
+ ):
816
1279
  """
817
1280
  Process image generation response and generate telemetry.
818
1281
  """
@@ -829,7 +1292,16 @@ def process_image_response(response, request_model, pricing_info, server_port, s
829
1292
  scope._kwargs = kwargs
830
1293
  scope._response_dict = response_dict
831
1294
 
832
- common_image_logic(scope, request_model, pricing_info, environment, application_name,
833
- 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
+ )
834
1306
 
835
1307
  return response