opentelemetry-instrumentation-openai 0.15.9__tar.gz → 0.15.11__tar.gz
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-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/PKG-INFO +1 -1
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +166 -49
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/v1/__init__.py +9 -1
- opentelemetry_instrumentation_openai-0.15.11/opentelemetry/instrumentation/openai/version.py +1 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/pyproject.toml +2 -2
- opentelemetry_instrumentation_openai-0.15.9/opentelemetry/instrumentation/openai/version.py +0 -1
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/README.md +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/config.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/utils.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/v0/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.15.9 → opentelemetry_instrumentation_openai-0.15.11}/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentelemetry-instrumentation-openai
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.11
|
|
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
|
|
@@ -8,7 +8,6 @@ from opentelemetry.semconv.ai import SpanAttributes, LLMRequestTypeValues
|
|
|
8
8
|
|
|
9
9
|
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
10
10
|
from opentelemetry.instrumentation.openai.utils import (
|
|
11
|
-
_with_tracer_wrapper,
|
|
12
11
|
_with_chat_telemetry_wrapper,
|
|
13
12
|
)
|
|
14
13
|
from opentelemetry.instrumentation.openai.shared import (
|
|
@@ -95,7 +94,7 @@ def chat_wrapper(
|
|
|
95
94
|
streaming_time_to_first_token,
|
|
96
95
|
streaming_time_to_generate,
|
|
97
96
|
start_time,
|
|
98
|
-
kwargs
|
|
97
|
+
kwargs,
|
|
99
98
|
)
|
|
100
99
|
|
|
101
100
|
duration = end_time - start_time
|
|
@@ -114,8 +113,20 @@ def chat_wrapper(
|
|
|
114
113
|
return response
|
|
115
114
|
|
|
116
115
|
|
|
117
|
-
@
|
|
118
|
-
async def achat_wrapper(
|
|
116
|
+
@_with_chat_telemetry_wrapper
|
|
117
|
+
async def achat_wrapper(
|
|
118
|
+
tracer: Tracer,
|
|
119
|
+
token_counter: Counter,
|
|
120
|
+
choice_counter: Counter,
|
|
121
|
+
duration_histogram: Histogram,
|
|
122
|
+
exception_counter: Counter,
|
|
123
|
+
streaming_time_to_first_token: Histogram,
|
|
124
|
+
streaming_time_to_generate: Histogram,
|
|
125
|
+
wrapped,
|
|
126
|
+
instance,
|
|
127
|
+
args,
|
|
128
|
+
kwargs,
|
|
129
|
+
):
|
|
119
130
|
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
120
131
|
return wrapped(*args, **kwargs)
|
|
121
132
|
|
|
@@ -125,13 +136,52 @@ async def achat_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
125
136
|
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value},
|
|
126
137
|
)
|
|
127
138
|
_handle_request(span, kwargs, instance)
|
|
128
|
-
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
start_time = time.time()
|
|
142
|
+
response = await wrapped(*args, **kwargs)
|
|
143
|
+
end_time = time.time()
|
|
144
|
+
except Exception as e: # pylint: disable=broad-except
|
|
145
|
+
end_time = time.time()
|
|
146
|
+
duration = end_time - start_time if "start_time" in locals() else 0
|
|
147
|
+
|
|
148
|
+
attributes = {
|
|
149
|
+
"error.type": e.__class__.__name__,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if duration > 0 and duration_histogram:
|
|
153
|
+
duration_histogram.record(duration, attributes=attributes)
|
|
154
|
+
if exception_counter:
|
|
155
|
+
exception_counter.add(1, attributes=attributes)
|
|
156
|
+
|
|
157
|
+
raise e
|
|
129
158
|
|
|
130
159
|
if is_streaming_response(response):
|
|
131
160
|
# span will be closed after the generator is done
|
|
132
|
-
return _abuild_from_streaming_response(
|
|
161
|
+
return _abuild_from_streaming_response(
|
|
162
|
+
span,
|
|
163
|
+
response,
|
|
164
|
+
instance,
|
|
165
|
+
token_counter,
|
|
166
|
+
choice_counter,
|
|
167
|
+
duration_histogram,
|
|
168
|
+
streaming_time_to_first_token,
|
|
169
|
+
streaming_time_to_generate,
|
|
170
|
+
start_time,
|
|
171
|
+
kwargs,
|
|
172
|
+
)
|
|
133
173
|
|
|
134
|
-
|
|
174
|
+
duration = end_time - start_time
|
|
175
|
+
|
|
176
|
+
_handle_response(
|
|
177
|
+
response,
|
|
178
|
+
span,
|
|
179
|
+
instance,
|
|
180
|
+
token_counter,
|
|
181
|
+
choice_counter,
|
|
182
|
+
duration_histogram,
|
|
183
|
+
duration,
|
|
184
|
+
)
|
|
135
185
|
span.end()
|
|
136
186
|
|
|
137
187
|
return response
|
|
@@ -285,6 +335,61 @@ def _set_completions(span, choices):
|
|
|
285
335
|
)
|
|
286
336
|
|
|
287
337
|
|
|
338
|
+
def _set_streaming_token_metrics(
|
|
339
|
+
request_kwargs, complete_response, span, token_counter, shared_attributes
|
|
340
|
+
):
|
|
341
|
+
# use tiktoken calculate token usage
|
|
342
|
+
if not should_record_stream_token_usage():
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
# kwargs={'model': 'gpt-3.5', 'messages': [{'role': 'user', 'content': '...'}], 'stream': True}
|
|
346
|
+
prompt_usage = -1
|
|
347
|
+
completion_usage = -1
|
|
348
|
+
|
|
349
|
+
# prompt_usage
|
|
350
|
+
if request_kwargs and request_kwargs.get("messages"):
|
|
351
|
+
prompt_content = ""
|
|
352
|
+
model_name = request_kwargs.get("model") or None
|
|
353
|
+
for msg in request_kwargs.get("messages"):
|
|
354
|
+
if msg.get("content"):
|
|
355
|
+
prompt_content += msg.get("content")
|
|
356
|
+
if model_name:
|
|
357
|
+
prompt_usage = get_token_count_from_string(prompt_content, model_name)
|
|
358
|
+
|
|
359
|
+
# completion_usage
|
|
360
|
+
if complete_response.get("choices"):
|
|
361
|
+
completion_content = ""
|
|
362
|
+
model_name = complete_response.get("model") or None
|
|
363
|
+
|
|
364
|
+
for choice in complete_response.get("choices"): # type: dict
|
|
365
|
+
if choice.get("message") and choice.get("message").get("content"):
|
|
366
|
+
completion_content += choice["message"]["content"]
|
|
367
|
+
|
|
368
|
+
if model_name:
|
|
369
|
+
completion_usage = get_token_count_from_string(
|
|
370
|
+
completion_content, model_name
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# span record
|
|
374
|
+
_set_span_stream_usage(span, prompt_usage, completion_usage)
|
|
375
|
+
|
|
376
|
+
# metrics record
|
|
377
|
+
if token_counter:
|
|
378
|
+
if type(prompt_usage) is int and prompt_usage >= 0:
|
|
379
|
+
attributes_with_token_type = {
|
|
380
|
+
**shared_attributes,
|
|
381
|
+
"llm.usage.token_type": "prompt",
|
|
382
|
+
}
|
|
383
|
+
token_counter.add(prompt_usage, attributes=attributes_with_token_type)
|
|
384
|
+
|
|
385
|
+
if type(completion_usage) is int and completion_usage >= 0:
|
|
386
|
+
attributes_with_token_type = {
|
|
387
|
+
**shared_attributes,
|
|
388
|
+
"llm.usage.token_type": "completion",
|
|
389
|
+
}
|
|
390
|
+
token_counter.add(completion_usage, attributes=attributes_with_token_type)
|
|
391
|
+
|
|
392
|
+
|
|
288
393
|
def _build_from_streaming_response(
|
|
289
394
|
span,
|
|
290
395
|
response,
|
|
@@ -295,7 +400,7 @@ def _build_from_streaming_response(
|
|
|
295
400
|
streaming_time_to_first_token=None,
|
|
296
401
|
streaming_time_to_generate=None,
|
|
297
402
|
start_time=None,
|
|
298
|
-
request_kwargs=None
|
|
403
|
+
request_kwargs=None,
|
|
299
404
|
):
|
|
300
405
|
complete_response = {"choices": [], "model": ""}
|
|
301
406
|
|
|
@@ -322,46 +427,9 @@ def _build_from_streaming_response(
|
|
|
322
427
|
"stream": True,
|
|
323
428
|
}
|
|
324
429
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
prompt_usage = -1
|
|
329
|
-
completion_usage = -1
|
|
330
|
-
|
|
331
|
-
# prompt_usage
|
|
332
|
-
if request_kwargs and request_kwargs.get("messages"):
|
|
333
|
-
prompt_content = ""
|
|
334
|
-
model_name = request_kwargs.get("model") or None
|
|
335
|
-
for msg in request_kwargs.get("messages"):
|
|
336
|
-
if msg.get("content"):
|
|
337
|
-
prompt_content += msg.get("content")
|
|
338
|
-
if model_name:
|
|
339
|
-
prompt_usage = get_token_count_from_string(prompt_content, model_name)
|
|
340
|
-
|
|
341
|
-
# completion_usage
|
|
342
|
-
if complete_response.get("choices"):
|
|
343
|
-
completion_content = ""
|
|
344
|
-
model_name = complete_response.get("model") or None
|
|
345
|
-
|
|
346
|
-
for choice in complete_response.get("choices"): # type: dict
|
|
347
|
-
if choice.get("message") and choice.get("message").get("content"):
|
|
348
|
-
completion_content += choice["message"]["content"]
|
|
349
|
-
|
|
350
|
-
if model_name:
|
|
351
|
-
completion_usage = get_token_count_from_string(completion_content, model_name)
|
|
352
|
-
|
|
353
|
-
# span record
|
|
354
|
-
_set_span_stream_usage(span, prompt_usage, completion_usage)
|
|
355
|
-
|
|
356
|
-
# metrics record
|
|
357
|
-
if token_counter:
|
|
358
|
-
if type(prompt_usage) is int and prompt_usage >= 0:
|
|
359
|
-
attributes_with_token_type = {**shared_attributes, "llm.usage.token_type": "prompt"}
|
|
360
|
-
token_counter.add(prompt_usage, attributes=attributes_with_token_type)
|
|
361
|
-
|
|
362
|
-
if type(completion_usage) is int and completion_usage >= 0:
|
|
363
|
-
attributes_with_token_type = {**shared_attributes, "llm.usage.token_type": "completion"}
|
|
364
|
-
token_counter.add(completion_usage, attributes=attributes_with_token_type)
|
|
430
|
+
_set_streaming_token_metrics(
|
|
431
|
+
request_kwargs, complete_response, span, token_counter, shared_attributes
|
|
432
|
+
)
|
|
365
433
|
|
|
366
434
|
# choice metrics
|
|
367
435
|
if choice_counter and complete_response.get("choices"):
|
|
@@ -388,14 +456,63 @@ def _build_from_streaming_response(
|
|
|
388
456
|
span.end()
|
|
389
457
|
|
|
390
458
|
|
|
391
|
-
async def _abuild_from_streaming_response(
|
|
459
|
+
async def _abuild_from_streaming_response(
|
|
460
|
+
span,
|
|
461
|
+
response,
|
|
462
|
+
instance=None,
|
|
463
|
+
token_counter=None,
|
|
464
|
+
choice_counter=None,
|
|
465
|
+
duration_histogram=None,
|
|
466
|
+
streaming_time_to_first_token=None,
|
|
467
|
+
streaming_time_to_generate=None,
|
|
468
|
+
start_time=None,
|
|
469
|
+
request_kwargs=None,
|
|
470
|
+
):
|
|
392
471
|
complete_response = {"choices": [], "model": ""}
|
|
472
|
+
|
|
473
|
+
first_token = True
|
|
474
|
+
time_of_first_token = start_time # will be updated when first token is received
|
|
475
|
+
|
|
393
476
|
async for item in response:
|
|
477
|
+
span.add_event(name="llm.content.completion.chunk")
|
|
478
|
+
|
|
394
479
|
item_to_yield = item
|
|
480
|
+
|
|
481
|
+
if first_token and streaming_time_to_first_token:
|
|
482
|
+
time_of_first_token = time.time()
|
|
483
|
+
streaming_time_to_first_token.record(time_of_first_token - start_time)
|
|
484
|
+
first_token = False
|
|
485
|
+
|
|
395
486
|
_accumulate_stream_items(item, complete_response)
|
|
396
487
|
|
|
397
488
|
yield item_to_yield
|
|
398
489
|
|
|
490
|
+
shared_attributes = {
|
|
491
|
+
"llm.response.model": complete_response.get("model") or None,
|
|
492
|
+
"server.address": _get_openai_base_url(instance),
|
|
493
|
+
"stream": True,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
_set_streaming_token_metrics(
|
|
497
|
+
request_kwargs, complete_response, span, token_counter, shared_attributes
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# choice metrics
|
|
501
|
+
if choice_counter and complete_response.get("choices"):
|
|
502
|
+
_set_choice_counter_metrics(
|
|
503
|
+
choice_counter, complete_response.get("choices"), shared_attributes
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# duration metrics
|
|
507
|
+
if start_time and isinstance(start_time, (float, int)):
|
|
508
|
+
duration = time.time() - start_time
|
|
509
|
+
else:
|
|
510
|
+
duration = None
|
|
511
|
+
if duration and isinstance(duration, (float, int)) and duration_histogram:
|
|
512
|
+
duration_histogram.record(duration, attributes=shared_attributes)
|
|
513
|
+
if streaming_time_to_generate and time_of_first_token:
|
|
514
|
+
streaming_time_to_generate.record(time.time() - time_of_first_token)
|
|
515
|
+
|
|
399
516
|
_set_response_attributes(span, complete_response)
|
|
400
517
|
|
|
401
518
|
if should_send_prompts():
|
|
@@ -160,7 +160,15 @@ class OpenAIV1Instrumentor(BaseInstrumentor):
|
|
|
160
160
|
wrap_function_wrapper(
|
|
161
161
|
"openai.resources.chat.completions",
|
|
162
162
|
"AsyncCompletions.create",
|
|
163
|
-
achat_wrapper(
|
|
163
|
+
achat_wrapper(
|
|
164
|
+
tracer,
|
|
165
|
+
chat_token_counter,
|
|
166
|
+
chat_choice_counter,
|
|
167
|
+
chat_duration_histogram,
|
|
168
|
+
chat_exception_counter,
|
|
169
|
+
streaming_time_to_first_token,
|
|
170
|
+
streaming_time_to_generate,
|
|
171
|
+
),
|
|
164
172
|
)
|
|
165
173
|
wrap_function_wrapper(
|
|
166
174
|
"openai.resources.completions",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.15.11"
|
|
@@ -8,7 +8,7 @@ show_missing = true
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry]
|
|
10
10
|
name = "opentelemetry-instrumentation-openai"
|
|
11
|
-
version = "0.15.
|
|
11
|
+
version = "0.15.11"
|
|
12
12
|
description = "OpenTelemetry OpenAI instrumentation"
|
|
13
13
|
authors = [
|
|
14
14
|
"Gal Kleinman <gal@traceloop.com>",
|
|
@@ -34,7 +34,7 @@ autopep8 = "2.1.0"
|
|
|
34
34
|
flake8 = "7.0.0"
|
|
35
35
|
|
|
36
36
|
[tool.poetry.group.test.dependencies]
|
|
37
|
-
pytest = "8.1.
|
|
37
|
+
pytest = "8.1.1"
|
|
38
38
|
pytest-sugar = "1.0.0"
|
|
39
39
|
vcrpy = "^6.0.1"
|
|
40
40
|
pytest-recording = "^0.13.1"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.15.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|