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.
- openlit/__helpers.py +235 -86
- openlit/__init__.py +16 -13
- openlit/_instrumentors.py +2 -1
- openlit/evals/all.py +50 -21
- openlit/evals/bias_detection.py +47 -20
- openlit/evals/hallucination.py +53 -22
- openlit/evals/toxicity.py +50 -21
- openlit/evals/utils.py +54 -30
- openlit/guard/all.py +61 -19
- openlit/guard/prompt_injection.py +34 -14
- openlit/guard/restrict_topic.py +46 -15
- openlit/guard/sensitive_topic.py +34 -14
- openlit/guard/utils.py +58 -22
- openlit/instrumentation/ag2/__init__.py +24 -8
- openlit/instrumentation/ag2/ag2.py +34 -13
- openlit/instrumentation/ag2/async_ag2.py +34 -13
- openlit/instrumentation/ag2/utils.py +133 -30
- openlit/instrumentation/ai21/__init__.py +43 -14
- openlit/instrumentation/ai21/ai21.py +47 -21
- openlit/instrumentation/ai21/async_ai21.py +47 -21
- openlit/instrumentation/ai21/utils.py +299 -78
- openlit/instrumentation/anthropic/__init__.py +21 -4
- openlit/instrumentation/anthropic/anthropic.py +28 -17
- openlit/instrumentation/anthropic/async_anthropic.py +28 -17
- openlit/instrumentation/anthropic/utils.py +145 -35
- openlit/instrumentation/assemblyai/__init__.py +11 -2
- openlit/instrumentation/assemblyai/assemblyai.py +15 -4
- openlit/instrumentation/assemblyai/utils.py +120 -25
- openlit/instrumentation/astra/__init__.py +43 -10
- openlit/instrumentation/astra/astra.py +28 -5
- openlit/instrumentation/astra/async_astra.py +28 -5
- openlit/instrumentation/astra/utils.py +151 -55
- openlit/instrumentation/azure_ai_inference/__init__.py +43 -10
- openlit/instrumentation/azure_ai_inference/async_azure_ai_inference.py +53 -21
- openlit/instrumentation/azure_ai_inference/azure_ai_inference.py +53 -21
- openlit/instrumentation/azure_ai_inference/utils.py +307 -83
- openlit/instrumentation/bedrock/__init__.py +21 -4
- openlit/instrumentation/bedrock/bedrock.py +63 -25
- openlit/instrumentation/bedrock/utils.py +139 -30
- openlit/instrumentation/chroma/__init__.py +89 -16
- openlit/instrumentation/chroma/chroma.py +28 -6
- openlit/instrumentation/chroma/utils.py +167 -51
- openlit/instrumentation/cohere/__init__.py +63 -18
- openlit/instrumentation/cohere/async_cohere.py +63 -24
- openlit/instrumentation/cohere/cohere.py +63 -24
- openlit/instrumentation/cohere/utils.py +286 -73
- openlit/instrumentation/controlflow/__init__.py +35 -9
- openlit/instrumentation/controlflow/controlflow.py +66 -33
- openlit/instrumentation/crawl4ai/__init__.py +25 -10
- openlit/instrumentation/crawl4ai/async_crawl4ai.py +78 -31
- openlit/instrumentation/crawl4ai/crawl4ai.py +78 -31
- openlit/instrumentation/crewai/__init__.py +111 -24
- openlit/instrumentation/crewai/async_crewai.py +114 -0
- openlit/instrumentation/crewai/crewai.py +104 -131
- openlit/instrumentation/crewai/utils.py +615 -0
- openlit/instrumentation/dynamiq/__init__.py +46 -12
- openlit/instrumentation/dynamiq/dynamiq.py +74 -33
- openlit/instrumentation/elevenlabs/__init__.py +23 -4
- openlit/instrumentation/elevenlabs/async_elevenlabs.py +16 -4
- openlit/instrumentation/elevenlabs/elevenlabs.py +16 -4
- openlit/instrumentation/elevenlabs/utils.py +128 -25
- openlit/instrumentation/embedchain/__init__.py +11 -2
- openlit/instrumentation/embedchain/embedchain.py +68 -35
- openlit/instrumentation/firecrawl/__init__.py +24 -7
- openlit/instrumentation/firecrawl/firecrawl.py +46 -20
- openlit/instrumentation/google_ai_studio/__init__.py +45 -10
- openlit/instrumentation/google_ai_studio/async_google_ai_studio.py +67 -44
- openlit/instrumentation/google_ai_studio/google_ai_studio.py +67 -44
- openlit/instrumentation/google_ai_studio/utils.py +180 -67
- openlit/instrumentation/gpt4all/__init__.py +22 -7
- openlit/instrumentation/gpt4all/gpt4all.py +67 -29
- openlit/instrumentation/gpt4all/utils.py +285 -61
- openlit/instrumentation/gpu/__init__.py +128 -47
- openlit/instrumentation/groq/__init__.py +21 -4
- openlit/instrumentation/groq/async_groq.py +33 -21
- openlit/instrumentation/groq/groq.py +33 -21
- openlit/instrumentation/groq/utils.py +192 -55
- openlit/instrumentation/haystack/__init__.py +70 -24
- openlit/instrumentation/haystack/async_haystack.py +28 -6
- openlit/instrumentation/haystack/haystack.py +28 -6
- openlit/instrumentation/haystack/utils.py +196 -74
- openlit/instrumentation/julep/__init__.py +69 -19
- openlit/instrumentation/julep/async_julep.py +53 -27
- openlit/instrumentation/julep/julep.py +53 -28
- openlit/instrumentation/langchain/__init__.py +74 -63
- openlit/instrumentation/langchain/callback_handler.py +1100 -0
- openlit/instrumentation/langchain_community/__init__.py +13 -2
- openlit/instrumentation/langchain_community/async_langchain_community.py +23 -5
- openlit/instrumentation/langchain_community/langchain_community.py +23 -5
- openlit/instrumentation/langchain_community/utils.py +35 -9
- openlit/instrumentation/letta/__init__.py +68 -15
- openlit/instrumentation/letta/letta.py +99 -54
- openlit/instrumentation/litellm/__init__.py +43 -14
- openlit/instrumentation/litellm/async_litellm.py +51 -26
- openlit/instrumentation/litellm/litellm.py +51 -26
- openlit/instrumentation/litellm/utils.py +312 -101
- openlit/instrumentation/llamaindex/__init__.py +267 -90
- openlit/instrumentation/llamaindex/async_llamaindex.py +28 -6
- openlit/instrumentation/llamaindex/llamaindex.py +28 -6
- openlit/instrumentation/llamaindex/utils.py +204 -91
- openlit/instrumentation/mem0/__init__.py +11 -2
- openlit/instrumentation/mem0/mem0.py +50 -29
- openlit/instrumentation/milvus/__init__.py +10 -2
- openlit/instrumentation/milvus/milvus.py +31 -6
- openlit/instrumentation/milvus/utils.py +166 -67
- openlit/instrumentation/mistral/__init__.py +63 -18
- openlit/instrumentation/mistral/async_mistral.py +63 -24
- openlit/instrumentation/mistral/mistral.py +63 -24
- openlit/instrumentation/mistral/utils.py +277 -69
- openlit/instrumentation/multion/__init__.py +69 -19
- openlit/instrumentation/multion/async_multion.py +57 -26
- openlit/instrumentation/multion/multion.py +57 -26
- openlit/instrumentation/ollama/__init__.py +39 -18
- openlit/instrumentation/ollama/async_ollama.py +57 -26
- openlit/instrumentation/ollama/ollama.py +57 -26
- openlit/instrumentation/ollama/utils.py +226 -50
- openlit/instrumentation/openai/__init__.py +156 -32
- openlit/instrumentation/openai/async_openai.py +147 -67
- openlit/instrumentation/openai/openai.py +150 -67
- openlit/instrumentation/openai/utils.py +660 -186
- openlit/instrumentation/openai_agents/__init__.py +6 -2
- openlit/instrumentation/openai_agents/processor.py +409 -537
- openlit/instrumentation/phidata/__init__.py +13 -5
- openlit/instrumentation/phidata/phidata.py +67 -32
- openlit/instrumentation/pinecone/__init__.py +48 -9
- openlit/instrumentation/pinecone/async_pinecone.py +27 -5
- openlit/instrumentation/pinecone/pinecone.py +27 -5
- openlit/instrumentation/pinecone/utils.py +153 -47
- openlit/instrumentation/premai/__init__.py +22 -7
- openlit/instrumentation/premai/premai.py +51 -26
- openlit/instrumentation/premai/utils.py +246 -59
- openlit/instrumentation/pydantic_ai/__init__.py +49 -22
- openlit/instrumentation/pydantic_ai/pydantic_ai.py +69 -16
- openlit/instrumentation/pydantic_ai/utils.py +89 -24
- openlit/instrumentation/qdrant/__init__.py +19 -4
- openlit/instrumentation/qdrant/async_qdrant.py +33 -7
- openlit/instrumentation/qdrant/qdrant.py +33 -7
- openlit/instrumentation/qdrant/utils.py +228 -93
- openlit/instrumentation/reka/__init__.py +23 -10
- openlit/instrumentation/reka/async_reka.py +17 -11
- openlit/instrumentation/reka/reka.py +17 -11
- openlit/instrumentation/reka/utils.py +138 -36
- openlit/instrumentation/together/__init__.py +44 -12
- openlit/instrumentation/together/async_together.py +50 -27
- openlit/instrumentation/together/together.py +50 -27
- openlit/instrumentation/together/utils.py +301 -71
- openlit/instrumentation/transformers/__init__.py +2 -1
- openlit/instrumentation/transformers/transformers.py +13 -3
- openlit/instrumentation/transformers/utils.py +139 -36
- openlit/instrumentation/vertexai/__init__.py +81 -16
- openlit/instrumentation/vertexai/async_vertexai.py +33 -15
- openlit/instrumentation/vertexai/utils.py +123 -27
- openlit/instrumentation/vertexai/vertexai.py +33 -15
- openlit/instrumentation/vllm/__init__.py +12 -5
- openlit/instrumentation/vllm/utils.py +121 -31
- openlit/instrumentation/vllm/vllm.py +16 -10
- openlit/otel/events.py +35 -10
- openlit/otel/metrics.py +32 -24
- openlit/otel/tracing.py +24 -9
- openlit/semcov/__init__.py +101 -7
- {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/METADATA +2 -1
- openlit-1.34.31.dist-info/RECORD +166 -0
- openlit/instrumentation/langchain/async_langchain.py +0 -102
- openlit/instrumentation/langchain/langchain.py +0 -102
- openlit/instrumentation/langchain/utils.py +0 -252
- openlit-1.34.29.dist-info/RECORD +0 -166
- {openlit-1.34.29.dist-info → openlit-1.34.31.dist-info}/LICENSE +0 -0
- {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,
|
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
|
64
|
-
elif
|
65
|
-
|
66
|
-
|
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
|
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
|
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
|
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": {
|
121
|
-
|
125
|
+
"function": {
|
126
|
+
"name": func.get("name", ""),
|
127
|
+
"arguments": func.get("arguments", ""),
|
128
|
+
},
|
129
|
+
"type": tool.get("type", "function"),
|
122
130
|
}
|
123
|
-
elif
|
124
|
-
scope._tools[idx]
|
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 =
|
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 =
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
228
|
-
|
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(
|
254
|
-
|
255
|
-
|
256
|
-
|
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(
|
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(
|
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(
|
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(
|
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 =
|
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 =
|
289
|
-
(
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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(
|
296
|
-
|
297
|
-
|
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(
|
301
|
-
|
302
|
-
|
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(
|
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(
|
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(
|
333
|
-
|
334
|
-
|
335
|
-
|
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
|
-
|
338
|
-
|
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(
|
344
|
-
|
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(
|
347
|
-
|
348
|
-
|
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
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
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(
|
408
|
-
|
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
|
-
|
413
|
-
|
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,
|
449
|
-
scope.
|
450
|
-
|
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(
|
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(
|
472
|
-
|
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(
|
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(
|
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 =
|
485
|
-
(
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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(
|
492
|
-
|
493
|
-
|
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(
|
497
|
-
|
498
|
-
|
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(
|
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(
|
525
|
-
|
526
|
-
|
527
|
-
|
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(
|
530
|
-
|
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(
|
536
|
-
|
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(
|
539
|
-
|
540
|
-
|
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 =
|
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 =
|
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(
|
578
|
-
|
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
|
-
|
583
|
-
|
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(
|
593
|
-
|
594
|
-
|
595
|
-
|
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(
|
606
|
-
|
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 =
|
613
|
-
|
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(
|
620
|
-
|
621
|
-
|
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
|
-
|
624
|
-
|
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(
|
631
|
-
|
632
|
-
|
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(
|
636
|
-
|
637
|
-
|
638
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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[
|
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(
|
722
|
-
|
723
|
-
|
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
|
-
|
726
|
-
|
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(
|
737
|
-
|
738
|
-
|
739
|
-
|
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(
|
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(
|
762
|
-
|
763
|
-
|
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(
|
766
|
-
|
767
|
-
|
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(
|
783
|
-
|
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
|
-
|
788
|
-
|
789
|
-
|
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(
|
807
|
-
|
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
|
-
|
812
|
-
|
813
|
-
|
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(
|
831
|
-
|
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
|