opentelemetry-instrumentation-openai 0.44.1__tar.gz → 0.44.3__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.
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/PKG-INFO +1 -1
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +167 -135
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +39 -34
- opentelemetry_instrumentation_openai-0.44.3/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +326 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +19 -2
- opentelemetry_instrumentation_openai-0.44.3/opentelemetry/instrumentation/openai/version.py +1 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/pyproject.toml +2 -2
- opentelemetry_instrumentation_openai-0.44.1/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +0 -318
- opentelemetry_instrumentation_openai-0.44.1/opentelemetry/instrumentation/openai/version.py +0 -1
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/README.md +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/config.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/event_emitter.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/event_models.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/shared/span_utils.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/utils.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/v0/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/v1/__init__.py +0 -0
- {opentelemetry_instrumentation_openai-0.44.1 → opentelemetry_instrumentation_openai-0.44.3}/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +0 -0
|
@@ -7,6 +7,7 @@ from functools import singledispatch
|
|
|
7
7
|
from typing import List, Optional, Union
|
|
8
8
|
|
|
9
9
|
from opentelemetry import context as context_api
|
|
10
|
+
import pydantic
|
|
10
11
|
from opentelemetry.instrumentation.openai.shared import (
|
|
11
12
|
OPENAI_LLM_USAGE_TOKEN_TYPES,
|
|
12
13
|
_get_openai_base_url,
|
|
@@ -47,12 +48,10 @@ from opentelemetry.semconv_ai import (
|
|
|
47
48
|
SpanAttributes,
|
|
48
49
|
)
|
|
49
50
|
from opentelemetry.trace import SpanKind, Tracer
|
|
51
|
+
from opentelemetry import trace
|
|
50
52
|
from opentelemetry.trace.status import Status, StatusCode
|
|
51
53
|
from wrapt import ObjectProxy
|
|
52
54
|
|
|
53
|
-
from openai.types.chat import ChatCompletionMessageToolCall
|
|
54
|
-
from openai.types.chat.chat_completion_message import FunctionCall
|
|
55
|
-
|
|
56
55
|
SPAN_NAME = "openai.chat"
|
|
57
56
|
PROMPT_FILTER_KEY = "prompt_filter_results"
|
|
58
57
|
CONTENT_FILTER_KEY = "content_filter_results"
|
|
@@ -88,75 +87,77 @@ def chat_wrapper(
|
|
|
88
87
|
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value},
|
|
89
88
|
)
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
duration_histogram.record(duration, attributes=attributes)
|
|
106
|
-
if exception_counter:
|
|
107
|
-
exception_counter.add(1, attributes=attributes)
|
|
108
|
-
|
|
109
|
-
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
110
|
-
span.record_exception(e)
|
|
111
|
-
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
112
|
-
span.end()
|
|
90
|
+
# Use the span as current context to ensure events get proper trace context
|
|
91
|
+
with trace.use_span(span, end_on_exit=False):
|
|
92
|
+
run_async(_handle_request(span, kwargs, instance))
|
|
93
|
+
try:
|
|
94
|
+
start_time = time.time()
|
|
95
|
+
response = wrapped(*args, **kwargs)
|
|
96
|
+
end_time = time.time()
|
|
97
|
+
except Exception as e: # pylint: disable=broad-except
|
|
98
|
+
end_time = time.time()
|
|
99
|
+
duration = end_time - start_time if "start_time" in locals() else 0
|
|
100
|
+
|
|
101
|
+
attributes = {
|
|
102
|
+
"error.type": e.__class__.__name__,
|
|
103
|
+
}
|
|
113
104
|
|
|
114
|
-
|
|
105
|
+
if duration > 0 and duration_histogram:
|
|
106
|
+
duration_histogram.record(duration, attributes=attributes)
|
|
107
|
+
if exception_counter:
|
|
108
|
+
exception_counter.add(1, attributes=attributes)
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
span,
|
|
121
|
-
response,
|
|
122
|
-
instance,
|
|
123
|
-
token_counter,
|
|
124
|
-
choice_counter,
|
|
125
|
-
duration_histogram,
|
|
126
|
-
streaming_time_to_first_token,
|
|
127
|
-
streaming_time_to_generate,
|
|
128
|
-
start_time,
|
|
129
|
-
kwargs,
|
|
130
|
-
)
|
|
131
|
-
else:
|
|
132
|
-
return _build_from_streaming_response(
|
|
133
|
-
span,
|
|
134
|
-
response,
|
|
135
|
-
instance,
|
|
136
|
-
token_counter,
|
|
137
|
-
choice_counter,
|
|
138
|
-
duration_histogram,
|
|
139
|
-
streaming_time_to_first_token,
|
|
140
|
-
streaming_time_to_generate,
|
|
141
|
-
start_time,
|
|
142
|
-
kwargs,
|
|
143
|
-
)
|
|
110
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
111
|
+
span.record_exception(e)
|
|
112
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
113
|
+
span.end()
|
|
144
114
|
|
|
145
|
-
|
|
115
|
+
raise
|
|
146
116
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
if is_streaming_response(response):
|
|
118
|
+
# span will be closed after the generator is done
|
|
119
|
+
if is_openai_v1():
|
|
120
|
+
return ChatStream(
|
|
121
|
+
span,
|
|
122
|
+
response,
|
|
123
|
+
instance,
|
|
124
|
+
token_counter,
|
|
125
|
+
choice_counter,
|
|
126
|
+
duration_histogram,
|
|
127
|
+
streaming_time_to_first_token,
|
|
128
|
+
streaming_time_to_generate,
|
|
129
|
+
start_time,
|
|
130
|
+
kwargs,
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
return _build_from_streaming_response(
|
|
134
|
+
span,
|
|
135
|
+
response,
|
|
136
|
+
instance,
|
|
137
|
+
token_counter,
|
|
138
|
+
choice_counter,
|
|
139
|
+
duration_histogram,
|
|
140
|
+
streaming_time_to_first_token,
|
|
141
|
+
streaming_time_to_generate,
|
|
142
|
+
start_time,
|
|
143
|
+
kwargs,
|
|
144
|
+
)
|
|
156
145
|
|
|
157
|
-
|
|
146
|
+
duration = end_time - start_time
|
|
158
147
|
|
|
159
|
-
|
|
148
|
+
_handle_response(
|
|
149
|
+
response,
|
|
150
|
+
span,
|
|
151
|
+
instance,
|
|
152
|
+
token_counter,
|
|
153
|
+
choice_counter,
|
|
154
|
+
duration_histogram,
|
|
155
|
+
duration,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
span.end()
|
|
159
|
+
|
|
160
|
+
return response
|
|
160
161
|
|
|
161
162
|
|
|
162
163
|
@_with_chat_telemetry_wrapper
|
|
@@ -184,78 +185,80 @@ async def achat_wrapper(
|
|
|
184
185
|
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value},
|
|
185
186
|
)
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
# Use the span as current context to ensure events get proper trace context
|
|
189
|
+
with trace.use_span(span, end_on_exit=False):
|
|
190
|
+
await _handle_request(span, kwargs, instance)
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if duration > 0 and duration_histogram:
|
|
204
|
-
duration_histogram.record(duration, attributes=attributes)
|
|
205
|
-
if exception_counter:
|
|
206
|
-
exception_counter.add(1, attributes=attributes)
|
|
207
|
-
|
|
208
|
-
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
209
|
-
span.record_exception(e)
|
|
210
|
-
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
211
|
-
span.end()
|
|
192
|
+
try:
|
|
193
|
+
start_time = time.time()
|
|
194
|
+
response = await wrapped(*args, **kwargs)
|
|
195
|
+
end_time = time.time()
|
|
196
|
+
except Exception as e: # pylint: disable=broad-except
|
|
197
|
+
end_time = time.time()
|
|
198
|
+
duration = end_time - start_time if "start_time" in locals() else 0
|
|
199
|
+
|
|
200
|
+
common_attributes = Config.get_common_metrics_attributes()
|
|
201
|
+
attributes = {
|
|
202
|
+
**common_attributes,
|
|
203
|
+
"error.type": e.__class__.__name__,
|
|
204
|
+
}
|
|
212
205
|
|
|
213
|
-
|
|
206
|
+
if duration > 0 and duration_histogram:
|
|
207
|
+
duration_histogram.record(duration, attributes=attributes)
|
|
208
|
+
if exception_counter:
|
|
209
|
+
exception_counter.add(1, attributes=attributes)
|
|
214
210
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
span,
|
|
220
|
-
response,
|
|
221
|
-
instance,
|
|
222
|
-
token_counter,
|
|
223
|
-
choice_counter,
|
|
224
|
-
duration_histogram,
|
|
225
|
-
streaming_time_to_first_token,
|
|
226
|
-
streaming_time_to_generate,
|
|
227
|
-
start_time,
|
|
228
|
-
kwargs,
|
|
229
|
-
)
|
|
230
|
-
else:
|
|
231
|
-
return _abuild_from_streaming_response(
|
|
232
|
-
span,
|
|
233
|
-
response,
|
|
234
|
-
instance,
|
|
235
|
-
token_counter,
|
|
236
|
-
choice_counter,
|
|
237
|
-
duration_histogram,
|
|
238
|
-
streaming_time_to_first_token,
|
|
239
|
-
streaming_time_to_generate,
|
|
240
|
-
start_time,
|
|
241
|
-
kwargs,
|
|
242
|
-
)
|
|
211
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
212
|
+
span.record_exception(e)
|
|
213
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
214
|
+
span.end()
|
|
243
215
|
|
|
244
|
-
|
|
216
|
+
raise
|
|
245
217
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
218
|
+
if is_streaming_response(response):
|
|
219
|
+
# span will be closed after the generator is done
|
|
220
|
+
if is_openai_v1():
|
|
221
|
+
return ChatStream(
|
|
222
|
+
span,
|
|
223
|
+
response,
|
|
224
|
+
instance,
|
|
225
|
+
token_counter,
|
|
226
|
+
choice_counter,
|
|
227
|
+
duration_histogram,
|
|
228
|
+
streaming_time_to_first_token,
|
|
229
|
+
streaming_time_to_generate,
|
|
230
|
+
start_time,
|
|
231
|
+
kwargs,
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
return _abuild_from_streaming_response(
|
|
235
|
+
span,
|
|
236
|
+
response,
|
|
237
|
+
instance,
|
|
238
|
+
token_counter,
|
|
239
|
+
choice_counter,
|
|
240
|
+
duration_histogram,
|
|
241
|
+
streaming_time_to_first_token,
|
|
242
|
+
streaming_time_to_generate,
|
|
243
|
+
start_time,
|
|
244
|
+
kwargs,
|
|
245
|
+
)
|
|
255
246
|
|
|
256
|
-
|
|
247
|
+
duration = end_time - start_time
|
|
257
248
|
|
|
258
|
-
|
|
249
|
+
_handle_response(
|
|
250
|
+
response,
|
|
251
|
+
span,
|
|
252
|
+
instance,
|
|
253
|
+
token_counter,
|
|
254
|
+
choice_counter,
|
|
255
|
+
duration_histogram,
|
|
256
|
+
duration,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
span.end()
|
|
260
|
+
|
|
261
|
+
return response
|
|
259
262
|
|
|
260
263
|
|
|
261
264
|
@dont_throw
|
|
@@ -961,8 +964,10 @@ async def _abuild_from_streaming_response(
|
|
|
961
964
|
span.end()
|
|
962
965
|
|
|
963
966
|
|
|
967
|
+
# pydantic.BaseModel here is ChatCompletionMessageFunctionToolCall (as of openai 1.99.7)
|
|
968
|
+
# but we keep to a parent type to support older versions
|
|
964
969
|
def _parse_tool_calls(
|
|
965
|
-
tool_calls: Optional[List[Union[dict,
|
|
970
|
+
tool_calls: Optional[List[Union[dict, pydantic.BaseModel]]],
|
|
966
971
|
) -> Union[List[ToolCall], None]:
|
|
967
972
|
"""
|
|
968
973
|
Util to correctly parse the tool calls data from the OpenAI API to this module's
|
|
@@ -976,12 +981,11 @@ def _parse_tool_calls(
|
|
|
976
981
|
for tool_call in tool_calls:
|
|
977
982
|
tool_call_data = None
|
|
978
983
|
|
|
979
|
-
# Handle dict or ChatCompletionMessageToolCall
|
|
980
984
|
if isinstance(tool_call, dict):
|
|
981
985
|
tool_call_data = copy.deepcopy(tool_call)
|
|
982
|
-
elif
|
|
986
|
+
elif _is_chat_message_function_tool_call(tool_call):
|
|
983
987
|
tool_call_data = tool_call.model_dump()
|
|
984
|
-
elif
|
|
988
|
+
elif _is_function_call(tool_call):
|
|
985
989
|
function_call = tool_call.model_dump()
|
|
986
990
|
tool_call_data = ToolCall(
|
|
987
991
|
id="",
|
|
@@ -996,6 +1000,34 @@ def _parse_tool_calls(
|
|
|
996
1000
|
return result
|
|
997
1001
|
|
|
998
1002
|
|
|
1003
|
+
def _is_chat_message_function_tool_call(model: Union[dict, pydantic.BaseModel]) -> bool:
|
|
1004
|
+
try:
|
|
1005
|
+
from openai.types.chat.chat_completion_message_function_tool_call import (
|
|
1006
|
+
ChatCompletionMessageFunctionToolCall,
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
return isinstance(model, ChatCompletionMessageFunctionToolCall)
|
|
1010
|
+
except Exception:
|
|
1011
|
+
try:
|
|
1012
|
+
# Since OpenAI 1.99.3, ChatCompletionMessageToolCall is a Union,
|
|
1013
|
+
# and the isinstance check will fail. This is fine, because in all
|
|
1014
|
+
# those versions, the check above will succeed.
|
|
1015
|
+
from openai.types.chat.chat_completion_message_tool_call import (
|
|
1016
|
+
ChatCompletionMessageToolCall,
|
|
1017
|
+
)
|
|
1018
|
+
return isinstance(model, ChatCompletionMessageToolCall)
|
|
1019
|
+
except Exception:
|
|
1020
|
+
return False
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def _is_function_call(model: Union[dict, pydantic.BaseModel]) -> bool:
|
|
1024
|
+
try:
|
|
1025
|
+
from openai.types.chat.chat_completion_message import FunctionCall
|
|
1026
|
+
return isinstance(model, FunctionCall)
|
|
1027
|
+
except Exception:
|
|
1028
|
+
return False
|
|
1029
|
+
|
|
1030
|
+
|
|
999
1031
|
@singledispatch
|
|
1000
1032
|
def _parse_choice_event(choice) -> ChoiceEvent:
|
|
1001
1033
|
has_message = choice.message is not None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from opentelemetry import context as context_api
|
|
4
|
+
from opentelemetry import trace
|
|
4
5
|
from opentelemetry.instrumentation.openai.shared import (
|
|
5
6
|
_set_client_attributes,
|
|
6
7
|
_set_functions_attributes,
|
|
@@ -55,25 +56,27 @@ def completion_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
55
56
|
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value},
|
|
56
57
|
)
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
# Use the span as current context to ensure events get proper trace context
|
|
60
|
+
with trace.use_span(span, end_on_exit=False):
|
|
61
|
+
_handle_request(span, kwargs, instance)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = wrapped(*args, **kwargs)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
67
|
+
span.record_exception(e)
|
|
68
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
69
|
+
span.end()
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
if is_streaming_response(response):
|
|
73
|
+
# span will be closed after the generator is done
|
|
74
|
+
return _build_from_streaming_response(span, kwargs, response)
|
|
75
|
+
else:
|
|
76
|
+
_handle_response(response, span, instance)
|
|
59
77
|
|
|
60
|
-
try:
|
|
61
|
-
response = wrapped(*args, **kwargs)
|
|
62
|
-
except Exception as e:
|
|
63
|
-
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
64
|
-
span.record_exception(e)
|
|
65
|
-
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
66
78
|
span.end()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if is_streaming_response(response):
|
|
70
|
-
# span will be closed after the generator is done
|
|
71
|
-
return _build_from_streaming_response(span, kwargs, response)
|
|
72
|
-
else:
|
|
73
|
-
_handle_response(response, span, instance)
|
|
74
|
-
|
|
75
|
-
span.end()
|
|
76
|
-
return response
|
|
79
|
+
return response
|
|
77
80
|
|
|
78
81
|
|
|
79
82
|
@_with_tracer_wrapper
|
|
@@ -89,25 +92,27 @@ async def acompletion_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
|
89
92
|
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLM_REQUEST_TYPE.value},
|
|
90
93
|
)
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
# Use the span as current context to ensure events get proper trace context
|
|
96
|
+
with trace.use_span(span, end_on_exit=False):
|
|
97
|
+
_handle_request(span, kwargs, instance)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
response = await wrapped(*args, **kwargs)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
103
|
+
span.record_exception(e)
|
|
104
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
105
|
+
span.end()
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
if is_streaming_response(response):
|
|
109
|
+
# span will be closed after the generator is done
|
|
110
|
+
return _abuild_from_streaming_response(span, kwargs, response)
|
|
111
|
+
else:
|
|
112
|
+
_handle_response(response, span, instance)
|
|
93
113
|
|
|
94
|
-
try:
|
|
95
|
-
response = await wrapped(*args, **kwargs)
|
|
96
|
-
except Exception as e:
|
|
97
|
-
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
98
|
-
span.record_exception(e)
|
|
99
|
-
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
100
114
|
span.end()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if is_streaming_response(response):
|
|
104
|
-
# span will be closed after the generator is done
|
|
105
|
-
return _abuild_from_streaming_response(span, kwargs, response)
|
|
106
|
-
else:
|
|
107
|
-
_handle_response(response, span, instance)
|
|
108
|
-
|
|
109
|
-
span.end()
|
|
110
|
-
return response
|
|
115
|
+
return response
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
@dont_throw
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from opentelemetry import context as context_api
|
|
5
|
+
from opentelemetry import trace
|
|
6
|
+
from opentelemetry.instrumentation.openai.shared import (
|
|
7
|
+
_set_span_attribute,
|
|
8
|
+
model_as_dict,
|
|
9
|
+
)
|
|
10
|
+
from opentelemetry.instrumentation.openai.shared.config import Config
|
|
11
|
+
from opentelemetry.instrumentation.openai.shared.event_emitter import emit_event
|
|
12
|
+
from opentelemetry.instrumentation.openai.shared.event_models import (
|
|
13
|
+
ChoiceEvent,
|
|
14
|
+
MessageEvent,
|
|
15
|
+
)
|
|
16
|
+
from 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_ai import LLMRequestTypeValues, SpanAttributes
|
|
24
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode
|
|
25
|
+
|
|
26
|
+
from openai._legacy_response import LegacyAPIResponse
|
|
27
|
+
from openai.types.beta.threads.run import Run
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
assistants = {}
|
|
32
|
+
runs = {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@_with_tracer_wrapper
|
|
36
|
+
def assistants_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
37
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
38
|
+
return wrapped(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
response = wrapped(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
assistants[response.id] = {
|
|
43
|
+
"model": kwargs.get("model"),
|
|
44
|
+
"instructions": kwargs.get("instructions"),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@_with_tracer_wrapper
|
|
51
|
+
def runs_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
52
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
53
|
+
return wrapped(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
thread_id = kwargs.get("thread_id")
|
|
56
|
+
instructions = kwargs.get("instructions")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
response = wrapped(*args, **kwargs)
|
|
60
|
+
response_dict = model_as_dict(response)
|
|
61
|
+
|
|
62
|
+
runs[thread_id] = {
|
|
63
|
+
"start_time": time.time_ns(),
|
|
64
|
+
"assistant_id": kwargs.get("assistant_id"),
|
|
65
|
+
"instructions": instructions,
|
|
66
|
+
"run_id": response_dict.get("id"),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return response
|
|
70
|
+
except Exception as e:
|
|
71
|
+
runs[thread_id] = {
|
|
72
|
+
"exception": e,
|
|
73
|
+
"end_time": time.time_ns(),
|
|
74
|
+
}
|
|
75
|
+
raise
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@_with_tracer_wrapper
|
|
79
|
+
def runs_retrieve_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
80
|
+
@dont_throw
|
|
81
|
+
def process_response(response):
|
|
82
|
+
if type(response) is LegacyAPIResponse:
|
|
83
|
+
parsed_response = response.parse()
|
|
84
|
+
else:
|
|
85
|
+
parsed_response = response
|
|
86
|
+
assert type(parsed_response) is Run
|
|
87
|
+
|
|
88
|
+
if parsed_response.thread_id in runs:
|
|
89
|
+
thread_id = parsed_response.thread_id
|
|
90
|
+
runs[thread_id]["end_time"] = time.time_ns()
|
|
91
|
+
if parsed_response.usage:
|
|
92
|
+
runs[thread_id]["usage"] = parsed_response.usage
|
|
93
|
+
|
|
94
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
95
|
+
return wrapped(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = wrapped(*args, **kwargs)
|
|
99
|
+
process_response(response)
|
|
100
|
+
return response
|
|
101
|
+
except Exception as e:
|
|
102
|
+
thread_id = kwargs.get("thread_id")
|
|
103
|
+
if thread_id in runs:
|
|
104
|
+
runs[thread_id]["exception"] = e
|
|
105
|
+
runs[thread_id]["end_time"] = time.time_ns()
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@_with_tracer_wrapper
|
|
110
|
+
def messages_list_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
111
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
112
|
+
return wrapped(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
id = kwargs.get("thread_id")
|
|
115
|
+
|
|
116
|
+
response = wrapped(*args, **kwargs)
|
|
117
|
+
|
|
118
|
+
response_dict = model_as_dict(response)
|
|
119
|
+
if id not in runs:
|
|
120
|
+
return response
|
|
121
|
+
|
|
122
|
+
run = runs[id]
|
|
123
|
+
messages = sorted(response_dict["data"], key=lambda x: x["created_at"])
|
|
124
|
+
|
|
125
|
+
span = tracer.start_span(
|
|
126
|
+
"openai.assistant.run",
|
|
127
|
+
kind=SpanKind.CLIENT,
|
|
128
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
129
|
+
start_time=run.get("start_time"),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Use the span as current context to ensure events get proper trace context
|
|
133
|
+
with trace.use_span(span, end_on_exit=False):
|
|
134
|
+
if exception := run.get("exception"):
|
|
135
|
+
span.set_attribute(ERROR_TYPE, exception.__class__.__name__)
|
|
136
|
+
span.record_exception(exception)
|
|
137
|
+
span.set_status(Status(StatusCode.ERROR, str(exception)))
|
|
138
|
+
span.end()
|
|
139
|
+
return response
|
|
140
|
+
|
|
141
|
+
prompt_index = 0
|
|
142
|
+
if assistants.get(run["assistant_id"]) is not None or Config.enrich_assistant:
|
|
143
|
+
if Config.enrich_assistant:
|
|
144
|
+
assistant = model_as_dict(
|
|
145
|
+
instance._client.beta.assistants.retrieve(run["assistant_id"])
|
|
146
|
+
)
|
|
147
|
+
assistants[run["assistant_id"]] = assistant
|
|
148
|
+
else:
|
|
149
|
+
assistant = assistants[run["assistant_id"]]
|
|
150
|
+
|
|
151
|
+
_set_span_attribute(
|
|
152
|
+
span,
|
|
153
|
+
SpanAttributes.LLM_SYSTEM,
|
|
154
|
+
"openai",
|
|
155
|
+
)
|
|
156
|
+
_set_span_attribute(
|
|
157
|
+
span,
|
|
158
|
+
SpanAttributes.LLM_REQUEST_MODEL,
|
|
159
|
+
assistant["model"],
|
|
160
|
+
)
|
|
161
|
+
_set_span_attribute(
|
|
162
|
+
span,
|
|
163
|
+
SpanAttributes.LLM_RESPONSE_MODEL,
|
|
164
|
+
assistant["model"],
|
|
165
|
+
)
|
|
166
|
+
if should_emit_events():
|
|
167
|
+
emit_event(MessageEvent(content=assistant["instructions"], role="system"))
|
|
168
|
+
else:
|
|
169
|
+
_set_span_attribute(
|
|
170
|
+
span, f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role", "system"
|
|
171
|
+
)
|
|
172
|
+
_set_span_attribute(
|
|
173
|
+
span,
|
|
174
|
+
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
175
|
+
assistant["instructions"],
|
|
176
|
+
)
|
|
177
|
+
prompt_index += 1
|
|
178
|
+
_set_span_attribute(
|
|
179
|
+
span, f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role", "system"
|
|
180
|
+
)
|
|
181
|
+
_set_span_attribute(
|
|
182
|
+
span,
|
|
183
|
+
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
184
|
+
run["instructions"],
|
|
185
|
+
)
|
|
186
|
+
if should_emit_events():
|
|
187
|
+
emit_event(MessageEvent(content=run["instructions"], role="system"))
|
|
188
|
+
prompt_index += 1
|
|
189
|
+
|
|
190
|
+
completion_index = 0
|
|
191
|
+
for msg in messages:
|
|
192
|
+
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{completion_index}"
|
|
193
|
+
content = msg.get("content")
|
|
194
|
+
|
|
195
|
+
message_content = content[0].get("text").get("value")
|
|
196
|
+
message_role = msg.get("role")
|
|
197
|
+
if message_role in ["user", "system"]:
|
|
198
|
+
if should_emit_events():
|
|
199
|
+
emit_event(MessageEvent(content=message_content, role=message_role))
|
|
200
|
+
else:
|
|
201
|
+
_set_span_attribute(
|
|
202
|
+
span,
|
|
203
|
+
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role",
|
|
204
|
+
message_role,
|
|
205
|
+
)
|
|
206
|
+
_set_span_attribute(
|
|
207
|
+
span,
|
|
208
|
+
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
209
|
+
message_content,
|
|
210
|
+
)
|
|
211
|
+
prompt_index += 1
|
|
212
|
+
else:
|
|
213
|
+
if should_emit_events():
|
|
214
|
+
emit_event(
|
|
215
|
+
ChoiceEvent(
|
|
216
|
+
index=completion_index,
|
|
217
|
+
message={"content": message_content, "role": message_role},
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
_set_span_attribute(span, f"{prefix}.role", msg.get("role"))
|
|
222
|
+
_set_span_attribute(span, f"{prefix}.content", message_content)
|
|
223
|
+
_set_span_attribute(
|
|
224
|
+
span, f"gen_ai.response.{completion_index}.id", msg.get("id")
|
|
225
|
+
)
|
|
226
|
+
completion_index += 1
|
|
227
|
+
|
|
228
|
+
if run.get("usage"):
|
|
229
|
+
usage_dict = model_as_dict(run.get("usage"))
|
|
230
|
+
_set_span_attribute(
|
|
231
|
+
span,
|
|
232
|
+
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
|
233
|
+
usage_dict.get("completion_tokens"),
|
|
234
|
+
)
|
|
235
|
+
_set_span_attribute(
|
|
236
|
+
span,
|
|
237
|
+
SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
|
|
238
|
+
usage_dict.get("prompt_tokens"),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
span.end(run.get("end_time"))
|
|
242
|
+
|
|
243
|
+
return response
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@_with_tracer_wrapper
|
|
247
|
+
def runs_create_and_stream_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
248
|
+
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
249
|
+
return wrapped(*args, **kwargs)
|
|
250
|
+
|
|
251
|
+
assistant_id = kwargs.get("assistant_id")
|
|
252
|
+
instructions = kwargs.get("instructions")
|
|
253
|
+
|
|
254
|
+
span = tracer.start_span(
|
|
255
|
+
"openai.assistant.run_stream",
|
|
256
|
+
kind=SpanKind.CLIENT,
|
|
257
|
+
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Use the span as current context to ensure events get proper trace context
|
|
261
|
+
with trace.use_span(span, end_on_exit=False):
|
|
262
|
+
i = 0
|
|
263
|
+
if assistants.get(assistant_id) is not None or Config.enrich_assistant:
|
|
264
|
+
if Config.enrich_assistant:
|
|
265
|
+
assistant = model_as_dict(
|
|
266
|
+
instance._client.beta.assistants.retrieve(assistant_id)
|
|
267
|
+
)
|
|
268
|
+
assistants[assistant_id] = assistant
|
|
269
|
+
else:
|
|
270
|
+
assistant = assistants[assistant_id]
|
|
271
|
+
|
|
272
|
+
_set_span_attribute(
|
|
273
|
+
span, SpanAttributes.LLM_REQUEST_MODEL, assistants[assistant_id]["model"]
|
|
274
|
+
)
|
|
275
|
+
_set_span_attribute(
|
|
276
|
+
span,
|
|
277
|
+
SpanAttributes.LLM_SYSTEM,
|
|
278
|
+
"openai",
|
|
279
|
+
)
|
|
280
|
+
_set_span_attribute(
|
|
281
|
+
span,
|
|
282
|
+
SpanAttributes.LLM_RESPONSE_MODEL,
|
|
283
|
+
assistants[assistant_id]["model"],
|
|
284
|
+
)
|
|
285
|
+
if should_emit_events():
|
|
286
|
+
emit_event(
|
|
287
|
+
MessageEvent(
|
|
288
|
+
content=assistants[assistant_id]["instructions"], role="system"
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
_set_span_attribute(
|
|
293
|
+
span, f"{SpanAttributes.LLM_PROMPTS}.{i}.role", "system"
|
|
294
|
+
)
|
|
295
|
+
_set_span_attribute(
|
|
296
|
+
span,
|
|
297
|
+
f"{SpanAttributes.LLM_PROMPTS}.{i}.content",
|
|
298
|
+
assistants[assistant_id]["instructions"],
|
|
299
|
+
)
|
|
300
|
+
i += 1
|
|
301
|
+
if should_emit_events():
|
|
302
|
+
emit_event(MessageEvent(content=instructions, role="system"))
|
|
303
|
+
else:
|
|
304
|
+
_set_span_attribute(span, f"{SpanAttributes.LLM_PROMPTS}.{i}.role", "system")
|
|
305
|
+
_set_span_attribute(
|
|
306
|
+
span, f"{SpanAttributes.LLM_PROMPTS}.{i}.content", instructions
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
from opentelemetry.instrumentation.openai.v1.event_handler_wrapper import (
|
|
310
|
+
EventHandleWrapper,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
kwargs["event_handler"] = EventHandleWrapper(
|
|
314
|
+
original_handler=kwargs["event_handler"],
|
|
315
|
+
span=span,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
response = wrapped(*args, **kwargs)
|
|
320
|
+
return response
|
|
321
|
+
except Exception as e:
|
|
322
|
+
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
323
|
+
span.record_exception(e)
|
|
324
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
325
|
+
span.end()
|
|
326
|
+
raise
|
|
@@ -447,6 +447,14 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
|
|
|
447
447
|
merged_tools = existing_data.get("tools", []) + request_tools
|
|
448
448
|
|
|
449
449
|
try:
|
|
450
|
+
parsed_response_output_text = None
|
|
451
|
+
if hasattr(parsed_response, "output_text"):
|
|
452
|
+
parsed_response_output_text = parsed_response.output_text
|
|
453
|
+
else:
|
|
454
|
+
try:
|
|
455
|
+
parsed_response_output_text = parsed_response.output[0].content[0].text
|
|
456
|
+
except Exception:
|
|
457
|
+
pass
|
|
450
458
|
traced_data = TracedData(
|
|
451
459
|
start_time=existing_data.get("start_time", start_time),
|
|
452
460
|
response_id=parsed_response.id,
|
|
@@ -456,7 +464,7 @@ def responses_get_or_create_wrapper(tracer: Tracer, wrapped, instance, args, kwa
|
|
|
456
464
|
output_blocks={block.id: block for block in parsed_response.output}
|
|
457
465
|
| existing_data.get("output_blocks", {}),
|
|
458
466
|
usage=existing_data.get("usage", parsed_response.usage),
|
|
459
|
-
output_text=existing_data.get("output_text",
|
|
467
|
+
output_text=existing_data.get("output_text", parsed_response_output_text),
|
|
460
468
|
request_model=existing_data.get("request_model", kwargs.get("model")),
|
|
461
469
|
response_model=existing_data.get("response_model", parsed_response.model),
|
|
462
470
|
)
|
|
@@ -541,6 +549,15 @@ async def async_responses_get_or_create_wrapper(
|
|
|
541
549
|
merged_tools = existing_data.get("tools", []) + request_tools
|
|
542
550
|
|
|
543
551
|
try:
|
|
552
|
+
parsed_response_output_text = None
|
|
553
|
+
if hasattr(parsed_response, "output_text"):
|
|
554
|
+
parsed_response_output_text = parsed_response.output_text
|
|
555
|
+
else:
|
|
556
|
+
try:
|
|
557
|
+
parsed_response_output_text = parsed_response.output[0].content[0].text
|
|
558
|
+
except Exception:
|
|
559
|
+
pass
|
|
560
|
+
|
|
544
561
|
traced_data = TracedData(
|
|
545
562
|
start_time=existing_data.get("start_time", start_time),
|
|
546
563
|
response_id=parsed_response.id,
|
|
@@ -550,7 +567,7 @@ async def async_responses_get_or_create_wrapper(
|
|
|
550
567
|
output_blocks={block.id: block for block in parsed_response.output}
|
|
551
568
|
| existing_data.get("output_blocks", {}),
|
|
552
569
|
usage=existing_data.get("usage", parsed_response.usage),
|
|
553
|
-
output_text=existing_data.get("output_text",
|
|
570
|
+
output_text=existing_data.get("output_text", parsed_response_output_text),
|
|
554
571
|
request_model=existing_data.get("request_model", kwargs.get("model")),
|
|
555
572
|
response_model=existing_data.get("response_model", parsed_response.model),
|
|
556
573
|
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.44.3"
|
|
@@ -8,7 +8,7 @@ show_missing = true
|
|
|
8
8
|
|
|
9
9
|
[tool.poetry]
|
|
10
10
|
name = "opentelemetry-instrumentation-openai"
|
|
11
|
-
version = "0.44.
|
|
11
|
+
version = "0.44.3"
|
|
12
12
|
description = "OpenTelemetry OpenAI instrumentation"
|
|
13
13
|
authors = [
|
|
14
14
|
"Gal Kleinman <gal@traceloop.com>",
|
|
@@ -38,7 +38,7 @@ pytest = "^8.2.2"
|
|
|
38
38
|
pytest-sugar = "1.0.0"
|
|
39
39
|
vcrpy = "^6.0.1"
|
|
40
40
|
pytest-recording = "^0.13.1"
|
|
41
|
-
openai = { extras = ["datalib"], version = "
|
|
41
|
+
openai = { extras = ["datalib"], version = "1.99.7" }
|
|
42
42
|
opentelemetry-sdk = "^1.27.0"
|
|
43
43
|
pytest-asyncio = "^0.23.7"
|
|
44
44
|
requests = "^2.31.0"
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from opentelemetry import context as context_api
|
|
5
|
-
from opentelemetry.instrumentation.openai.shared import (
|
|
6
|
-
_set_span_attribute,
|
|
7
|
-
model_as_dict,
|
|
8
|
-
)
|
|
9
|
-
from opentelemetry.instrumentation.openai.shared.config import Config
|
|
10
|
-
from opentelemetry.instrumentation.openai.shared.event_emitter import emit_event
|
|
11
|
-
from opentelemetry.instrumentation.openai.shared.event_models import (
|
|
12
|
-
ChoiceEvent,
|
|
13
|
-
MessageEvent,
|
|
14
|
-
)
|
|
15
|
-
from opentelemetry.instrumentation.openai.utils import (
|
|
16
|
-
_with_tracer_wrapper,
|
|
17
|
-
dont_throw,
|
|
18
|
-
should_emit_events,
|
|
19
|
-
)
|
|
20
|
-
from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
|
|
21
|
-
from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
|
|
22
|
-
from opentelemetry.semconv_ai import LLMRequestTypeValues, SpanAttributes
|
|
23
|
-
from opentelemetry.trace import SpanKind, Status, StatusCode
|
|
24
|
-
|
|
25
|
-
from openai._legacy_response import LegacyAPIResponse
|
|
26
|
-
from openai.types.beta.threads.run import Run
|
|
27
|
-
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
assistants = {}
|
|
31
|
-
runs = {}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@_with_tracer_wrapper
|
|
35
|
-
def assistants_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
36
|
-
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
37
|
-
return wrapped(*args, **kwargs)
|
|
38
|
-
|
|
39
|
-
response = wrapped(*args, **kwargs)
|
|
40
|
-
|
|
41
|
-
assistants[response.id] = {
|
|
42
|
-
"model": kwargs.get("model"),
|
|
43
|
-
"instructions": kwargs.get("instructions"),
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return response
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@_with_tracer_wrapper
|
|
50
|
-
def runs_create_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
51
|
-
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
52
|
-
return wrapped(*args, **kwargs)
|
|
53
|
-
|
|
54
|
-
thread_id = kwargs.get("thread_id")
|
|
55
|
-
instructions = kwargs.get("instructions")
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
response = wrapped(*args, **kwargs)
|
|
59
|
-
response_dict = model_as_dict(response)
|
|
60
|
-
|
|
61
|
-
runs[thread_id] = {
|
|
62
|
-
"start_time": time.time_ns(),
|
|
63
|
-
"assistant_id": kwargs.get("assistant_id"),
|
|
64
|
-
"instructions": instructions,
|
|
65
|
-
"run_id": response_dict.get("id"),
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return response
|
|
69
|
-
except Exception as e:
|
|
70
|
-
runs[thread_id] = {
|
|
71
|
-
"exception": e,
|
|
72
|
-
"end_time": time.time_ns(),
|
|
73
|
-
}
|
|
74
|
-
raise
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@_with_tracer_wrapper
|
|
78
|
-
def runs_retrieve_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
79
|
-
@dont_throw
|
|
80
|
-
def process_response(response):
|
|
81
|
-
if type(response) is LegacyAPIResponse:
|
|
82
|
-
parsed_response = response.parse()
|
|
83
|
-
else:
|
|
84
|
-
parsed_response = response
|
|
85
|
-
assert type(parsed_response) is Run
|
|
86
|
-
|
|
87
|
-
if parsed_response.thread_id in runs:
|
|
88
|
-
thread_id = parsed_response.thread_id
|
|
89
|
-
runs[thread_id]["end_time"] = time.time_ns()
|
|
90
|
-
if parsed_response.usage:
|
|
91
|
-
runs[thread_id]["usage"] = parsed_response.usage
|
|
92
|
-
|
|
93
|
-
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
94
|
-
return wrapped(*args, **kwargs)
|
|
95
|
-
|
|
96
|
-
try:
|
|
97
|
-
response = wrapped(*args, **kwargs)
|
|
98
|
-
process_response(response)
|
|
99
|
-
return response
|
|
100
|
-
except Exception as e:
|
|
101
|
-
thread_id = kwargs.get("thread_id")
|
|
102
|
-
if thread_id in runs:
|
|
103
|
-
runs[thread_id]["exception"] = e
|
|
104
|
-
runs[thread_id]["end_time"] = time.time_ns()
|
|
105
|
-
raise
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@_with_tracer_wrapper
|
|
109
|
-
def messages_list_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
110
|
-
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
111
|
-
return wrapped(*args, **kwargs)
|
|
112
|
-
|
|
113
|
-
id = kwargs.get("thread_id")
|
|
114
|
-
|
|
115
|
-
response = wrapped(*args, **kwargs)
|
|
116
|
-
|
|
117
|
-
response_dict = model_as_dict(response)
|
|
118
|
-
if id not in runs:
|
|
119
|
-
return response
|
|
120
|
-
|
|
121
|
-
run = runs[id]
|
|
122
|
-
messages = sorted(response_dict["data"], key=lambda x: x["created_at"])
|
|
123
|
-
|
|
124
|
-
span = tracer.start_span(
|
|
125
|
-
"openai.assistant.run",
|
|
126
|
-
kind=SpanKind.CLIENT,
|
|
127
|
-
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
128
|
-
start_time=run.get("start_time"),
|
|
129
|
-
)
|
|
130
|
-
if exception := run.get("exception"):
|
|
131
|
-
span.set_attribute(ERROR_TYPE, exception.__class__.__name__)
|
|
132
|
-
span.record_exception(exception)
|
|
133
|
-
span.set_status(Status(StatusCode.ERROR, str(exception)))
|
|
134
|
-
span.end(run.get("end_time"))
|
|
135
|
-
|
|
136
|
-
prompt_index = 0
|
|
137
|
-
if assistants.get(run["assistant_id"]) is not None or Config.enrich_assistant:
|
|
138
|
-
if Config.enrich_assistant:
|
|
139
|
-
assistant = model_as_dict(
|
|
140
|
-
instance._client.beta.assistants.retrieve(run["assistant_id"])
|
|
141
|
-
)
|
|
142
|
-
assistants[run["assistant_id"]] = assistant
|
|
143
|
-
else:
|
|
144
|
-
assistant = assistants[run["assistant_id"]]
|
|
145
|
-
|
|
146
|
-
_set_span_attribute(
|
|
147
|
-
span,
|
|
148
|
-
SpanAttributes.LLM_SYSTEM,
|
|
149
|
-
"openai",
|
|
150
|
-
)
|
|
151
|
-
_set_span_attribute(
|
|
152
|
-
span,
|
|
153
|
-
SpanAttributes.LLM_REQUEST_MODEL,
|
|
154
|
-
assistant["model"],
|
|
155
|
-
)
|
|
156
|
-
_set_span_attribute(
|
|
157
|
-
span,
|
|
158
|
-
SpanAttributes.LLM_RESPONSE_MODEL,
|
|
159
|
-
assistant["model"],
|
|
160
|
-
)
|
|
161
|
-
if should_emit_events():
|
|
162
|
-
emit_event(MessageEvent(content=assistant["instructions"], role="system"))
|
|
163
|
-
else:
|
|
164
|
-
_set_span_attribute(
|
|
165
|
-
span, f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role", "system"
|
|
166
|
-
)
|
|
167
|
-
_set_span_attribute(
|
|
168
|
-
span,
|
|
169
|
-
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
170
|
-
assistant["instructions"],
|
|
171
|
-
)
|
|
172
|
-
prompt_index += 1
|
|
173
|
-
_set_span_attribute(
|
|
174
|
-
span, f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role", "system"
|
|
175
|
-
)
|
|
176
|
-
_set_span_attribute(
|
|
177
|
-
span,
|
|
178
|
-
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
179
|
-
run["instructions"],
|
|
180
|
-
)
|
|
181
|
-
emit_event(MessageEvent(content=run["instructions"], role="system"))
|
|
182
|
-
prompt_index += 1
|
|
183
|
-
|
|
184
|
-
completion_index = 0
|
|
185
|
-
for msg in messages:
|
|
186
|
-
prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{completion_index}"
|
|
187
|
-
content = msg.get("content")
|
|
188
|
-
|
|
189
|
-
message_content = content[0].get("text").get("value")
|
|
190
|
-
message_role = msg.get("role")
|
|
191
|
-
if message_role in ["user", "system"]:
|
|
192
|
-
if should_emit_events():
|
|
193
|
-
emit_event(MessageEvent(content=message_content, role=message_role))
|
|
194
|
-
else:
|
|
195
|
-
_set_span_attribute(
|
|
196
|
-
span,
|
|
197
|
-
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.role",
|
|
198
|
-
message_role,
|
|
199
|
-
)
|
|
200
|
-
_set_span_attribute(
|
|
201
|
-
span,
|
|
202
|
-
f"{SpanAttributes.LLM_PROMPTS}.{prompt_index}.content",
|
|
203
|
-
message_content,
|
|
204
|
-
)
|
|
205
|
-
prompt_index += 1
|
|
206
|
-
else:
|
|
207
|
-
if should_emit_events():
|
|
208
|
-
emit_event(
|
|
209
|
-
ChoiceEvent(
|
|
210
|
-
index=completion_index,
|
|
211
|
-
message={"content": message_content, "role": message_role},
|
|
212
|
-
)
|
|
213
|
-
)
|
|
214
|
-
else:
|
|
215
|
-
_set_span_attribute(span, f"{prefix}.role", msg.get("role"))
|
|
216
|
-
_set_span_attribute(span, f"{prefix}.content", message_content)
|
|
217
|
-
_set_span_attribute(
|
|
218
|
-
span, f"gen_ai.response.{completion_index}.id", msg.get("id")
|
|
219
|
-
)
|
|
220
|
-
completion_index += 1
|
|
221
|
-
|
|
222
|
-
if run.get("usage"):
|
|
223
|
-
usage_dict = model_as_dict(run.get("usage"))
|
|
224
|
-
_set_span_attribute(
|
|
225
|
-
span,
|
|
226
|
-
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
|
227
|
-
usage_dict.get("completion_tokens"),
|
|
228
|
-
)
|
|
229
|
-
_set_span_attribute(
|
|
230
|
-
span,
|
|
231
|
-
SpanAttributes.LLM_USAGE_PROMPT_TOKENS,
|
|
232
|
-
usage_dict.get("prompt_tokens"),
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
span.end(run.get("end_time"))
|
|
236
|
-
|
|
237
|
-
return response
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
@_with_tracer_wrapper
|
|
241
|
-
def runs_create_and_stream_wrapper(tracer, wrapped, instance, args, kwargs):
|
|
242
|
-
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
|
|
243
|
-
return wrapped(*args, **kwargs)
|
|
244
|
-
|
|
245
|
-
assistant_id = kwargs.get("assistant_id")
|
|
246
|
-
instructions = kwargs.get("instructions")
|
|
247
|
-
|
|
248
|
-
span = tracer.start_span(
|
|
249
|
-
"openai.assistant.run_stream",
|
|
250
|
-
kind=SpanKind.CLIENT,
|
|
251
|
-
attributes={SpanAttributes.LLM_REQUEST_TYPE: LLMRequestTypeValues.CHAT.value},
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
i = 0
|
|
255
|
-
if assistants.get(assistant_id) is not None or Config.enrich_assistant:
|
|
256
|
-
if Config.enrich_assistant:
|
|
257
|
-
assistant = model_as_dict(
|
|
258
|
-
instance._client.beta.assistants.retrieve(assistant_id)
|
|
259
|
-
)
|
|
260
|
-
assistants[assistant_id] = assistant
|
|
261
|
-
else:
|
|
262
|
-
assistant = assistants[assistant_id]
|
|
263
|
-
|
|
264
|
-
_set_span_attribute(
|
|
265
|
-
span, SpanAttributes.LLM_REQUEST_MODEL, assistants[assistant_id]["model"]
|
|
266
|
-
)
|
|
267
|
-
_set_span_attribute(
|
|
268
|
-
span,
|
|
269
|
-
SpanAttributes.LLM_SYSTEM,
|
|
270
|
-
"openai",
|
|
271
|
-
)
|
|
272
|
-
_set_span_attribute(
|
|
273
|
-
span,
|
|
274
|
-
SpanAttributes.LLM_RESPONSE_MODEL,
|
|
275
|
-
assistants[assistant_id]["model"],
|
|
276
|
-
)
|
|
277
|
-
if should_emit_events():
|
|
278
|
-
emit_event(
|
|
279
|
-
MessageEvent(
|
|
280
|
-
content=assistants[assistant_id]["instructions"], role="system"
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
else:
|
|
284
|
-
_set_span_attribute(
|
|
285
|
-
span, f"{SpanAttributes.LLM_PROMPTS}.{i}.role", "system"
|
|
286
|
-
)
|
|
287
|
-
_set_span_attribute(
|
|
288
|
-
span,
|
|
289
|
-
f"{SpanAttributes.LLM_PROMPTS}.{i}.content",
|
|
290
|
-
assistants[assistant_id]["instructions"],
|
|
291
|
-
)
|
|
292
|
-
i += 1
|
|
293
|
-
if should_emit_events():
|
|
294
|
-
emit_event(MessageEvent(content=instructions, role="system"))
|
|
295
|
-
else:
|
|
296
|
-
_set_span_attribute(span, f"{SpanAttributes.LLM_PROMPTS}.{i}.role", "system")
|
|
297
|
-
_set_span_attribute(
|
|
298
|
-
span, f"{SpanAttributes.LLM_PROMPTS}.{i}.content", instructions
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
from opentelemetry.instrumentation.openai.v1.event_handler_wrapper import (
|
|
302
|
-
EventHandleWrapper,
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
kwargs["event_handler"] = EventHandleWrapper(
|
|
306
|
-
original_handler=kwargs["event_handler"],
|
|
307
|
-
span=span,
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
try:
|
|
311
|
-
response = wrapped(*args, **kwargs)
|
|
312
|
-
return response
|
|
313
|
-
except Exception as e:
|
|
314
|
-
span.set_attribute(ERROR_TYPE, e.__class__.__name__)
|
|
315
|
-
span.record_exception(e)
|
|
316
|
-
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
317
|
-
span.end()
|
|
318
|
-
raise
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.44.1"
|
|
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
|
|
File without changes
|
|
File without changes
|