lmnr 0.6.16__py3-none-any.whl → 0.7.26__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.
Files changed (113) hide show
  1. lmnr/__init__.py +6 -15
  2. lmnr/cli/__init__.py +270 -0
  3. lmnr/cli/datasets.py +371 -0
  4. lmnr/{cli.py → cli/evals.py} +20 -102
  5. lmnr/cli/rules.py +42 -0
  6. lmnr/opentelemetry_lib/__init__.py +9 -2
  7. lmnr/opentelemetry_lib/decorators/__init__.py +274 -168
  8. lmnr/opentelemetry_lib/litellm/__init__.py +352 -38
  9. lmnr/opentelemetry_lib/litellm/utils.py +82 -0
  10. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
  11. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
  12. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
  13. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
  14. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
  15. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
  16. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
  17. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
  18. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
  19. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
  20. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
  21. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
  22. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
  23. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +191 -129
  24. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
  25. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +126 -41
  26. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
  27. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
  28. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
  29. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
  30. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
  31. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
  32. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
  33. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
  34. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
  35. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +16 -16
  36. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
  37. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
  38. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
  39. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
  40. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
  41. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
  42. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
  43. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
  44. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
  45. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
  46. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
  47. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
  48. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
  49. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
  50. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
  51. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
  52. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
  53. lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
  54. lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +59 -61
  55. lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
  56. lmnr/opentelemetry_lib/tracing/__init__.py +119 -18
  57. lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +124 -25
  58. lmnr/opentelemetry_lib/tracing/attributes.py +4 -0
  59. lmnr/opentelemetry_lib/tracing/context.py +200 -0
  60. lmnr/opentelemetry_lib/tracing/exporter.py +109 -15
  61. lmnr/opentelemetry_lib/tracing/instruments.py +22 -5
  62. lmnr/opentelemetry_lib/tracing/processor.py +128 -30
  63. lmnr/opentelemetry_lib/tracing/span.py +398 -0
  64. lmnr/opentelemetry_lib/tracing/tracer.py +40 -1
  65. lmnr/opentelemetry_lib/tracing/utils.py +62 -0
  66. lmnr/opentelemetry_lib/utils/package_check.py +9 -0
  67. lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
  68. lmnr/sdk/browser/background_send_events.py +158 -0
  69. lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
  70. lmnr/sdk/browser/browser_use_otel.py +12 -12
  71. lmnr/sdk/browser/bubus_otel.py +71 -0
  72. lmnr/sdk/browser/cdp_utils.py +518 -0
  73. lmnr/sdk/browser/inject_script.js +514 -0
  74. lmnr/sdk/browser/patchright_otel.py +18 -44
  75. lmnr/sdk/browser/playwright_otel.py +104 -187
  76. lmnr/sdk/browser/pw_utils.py +249 -210
  77. lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
  78. lmnr/sdk/browser/utils.py +1 -1
  79. lmnr/sdk/client/asynchronous/async_client.py +47 -15
  80. lmnr/sdk/client/asynchronous/resources/__init__.py +2 -7
  81. lmnr/sdk/client/asynchronous/resources/browser_events.py +1 -0
  82. lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
  83. lmnr/sdk/client/asynchronous/resources/evals.py +122 -18
  84. lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
  85. lmnr/sdk/client/asynchronous/resources/tags.py +4 -10
  86. lmnr/sdk/client/synchronous/resources/__init__.py +2 -2
  87. lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
  88. lmnr/sdk/client/synchronous/resources/evals.py +83 -17
  89. lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
  90. lmnr/sdk/client/synchronous/resources/tags.py +4 -10
  91. lmnr/sdk/client/synchronous/sync_client.py +47 -15
  92. lmnr/sdk/datasets/__init__.py +94 -0
  93. lmnr/sdk/datasets/file_utils.py +91 -0
  94. lmnr/sdk/decorators.py +103 -23
  95. lmnr/sdk/evaluations.py +122 -33
  96. lmnr/sdk/laminar.py +816 -333
  97. lmnr/sdk/log.py +7 -2
  98. lmnr/sdk/types.py +124 -143
  99. lmnr/sdk/utils.py +115 -2
  100. lmnr/version.py +1 -1
  101. {lmnr-0.6.16.dist-info → lmnr-0.7.26.dist-info}/METADATA +71 -78
  102. lmnr-0.7.26.dist-info/RECORD +116 -0
  103. lmnr-0.7.26.dist-info/WHEEL +4 -0
  104. lmnr-0.7.26.dist-info/entry_points.txt +3 -0
  105. lmnr/opentelemetry_lib/tracing/context_properties.py +0 -65
  106. lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -98
  107. lmnr/sdk/client/asynchronous/resources/agent.py +0 -329
  108. lmnr/sdk/client/synchronous/resources/agent.py +0 -323
  109. lmnr/sdk/datasets.py +0 -60
  110. lmnr-0.6.16.dist-info/LICENSE +0 -75
  111. lmnr-0.6.16.dist-info/RECORD +0 -61
  112. lmnr-0.6.16.dist-info/WHEEL +0 -4
  113. lmnr-0.6.16.dist-info/entry_points.txt +0 -3
@@ -0,0 +1,425 @@
1
+ import logging
2
+ import time
3
+ from typing import Optional
4
+
5
+ from opentelemetry._events import EventLogger
6
+ from .config import Config
7
+ from .event_emitter import (
8
+ emit_streaming_response_events,
9
+ )
10
+ from .span_utils import (
11
+ set_streaming_response_attributes,
12
+ )
13
+ from .utils import (
14
+ count_prompt_tokens_from_request,
15
+ dont_throw,
16
+ error_metrics_attributes,
17
+ set_span_attribute,
18
+ shared_metrics_attributes,
19
+ should_emit_events,
20
+ )
21
+ from opentelemetry.metrics import Counter, Histogram
22
+ from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
23
+ GEN_AI_RESPONSE_ID,
24
+ )
25
+ from opentelemetry.semconv_ai import SpanAttributes
26
+ from opentelemetry.trace.status import Status, StatusCode
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ @dont_throw
32
+ def _process_response_item(item, complete_response):
33
+ if item.type == "message_start":
34
+ complete_response["model"] = item.message.model
35
+ usage = dict(item.message.usage)
36
+ complete_response["usage"] = usage
37
+ complete_response["service_tier"] = usage.get("service_tier") or None
38
+ complete_response["id"] = item.message.id
39
+ elif item.type == "content_block_start":
40
+ index = item.index
41
+ if len(complete_response.get("events")) <= index:
42
+ complete_response["events"].append(
43
+ {"index": index, "text": "", "type": item.content_block.type}
44
+ )
45
+ if item.content_block.type == "tool_use":
46
+ complete_response["events"][index]["id"] = item.content_block.id
47
+ complete_response["events"][index]["name"] = item.content_block.name
48
+ complete_response["events"][index]["input"] = ""
49
+
50
+ elif item.type == "content_block_delta":
51
+ index = item.index
52
+ if item.delta.type == "thinking_delta":
53
+ complete_response["events"][index]["text"] += item.delta.thinking or ""
54
+ elif item.delta.type == "text_delta":
55
+ complete_response["events"][index]["text"] += item.delta.text or ""
56
+ elif item.delta.type == "input_json_delta":
57
+ complete_response["events"][index]["input"] += item.delta.partial_json
58
+ elif item.type == "message_delta":
59
+ for event in complete_response.get("events", []):
60
+ event["finish_reason"] = item.delta.stop_reason
61
+ if item.usage:
62
+ if "usage" in complete_response:
63
+ item_output_tokens = dict(item.usage).get("output_tokens", 0)
64
+ existing_output_tokens = complete_response["usage"].get(
65
+ "output_tokens", 0
66
+ )
67
+ complete_response["usage"]["output_tokens"] = (
68
+ item_output_tokens + existing_output_tokens
69
+ )
70
+ else:
71
+ complete_response["usage"] = dict(item.usage)
72
+ elif item.type in ["message_stop", "message_start"]:
73
+ # raw stream returns the service_tier in the message_start event
74
+ # messages.stream returns the service_tier in the message_stop event
75
+ usage = dict(item.message.usage or {})
76
+ complete_response["service_tier"] = usage.get("service_tier")
77
+
78
+
79
+ def _set_token_usage(
80
+ span,
81
+ complete_response,
82
+ prompt_tokens,
83
+ completion_tokens,
84
+ metric_attributes: dict = {},
85
+ token_histogram: Histogram = None,
86
+ choice_counter: Counter = None,
87
+ ):
88
+ cache_read_tokens = (
89
+ complete_response.get("usage", {}).get("cache_read_input_tokens", 0) or 0
90
+ )
91
+ cache_creation_tokens = (
92
+ complete_response.get("usage", {}).get("cache_creation_input_tokens", 0) or 0
93
+ )
94
+
95
+ input_tokens = prompt_tokens + cache_read_tokens + cache_creation_tokens
96
+ total_tokens = input_tokens + completion_tokens
97
+
98
+ set_span_attribute(span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS, input_tokens)
99
+ set_span_attribute(
100
+ span, SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, completion_tokens
101
+ )
102
+ set_span_attribute(span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, total_tokens)
103
+
104
+ set_span_attribute(
105
+ span, SpanAttributes.LLM_RESPONSE_MODEL, complete_response.get("model")
106
+ )
107
+ set_span_attribute(
108
+ span, SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS, cache_read_tokens
109
+ )
110
+ set_span_attribute(
111
+ span,
112
+ SpanAttributes.LLM_USAGE_CACHE_CREATION_INPUT_TOKENS,
113
+ cache_creation_tokens,
114
+ )
115
+
116
+ if token_histogram and type(input_tokens) is int and input_tokens >= 0:
117
+ token_histogram.record(
118
+ input_tokens,
119
+ attributes={
120
+ **metric_attributes,
121
+ SpanAttributes.LLM_TOKEN_TYPE: "input",
122
+ },
123
+ )
124
+
125
+ if token_histogram and type(completion_tokens) is int and completion_tokens >= 0:
126
+ token_histogram.record(
127
+ completion_tokens,
128
+ attributes={
129
+ **metric_attributes,
130
+ SpanAttributes.LLM_TOKEN_TYPE: "output",
131
+ },
132
+ )
133
+
134
+ if type(complete_response.get("events")) is list and choice_counter:
135
+ for event in complete_response.get("events"):
136
+ choice_counter.add(
137
+ 1,
138
+ attributes={
139
+ **metric_attributes,
140
+ SpanAttributes.LLM_RESPONSE_FINISH_REASON: event.get(
141
+ "finish_reason"
142
+ ),
143
+ },
144
+ )
145
+
146
+
147
+ def _handle_streaming_response(span, event_logger, complete_response):
148
+ if should_emit_events() and event_logger:
149
+ emit_streaming_response_events(event_logger, complete_response)
150
+ else:
151
+ if not span.is_recording():
152
+ return
153
+ set_streaming_response_attributes(span, complete_response.get("events"))
154
+
155
+
156
+ @dont_throw
157
+ def build_from_streaming_response(
158
+ span,
159
+ response,
160
+ instance,
161
+ start_time,
162
+ token_histogram: Histogram = None,
163
+ choice_counter: Counter = None,
164
+ duration_histogram: Histogram = None,
165
+ exception_counter: Counter = None,
166
+ event_logger: Optional[EventLogger] = None,
167
+ kwargs: dict = {},
168
+ ):
169
+ complete_response = {
170
+ "events": [],
171
+ "model": "",
172
+ "usage": {},
173
+ "id": "",
174
+ "service_tier": None,
175
+ }
176
+
177
+ for item in response:
178
+ try:
179
+ yield item
180
+ except Exception as e:
181
+ attributes = error_metrics_attributes(e)
182
+ if exception_counter:
183
+ exception_counter.add(1, attributes=attributes)
184
+ raise e
185
+ _process_response_item(item, complete_response)
186
+
187
+ metric_attributes = shared_metrics_attributes(complete_response)
188
+ set_span_attribute(span, GEN_AI_RESPONSE_ID, complete_response.get("id"))
189
+ set_span_attribute(
190
+ span,
191
+ "anthropic.response.service_tier",
192
+ complete_response.get("service_tier"),
193
+ )
194
+ if duration_histogram:
195
+ duration = time.time() - start_time
196
+ duration_histogram.record(
197
+ duration,
198
+ attributes=metric_attributes,
199
+ )
200
+
201
+ # calculate token usage
202
+ if Config.enrich_token_usage:
203
+ try:
204
+ completion_tokens = -1
205
+ # prompt_usage
206
+ if usage := complete_response.get("usage"):
207
+ prompt_tokens = usage.get("input_tokens", 0) or 0
208
+ else:
209
+ prompt_tokens = count_prompt_tokens_from_request(instance, kwargs)
210
+
211
+ # completion_usage
212
+ if usage := complete_response.get("usage"):
213
+ completion_tokens = usage.get("output_tokens", 0) or 0
214
+ else:
215
+ completion_content = ""
216
+ if complete_response.get("events"):
217
+ model_name = complete_response.get("model") or None
218
+ for event in complete_response.get("events"):
219
+ if event.get("text"):
220
+ completion_content += event.get("text")
221
+
222
+ if model_name and hasattr(instance, "count_tokens"):
223
+ completion_tokens = instance.count_tokens(completion_content)
224
+
225
+ _set_token_usage(
226
+ span,
227
+ complete_response,
228
+ prompt_tokens,
229
+ completion_tokens,
230
+ metric_attributes,
231
+ token_histogram,
232
+ choice_counter,
233
+ )
234
+ except Exception as e:
235
+ logger.warning("Failed to set token usage, error: %s", e)
236
+
237
+ _handle_streaming_response(span, event_logger, complete_response)
238
+
239
+ if span.is_recording():
240
+ span.set_status(Status(StatusCode.OK))
241
+ span.end()
242
+
243
+
244
+ @dont_throw
245
+ async def abuild_from_streaming_response(
246
+ span,
247
+ response,
248
+ instance,
249
+ start_time,
250
+ token_histogram: Histogram = None,
251
+ choice_counter: Counter = None,
252
+ duration_histogram: Histogram = None,
253
+ exception_counter: Counter = None,
254
+ event_logger: Optional[EventLogger] = None,
255
+ kwargs: dict = {},
256
+ ):
257
+ complete_response = {
258
+ "events": [],
259
+ "model": "",
260
+ "usage": {},
261
+ "id": "",
262
+ "service_tier": None,
263
+ }
264
+ async for item in response:
265
+ try:
266
+ yield item
267
+ except Exception as e:
268
+ attributes = error_metrics_attributes(e)
269
+ if exception_counter:
270
+ exception_counter.add(1, attributes=attributes)
271
+ raise e
272
+ _process_response_item(item, complete_response)
273
+
274
+ set_span_attribute(span, GEN_AI_RESPONSE_ID, complete_response.get("id"))
275
+ set_span_attribute(
276
+ span,
277
+ "anthropic.response.service_tier",
278
+ complete_response.get("service_tier"),
279
+ )
280
+
281
+ metric_attributes = shared_metrics_attributes(complete_response)
282
+
283
+ if duration_histogram:
284
+ duration = time.time() - start_time
285
+ duration_histogram.record(
286
+ duration,
287
+ attributes=metric_attributes,
288
+ )
289
+
290
+ # calculate token usage
291
+ if Config.enrich_token_usage:
292
+ try:
293
+ # prompt_usage
294
+ if usage := complete_response.get("usage"):
295
+ prompt_tokens = usage.get("input_tokens", 0)
296
+ else:
297
+ prompt_tokens = count_prompt_tokens_from_request(instance, kwargs)
298
+
299
+ # completion_usage
300
+ if usage := complete_response.get("usage"):
301
+ completion_tokens = usage.get("output_tokens", 0)
302
+ else:
303
+ completion_content = ""
304
+ if complete_response.get("events"):
305
+ model_name = complete_response.get("model") or None
306
+ for event in complete_response.get("events"):
307
+ if event.get("text"):
308
+ completion_content += event.get("text")
309
+
310
+ if model_name and hasattr(instance, "count_tokens"):
311
+ completion_tokens = instance.count_tokens(completion_content)
312
+
313
+ _set_token_usage(
314
+ span,
315
+ complete_response,
316
+ prompt_tokens,
317
+ completion_tokens,
318
+ metric_attributes,
319
+ token_histogram,
320
+ choice_counter,
321
+ )
322
+ except Exception as e:
323
+ logger.warning("Failed to set token usage, error: %s", str(e))
324
+
325
+ _handle_streaming_response(span, event_logger, complete_response)
326
+
327
+ if span.is_recording():
328
+ span.set_status(Status(StatusCode.OK))
329
+ span.end()
330
+
331
+
332
+ class WrappedMessageStreamManager:
333
+ """Wrapper for MessageStreamManager that handles instrumentation"""
334
+
335
+ def __init__(
336
+ self,
337
+ stream_manager,
338
+ span,
339
+ instance,
340
+ start_time,
341
+ token_histogram,
342
+ choice_counter,
343
+ duration_histogram,
344
+ exception_counter,
345
+ event_logger,
346
+ kwargs,
347
+ ):
348
+ self._stream_manager = stream_manager
349
+ self._span = span
350
+ self._instance = instance
351
+ self._start_time = start_time
352
+ self._token_histogram = token_histogram
353
+ self._choice_counter = choice_counter
354
+ self._duration_histogram = duration_histogram
355
+ self._exception_counter = exception_counter
356
+ self._event_logger = event_logger
357
+ self._kwargs = kwargs
358
+
359
+ def __enter__(self):
360
+ # Call the original stream manager's __enter__ to get the actual stream
361
+ stream = self._stream_manager.__enter__()
362
+ # Return the wrapped stream
363
+ return build_from_streaming_response(
364
+ self._span,
365
+ stream,
366
+ self._instance,
367
+ self._start_time,
368
+ self._token_histogram,
369
+ self._choice_counter,
370
+ self._duration_histogram,
371
+ self._exception_counter,
372
+ self._event_logger,
373
+ self._kwargs,
374
+ )
375
+
376
+ def __exit__(self, exc_type, exc_val, exc_tb):
377
+ return self._stream_manager.__exit__(exc_type, exc_val, exc_tb)
378
+
379
+
380
+ class WrappedAsyncMessageStreamManager:
381
+ """Wrapper for AsyncMessageStreamManager that handles instrumentation"""
382
+
383
+ def __init__(
384
+ self,
385
+ stream_manager,
386
+ span,
387
+ instance,
388
+ start_time,
389
+ token_histogram,
390
+ choice_counter,
391
+ duration_histogram,
392
+ exception_counter,
393
+ event_logger,
394
+ kwargs,
395
+ ):
396
+ self._stream_manager = stream_manager
397
+ self._span = span
398
+ self._instance = instance
399
+ self._start_time = start_time
400
+ self._token_histogram = token_histogram
401
+ self._choice_counter = choice_counter
402
+ self._duration_histogram = duration_histogram
403
+ self._exception_counter = exception_counter
404
+ self._event_logger = event_logger
405
+ self._kwargs = kwargs
406
+
407
+ async def __aenter__(self):
408
+ # Call the original stream manager's __aenter__ to get the actual stream
409
+ stream = await self._stream_manager.__aenter__()
410
+ # Return the wrapped stream
411
+ return abuild_from_streaming_response(
412
+ self._span,
413
+ stream,
414
+ self._instance,
415
+ self._start_time,
416
+ self._token_histogram,
417
+ self._choice_counter,
418
+ self._duration_histogram,
419
+ self._exception_counter,
420
+ self._event_logger,
421
+ self._kwargs,
422
+ )
423
+
424
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
425
+ return await self._stream_manager.__aexit__(exc_type, exc_val, exc_tb)