opentelemetry-instrumentation-openai 0.16.5__py3-none-any.whl → 0.16.7__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.
Potentially problematic release.
This version of opentelemetry-instrumentation-openai might be problematic. Click here for more details.
- opentelemetry/instrumentation/openai/__init__.py +5 -1
- opentelemetry/instrumentation/openai/shared/__init__.py +54 -78
- opentelemetry/instrumentation/openai/shared/chat_wrappers.py +30 -21
- opentelemetry/instrumentation/openai/shared/completion_wrappers.py +65 -65
- opentelemetry/instrumentation/openai/shared/config.py +1 -0
- opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +12 -14
- opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +16 -6
- opentelemetry/instrumentation/openai/utils.py +29 -0
- opentelemetry/instrumentation/openai/version.py +1 -1
- {opentelemetry_instrumentation_openai-0.16.5.dist-info → opentelemetry_instrumentation_openai-0.16.7.dist-info}/METADATA +1 -1
- opentelemetry_instrumentation_openai-0.16.7.dist-info/RECORD +17 -0
- opentelemetry_instrumentation_openai-0.16.5.dist-info/RECORD +0 -17
- {opentelemetry_instrumentation_openai-0.16.5.dist-info → opentelemetry_instrumentation_openai-0.16.7.dist-info}/WHEEL +0 -0
- {opentelemetry_instrumentation_openai-0.16.5.dist-info → opentelemetry_instrumentation_openai-0.16.7.dist-info}/entry_points.txt +0 -0
|
@@ -14,11 +14,15 @@ class OpenAIInstrumentor(BaseInstrumentor):
|
|
|
14
14
|
"""An instrumentor for OpenAI's client library."""
|
|
15
15
|
|
|
16
16
|
def __init__(
|
|
17
|
-
self,
|
|
17
|
+
self,
|
|
18
|
+
enrich_assistant: bool = False,
|
|
19
|
+
enrich_token_usage: bool = False,
|
|
20
|
+
exception_logger=None,
|
|
18
21
|
):
|
|
19
22
|
super().__init__()
|
|
20
23
|
Config.enrich_assistant = enrich_assistant
|
|
21
24
|
Config.enrich_token_usage = enrich_token_usage
|
|
25
|
+
Config.exception_logger = exception_logger
|
|
22
26
|
|
|
23
27
|
def instrumentation_dependencies(self) -> Collection[str]:
|
|
24
28
|
return _instruments
|
|
@@ -10,6 +10,7 @@ from opentelemetry import context as context_api
|
|
|
10
10
|
|
|
11
11
|
from opentelemetry.semconv.ai import SpanAttributes
|
|
12
12
|
from opentelemetry.instrumentation.openai.utils import (
|
|
13
|
+
dont_throw,
|
|
13
14
|
is_openai_v1,
|
|
14
15
|
should_record_stream_token_usage,
|
|
15
16
|
)
|
|
@@ -46,19 +47,13 @@ def _set_client_attributes(span, instance):
|
|
|
46
47
|
if not is_openai_v1():
|
|
47
48
|
return
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
) # pylint: disable=protected-access
|
|
57
|
-
|
|
58
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
59
|
-
logger.warning(
|
|
60
|
-
"Failed to set api attributes for openai v1 span, error: %s", str(ex)
|
|
61
|
-
)
|
|
50
|
+
client = instance._client # pylint: disable=protected-access
|
|
51
|
+
if isinstance(client, (openai.AsyncOpenAI, openai.OpenAI)):
|
|
52
|
+
_set_span_attribute(span, OPENAI_API_BASE, str(client.base_url))
|
|
53
|
+
if isinstance(client, (openai.AsyncAzureOpenAI, openai.AzureOpenAI)):
|
|
54
|
+
_set_span_attribute(
|
|
55
|
+
span, OPENAI_API_VERSION, client._api_version
|
|
56
|
+
) # pylint: disable=protected-access
|
|
62
57
|
|
|
63
58
|
|
|
64
59
|
def _set_api_attributes(span):
|
|
@@ -68,16 +63,11 @@ def _set_api_attributes(span):
|
|
|
68
63
|
if is_openai_v1():
|
|
69
64
|
return
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
base_url = openai.base_url if hasattr(openai, "base_url") else openai.api_base
|
|
66
|
+
base_url = openai.base_url if hasattr(openai, "base_url") else openai.api_base
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
78
|
-
logger.warning(
|
|
79
|
-
"Failed to set api attributes for openai span, error: %s", str(ex)
|
|
80
|
-
)
|
|
68
|
+
_set_span_attribute(span, OPENAI_API_BASE, base_url)
|
|
69
|
+
_set_span_attribute(span, OPENAI_API_TYPE, openai.api_type)
|
|
70
|
+
_set_span_attribute(span, OPENAI_API_VERSION, openai.api_version)
|
|
81
71
|
|
|
82
72
|
return
|
|
83
73
|
|
|
@@ -116,76 +106,62 @@ def _set_request_attributes(span, kwargs):
|
|
|
116
106
|
if not span.is_recording():
|
|
117
107
|
return
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
_set_span_attribute(
|
|
138
|
-
span, SpanAttributes.LLM_HEADERS, str(kwargs.get("headers"))
|
|
139
|
-
)
|
|
140
|
-
# The new OpenAI SDK removed the `headers` and create new field called `extra_headers`
|
|
141
|
-
if kwargs.get("extra_headers") is not None:
|
|
142
|
-
_set_span_attribute(
|
|
143
|
-
span, SpanAttributes.LLM_HEADERS, str(kwargs.get("extra_headers"))
|
|
144
|
-
)
|
|
109
|
+
_set_api_attributes(span)
|
|
110
|
+
_set_span_attribute(span, SpanAttributes.LLM_VENDOR, "OpenAI")
|
|
111
|
+
_set_span_attribute(span, SpanAttributes.LLM_REQUEST_MODEL, kwargs.get("model"))
|
|
112
|
+
_set_span_attribute(
|
|
113
|
+
span, SpanAttributes.LLM_REQUEST_MAX_TOKENS, kwargs.get("max_tokens")
|
|
114
|
+
)
|
|
115
|
+
_set_span_attribute(span, SpanAttributes.LLM_TEMPERATURE, kwargs.get("temperature"))
|
|
116
|
+
_set_span_attribute(span, SpanAttributes.LLM_TOP_P, kwargs.get("top_p"))
|
|
117
|
+
_set_span_attribute(
|
|
118
|
+
span, SpanAttributes.LLM_FREQUENCY_PENALTY, kwargs.get("frequency_penalty")
|
|
119
|
+
)
|
|
120
|
+
_set_span_attribute(
|
|
121
|
+
span, SpanAttributes.LLM_PRESENCE_PENALTY, kwargs.get("presence_penalty")
|
|
122
|
+
)
|
|
123
|
+
_set_span_attribute(span, SpanAttributes.LLM_USER, kwargs.get("user"))
|
|
124
|
+
_set_span_attribute(span, SpanAttributes.LLM_HEADERS, str(kwargs.get("headers")))
|
|
125
|
+
# The new OpenAI SDK removed the `headers` and create new field called `extra_headers`
|
|
126
|
+
if kwargs.get("extra_headers") is not None:
|
|
145
127
|
_set_span_attribute(
|
|
146
|
-
span, SpanAttributes.
|
|
147
|
-
)
|
|
148
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
149
|
-
logger.warning(
|
|
150
|
-
"Failed to set input attributes for openai span, error: %s", str(ex)
|
|
128
|
+
span, SpanAttributes.LLM_HEADERS, str(kwargs.get("extra_headers"))
|
|
151
129
|
)
|
|
130
|
+
_set_span_attribute(
|
|
131
|
+
span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False
|
|
132
|
+
)
|
|
152
133
|
|
|
153
134
|
|
|
135
|
+
@dont_throw
|
|
154
136
|
def _set_response_attributes(span, response):
|
|
155
137
|
if not span.is_recording():
|
|
156
138
|
return
|
|
157
139
|
|
|
158
|
-
|
|
159
|
-
_set_span_attribute(
|
|
160
|
-
span, SpanAttributes.LLM_RESPONSE_MODEL, response.get("model")
|
|
161
|
-
)
|
|
140
|
+
_set_span_attribute(span, SpanAttributes.LLM_RESPONSE_MODEL, response.get("model"))
|
|
162
141
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
142
|
+
usage = response.get("usage")
|
|
143
|
+
if not usage:
|
|
144
|
+
return
|
|
166
145
|
|
|
167
|
-
|
|
168
|
-
|
|
146
|
+
if is_openai_v1() and not isinstance(usage, dict):
|
|
147
|
+
usage = usage.__dict__
|
|
169
148
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
149
|
+
_set_span_attribute(
|
|
150
|
+
span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, usage.get("total_tokens")
|
|
151
|
+
)
|
|
152
|
+
_set_span_attribute(
|
|
153
|
+
span,
|
|
154
|
+
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
|
155
|
+
usage.get("completion_tokens"),
|
|
156
|
+
)
|
|
157
|
+
_set_span_attribute(
|
|
158
|
+
span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS, usage.get("prompt_tokens")
|
|
159
|
+
)
|
|
181
160
|
|
|
182
|
-
|
|
183
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
184
|
-
logger.warning(
|
|
185
|
-
"Failed to set response attributes for openai span, error: %s", str(ex)
|
|
186
|
-
)
|
|
161
|
+
return
|
|
187
162
|
|
|
188
163
|
|
|
164
|
+
@dont_throw
|
|
189
165
|
def _set_span_stream_usage(span, prompt_tokens, completion_tokens):
|
|
190
166
|
if not span.is_recording():
|
|
191
167
|
return
|
|
@@ -9,6 +9,7 @@ from opentelemetry.semconv.ai import SpanAttributes, LLMRequestTypeValues
|
|
|
9
9
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
10
10
|
from opentelemetry.instrumentation.openai.utils import (
|
|
11
11
|
_with_chat_telemetry_wrapper,
|
|
12
|
+
dont_throw,
|
|
12
13
|
)
|
|
13
14
|
from opentelemetry.instrumentation.openai.shared import (
|
|
14
15
|
_set_client_attributes,
|
|
@@ -29,7 +30,7 @@ from opentelemetry.instrumentation.openai.shared import (
|
|
|
29
30
|
from opentelemetry.trace import SpanKind, Tracer
|
|
30
31
|
from opentelemetry.trace.status import Status, StatusCode
|
|
31
32
|
|
|
32
|
-
from opentelemetry.instrumentation.openai.utils import is_openai_v1
|
|
33
|
+
from opentelemetry.instrumentation.openai.utils import is_openai_v1, is_azure_openai
|
|
33
34
|
|
|
34
35
|
SPAN_NAME = "openai.chat"
|
|
35
36
|
LLM_REQUEST_TYPE = LLMRequestTypeValues.CHAT
|
|
@@ -187,6 +188,7 @@ async def achat_wrapper(
|
|
|
187
188
|
return response
|
|
188
189
|
|
|
189
190
|
|
|
191
|
+
@dont_throw
|
|
190
192
|
def _handle_request(span, kwargs, instance):
|
|
191
193
|
_set_request_attributes(span, kwargs)
|
|
192
194
|
_set_client_attributes(span, instance)
|
|
@@ -198,6 +200,7 @@ def _handle_request(span, kwargs, instance):
|
|
|
198
200
|
set_tools_attributes(span, kwargs.get("tools"))
|
|
199
201
|
|
|
200
202
|
|
|
203
|
+
@dont_throw
|
|
201
204
|
def _handle_response(
|
|
202
205
|
response,
|
|
203
206
|
span,
|
|
@@ -278,18 +281,15 @@ def _set_prompts(span, messages):
|
|
|
278
281
|
if not span.is_recording() or messages is None:
|
|
279
282
|
return
|
|
280
283
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
content = json.dumps(msg.get("content"))
|
|
284
|
+
for i, msg in enumerate(messages):
|
|
285
|
+
prefix = f"{SpanAttributes.LLM_PROMPTS}.{i}"
|
|
286
|
+
if isinstance(msg.get("content"), str):
|
|
287
|
+
content = msg.get("content")
|
|
288
|
+
elif isinstance(msg.get("content"), list):
|
|
289
|
+
content = json.dumps(msg.get("content"))
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
292
|
-
logger.warning("Failed to set prompts for openai span, error: %s", str(ex))
|
|
291
|
+
_set_span_attribute(span, f"{prefix}.role", msg.get("role"))
|
|
292
|
+
_set_span_attribute(span, f"{prefix}.content", content)
|
|
293
293
|
|
|
294
294
|
|
|
295
295
|
def _set_completions(span, choices):
|
|
@@ -303,6 +303,11 @@ def _set_completions(span, choices):
|
|
|
303
303
|
span, f"{prefix}.finish_reason", choice.get("finish_reason")
|
|
304
304
|
)
|
|
305
305
|
|
|
306
|
+
if choice.get("finish_reason") == "content_filter":
|
|
307
|
+
_set_span_attribute(span, f"{prefix}.role", "assistant")
|
|
308
|
+
_set_span_attribute(span, f"{prefix}.content", "FILTERED")
|
|
309
|
+
return
|
|
310
|
+
|
|
306
311
|
message = choice.get("message")
|
|
307
312
|
if not message:
|
|
308
313
|
return
|
|
@@ -361,7 +366,7 @@ def _set_streaming_token_metrics(
|
|
|
361
366
|
completion_content = ""
|
|
362
367
|
model_name = complete_response.get("model") or None
|
|
363
368
|
|
|
364
|
-
for choice in complete_response.get("choices"):
|
|
369
|
+
for choice in complete_response.get("choices"):
|
|
365
370
|
if choice.get("message") and choice.get("message").get("content"):
|
|
366
371
|
completion_content += choice["message"]["content"]
|
|
367
372
|
|
|
@@ -390,6 +395,7 @@ def _set_streaming_token_metrics(
|
|
|
390
395
|
token_counter.add(completion_usage, attributes=attributes_with_token_type)
|
|
391
396
|
|
|
392
397
|
|
|
398
|
+
@dont_throw
|
|
393
399
|
def _build_from_streaming_response(
|
|
394
400
|
span,
|
|
395
401
|
response,
|
|
@@ -427,9 +433,10 @@ def _build_from_streaming_response(
|
|
|
427
433
|
"stream": True,
|
|
428
434
|
}
|
|
429
435
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
if not is_azure_openai(instance):
|
|
437
|
+
_set_streaming_token_metrics(
|
|
438
|
+
request_kwargs, complete_response, span, token_counter, shared_attributes
|
|
439
|
+
)
|
|
433
440
|
|
|
434
441
|
# choice metrics
|
|
435
442
|
if choice_counter and complete_response.get("choices"):
|
|
@@ -456,6 +463,7 @@ def _build_from_streaming_response(
|
|
|
456
463
|
span.end()
|
|
457
464
|
|
|
458
465
|
|
|
466
|
+
@dont_throw
|
|
459
467
|
async def _abuild_from_streaming_response(
|
|
460
468
|
span,
|
|
461
469
|
response,
|
|
@@ -493,9 +501,10 @@ async def _abuild_from_streaming_response(
|
|
|
493
501
|
"stream": True,
|
|
494
502
|
}
|
|
495
503
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
504
|
+
if not is_azure_openai(instance):
|
|
505
|
+
_set_streaming_token_metrics(
|
|
506
|
+
request_kwargs, complete_response, span, token_counter, shared_attributes
|
|
507
|
+
)
|
|
499
508
|
|
|
500
509
|
# choice metrics
|
|
501
510
|
if choice_counter and complete_response.get("choices"):
|
|
@@ -540,7 +549,7 @@ def _accumulate_stream_items(item, complete_response):
|
|
|
540
549
|
|
|
541
550
|
delta = choice.get("delta")
|
|
542
551
|
|
|
543
|
-
if delta.get("content"):
|
|
552
|
+
if delta and delta.get("content"):
|
|
544
553
|
complete_choice["message"]["content"] += delta.get("content")
|
|
545
|
-
if delta.get("role"):
|
|
554
|
+
if delta and delta.get("role"):
|
|
546
555
|
complete_choice["message"]["role"] = delta.get("role")
|
|
@@ -5,7 +5,7 @@ from opentelemetry import context as context_api
|
|
|
5
5
|
from opentelemetry.semconv.ai import SpanAttributes, LLMRequestTypeValues
|
|
6
6
|
|
|
7
7
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
8
|
-
from opentelemetry.instrumentation.openai.utils import _with_tracer_wrapper
|
|
8
|
+
from opentelemetry.instrumentation.openai.utils import _with_tracer_wrapper, dont_throw
|
|
9
9
|
from opentelemetry.instrumentation.openai.shared import (
|
|
10
10
|
_set_client_attributes,
|
|
11
11
|
_set_request_attributes,
|
|
@@ -48,7 +48,7 @@ def completion_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
48
48
|
|
|
49
49
|
if is_streaming_response(response):
|
|
50
50
|
# span will be closed after the generator is done
|
|
51
|
-
return _build_from_streaming_response(span,
|
|
51
|
+
return _build_from_streaming_response(span, kwargs, response)
|
|
52
52
|
else:
|
|
53
53
|
_handle_response(response, span)
|
|
54
54
|
|
|
@@ -72,7 +72,7 @@ async def acompletion_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
72
72
|
|
|
73
73
|
if is_streaming_response(response):
|
|
74
74
|
# span will be closed after the generator is done
|
|
75
|
-
return _abuild_from_streaming_response(span, response)
|
|
75
|
+
return _abuild_from_streaming_response(span, kwargs, response)
|
|
76
76
|
else:
|
|
77
77
|
_handle_response(response, span)
|
|
78
78
|
|
|
@@ -80,6 +80,7 @@ async def acompletion_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
80
80
|
return response
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
@dont_throw
|
|
83
84
|
def _handle_request(span, kwargs, instance):
|
|
84
85
|
_set_request_attributes(span, kwargs)
|
|
85
86
|
if should_send_prompts():
|
|
@@ -88,6 +89,7 @@ def _handle_request(span, kwargs, instance):
|
|
|
88
89
|
_set_client_attributes(span, instance)
|
|
89
90
|
|
|
90
91
|
|
|
92
|
+
@dont_throw
|
|
91
93
|
def _handle_response(response, span):
|
|
92
94
|
if is_openai_v1():
|
|
93
95
|
response_dict = model_as_dict(response)
|
|
@@ -104,56 +106,65 @@ def _set_prompts(span, prompt):
|
|
|
104
106
|
if not span.is_recording() or not prompt:
|
|
105
107
|
return
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
113
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
114
|
-
logger.warning("Failed to set prompts for openai span, error: %s", str(ex))
|
|
109
|
+
_set_span_attribute(
|
|
110
|
+
span,
|
|
111
|
+
f"{SpanAttributes.LLM_PROMPTS}.0.user",
|
|
112
|
+
prompt[0] if isinstance(prompt, list) else prompt,
|
|
113
|
+
)
|
|
115
114
|
|
|
116
115
|
|
|
116
|
+
@dont_throw
|
|
117
117
|
def _set_completions(span, choices):
|
|
118
118
|
if not span.is_recording() or not choices:
|
|
119
119
|
return
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
_set_span_attribute(span, f"{prefix}.content", choice.get("text"))
|
|
129
|
-
except Exception as e:
|
|
130
|
-
logger.warning("Failed to set completion attributes, error: %s", str(e))
|
|
121
|
+
for choice in choices:
|
|
122
|
+
index = choice.get("index")
|
|
123
|
+
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{index}"
|
|
124
|
+
_set_span_attribute(
|
|
125
|
+
span, f"{prefix}.finish_reason", choice.get("finish_reason")
|
|
126
|
+
)
|
|
127
|
+
_set_span_attribute(span, f"{prefix}.content", choice.get("text"))
|
|
131
128
|
|
|
132
129
|
|
|
133
|
-
|
|
130
|
+
@dont_throw
|
|
131
|
+
def _build_from_streaming_response(span, request_kwargs, response):
|
|
134
132
|
complete_response = {"choices": [], "model": ""}
|
|
135
133
|
for item in response:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
item = model_as_dict(item)
|
|
134
|
+
yield item
|
|
135
|
+
_accumulate_streaming_response(complete_response, item)
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
_set_response_attributes(span, complete_response)
|
|
141
138
|
|
|
142
|
-
|
|
143
|
-
index = choice.get("index")
|
|
144
|
-
if len(complete_response.get("choices")) <= index:
|
|
145
|
-
complete_response["choices"].append({"index": index, "text": ""})
|
|
146
|
-
complete_choice = complete_response.get("choices")[index]
|
|
147
|
-
if choice.get("finish_reason"):
|
|
148
|
-
complete_choice["finish_reason"] = choice.get("finish_reason")
|
|
139
|
+
_set_token_usage(span, request_kwargs, complete_response)
|
|
149
140
|
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
if should_send_prompts():
|
|
142
|
+
_set_completions(span, complete_response.get("choices"))
|
|
152
143
|
|
|
153
|
-
|
|
144
|
+
span.set_status(Status(StatusCode.OK))
|
|
145
|
+
span.end()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dont_throw
|
|
149
|
+
async def _abuild_from_streaming_response(span, request_kwargs, response):
|
|
150
|
+
complete_response = {"choices": [], "model": ""}
|
|
151
|
+
async for item in response:
|
|
152
|
+
yield item
|
|
153
|
+
_accumulate_streaming_response(complete_response, item)
|
|
154
154
|
|
|
155
155
|
_set_response_attributes(span, complete_response)
|
|
156
156
|
|
|
157
|
+
_set_token_usage(span, request_kwargs, complete_response)
|
|
158
|
+
|
|
159
|
+
if should_send_prompts():
|
|
160
|
+
_set_completions(span, complete_response.get("choices"))
|
|
161
|
+
|
|
162
|
+
span.set_status(Status(StatusCode.OK))
|
|
163
|
+
span.end()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dont_throw
|
|
167
|
+
def _set_token_usage(span, request_kwargs, complete_response):
|
|
157
168
|
# use tiktoken calculate token usage
|
|
158
169
|
if should_record_stream_token_usage():
|
|
159
170
|
prompt_usage = -1
|
|
@@ -172,46 +183,35 @@ def _build_from_streaming_response(span, response, request_kwargs=None):
|
|
|
172
183
|
completion_content = ""
|
|
173
184
|
model_name = complete_response.get("model") or None
|
|
174
185
|
|
|
175
|
-
for choice in complete_response.get("choices"):
|
|
186
|
+
for choice in complete_response.get("choices"):
|
|
176
187
|
if choice.get("text"):
|
|
177
188
|
completion_content += choice.get("text")
|
|
178
189
|
|
|
179
190
|
if model_name:
|
|
180
|
-
completion_usage = get_token_count_from_string(
|
|
191
|
+
completion_usage = get_token_count_from_string(
|
|
192
|
+
completion_content, model_name
|
|
193
|
+
)
|
|
181
194
|
|
|
182
195
|
# span record
|
|
183
196
|
_set_span_stream_usage(span, prompt_usage, completion_usage)
|
|
184
197
|
|
|
185
|
-
if should_send_prompts():
|
|
186
|
-
_set_completions(span, complete_response.get("choices"))
|
|
187
198
|
|
|
188
|
-
|
|
189
|
-
|
|
199
|
+
@dont_throw
|
|
200
|
+
def _accumulate_streaming_response(complete_response, item):
|
|
201
|
+
if is_openai_v1():
|
|
202
|
+
item = model_as_dict(item)
|
|
190
203
|
|
|
204
|
+
complete_response["model"] = item.get("model")
|
|
191
205
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
for choice in item.get("choices"):
|
|
200
|
-
index = choice.get("index")
|
|
201
|
-
if len(complete_response.get("choices")) <= index:
|
|
202
|
-
complete_response["choices"].append({"index": index, "text": ""})
|
|
203
|
-
complete_choice = complete_response.get("choices")[index]
|
|
204
|
-
if choice.get("finish_reason"):
|
|
205
|
-
complete_choice["finish_reason"] = choice.get("finish_reason")
|
|
206
|
+
for choice in item.get("choices"):
|
|
207
|
+
index = choice.get("index")
|
|
208
|
+
if len(complete_response.get("choices")) <= index:
|
|
209
|
+
complete_response["choices"].append({"index": index, "text": ""})
|
|
210
|
+
complete_choice = complete_response.get("choices")[index]
|
|
211
|
+
if choice.get("finish_reason"):
|
|
212
|
+
complete_choice["finish_reason"] = choice.get("finish_reason")
|
|
206
213
|
|
|
214
|
+
if choice.get("text"):
|
|
207
215
|
complete_choice["text"] += choice.get("text")
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
_set_response_attributes(span, complete_response)
|
|
212
|
-
|
|
213
|
-
if should_send_prompts():
|
|
214
|
-
_set_completions(span, complete_response.get("choices"))
|
|
215
|
-
|
|
216
|
-
span.set_status(Status(StatusCode.OK))
|
|
217
|
-
span.end()
|
|
217
|
+
return complete_response
|
|
@@ -7,6 +7,7 @@ from opentelemetry.semconv.ai import SpanAttributes, LLMRequestTypeValues
|
|
|
7
7
|
|
|
8
8
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
9
9
|
from opentelemetry.instrumentation.openai.utils import (
|
|
10
|
+
dont_throw,
|
|
10
11
|
start_as_current_span_async,
|
|
11
12
|
_with_embeddings_telemetry_wrapper,
|
|
12
13
|
)
|
|
@@ -144,6 +145,7 @@ async def aembeddings_wrapper(
|
|
|
144
145
|
return response
|
|
145
146
|
|
|
146
147
|
|
|
148
|
+
@dont_throw
|
|
147
149
|
def _handle_request(span, kwargs, instance):
|
|
148
150
|
_set_request_attributes(span, kwargs)
|
|
149
151
|
if should_send_prompts():
|
|
@@ -151,6 +153,7 @@ def _handle_request(span, kwargs, instance):
|
|
|
151
153
|
_set_client_attributes(span, instance)
|
|
152
154
|
|
|
153
155
|
|
|
156
|
+
@dont_throw
|
|
154
157
|
def _handle_response(
|
|
155
158
|
response,
|
|
156
159
|
span,
|
|
@@ -217,17 +220,12 @@ def _set_prompts(span, prompt):
|
|
|
217
220
|
if not span.is_recording() or not prompt:
|
|
218
221
|
return
|
|
219
222
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
f"{SpanAttributes.LLM_PROMPTS}.0.content",
|
|
230
|
-
prompt,
|
|
231
|
-
)
|
|
232
|
-
except Exception as ex: # pylint: disable=broad-except
|
|
233
|
-
logger.warning("Failed to set prompts for openai span, error: %s", str(ex))
|
|
223
|
+
if isinstance(prompt, list):
|
|
224
|
+
for i, p in enumerate(prompt):
|
|
225
|
+
_set_span_attribute(span, f"{SpanAttributes.LLM_PROMPTS}.{i}.content", p)
|
|
226
|
+
else:
|
|
227
|
+
_set_span_attribute(
|
|
228
|
+
span,
|
|
229
|
+
f"{SpanAttributes.LLM_PROMPTS}.0.content",
|
|
230
|
+
prompt,
|
|
231
|
+
)
|
|
@@ -6,14 +6,24 @@ from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
|
6
6
|
from opentelemetry.metrics import Counter, Histogram
|
|
7
7
|
|
|
8
8
|
from opentelemetry.instrumentation.openai import is_openai_v1
|
|
9
|
-
from opentelemetry.instrumentation.openai.shared import
|
|
10
|
-
|
|
9
|
+
from opentelemetry.instrumentation.openai.shared import (
|
|
10
|
+
_get_openai_base_url,
|
|
11
|
+
model_as_dict,
|
|
12
|
+
)
|
|
13
|
+
from opentelemetry.instrumentation.openai.utils import (
|
|
14
|
+
_with_image_gen_metric_wrapper,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
@_with_image_gen_metric_wrapper
|
|
14
|
-
def image_gen_metrics_wrapper(
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
def image_gen_metrics_wrapper(
|
|
20
|
+
duration_histogram: Histogram,
|
|
21
|
+
exception_counter: Counter,
|
|
22
|
+
wrapped,
|
|
23
|
+
instance,
|
|
24
|
+
args,
|
|
25
|
+
kwargs,
|
|
26
|
+
):
|
|
17
27
|
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
18
28
|
return wrapped(*args, **kwargs)
|
|
19
29
|
|
|
@@ -24,7 +34,7 @@ def image_gen_metrics_wrapper(duration_histogram: Histogram,
|
|
|
24
34
|
end_time = time.time()
|
|
25
35
|
except Exception as e: # pylint: disable=broad-except
|
|
26
36
|
end_time = time.time()
|
|
27
|
-
duration = end_time - start_time if
|
|
37
|
+
duration = end_time - start_time if "start_time" in locals() else 0
|
|
28
38
|
|
|
29
39
|
attributes = {
|
|
30
40
|
"error.type": e.__class__.__name__,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from importlib.metadata import version
|
|
2
2
|
from contextlib import asynccontextmanager
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
|
|
6
|
+
import openai
|
|
5
7
|
from opentelemetry.instrumentation.openai.shared.config import Config
|
|
6
8
|
|
|
7
9
|
|
|
@@ -9,6 +11,12 @@ def is_openai_v1():
|
|
|
9
11
|
return version("openai") >= "1.0.0"
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
def is_azure_openai(instance):
|
|
15
|
+
return is_openai_v1() and isinstance(
|
|
16
|
+
instance._client, (openai.AsyncAzureOpenAI, openai.AzureOpenAI)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
12
20
|
def is_metrics_enabled() -> bool:
|
|
13
21
|
return (os.getenv("TRACELOOP_METRICS_ENABLED") or "true").lower() == "true"
|
|
14
22
|
|
|
@@ -99,3 +107,24 @@ def _with_tracer_wrapper(func):
|
|
|
99
107
|
async def start_as_current_span_async(tracer, *args, **kwargs):
|
|
100
108
|
with tracer.start_as_current_span(*args, **kwargs) as span:
|
|
101
109
|
yield span
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def dont_throw(func):
|
|
113
|
+
"""
|
|
114
|
+
A decorator that wraps the passed in function and logs exceptions instead of throwing them.
|
|
115
|
+
|
|
116
|
+
@param func: The function to wrap
|
|
117
|
+
@return: The wrapper function
|
|
118
|
+
"""
|
|
119
|
+
# Obtain a logger specific to the function's module
|
|
120
|
+
logger = logging.getLogger(func.__module__)
|
|
121
|
+
|
|
122
|
+
def wrapper(*args, **kwargs):
|
|
123
|
+
try:
|
|
124
|
+
return func(*args, **kwargs)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.warning("Failed to execute %s, error: %s", func.__name__, str(e))
|
|
127
|
+
if Config.exception_logger:
|
|
128
|
+
Config.exception_logger(e)
|
|
129
|
+
|
|
130
|
+
return wrapper
|
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.16.
|
|
1
|
+
__version__ = "0.16.7"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentelemetry-instrumentation-openai
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.7
|
|
4
4
|
Summary: OpenTelemetry OpenAI instrumentation
|
|
5
5
|
Home-page: https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-openai
|
|
6
6
|
License: Apache-2.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
opentelemetry/instrumentation/openai/__init__.py,sha256=xl3Kvqry9glVhu8VtdknfUE9FpXQ7KWAFqtVlpjE-40,1344
|
|
2
|
+
opentelemetry/instrumentation/openai/shared/__init__.py,sha256=h5H3SCwmBC4WzQ4MPrf3yqMfFb0sVRvTEpyTLZAAtwA,7343
|
|
3
|
+
opentelemetry/instrumentation/openai/shared/chat_wrappers.py,sha256=9u2G8gLbQpo-KZfBVguFMKEA5sJurYNjeQ1kdR17pEY,16993
|
|
4
|
+
opentelemetry/instrumentation/openai/shared/completion_wrappers.py,sha256=-JHfgyxic5I3Wr3Uc_L-U7ztDVFcyovtF37tNLtaW3s,6604
|
|
5
|
+
opentelemetry/instrumentation/openai/shared/config.py,sha256=5uekQEnmYo1o6tsTD2IGc-cVmHUo5KmUC4pOVdrFFNk,102
|
|
6
|
+
opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py,sha256=OIKWoc8Ah26E5Our0lkpGwZpIoJrWE-8WX_gvUrROio,6586
|
|
7
|
+
opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py,sha256=9buL2SHcu4cEWYdoavr8lCxpmWo_1dpciPXpXCofvIs,1888
|
|
8
|
+
opentelemetry/instrumentation/openai/utils.py,sha256=IM5l_MjM7XnO0e7tDGsPml-juQ9SI1QK200X0atiAyE,3357
|
|
9
|
+
opentelemetry/instrumentation/openai/v0/__init__.py,sha256=ngmmYyfTwRQSjTZAvNpBIOHQ1BIUXeQnOQz126Iucp0,6116
|
|
10
|
+
opentelemetry/instrumentation/openai/v1/__init__.py,sha256=6XHk11JhkpZixgMDsjb0b-efd8LlBTG0jjMCyf0fOSo,8652
|
|
11
|
+
opentelemetry/instrumentation/openai/v1/assistant_wrappers.py,sha256=T6Vtdp1fAZdcYjGiTMZwkn4F4DgsltD4p4xLEFW-GhI,5874
|
|
12
|
+
opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py,sha256=SAzYoun2yyOloofyOWtxpm8E2M9TL3Nm8TgKdNyXHuY,2779
|
|
13
|
+
opentelemetry/instrumentation/openai/version.py,sha256=d1jssHDng02ej4s_3Yzl--DPqN1B-xx2byinKhvGh94,23
|
|
14
|
+
opentelemetry_instrumentation_openai-0.16.7.dist-info/METADATA,sha256=ZUfCo0ht5swUkuBriVUHO_SWQdRMqt-8ITyzrpMmkHE,2218
|
|
15
|
+
opentelemetry_instrumentation_openai-0.16.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
16
|
+
opentelemetry_instrumentation_openai-0.16.7.dist-info/entry_points.txt,sha256=vTBfiX5yXji5YHikuJHEOoBZ1TFdPQ1EI4ctd2pZSeE,93
|
|
17
|
+
opentelemetry_instrumentation_openai-0.16.7.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
opentelemetry/instrumentation/openai/__init__.py,sha256=VSQyN0asXvBQD4jAm46xnHRHjKU74Zqzo1UimaC0ws8,1245
|
|
2
|
-
opentelemetry/instrumentation/openai/shared/__init__.py,sha256=o0QGgSMDC8aE2PmV3z6AMdH4nP-SiUQ0w3aKhXXBvzg,8320
|
|
3
|
-
opentelemetry/instrumentation/openai/shared/chat_wrappers.py,sha256=4DliMcFOJCBLGh0nLtdbdG811K_1bxJWAvBJK5Rpd2A,16771
|
|
4
|
-
opentelemetry/instrumentation/openai/shared/completion_wrappers.py,sha256=xg9qNqijT5ZJOk509l9Q7kVHLwo88DUH-Uv4tzSkeOQ,7045
|
|
5
|
-
opentelemetry/instrumentation/openai/shared/config.py,sha256=hd2oUeASejh5bEKot76pvQNONpWKsrw2S3iH3j3XsVs,74
|
|
6
|
-
opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py,sha256=nIcUJonuhjaz_-HgCaX6A0R-NQ0eCvOGbXrfLSI_z10,6773
|
|
7
|
-
opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py,sha256=SnmhXnnO5pyc0x9wvbYb1S_OVM4TYr7qeJPZbs3YxJg,1899
|
|
8
|
-
opentelemetry/instrumentation/openai/utils.py,sha256=l63pdGpBMEy8oLPlt4QrNL35qYW4nf5ItNsZYYZG7jU,2554
|
|
9
|
-
opentelemetry/instrumentation/openai/v0/__init__.py,sha256=ngmmYyfTwRQSjTZAvNpBIOHQ1BIUXeQnOQz126Iucp0,6116
|
|
10
|
-
opentelemetry/instrumentation/openai/v1/__init__.py,sha256=6XHk11JhkpZixgMDsjb0b-efd8LlBTG0jjMCyf0fOSo,8652
|
|
11
|
-
opentelemetry/instrumentation/openai/v1/assistant_wrappers.py,sha256=T6Vtdp1fAZdcYjGiTMZwkn4F4DgsltD4p4xLEFW-GhI,5874
|
|
12
|
-
opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py,sha256=SAzYoun2yyOloofyOWtxpm8E2M9TL3Nm8TgKdNyXHuY,2779
|
|
13
|
-
opentelemetry/instrumentation/openai/version.py,sha256=r0anPjT1FU7ZF59Wu8yrTS7JfVcf5Cbwqs29-uSLGyo,23
|
|
14
|
-
opentelemetry_instrumentation_openai-0.16.5.dist-info/METADATA,sha256=-ds4WFxRIyDpiVcDNlf7Uug5lNO_-623usUUvB0JNkI,2218
|
|
15
|
-
opentelemetry_instrumentation_openai-0.16.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
16
|
-
opentelemetry_instrumentation_openai-0.16.5.dist-info/entry_points.txt,sha256=vTBfiX5yXji5YHikuJHEOoBZ1TFdPQ1EI4ctd2pZSeE,93
|
|
17
|
-
opentelemetry_instrumentation_openai-0.16.5.dist-info/RECORD,,
|
|
File without changes
|