lmnr 0.7.10__py3-none-any.whl → 0.7.12__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.
- lmnr/opentelemetry_lib/__init__.py +6 -0
- lmnr/opentelemetry_lib/decorators/__init__.py +1 -1
- lmnr/opentelemetry_lib/litellm/__init__.py +277 -32
- lmnr/opentelemetry_lib/litellm/utils.py +76 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +136 -44
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +93 -6
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +155 -3
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +477 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +14 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +10 -1
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +100 -8
- lmnr/opentelemetry_lib/tracing/__init__.py +9 -0
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +56 -3
- lmnr/opentelemetry_lib/tracing/exporter.py +24 -9
- lmnr/opentelemetry_lib/tracing/instruments.py +8 -0
- lmnr/opentelemetry_lib/tracing/processor.py +26 -0
- lmnr/sdk/browser/browser_use_cdp_otel.py +12 -7
- lmnr/sdk/browser/bubus_otel.py +71 -0
- lmnr/sdk/browser/cdp_utils.py +318 -87
- lmnr/sdk/evaluations.py +22 -2
- lmnr/sdk/laminar.py +17 -3
- lmnr/version.py +1 -1
- {lmnr-0.7.10.dist-info → lmnr-0.7.12.dist-info}/METADATA +50 -50
- {lmnr-0.7.10.dist-info → lmnr-0.7.12.dist-info}/RECORD +28 -24
- {lmnr-0.7.10.dist-info → lmnr-0.7.12.dist-info}/WHEEL +0 -0
- {lmnr-0.7.10.dist-info → lmnr-0.7.12.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
"""OpenTelemetry CUA instrumentation"""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from typing import Any, AsyncGenerator, Collection
|
5
|
+
|
6
|
+
from lmnr.opentelemetry_lib.decorators import json_dumps
|
7
|
+
from lmnr import Laminar
|
8
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
9
|
+
from opentelemetry.instrumentation.utils import unwrap
|
10
|
+
|
11
|
+
from opentelemetry.trace import Span
|
12
|
+
from opentelemetry.trace.status import Status, StatusCode
|
13
|
+
from wrapt import wrap_function_wrapper
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
_instruments = ("cua-agent >= 0.4.0",)
|
18
|
+
|
19
|
+
|
20
|
+
def _wrap_run(
|
21
|
+
wrapped,
|
22
|
+
instance,
|
23
|
+
args,
|
24
|
+
kwargs,
|
25
|
+
):
|
26
|
+
parent_span = Laminar.start_span("ComputerAgent.run")
|
27
|
+
instance._lmnr_parent_span = parent_span
|
28
|
+
|
29
|
+
try:
|
30
|
+
result: AsyncGenerator[dict[str, Any], None] = wrapped(*args, **kwargs)
|
31
|
+
return _abuild_from_streaming_response(parent_span, result)
|
32
|
+
except Exception as e:
|
33
|
+
if parent_span.is_recording():
|
34
|
+
parent_span.set_status(Status(StatusCode.ERROR))
|
35
|
+
parent_span.record_exception(e)
|
36
|
+
parent_span.end()
|
37
|
+
raise
|
38
|
+
|
39
|
+
|
40
|
+
async def _abuild_from_streaming_response(
|
41
|
+
parent_span: Span, response: AsyncGenerator[dict[str, Any], None]
|
42
|
+
) -> AsyncGenerator[dict[str, Any], None]:
|
43
|
+
with Laminar.use_span(parent_span, end_on_exit=True):
|
44
|
+
response_iter = aiter(response)
|
45
|
+
while True:
|
46
|
+
step = None
|
47
|
+
step_span = Laminar.start_span("ComputerAgent.step")
|
48
|
+
with Laminar.use_span(step_span):
|
49
|
+
try:
|
50
|
+
step = await anext(response_iter)
|
51
|
+
step_span.set_attribute("lmnr.span.output", json_dumps(step))
|
52
|
+
try:
|
53
|
+
# When processing tool calls, each output item is processed separately,
|
54
|
+
# if the output is message, agent.step returns an empty array
|
55
|
+
# https://github.com/trycua/cua/blob/17d670962970a1d1774daaec029ebf92f1f9235e/libs/python/agent/agent/agent.py#L459
|
56
|
+
if len(step.get("output", [])) == 0:
|
57
|
+
continue
|
58
|
+
except Exception:
|
59
|
+
pass
|
60
|
+
if step_span.is_recording():
|
61
|
+
step_span.end()
|
62
|
+
except StopAsyncIteration:
|
63
|
+
# don't end on purpose, there is no iteration step here.
|
64
|
+
break
|
65
|
+
|
66
|
+
if step is not None:
|
67
|
+
yield step
|
68
|
+
|
69
|
+
|
70
|
+
class CuaAgentInstrumentor(BaseInstrumentor):
|
71
|
+
def __init__(self):
|
72
|
+
super().__init__()
|
73
|
+
|
74
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
75
|
+
return _instruments
|
76
|
+
|
77
|
+
def _instrument(self, **kwargs):
|
78
|
+
wrap_package = "agent.agent"
|
79
|
+
wrap_object = "ComputerAgent"
|
80
|
+
wrap_method = "run"
|
81
|
+
try:
|
82
|
+
wrap_function_wrapper(
|
83
|
+
wrap_package,
|
84
|
+
f"{wrap_object}.{wrap_method}",
|
85
|
+
_wrap_run,
|
86
|
+
)
|
87
|
+
except ModuleNotFoundError:
|
88
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
89
|
+
|
90
|
+
def _uninstrument(self, **kwargs):
|
91
|
+
wrap_package = "agent.agent"
|
92
|
+
wrap_object = "ComputerAgent"
|
93
|
+
wrap_method = "run"
|
94
|
+
try:
|
95
|
+
unwrap(
|
96
|
+
f"{wrap_package}.{wrap_object}",
|
97
|
+
wrap_method,
|
98
|
+
)
|
99
|
+
except ModuleNotFoundError:
|
100
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
@@ -0,0 +1,477 @@
|
|
1
|
+
"""OpenTelemetry CUA instrumentation"""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from typing import Collection
|
5
|
+
|
6
|
+
from lmnr.opentelemetry_lib.decorators import json_dumps
|
7
|
+
from lmnr.sdk.utils import get_input_from_func_args
|
8
|
+
from lmnr import Laminar
|
9
|
+
from lmnr.opentelemetry_lib.tracing.context import get_current_context
|
10
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
11
|
+
from opentelemetry.instrumentation.utils import unwrap
|
12
|
+
|
13
|
+
from opentelemetry import trace
|
14
|
+
from opentelemetry.trace import Span
|
15
|
+
from opentelemetry.trace.status import Status, StatusCode
|
16
|
+
from wrapt import wrap_function_wrapper
|
17
|
+
|
18
|
+
from .utils import payload_to_placeholder
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
_instruments = ("cua-computer >= 0.4.0",)
|
23
|
+
|
24
|
+
|
25
|
+
WRAPPED_METHODS = [
|
26
|
+
{
|
27
|
+
"package": "computer.interface.generic",
|
28
|
+
"object": "GenericComputerInterface",
|
29
|
+
"method": "close",
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"package": "computer.interface.generic",
|
33
|
+
"object": "GenericComputerInterface",
|
34
|
+
"method": "force_close",
|
35
|
+
},
|
36
|
+
]
|
37
|
+
WRAPPED_AMETHODS = [
|
38
|
+
{
|
39
|
+
"package": "computer.computer",
|
40
|
+
"object": "Computer",
|
41
|
+
"method": "__aenter__",
|
42
|
+
"action": "start_parent_span",
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"package": "computer.computer",
|
46
|
+
"object": "Computer",
|
47
|
+
"method": "__aexit__",
|
48
|
+
"action": "end_parent_span",
|
49
|
+
},
|
50
|
+
{
|
51
|
+
"package": "computer.interface.generic",
|
52
|
+
"object": "GenericComputerInterface",
|
53
|
+
"method": "mouse_down",
|
54
|
+
},
|
55
|
+
{
|
56
|
+
"package": "computer.interface.generic",
|
57
|
+
"object": "GenericComputerInterface",
|
58
|
+
"method": "mouse_up",
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"package": "computer.interface.generic",
|
62
|
+
"object": "GenericComputerInterface",
|
63
|
+
"method": "left_click",
|
64
|
+
},
|
65
|
+
{
|
66
|
+
"package": "computer.interface.generic",
|
67
|
+
"object": "GenericComputerInterface",
|
68
|
+
"method": "right_click",
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"package": "computer.interface.generic",
|
72
|
+
"object": "GenericComputerInterface",
|
73
|
+
"method": "double_click",
|
74
|
+
},
|
75
|
+
{
|
76
|
+
"package": "computer.interface.generic",
|
77
|
+
"object": "GenericComputerInterface",
|
78
|
+
"method": "move_cursor",
|
79
|
+
},
|
80
|
+
{
|
81
|
+
"package": "computer.interface.generic",
|
82
|
+
"object": "GenericComputerInterface",
|
83
|
+
"method": "drag_to",
|
84
|
+
},
|
85
|
+
{
|
86
|
+
"package": "computer.interface.generic",
|
87
|
+
"object": "GenericComputerInterface",
|
88
|
+
"method": "drag",
|
89
|
+
},
|
90
|
+
{
|
91
|
+
"package": "computer.interface.generic",
|
92
|
+
"object": "GenericComputerInterface",
|
93
|
+
"method": "key_down",
|
94
|
+
},
|
95
|
+
{
|
96
|
+
"package": "computer.interface.generic",
|
97
|
+
"object": "GenericComputerInterface",
|
98
|
+
"method": "key_up",
|
99
|
+
},
|
100
|
+
{
|
101
|
+
"package": "computer.interface.generic",
|
102
|
+
"object": "GenericComputerInterface",
|
103
|
+
"method": "type_text",
|
104
|
+
},
|
105
|
+
{
|
106
|
+
"package": "computer.interface.generic",
|
107
|
+
"object": "GenericComputerInterface",
|
108
|
+
"method": "press",
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"package": "computer.interface.generic",
|
112
|
+
"object": "GenericComputerInterface",
|
113
|
+
"method": "hotkey",
|
114
|
+
},
|
115
|
+
{
|
116
|
+
"package": "computer.interface.generic",
|
117
|
+
"object": "GenericComputerInterface",
|
118
|
+
"method": "scroll",
|
119
|
+
},
|
120
|
+
{
|
121
|
+
"package": "computer.interface.generic",
|
122
|
+
"object": "GenericComputerInterface",
|
123
|
+
"method": "scroll_down",
|
124
|
+
},
|
125
|
+
{
|
126
|
+
"package": "computer.interface.generic",
|
127
|
+
"object": "GenericComputerInterface",
|
128
|
+
"method": "scroll_up",
|
129
|
+
},
|
130
|
+
{
|
131
|
+
"package": "computer.interface.generic",
|
132
|
+
"object": "GenericComputerInterface",
|
133
|
+
"method": "screenshot",
|
134
|
+
"output_formatter": payload_to_placeholder,
|
135
|
+
},
|
136
|
+
{
|
137
|
+
"package": "computer.interface.generic",
|
138
|
+
"object": "GenericComputerInterface",
|
139
|
+
"method": "get_screen_size",
|
140
|
+
},
|
141
|
+
{
|
142
|
+
"package": "computer.interface.generic",
|
143
|
+
"object": "GenericComputerInterface",
|
144
|
+
"method": "get_cursor_position",
|
145
|
+
},
|
146
|
+
{
|
147
|
+
"package": "computer.interface.generic",
|
148
|
+
"object": "GenericComputerInterface",
|
149
|
+
"method": "copy_to_clipboard",
|
150
|
+
},
|
151
|
+
{
|
152
|
+
"package": "computer.interface.generic",
|
153
|
+
"object": "GenericComputerInterface",
|
154
|
+
"method": "set_clipboard",
|
155
|
+
},
|
156
|
+
{
|
157
|
+
"package": "computer.interface.generic",
|
158
|
+
"object": "GenericComputerInterface",
|
159
|
+
"method": "file_exists",
|
160
|
+
},
|
161
|
+
{
|
162
|
+
"package": "computer.interface.generic",
|
163
|
+
"object": "GenericComputerInterface",
|
164
|
+
"method": "directory_exists",
|
165
|
+
},
|
166
|
+
{
|
167
|
+
"package": "computer.interface.generic",
|
168
|
+
"object": "GenericComputerInterface",
|
169
|
+
"method": "list_dir",
|
170
|
+
},
|
171
|
+
{
|
172
|
+
"package": "computer.interface.generic",
|
173
|
+
"object": "GenericComputerInterface",
|
174
|
+
"method": "read_text",
|
175
|
+
},
|
176
|
+
{
|
177
|
+
"package": "computer.interface.generic",
|
178
|
+
"object": "GenericComputerInterface",
|
179
|
+
"method": "write_text",
|
180
|
+
},
|
181
|
+
{
|
182
|
+
"package": "computer.interface.generic",
|
183
|
+
"object": "GenericComputerInterface",
|
184
|
+
"method": "read_bytes",
|
185
|
+
},
|
186
|
+
{
|
187
|
+
"package": "computer.interface.generic",
|
188
|
+
"object": "GenericComputerInterface",
|
189
|
+
"method": "write_bytes",
|
190
|
+
},
|
191
|
+
{
|
192
|
+
"package": "computer.interface.generic",
|
193
|
+
"object": "GenericComputerInterface",
|
194
|
+
"method": "delete_file",
|
195
|
+
},
|
196
|
+
{
|
197
|
+
"package": "computer.interface.generic",
|
198
|
+
"object": "GenericComputerInterface",
|
199
|
+
"method": "create_dir",
|
200
|
+
},
|
201
|
+
{
|
202
|
+
"package": "computer.interface.generic",
|
203
|
+
"object": "GenericComputerInterface",
|
204
|
+
"method": "delete_dir",
|
205
|
+
},
|
206
|
+
{
|
207
|
+
"package": "computer.interface.generic",
|
208
|
+
"object": "GenericComputerInterface",
|
209
|
+
"method": "get_file_size",
|
210
|
+
},
|
211
|
+
{
|
212
|
+
"package": "computer.interface.generic",
|
213
|
+
"object": "GenericComputerInterface",
|
214
|
+
"method": "run_command",
|
215
|
+
},
|
216
|
+
{
|
217
|
+
"package": "computer.interface.generic",
|
218
|
+
"object": "GenericComputerInterface",
|
219
|
+
"method": "get_accessibility_tree",
|
220
|
+
},
|
221
|
+
{
|
222
|
+
"package": "computer.interface.generic",
|
223
|
+
"object": "GenericComputerInterface",
|
224
|
+
"method": "to_screen_coordinates",
|
225
|
+
},
|
226
|
+
{
|
227
|
+
"package": "computer.interface.generic",
|
228
|
+
"object": "GenericComputerInterface",
|
229
|
+
"method": "get_active_window_bounds",
|
230
|
+
},
|
231
|
+
{
|
232
|
+
"package": "computer.interface.generic",
|
233
|
+
"object": "GenericComputerInterface",
|
234
|
+
"method": "to_screenshot_coordinates",
|
235
|
+
},
|
236
|
+
]
|
237
|
+
|
238
|
+
|
239
|
+
def _with_wrapper(func):
|
240
|
+
"""Helper for providing tracer for wrapper functions. Includes metric collectors."""
|
241
|
+
|
242
|
+
def wrapper(
|
243
|
+
to_wrap,
|
244
|
+
):
|
245
|
+
def wrapper(wrapped, instance, args, kwargs):
|
246
|
+
return func(
|
247
|
+
to_wrap,
|
248
|
+
wrapped,
|
249
|
+
instance,
|
250
|
+
args,
|
251
|
+
kwargs,
|
252
|
+
)
|
253
|
+
|
254
|
+
return wrapper
|
255
|
+
|
256
|
+
return wrapper
|
257
|
+
|
258
|
+
|
259
|
+
def add_input_to_parent_span(span, instance):
|
260
|
+
# api_key is skipped on purpose
|
261
|
+
params = {}
|
262
|
+
if hasattr(instance, "display"):
|
263
|
+
params["display"] = instance.display
|
264
|
+
if hasattr(instance, "memory"):
|
265
|
+
params["memory"] = instance.memory
|
266
|
+
if hasattr(instance, "cpu"):
|
267
|
+
params["cpu"] = instance.cpu
|
268
|
+
if hasattr(instance, "os_type"):
|
269
|
+
params["os_type"] = instance.os_type
|
270
|
+
if hasattr(instance, "name"):
|
271
|
+
params["name"] = instance.name
|
272
|
+
if hasattr(instance, "image"):
|
273
|
+
params["image"] = instance.image
|
274
|
+
if hasattr(instance, "shared_directories"):
|
275
|
+
params["shared_directories"] = instance.shared_directories
|
276
|
+
if hasattr(instance, "use_host_computer_server"):
|
277
|
+
params["use_host_computer_server"] = instance.use_host_computer_server
|
278
|
+
if hasattr(instance, "verbosity"):
|
279
|
+
if (
|
280
|
+
isinstance(instance.verbosity, int)
|
281
|
+
and instance.verbosity in logging._levelToName
|
282
|
+
):
|
283
|
+
params["verbosity"] = logging._levelToName[instance.verbosity]
|
284
|
+
else:
|
285
|
+
params["verbosity"] = instance.verbosity
|
286
|
+
if hasattr(instance, "telemetry_enabled"):
|
287
|
+
params["telemetry_enabled"] = instance.telemetry_enabled
|
288
|
+
if hasattr(instance, "provider_type"):
|
289
|
+
params["provider_type"] = instance.provider_type
|
290
|
+
if hasattr(instance, "port"):
|
291
|
+
params["port"] = instance.port
|
292
|
+
if hasattr(instance, "noVNC_port"):
|
293
|
+
params["noVNC_port"] = instance.noVNC_port
|
294
|
+
if hasattr(instance, "host"):
|
295
|
+
params["host"] = instance.host
|
296
|
+
if hasattr(instance, "storage"):
|
297
|
+
params["storage"] = instance.storage
|
298
|
+
if hasattr(instance, "ephemeral"):
|
299
|
+
params["ephemeral"] = instance.ephemeral
|
300
|
+
if hasattr(instance, "experiments"):
|
301
|
+
params["experiments"] = instance.experiments
|
302
|
+
span.set_attribute("lmnr.span.input", json_dumps(params))
|
303
|
+
|
304
|
+
|
305
|
+
@_with_wrapper
|
306
|
+
def _wrap(
|
307
|
+
to_wrap,
|
308
|
+
wrapped,
|
309
|
+
instance,
|
310
|
+
args,
|
311
|
+
kwargs,
|
312
|
+
):
|
313
|
+
if to_wrap.get("action") == "start_parent_span":
|
314
|
+
parent_span = Laminar.start_span("computer.run")
|
315
|
+
add_input_to_parent_span(parent_span, instance)
|
316
|
+
result = wrapped(*args, **kwargs)
|
317
|
+
try:
|
318
|
+
instance._interface._lmnr_parent_span = parent_span
|
319
|
+
except Exception:
|
320
|
+
pass
|
321
|
+
return result
|
322
|
+
elif to_wrap.get("action") == "end_parent_span":
|
323
|
+
result = wrapped(*args, **kwargs)
|
324
|
+
try:
|
325
|
+
parent_span: Span = instance._interface._lmnr_parent_span
|
326
|
+
if parent_span and parent_span.is_recording():
|
327
|
+
parent_span.end()
|
328
|
+
except Exception:
|
329
|
+
pass
|
330
|
+
return result
|
331
|
+
|
332
|
+
# if there's no parent span, use
|
333
|
+
parent_span = trace.get_current_span(context=get_current_context())
|
334
|
+
try:
|
335
|
+
if instance._lmnr_parent_span:
|
336
|
+
parent_span: Span = instance._lmnr_parent_span
|
337
|
+
except Exception:
|
338
|
+
pass
|
339
|
+
|
340
|
+
with Laminar.use_span(parent_span):
|
341
|
+
instance_name = "interface"
|
342
|
+
with Laminar.start_as_current_span(
|
343
|
+
f"{instance_name}.{to_wrap.get('method')}", span_type="TOOL"
|
344
|
+
) as span:
|
345
|
+
span.set_attribute(
|
346
|
+
"lmnr.span.input",
|
347
|
+
json_dumps(get_input_from_func_args(wrapped, True, args, kwargs)),
|
348
|
+
)
|
349
|
+
try:
|
350
|
+
result = wrapped(*args, **kwargs)
|
351
|
+
except Exception as e: # pylint: disable=broad-except
|
352
|
+
span.set_status(Status(StatusCode.ERROR))
|
353
|
+
span.record_exception(e)
|
354
|
+
span.end()
|
355
|
+
raise
|
356
|
+
output_formatter = to_wrap.get("output_formatter") or (
|
357
|
+
lambda x: json_dumps(x)
|
358
|
+
)
|
359
|
+
span.set_attribute("lmnr.span.output", output_formatter(result))
|
360
|
+
return result
|
361
|
+
|
362
|
+
|
363
|
+
@_with_wrapper
|
364
|
+
async def _wrap_async(
|
365
|
+
to_wrap,
|
366
|
+
wrapped,
|
367
|
+
instance,
|
368
|
+
args,
|
369
|
+
kwargs,
|
370
|
+
):
|
371
|
+
if to_wrap.get("action") == "start_parent_span":
|
372
|
+
parent_span = Laminar.start_span("computer.run")
|
373
|
+
add_input_to_parent_span(parent_span, instance)
|
374
|
+
result = await wrapped(*args, **kwargs)
|
375
|
+
try:
|
376
|
+
instance._interface._lmnr_parent_span = parent_span
|
377
|
+
except Exception:
|
378
|
+
pass
|
379
|
+
return result
|
380
|
+
elif to_wrap.get("action") == "end_parent_span":
|
381
|
+
result = await wrapped(*args, **kwargs)
|
382
|
+
try:
|
383
|
+
parent_span: Span = instance._interface._lmnr_parent_span
|
384
|
+
if parent_span and parent_span.is_recording():
|
385
|
+
parent_span.end()
|
386
|
+
except Exception:
|
387
|
+
pass
|
388
|
+
return result
|
389
|
+
|
390
|
+
# if there's no parent span, use
|
391
|
+
parent_span = trace.get_current_span(context=get_current_context())
|
392
|
+
try:
|
393
|
+
parent_span: Span = instance._lmnr_parent_span
|
394
|
+
except Exception:
|
395
|
+
pass
|
396
|
+
|
397
|
+
with Laminar.use_span(parent_span):
|
398
|
+
instance_name = "interface"
|
399
|
+
with Laminar.start_as_current_span(
|
400
|
+
f"{instance_name}.{to_wrap.get('method')}",
|
401
|
+
span_type="TOOL",
|
402
|
+
) as span:
|
403
|
+
span.set_attribute(
|
404
|
+
"lmnr.span.input",
|
405
|
+
json_dumps(get_input_from_func_args(wrapped, True, args, kwargs)),
|
406
|
+
)
|
407
|
+
try:
|
408
|
+
result = await wrapped(*args, **kwargs)
|
409
|
+
except Exception as e: # pylint: disable=broad-except
|
410
|
+
span.set_status(Status(StatusCode.ERROR))
|
411
|
+
span.record_exception(e)
|
412
|
+
span.end()
|
413
|
+
raise
|
414
|
+
output_formatter = to_wrap.get("output_formatter") or (
|
415
|
+
lambda x: json_dumps(x)
|
416
|
+
)
|
417
|
+
span.set_attribute("lmnr.span.output", output_formatter(result))
|
418
|
+
return result
|
419
|
+
|
420
|
+
|
421
|
+
class CuaComputerInstrumentor(BaseInstrumentor):
|
422
|
+
def __init__(self):
|
423
|
+
super().__init__()
|
424
|
+
|
425
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
426
|
+
return _instruments
|
427
|
+
|
428
|
+
def _instrument(self, **kwargs):
|
429
|
+
for wrapped_method in WRAPPED_METHODS:
|
430
|
+
wrap_package = wrapped_method.get("package")
|
431
|
+
wrap_object = wrapped_method.get("object")
|
432
|
+
wrap_method = wrapped_method.get("method")
|
433
|
+
|
434
|
+
try:
|
435
|
+
wrap_function_wrapper(
|
436
|
+
wrap_package,
|
437
|
+
f"{wrap_object}.{wrap_method}",
|
438
|
+
_wrap(wrapped_method),
|
439
|
+
)
|
440
|
+
except ModuleNotFoundError:
|
441
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
442
|
+
|
443
|
+
for wrapped_method in WRAPPED_AMETHODS:
|
444
|
+
wrap_package = wrapped_method.get("package")
|
445
|
+
wrap_object = wrapped_method.get("object")
|
446
|
+
wrap_method = wrapped_method.get("method")
|
447
|
+
try:
|
448
|
+
wrap_function_wrapper(
|
449
|
+
wrap_package,
|
450
|
+
f"{wrap_object}.{wrap_method}",
|
451
|
+
_wrap_async(wrapped_method),
|
452
|
+
)
|
453
|
+
except ModuleNotFoundError:
|
454
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
455
|
+
|
456
|
+
def _uninstrument(self, **kwargs):
|
457
|
+
for wrapped_method in WRAPPED_METHODS:
|
458
|
+
wrap_package = wrapped_method.get("package")
|
459
|
+
wrap_object = wrapped_method.get("object")
|
460
|
+
try:
|
461
|
+
unwrap(
|
462
|
+
f"{wrap_package}.{wrap_object}",
|
463
|
+
wrapped_method.get("method"),
|
464
|
+
)
|
465
|
+
except ModuleNotFoundError:
|
466
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
467
|
+
|
468
|
+
for wrapped_method in WRAPPED_AMETHODS:
|
469
|
+
wrap_package = wrapped_method.get("package")
|
470
|
+
wrap_object = wrapped_method.get("object")
|
471
|
+
try:
|
472
|
+
unwrap(
|
473
|
+
f"{wrap_package}.{wrap_object}",
|
474
|
+
wrapped_method.get("method"),
|
475
|
+
)
|
476
|
+
except ModuleNotFoundError:
|
477
|
+
pass # that's ok, we don't want to fail if some methods do not exist
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import base64
|
2
|
+
import orjson
|
3
|
+
|
4
|
+
|
5
|
+
def payload_to_base64url(payload_bytes: bytes) -> bytes:
|
6
|
+
data = base64.b64encode(payload_bytes).decode("utf-8")
|
7
|
+
url = f"data:image/png;base64,{data}"
|
8
|
+
return orjson.dumps({"base64url": url})
|
9
|
+
|
10
|
+
|
11
|
+
def payload_to_placeholder(payload_bytes: bytes) -> str:
|
12
|
+
return "<BINARY_BLOB_SCREENSHOT>"
|
@@ -144,6 +144,11 @@ def _set_request_attributes(span, kwargs, instance=None):
|
|
144
144
|
_set_span_attribute(
|
145
145
|
span, SpanAttributes.LLM_IS_STREAMING, kwargs.get("stream") or False
|
146
146
|
)
|
147
|
+
_set_span_attribute(
|
148
|
+
span,
|
149
|
+
SpanAttributes.LLM_REQUEST_REASONING_EFFORT,
|
150
|
+
kwargs.get("reasoning_effort"),
|
151
|
+
)
|
147
152
|
if response_format := kwargs.get("response_format"):
|
148
153
|
# backward-compatible check for
|
149
154
|
# openai.types.shared_params.response_format_json_schema.ResponseFormatJSONSchema
|
@@ -263,6 +268,15 @@ def _set_response_attributes(span, response):
|
|
263
268
|
SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS,
|
264
269
|
prompt_tokens_details.get("cached_tokens", 0),
|
265
270
|
)
|
271
|
+
|
272
|
+
if completion_token_details := dict(usage.get("completion_tokens_details", {})):
|
273
|
+
reasoning_tokens = completion_token_details.get("reasoning_tokens")
|
274
|
+
_set_span_attribute(
|
275
|
+
span,
|
276
|
+
SpanAttributes.LLM_USAGE_REASONING_TOKENS,
|
277
|
+
reasoning_tokens or 0,
|
278
|
+
)
|
279
|
+
|
266
280
|
return
|
267
281
|
|
268
282
|
|
@@ -5,6 +5,7 @@ import threading
|
|
5
5
|
import traceback
|
6
6
|
from contextlib import asynccontextmanager
|
7
7
|
from importlib.metadata import version
|
8
|
+
from packaging.version import parse
|
8
9
|
|
9
10
|
from opentelemetry import context as context_api
|
10
11
|
from opentelemetry._events import EventLogger
|
@@ -19,7 +20,15 @@ LMNR_TRACE_CONTENT = "LMNR_TRACE_CONTENT"
|
|
19
20
|
|
20
21
|
|
21
22
|
def is_openai_v1():
|
22
|
-
return _OPENAI_VERSION >= "1.0.0"
|
23
|
+
return parse(_OPENAI_VERSION) >= parse("1.0.0")
|
24
|
+
|
25
|
+
|
26
|
+
def is_reasoning_supported():
|
27
|
+
# Reasoning has been introduced in OpenAI API on Dec 17, 2024
|
28
|
+
# as per https://platform.openai.com/docs/changelog.
|
29
|
+
# The updated OpenAI library version is 1.58.0
|
30
|
+
# as per https://pypi.org/project/openai/.
|
31
|
+
return parse(_OPENAI_VERSION) >= parse("1.58.0")
|
23
32
|
|
24
33
|
|
25
34
|
def is_azure_openai(instance):
|