paid-python 0.3.3__py3-none-any.whl → 0.3.5__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.
- paid/_vendor/__init__.py +0 -0
- paid/_vendor/opentelemetry/__init__.py +0 -0
- paid/_vendor/opentelemetry/instrumentation/__init__.py +0 -0
- paid/_vendor/opentelemetry/instrumentation/openai/__init__.py +54 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/__init__.py +399 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1192 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +292 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/config.py +15 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +311 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/event_emitter.py +108 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
- paid/_vendor/opentelemetry/instrumentation/openai/shared/span_utils.py +0 -0
- paid/_vendor/opentelemetry/instrumentation/openai/utils.py +190 -0
- paid/_vendor/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
- paid/_vendor/opentelemetry/instrumentation/openai/v1/__init__.py +358 -0
- paid/_vendor/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +329 -0
- paid/_vendor/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +134 -0
- paid/_vendor/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +996 -0
- paid/_vendor/opentelemetry/instrumentation/openai/version.py +1 -0
- paid/tracing/autoinstrumentation.py +2 -1
- paid/tracing/tracing.py +8 -2
- {paid_python-0.3.3.dist-info → paid_python-0.3.5.dist-info}/METADATA +51 -2
- {paid_python-0.3.3.dist-info → paid_python-0.3.5.dist-info}/RECORD +26 -6
- {paid_python-0.3.3.dist-info → paid_python-0.3.5.dist-info}/LICENSE +0 -0
- {paid_python-0.3.3.dist-info → paid_python-0.3.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
from typing import Collection
|
|
2
|
+
|
|
3
|
+
from opentelemetry._logs import get_logger
|
|
4
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
5
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.chat_wrappers import (
|
|
6
|
+
achat_wrapper,
|
|
7
|
+
chat_wrapper,
|
|
8
|
+
)
|
|
9
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.completion_wrappers import (
|
|
10
|
+
acompletion_wrapper,
|
|
11
|
+
completion_wrapper,
|
|
12
|
+
)
|
|
13
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.config import Config
|
|
14
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.embeddings_wrappers import (
|
|
15
|
+
aembeddings_wrapper,
|
|
16
|
+
embeddings_wrapper,
|
|
17
|
+
)
|
|
18
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.image_gen_wrappers import (
|
|
19
|
+
image_gen_metrics_wrapper,
|
|
20
|
+
)
|
|
21
|
+
from paid._vendor.opentelemetry.instrumentation.openai.utils import is_metrics_enabled
|
|
22
|
+
from paid._vendor.opentelemetry.instrumentation.openai.v1.assistant_wrappers import (
|
|
23
|
+
assistants_create_wrapper,
|
|
24
|
+
messages_list_wrapper,
|
|
25
|
+
runs_create_and_stream_wrapper,
|
|
26
|
+
runs_create_wrapper,
|
|
27
|
+
runs_retrieve_wrapper,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
from paid._vendor.opentelemetry.instrumentation.openai.v1.responses_wrappers import (
|
|
31
|
+
async_responses_cancel_wrapper,
|
|
32
|
+
async_responses_get_or_create_wrapper,
|
|
33
|
+
responses_cancel_wrapper,
|
|
34
|
+
responses_get_or_create_wrapper,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
from paid._vendor.opentelemetry.instrumentation.openai.version import __version__
|
|
38
|
+
from opentelemetry.instrumentation.utils import unwrap
|
|
39
|
+
from opentelemetry.metrics import get_meter
|
|
40
|
+
from opentelemetry.semconv._incubating.metrics import gen_ai_metrics as GenAIMetrics
|
|
41
|
+
from opentelemetry.semconv_ai import Meters
|
|
42
|
+
from opentelemetry.trace import get_tracer
|
|
43
|
+
from wrapt import wrap_function_wrapper
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_instruments = ("openai >= 1.0.0",)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OpenAIV1Instrumentor(BaseInstrumentor):
|
|
50
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
51
|
+
return _instruments
|
|
52
|
+
|
|
53
|
+
def _try_wrap(self, module, function, wrapper):
|
|
54
|
+
"""
|
|
55
|
+
Wrap a function if it exists, otherwise do nothing.
|
|
56
|
+
This is useful for handling cases where the function is not available in
|
|
57
|
+
the older versions of the library.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
module (str): The module to wrap, e.g. "openai.resources.chat.completions"
|
|
61
|
+
function (str): "Object.function" to wrap, e.g. "Completions.parse"
|
|
62
|
+
wrapper (callable): The wrapper to apply to the function.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
wrap_function_wrapper(module, function, wrapper)
|
|
66
|
+
except (AttributeError, ModuleNotFoundError):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def _instrument(self, **kwargs):
|
|
70
|
+
tracer_provider = kwargs.get("tracer_provider")
|
|
71
|
+
tracer = get_tracer(__name__, __version__, tracer_provider)
|
|
72
|
+
|
|
73
|
+
# meter and counters are inited here
|
|
74
|
+
meter_provider = kwargs.get("meter_provider")
|
|
75
|
+
meter = get_meter(__name__, __version__, meter_provider)
|
|
76
|
+
|
|
77
|
+
if not Config.use_legacy_attributes:
|
|
78
|
+
logger_provider = kwargs.get("logger_provider")
|
|
79
|
+
Config.event_logger = get_logger(
|
|
80
|
+
__name__, __version__, logger_provider=logger_provider
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if is_metrics_enabled():
|
|
84
|
+
tokens_histogram = meter.create_histogram(
|
|
85
|
+
name=Meters.LLM_TOKEN_USAGE,
|
|
86
|
+
unit="token",
|
|
87
|
+
description="Measures number of input and output tokens used",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
chat_choice_counter = meter.create_counter(
|
|
91
|
+
name=Meters.LLM_GENERATION_CHOICES,
|
|
92
|
+
unit="choice",
|
|
93
|
+
description="Number of choices returned by chat completions call",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
duration_histogram = meter.create_histogram(
|
|
97
|
+
name=Meters.LLM_OPERATION_DURATION,
|
|
98
|
+
unit="s",
|
|
99
|
+
description="GenAI operation duration",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
chat_exception_counter = meter.create_counter(
|
|
103
|
+
name=Meters.LLM_COMPLETIONS_EXCEPTIONS,
|
|
104
|
+
unit="time",
|
|
105
|
+
description="Number of exceptions occurred during chat completions",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
streaming_time_to_first_token = meter.create_histogram(
|
|
109
|
+
name=GenAIMetrics.GEN_AI_SERVER_TIME_TO_FIRST_TOKEN,
|
|
110
|
+
unit="s",
|
|
111
|
+
description="Time to first token in streaming chat completions",
|
|
112
|
+
)
|
|
113
|
+
streaming_time_to_generate = meter.create_histogram(
|
|
114
|
+
name=Meters.LLM_STREAMING_TIME_TO_GENERATE,
|
|
115
|
+
unit="s",
|
|
116
|
+
description="Time between first token and completion in streaming chat completions",
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
(
|
|
120
|
+
tokens_histogram,
|
|
121
|
+
chat_choice_counter,
|
|
122
|
+
duration_histogram,
|
|
123
|
+
chat_exception_counter,
|
|
124
|
+
streaming_time_to_first_token,
|
|
125
|
+
streaming_time_to_generate,
|
|
126
|
+
) = (None, None, None, None, None, None)
|
|
127
|
+
|
|
128
|
+
wrap_function_wrapper(
|
|
129
|
+
"openai.resources.chat.completions",
|
|
130
|
+
"Completions.create",
|
|
131
|
+
chat_wrapper(
|
|
132
|
+
tracer,
|
|
133
|
+
tokens_histogram,
|
|
134
|
+
chat_choice_counter,
|
|
135
|
+
duration_histogram,
|
|
136
|
+
chat_exception_counter,
|
|
137
|
+
streaming_time_to_first_token,
|
|
138
|
+
streaming_time_to_generate,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
wrap_function_wrapper(
|
|
143
|
+
"openai.resources.completions",
|
|
144
|
+
"Completions.create",
|
|
145
|
+
completion_wrapper(tracer),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if is_metrics_enabled():
|
|
149
|
+
embeddings_vector_size_counter = meter.create_counter(
|
|
150
|
+
name=Meters.LLM_EMBEDDINGS_VECTOR_SIZE,
|
|
151
|
+
unit="element",
|
|
152
|
+
description="he size of returned vector",
|
|
153
|
+
)
|
|
154
|
+
embeddings_exception_counter = meter.create_counter(
|
|
155
|
+
name=Meters.LLM_EMBEDDINGS_EXCEPTIONS,
|
|
156
|
+
unit="time",
|
|
157
|
+
description="Number of exceptions occurred during embeddings operation",
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
(
|
|
161
|
+
tokens_histogram,
|
|
162
|
+
embeddings_vector_size_counter,
|
|
163
|
+
embeddings_exception_counter,
|
|
164
|
+
) = (None, None, None)
|
|
165
|
+
|
|
166
|
+
wrap_function_wrapper(
|
|
167
|
+
"openai.resources.embeddings",
|
|
168
|
+
"Embeddings.create",
|
|
169
|
+
embeddings_wrapper(
|
|
170
|
+
tracer,
|
|
171
|
+
tokens_histogram,
|
|
172
|
+
embeddings_vector_size_counter,
|
|
173
|
+
duration_histogram,
|
|
174
|
+
embeddings_exception_counter,
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
wrap_function_wrapper(
|
|
179
|
+
"openai.resources.chat.completions",
|
|
180
|
+
"AsyncCompletions.create",
|
|
181
|
+
achat_wrapper(
|
|
182
|
+
tracer,
|
|
183
|
+
tokens_histogram,
|
|
184
|
+
chat_choice_counter,
|
|
185
|
+
duration_histogram,
|
|
186
|
+
chat_exception_counter,
|
|
187
|
+
streaming_time_to_first_token,
|
|
188
|
+
streaming_time_to_generate,
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
wrap_function_wrapper(
|
|
192
|
+
"openai.resources.completions",
|
|
193
|
+
"AsyncCompletions.create",
|
|
194
|
+
acompletion_wrapper(tracer),
|
|
195
|
+
)
|
|
196
|
+
wrap_function_wrapper(
|
|
197
|
+
"openai.resources.embeddings",
|
|
198
|
+
"AsyncEmbeddings.create",
|
|
199
|
+
aembeddings_wrapper(
|
|
200
|
+
tracer,
|
|
201
|
+
tokens_histogram,
|
|
202
|
+
embeddings_vector_size_counter,
|
|
203
|
+
duration_histogram,
|
|
204
|
+
embeddings_exception_counter,
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
# in newer versions, Completions.parse are out of beta
|
|
208
|
+
self._try_wrap(
|
|
209
|
+
"openai.resources.chat.completions",
|
|
210
|
+
"Completions.parse",
|
|
211
|
+
chat_wrapper(
|
|
212
|
+
tracer,
|
|
213
|
+
tokens_histogram,
|
|
214
|
+
chat_choice_counter,
|
|
215
|
+
duration_histogram,
|
|
216
|
+
chat_exception_counter,
|
|
217
|
+
streaming_time_to_first_token,
|
|
218
|
+
streaming_time_to_generate,
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
self._try_wrap(
|
|
222
|
+
"openai.resources.chat.completions",
|
|
223
|
+
"AsyncCompletions.parse",
|
|
224
|
+
achat_wrapper(
|
|
225
|
+
tracer,
|
|
226
|
+
tokens_histogram,
|
|
227
|
+
chat_choice_counter,
|
|
228
|
+
duration_histogram,
|
|
229
|
+
chat_exception_counter,
|
|
230
|
+
streaming_time_to_first_token,
|
|
231
|
+
streaming_time_to_generate,
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if is_metrics_enabled():
|
|
236
|
+
image_gen_exception_counter = meter.create_counter(
|
|
237
|
+
name=Meters.LLM_IMAGE_GENERATIONS_EXCEPTIONS,
|
|
238
|
+
unit="time",
|
|
239
|
+
description="Number of exceptions occurred during image generations operation",
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
image_gen_exception_counter = None
|
|
243
|
+
|
|
244
|
+
wrap_function_wrapper(
|
|
245
|
+
"openai.resources.images",
|
|
246
|
+
"Images.generate",
|
|
247
|
+
image_gen_metrics_wrapper(duration_histogram, image_gen_exception_counter),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Beta APIs may not be available consistently in all versions
|
|
251
|
+
self._try_wrap(
|
|
252
|
+
"openai.resources.beta.assistants",
|
|
253
|
+
"Assistants.create",
|
|
254
|
+
assistants_create_wrapper(tracer),
|
|
255
|
+
)
|
|
256
|
+
self._try_wrap(
|
|
257
|
+
"openai.resources.beta.chat.completions",
|
|
258
|
+
"Completions.parse",
|
|
259
|
+
chat_wrapper(
|
|
260
|
+
tracer,
|
|
261
|
+
tokens_histogram,
|
|
262
|
+
chat_choice_counter,
|
|
263
|
+
duration_histogram,
|
|
264
|
+
chat_exception_counter,
|
|
265
|
+
streaming_time_to_first_token,
|
|
266
|
+
streaming_time_to_generate,
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
self._try_wrap(
|
|
270
|
+
"openai.resources.beta.chat.completions",
|
|
271
|
+
"AsyncCompletions.parse",
|
|
272
|
+
achat_wrapper(
|
|
273
|
+
tracer,
|
|
274
|
+
tokens_histogram,
|
|
275
|
+
chat_choice_counter,
|
|
276
|
+
duration_histogram,
|
|
277
|
+
chat_exception_counter,
|
|
278
|
+
streaming_time_to_first_token,
|
|
279
|
+
streaming_time_to_generate,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
self._try_wrap(
|
|
283
|
+
"openai.resources.beta.threads.runs",
|
|
284
|
+
"Runs.create",
|
|
285
|
+
runs_create_wrapper(tracer),
|
|
286
|
+
)
|
|
287
|
+
self._try_wrap(
|
|
288
|
+
"openai.resources.beta.threads.runs",
|
|
289
|
+
"Runs.retrieve",
|
|
290
|
+
runs_retrieve_wrapper(tracer),
|
|
291
|
+
)
|
|
292
|
+
self._try_wrap(
|
|
293
|
+
"openai.resources.beta.threads.runs",
|
|
294
|
+
"Runs.create_and_stream",
|
|
295
|
+
runs_create_and_stream_wrapper(tracer),
|
|
296
|
+
)
|
|
297
|
+
self._try_wrap(
|
|
298
|
+
"openai.resources.beta.threads.messages",
|
|
299
|
+
"Messages.list",
|
|
300
|
+
messages_list_wrapper(tracer),
|
|
301
|
+
)
|
|
302
|
+
self._try_wrap(
|
|
303
|
+
"openai.resources.responses",
|
|
304
|
+
"Responses.create",
|
|
305
|
+
responses_get_or_create_wrapper(tracer),
|
|
306
|
+
)
|
|
307
|
+
self._try_wrap(
|
|
308
|
+
"openai.resources.responses",
|
|
309
|
+
"Responses.retrieve",
|
|
310
|
+
responses_get_or_create_wrapper(tracer),
|
|
311
|
+
)
|
|
312
|
+
self._try_wrap(
|
|
313
|
+
"openai.resources.responses",
|
|
314
|
+
"Responses.cancel",
|
|
315
|
+
responses_cancel_wrapper(tracer),
|
|
316
|
+
)
|
|
317
|
+
self._try_wrap(
|
|
318
|
+
"openai.resources.responses",
|
|
319
|
+
"AsyncResponses.create",
|
|
320
|
+
async_responses_get_or_create_wrapper(tracer),
|
|
321
|
+
)
|
|
322
|
+
self._try_wrap(
|
|
323
|
+
"openai.resources.responses",
|
|
324
|
+
"AsyncResponses.retrieve",
|
|
325
|
+
async_responses_get_or_create_wrapper(tracer),
|
|
326
|
+
)
|
|
327
|
+
self._try_wrap(
|
|
328
|
+
"openai.resources.responses",
|
|
329
|
+
"AsyncResponses.cancel",
|
|
330
|
+
async_responses_cancel_wrapper(tracer),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def _uninstrument(self, **kwargs):
|
|
334
|
+
unwrap("openai.resources.chat.completions", "Completions.create")
|
|
335
|
+
unwrap("openai.resources.completions", "Completions.create")
|
|
336
|
+
unwrap("openai.resources.embeddings", "Embeddings.create")
|
|
337
|
+
unwrap("openai.resources.chat.completions", "AsyncCompletions.create")
|
|
338
|
+
unwrap("openai.resources.completions", "AsyncCompletions.create")
|
|
339
|
+
unwrap("openai.resources.embeddings", "AsyncEmbeddings.create")
|
|
340
|
+
unwrap("openai.resources.images", "Images.generate")
|
|
341
|
+
|
|
342
|
+
# Beta APIs may not be available consistently in all versions
|
|
343
|
+
try:
|
|
344
|
+
unwrap("openai.resources.beta.assistants", "Assistants.create")
|
|
345
|
+
unwrap("openai.resources.beta.chat.completions", "Completions.parse")
|
|
346
|
+
unwrap("openai.resources.beta.chat.completions", "AsyncCompletions.parse")
|
|
347
|
+
unwrap("openai.resources.beta.threads.runs", "Runs.create")
|
|
348
|
+
unwrap("openai.resources.beta.threads.runs", "Runs.retrieve")
|
|
349
|
+
unwrap("openai.resources.beta.threads.runs", "Runs.create_and_stream")
|
|
350
|
+
unwrap("openai.resources.beta.threads.messages", "Messages.list")
|
|
351
|
+
unwrap("openai.resources.responses", "Responses.create")
|
|
352
|
+
unwrap("openai.resources.responses", "Responses.retrieve")
|
|
353
|
+
unwrap("openai.resources.responses", "Responses.cancel")
|
|
354
|
+
unwrap("openai.resources.responses", "AsyncResponses.create")
|
|
355
|
+
unwrap("openai.resources.responses", "AsyncResponses.retrieve")
|
|
356
|
+
unwrap("openai.resources.responses", "AsyncResponses.cancel")
|
|
357
|
+
except ImportError:
|
|
358
|
+
pass
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from opentelemetry import context as context_api
|
|
5
|
+
from opentelemetry import trace
|
|
6
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared import (
|
|
7
|
+
_set_span_attribute,
|
|
8
|
+
model_as_dict,
|
|
9
|
+
)
|
|
10
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.config import Config
|
|
11
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.event_emitter import emit_event
|
|
12
|
+
from paid._vendor.opentelemetry.instrumentation.openai.shared.event_models import (
|
|
13
|
+
ChoiceEvent,
|
|
14
|
+
MessageEvent,
|
|
15
|
+
)
|
|
16
|
+
from paid._vendor.opentelemetry.instrumentation.openai.utils import (
|
|
17
|
+
_with_tracer_wrapper,
|
|
18
|
+
dont_throw,
|
|
19
|
+
should_emit_events,
|
|
20
|
+
)
|
|
21
|
+
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
22
|
+
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
|
23
|
+
from opentelemetry.semconv._incubating.attributes import (
|
|
24
|
+
gen_ai_attributes as GenAIAttributes,
|
|
25
|
+
)
|
|
26
|
+
from opentelemetry.semconv_ai import LLMRequestTypeValues, SpanAttributes
|
|
27
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode
|
|
28
|
+
|
|
29
|
+
from openai._legacy_response import LegacyAPIResponse
|
|
30
|
+
from openai.types.beta.threads.run import Run
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
assistants = {}
|
|
35
|
+
runs = {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@_with_tracer_wrapper
|
|
39
|
+
def assistants_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
40
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
41
|
+
return wrapped(*args, **kwargs)
|
|
42
|
+
|
|
43
|
+
response = wrapped(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
assistants[response.id] = {
|
|
46
|
+
"model": kwargs.get("model"),
|
|
47
|
+
"instructions": kwargs.get("instructions"),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return response
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@_with_tracer_wrapper
|
|
54
|
+
def runs_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
55
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
56
|
+
return wrapped(*args, **kwargs)
|
|
57
|
+
|
|
58
|
+
thread_id = kwargs.get("thread_id")
|
|
59
|
+
instructions = kwargs.get("instructions")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
response = wrapped(*args, **kwargs)
|
|
63
|
+
response_dict = model_as_dict(response)
|
|
64
|
+
|
|
65
|
+
runs[thread_id] = {
|
|
66
|
+
"start_time": time.time_ns(),
|
|
67
|
+
"assistant_id": kwargs.get("assistant_id"),
|
|
68
|
+
"instructions": instructions,
|
|
69
|
+
"run_id": response_dict.get("id"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return response
|
|
73
|
+
except Exception as e:
|
|
74
|
+
runs[thread_id] = {
|
|
75
|
+
"exception": e,
|
|
76
|
+
"end_time": time.time_ns(),
|
|
77
|
+
}
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@_with_tracer_wrapper
|
|
82
|
+
def runs_retrieve_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
83
|
+
@dont_throw
|
|
84
|
+
def process_response(response):
|
|
85
|
+
if type(response) is LegacyAPIResponse:
|
|
86
|
+
parsed_response = response.parse()
|
|
87
|
+
else:
|
|
88
|
+
parsed_response = response
|
|
89
|
+
assert type(parsed_response) is Run
|
|
90
|
+
|
|
91
|
+
if parsed_response.thread_id in runs:
|
|
92
|
+
thread_id = parsed_response.thread_id
|
|
93
|
+
runs[thread_id]["end_time"] = time.time_ns()
|
|
94
|
+
if parsed_response.usage:
|
|
95
|
+
runs[thread_id]["usage"] = parsed_response.usage
|
|
96
|
+
|
|
97
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
98
|
+
return wrapped(*args, **kwargs)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
response = wrapped(*args, **kwargs)
|
|
102
|
+
process_response(response)
|
|
103
|
+
return response
|
|
104
|
+
except Exception as e:
|
|
105
|
+
thread_id = kwargs.get("thread_id")
|
|
106
|
+
if thread_id in runs:
|
|
107
|
+
runs[thread_id]["exception"] = e
|
|
108
|
+
runs[thread_id]["end_time"] = time.time_ns()
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@_with_tracer_wrapper
|
|
113
|
+
def messages_list_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
114
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
115
|
+
return wrapped(*args, **kwargs)
|
|
116
|
+
|
|
117
|
+
id = kwargs.get("thread_id")
|
|
118
|
+
|
|
119
|
+
response = wrapped(*args, **kwargs)
|
|
120
|
+
|
|
121
|
+
response_dict = model_as_dict(response)
|
|
122
|
+
if id not in runs:
|
|
123
|
+
return response
|
|
124
|
+
|
|
125
|
+
run = runs[id]
|
|
126
|
+
messages = sorted(response_dict["data"], key=lambda x: x["created_at"])
|
|
127
|
+
|
|
128
|
+
span = tracer.start_span(
|
|
129
|
+
"openai.assistant.run",
|
|
130
|
+
kind=SpanKind.CLIENT,
|
|
131
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
132
|
+
start_time=run.get("start_time"),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Use the span as current context to ensure events get proper trace context
|
|
136
|
+
with trace.use_span(span, end_on_exit=False):
|
|
137
|
+
if exception := run.get("exception"):
|
|
138
|
+
span.set_attribute(ERROR_TYPE, exception.__class__.__name__)
|
|
139
|
+
span.record_exception(exception)
|
|
140
|
+
span.set_status(Status(StatusCode.ERROR, str(exception)))
|
|
141
|
+
span.end()
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
prompt_index = 0
|
|
145
|
+
if assistants.get(run["assistant_id"]) is not None or Config.enrich_assistant:
|
|
146
|
+
if Config.enrich_assistant:
|
|
147
|
+
assistant = model_as_dict(
|
|
148
|
+
instance._client.beta.assistants.retrieve(run["assistant_id"])
|
|
149
|
+
)
|
|
150
|
+
assistants[run["assistant_id"]] = assistant
|
|
151
|
+
else:
|
|
152
|
+
assistant = assistants[run["assistant_id"]]
|
|
153
|
+
|
|
154
|
+
_set_span_attribute(
|
|
155
|
+
span,
|
|
156
|
+
GenAIAttributes.GEN_AI_SYSTEM,
|
|
157
|
+
"openai",
|
|
158
|
+
)
|
|
159
|
+
_set_span_attribute(
|
|
160
|
+
span,
|
|
161
|
+
GenAIAttributes.GEN_AI_REQUEST_MODEL,
|
|
162
|
+
assistant["model"],
|
|
163
|
+
)
|
|
164
|
+
_set_span_attribute(
|
|
165
|
+
span,
|
|
166
|
+
GenAIAttributes.GEN_AI_RESPONSE_MODEL,
|
|
167
|
+
assistant["model"],
|
|
168
|
+
)
|
|
169
|
+
if should_emit_events():
|
|
170
|
+
emit_event(MessageEvent(content=assistant["instructions"], role="system"))
|
|
171
|
+
else:
|
|
172
|
+
_set_span_attribute(
|
|
173
|
+
span, f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.role", "system"
|
|
174
|
+
)
|
|
175
|
+
_set_span_attribute(
|
|
176
|
+
span,
|
|
177
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.content",
|
|
178
|
+
assistant["instructions"],
|
|
179
|
+
)
|
|
180
|
+
prompt_index += 1
|
|
181
|
+
_set_span_attribute(
|
|
182
|
+
span, f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.role", "system"
|
|
183
|
+
)
|
|
184
|
+
_set_span_attribute(
|
|
185
|
+
span,
|
|
186
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.content",
|
|
187
|
+
run["instructions"],
|
|
188
|
+
)
|
|
189
|
+
if should_emit_events():
|
|
190
|
+
emit_event(MessageEvent(content=run["instructions"], role="system"))
|
|
191
|
+
prompt_index += 1
|
|
192
|
+
|
|
193
|
+
completion_index = 0
|
|
194
|
+
for msg in messages:
|
|
195
|
+
prefix = f"{GenAIAttributes.GEN_AI_COMPLETION}.{completion_index}"
|
|
196
|
+
content = msg.get("content")
|
|
197
|
+
|
|
198
|
+
message_content = content[0].get("text").get("value")
|
|
199
|
+
message_role = msg.get("role")
|
|
200
|
+
if message_role in ["user", "system"]:
|
|
201
|
+
if should_emit_events():
|
|
202
|
+
emit_event(MessageEvent(content=message_content, role=message_role))
|
|
203
|
+
else:
|
|
204
|
+
_set_span_attribute(
|
|
205
|
+
span,
|
|
206
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.role",
|
|
207
|
+
message_role,
|
|
208
|
+
)
|
|
209
|
+
_set_span_attribute(
|
|
210
|
+
span,
|
|
211
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{prompt_index}.content",
|
|
212
|
+
message_content,
|
|
213
|
+
)
|
|
214
|
+
prompt_index += 1
|
|
215
|
+
else:
|
|
216
|
+
if should_emit_events():
|
|
217
|
+
emit_event(
|
|
218
|
+
ChoiceEvent(
|
|
219
|
+
index=completion_index,
|
|
220
|
+
message={"content": message_content, "role": message_role},
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
else:
|
|
224
|
+
_set_span_attribute(span, f"{prefix}.role", msg.get("role"))
|
|
225
|
+
_set_span_attribute(span, f"{prefix}.content", message_content)
|
|
226
|
+
_set_span_attribute(
|
|
227
|
+
span, f"gen_ai.response.{completion_index}.id", msg.get("id")
|
|
228
|
+
)
|
|
229
|
+
completion_index += 1
|
|
230
|
+
|
|
231
|
+
if run.get("usage"):
|
|
232
|
+
usage_dict = model_as_dict(run.get("usage"))
|
|
233
|
+
_set_span_attribute(
|
|
234
|
+
span,
|
|
235
|
+
GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS,
|
|
236
|
+
usage_dict.get("completion_tokens"),
|
|
237
|
+
)
|
|
238
|
+
_set_span_attribute(
|
|
239
|
+
span,
|
|
240
|
+
GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS,
|
|
241
|
+
usage_dict.get("prompt_tokens"),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
span.end(run.get("end_time"))
|
|
245
|
+
|
|
246
|
+
return response
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@_with_tracer_wrapper
|
|
250
|
+
def runs_create_and_stream_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
251
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
252
|
+
return wrapped(*args, **kwargs)
|
|
253
|
+
|
|
254
|
+
assistant_id = kwargs.get("assistant_id")
|
|
255
|
+
instructions = kwargs.get("instructions")
|
|
256
|
+
|
|
257
|
+
span = tracer.start_span(
|
|
258
|
+
"openai.assistant.run_stream",
|
|
259
|
+
kind=SpanKind.CLIENT,
|
|
260
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Use the span as current context to ensure events get proper trace context
|
|
264
|
+
with trace.use_span(span, end_on_exit=False):
|
|
265
|
+
i = 0
|
|
266
|
+
if assistants.get(assistant_id) is not None or Config.enrich_assistant:
|
|
267
|
+
if Config.enrich_assistant:
|
|
268
|
+
assistant = model_as_dict(
|
|
269
|
+
instance._client.beta.assistants.retrieve(assistant_id)
|
|
270
|
+
)
|
|
271
|
+
assistants[assistant_id] = assistant
|
|
272
|
+
else:
|
|
273
|
+
assistant = assistants[assistant_id]
|
|
274
|
+
|
|
275
|
+
_set_span_attribute(
|
|
276
|
+
span, GenAIAttributes.GEN_AI_REQUEST_MODEL, assistants[assistant_id]["model"]
|
|
277
|
+
)
|
|
278
|
+
_set_span_attribute(
|
|
279
|
+
span,
|
|
280
|
+
GenAIAttributes.GEN_AI_SYSTEM,
|
|
281
|
+
"openai",
|
|
282
|
+
)
|
|
283
|
+
_set_span_attribute(
|
|
284
|
+
span,
|
|
285
|
+
GenAIAttributes.GEN_AI_RESPONSE_MODEL,
|
|
286
|
+
assistants[assistant_id]["model"],
|
|
287
|
+
)
|
|
288
|
+
if should_emit_events():
|
|
289
|
+
emit_event(
|
|
290
|
+
MessageEvent(
|
|
291
|
+
content=assistants[assistant_id]["instructions"], role="system"
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
_set_span_attribute(
|
|
296
|
+
span, f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.role", "system"
|
|
297
|
+
)
|
|
298
|
+
_set_span_attribute(
|
|
299
|
+
span,
|
|
300
|
+
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.content",
|
|
301
|
+
assistants[assistant_id]["instructions"],
|
|
302
|
+
)
|
|
303
|
+
i += 1
|
|
304
|
+
if should_emit_events():
|
|
305
|
+
emit_event(MessageEvent(content=instructions, role="system"))
|
|
306
|
+
else:
|
|
307
|
+
_set_span_attribute(span, f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.role", "system")
|
|
308
|
+
_set_span_attribute(
|
|
309
|
+
span, f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.content", instructions
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
from paid._vendor.opentelemetry.instrumentation.openai.v1.event_handler_wrapper import (
|
|
313
|
+
EventHandleWrapper,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
kwargs["event_handler"] = EventHandleWrapper(
|
|
317
|
+
original_handler=kwargs["event_handler"],
|
|
318
|
+
span=span,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
response = wrapped(*args, **kwargs)
|
|
323
|
+
return response
|
|
324
|
+
except Exception as e:
|
|
325
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
326
|
+
span.record_exception(e)
|
|
327
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
328
|
+
span.end()
|
|
329
|
+
raise
|