telemetry-dev 0.1.0__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.
@@ -0,0 +1,676 @@
1
+ from __future__ import annotations
2
+
3
+ import traceback
4
+ from collections.abc import Callable, Mapping, Sequence
5
+ from contextvars import Token
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from typing import Any, Literal, TypedDict
9
+ from weakref import WeakKeyDictionary
10
+
11
+ from opentelemetry import context as otel_context
12
+ from opentelemetry import trace
13
+ from opentelemetry.context import Context
14
+ from opentelemetry.sdk.trace import Span as SdkSpan
15
+ from opentelemetry.trace import Span, SpanContext, Status, StatusCode
16
+ from opentelemetry.util import types as otel_types
17
+
18
+ from ._client import Client, get_client
19
+ from ._config import logger
20
+ from ._context import context_from_parent, traceparent_of
21
+ from ._semconv import (
22
+ ATTR_AGENT_ID,
23
+ ATTR_AGENT_NAME,
24
+ ATTR_COST,
25
+ ATTR_ERROR_TYPE,
26
+ ATTR_FINISH_REASONS,
27
+ ATTR_OPERATION,
28
+ ATTR_OUTPUT_TYPE,
29
+ ATTR_PROVIDER,
30
+ ATTR_REQUEST_MODEL,
31
+ ATTR_RESPONSE_ID,
32
+ ATTR_RESPONSE_MODEL,
33
+ ATTR_SYSTEM_INSTRUCTIONS,
34
+ ATTR_TIME_TO_FIRST_CHUNK,
35
+ ATTR_TOOL_CALL_ID,
36
+ ATTR_TOOL_DESCRIPTION,
37
+ ATTR_TOOL_NAME,
38
+ METADATA_PREFIX,
39
+ RESERVED_METADATA_KEYS,
40
+ SAMPLING_ATTRS,
41
+ SEVERITY,
42
+ SPAN_TYPE_TO_OPERATION,
43
+ USAGE_ATTRS,
44
+ SpanType,
45
+ input_key,
46
+ output_key,
47
+ )
48
+ from ._serialize import AttributeValue, coerce_attr_value
49
+
50
+
51
+ class _NotGiven:
52
+ def __repr__(self) -> str:
53
+ return "NOT_GIVEN"
54
+
55
+ def __bool__(self) -> bool:
56
+ return False
57
+
58
+
59
+ NOT_GIVEN: Any = _NotGiven()
60
+
61
+
62
+ class Usage(TypedDict, total=False):
63
+ """Token usage — exactly these six fields are emitted; unknown keys are dropped."""
64
+
65
+ input_tokens: int
66
+ output_tokens: int
67
+ total_tokens: int
68
+ cache_read_input_tokens: int
69
+ cache_creation_input_tokens: int
70
+ reasoning_output_tokens: int
71
+
72
+
73
+ ParentType = str | Context | SpanContext | None
74
+ TimeInput = datetime | int | None
75
+
76
+
77
+ @dataclass
78
+ class _SpanState:
79
+ operation: str
80
+ capture_input: bool
81
+ capture_output: bool
82
+
83
+
84
+ _SPAN_STATES: WeakKeyDictionary[Span, _SpanState] = WeakKeyDictionary()
85
+
86
+
87
+ def _to_ns(value: TimeInput) -> int | None:
88
+ if value is None:
89
+ return None
90
+ if isinstance(value, datetime):
91
+ return int(value.timestamp() * 1_000_000_000)
92
+ return int(value)
93
+
94
+
95
+ def _record_error_on_span(span: Span, error: BaseException) -> None:
96
+ error_type = type(error).__name__
97
+ stacktrace = "".join(traceback.format_exception(type(error), error, error.__traceback__))
98
+ span.set_attribute(ATTR_ERROR_TYPE, error_type)
99
+ span.add_event(
100
+ "exception",
101
+ {
102
+ "exception.type": error_type,
103
+ "exception.message": str(error),
104
+ "exception.stacktrace": stacktrace,
105
+ "log.severity_number": SEVERITY["error"],
106
+ },
107
+ )
108
+ span.set_status(Status(StatusCode.ERROR))
109
+
110
+
111
+ def _metadata_attrs(
112
+ metadata: Mapping[str, Any],
113
+ max_len: int,
114
+ on_error: Callable[[BaseException], None] | None,
115
+ ) -> dict[str, AttributeValue]:
116
+ attrs: dict[str, AttributeValue] = {}
117
+ for key, value in metadata.items():
118
+ if key in RESERVED_METADATA_KEYS:
119
+ logger.debug(
120
+ "telemetry-dev: dropping reserved metadata key %r "
121
+ "(use propagate_attributes user_id/session_id instead)",
122
+ key,
123
+ )
124
+ continue
125
+ attr = coerce_attr_value(
126
+ value,
127
+ max_len=max_len,
128
+ key=f"{METADATA_PREFIX}{key}",
129
+ on_error=on_error,
130
+ )
131
+ if attr is not None:
132
+ attrs[f"{METADATA_PREFIX}{key}"] = attr
133
+ return attrs
134
+
135
+
136
+ def _usage_attrs(usage: Mapping[str, Any]) -> dict[str, AttributeValue]:
137
+ attrs: dict[str, AttributeValue] = {}
138
+ for key, value in usage.items():
139
+ attr = USAGE_ATTRS.get(key)
140
+ if attr is None:
141
+ logger.debug("telemetry-dev: dropping unknown usage key %r", key)
142
+ continue
143
+ if isinstance(value, int | float) and not isinstance(value, bool):
144
+ attrs[attr] = value
145
+ return attrs
146
+
147
+
148
+ def _apply_fields(client: Client, span: Span, state: _SpanState, fields: dict[str, Any]) -> None:
149
+ """Map the shared snake_case field set onto gen_ai.* attributes; raw `attributes` merge last."""
150
+ attrs: dict[str, otel_types.AttributeValue] = {}
151
+
152
+ name = fields.get("name")
153
+ if name is not None:
154
+ span.update_name(name)
155
+
156
+ for field, attr in (
157
+ ("model", ATTR_REQUEST_MODEL),
158
+ ("provider", ATTR_PROVIDER),
159
+ ("response_model", ATTR_RESPONSE_MODEL),
160
+ ("response_id", ATTR_RESPONSE_ID),
161
+ ("output_type", ATTR_OUTPUT_TYPE),
162
+ ("tool_name", ATTR_TOOL_NAME),
163
+ ("tool_call_id", ATTR_TOOL_CALL_ID),
164
+ ("tool_description", ATTR_TOOL_DESCRIPTION),
165
+ ("agent_name", ATTR_AGENT_NAME),
166
+ ("agent_id", ATTR_AGENT_ID),
167
+ ):
168
+ value = fields.get(field)
169
+ if value is not None:
170
+ attrs[attr] = value
171
+
172
+ finish_reason = fields.get("finish_reason")
173
+ if finish_reason:
174
+ attrs[ATTR_FINISH_REASONS] = [finish_reason]
175
+
176
+ usage = fields.get("usage")
177
+ if usage is not None:
178
+ attrs.update(_usage_attrs(usage))
179
+
180
+ cost_usd = fields.get("cost_usd")
181
+ if cost_usd is not None:
182
+ attrs[ATTR_COST] = float(cost_usd)
183
+
184
+ for field, attr in SAMPLING_ATTRS.items():
185
+ value = fields.get(field)
186
+ if value is not None:
187
+ attrs[attr] = list(value) if field == "stop_sequences" else value
188
+
189
+ time_to_first_chunk_ms = fields.get("time_to_first_chunk_ms")
190
+ if time_to_first_chunk_ms is not None:
191
+ attrs[ATTR_TIME_TO_FIRST_CHUNK] = time_to_first_chunk_ms / 1000
192
+
193
+ metadata = fields.get("metadata")
194
+ if metadata is not None:
195
+ attrs.update(_metadata_attrs(metadata, client.max_attribute_length, client.on_error))
196
+
197
+ if "input" in fields and state.capture_input:
198
+ key = input_key(state.operation)
199
+ serialized = client.serialize(fields["input"], key)
200
+ if serialized is not None:
201
+ attrs[key] = serialized
202
+ system_instructions = fields.get("system_instructions")
203
+ if system_instructions is not None and state.capture_input:
204
+ serialized = client.serialize(system_instructions, ATTR_SYSTEM_INSTRUCTIONS)
205
+ if serialized is not None:
206
+ attrs[ATTR_SYSTEM_INSTRUCTIONS] = serialized
207
+ if "output" in fields and state.capture_output:
208
+ key = output_key(state.operation)
209
+ serialized = client.serialize(fields["output"], key)
210
+ if serialized is not None:
211
+ attrs[key] = serialized
212
+
213
+ raw = fields.get("attributes")
214
+ if raw is not None:
215
+ for key, value in raw.items():
216
+ attr = coerce_attr_value(
217
+ value,
218
+ max_len=client.max_attribute_length,
219
+ key=key,
220
+ on_error=client.on_error,
221
+ )
222
+ if attr is not None:
223
+ attrs[key] = attr
224
+
225
+ if attrs:
226
+ span.set_attributes(attrs)
227
+
228
+ error = fields.get("error")
229
+ if error is not None:
230
+ _record_error_on_span(span, error)
231
+
232
+
233
+ def _collect_fields(
234
+ *,
235
+ input: Any = NOT_GIVEN,
236
+ output: Any = NOT_GIVEN,
237
+ name: str | None = None,
238
+ model: str | None = None,
239
+ provider: str | None = None,
240
+ system_instructions: Any = None,
241
+ response_model: str | None = None,
242
+ response_id: str | None = None,
243
+ output_type: str | None = None,
244
+ finish_reason: str | None = None,
245
+ usage: Usage | Mapping[str, int] | None = None,
246
+ cost_usd: float | None = None,
247
+ temperature: float | None = None,
248
+ top_p: float | None = None,
249
+ top_k: float | None = None,
250
+ max_tokens: int | None = None,
251
+ stop_sequences: Sequence[str] | None = None,
252
+ seed: int | None = None,
253
+ frequency_penalty: float | None = None,
254
+ presence_penalty: float | None = None,
255
+ time_to_first_chunk_ms: float | None = None,
256
+ tool_name: str | None = None,
257
+ tool_call_id: str | None = None,
258
+ tool_description: str | None = None,
259
+ agent_name: str | None = None,
260
+ agent_id: str | None = None,
261
+ metadata: Mapping[str, Any] | None = None,
262
+ attributes: Mapping[str, AttributeValue] | None = None,
263
+ error: BaseException | None = None,
264
+ ) -> dict[str, Any]:
265
+ fields: dict[str, Any] = {}
266
+ if input is not NOT_GIVEN:
267
+ fields["input"] = input
268
+ if output is not NOT_GIVEN:
269
+ fields["output"] = output
270
+ for key, value in (
271
+ ("name", name),
272
+ ("model", model),
273
+ ("provider", provider),
274
+ ("system_instructions", system_instructions),
275
+ ("response_model", response_model),
276
+ ("response_id", response_id),
277
+ ("output_type", output_type),
278
+ ("finish_reason", finish_reason),
279
+ ("usage", usage),
280
+ ("cost_usd", cost_usd),
281
+ ("temperature", temperature),
282
+ ("top_p", top_p),
283
+ ("top_k", top_k),
284
+ ("max_tokens", max_tokens),
285
+ ("stop_sequences", stop_sequences),
286
+ ("seed", seed),
287
+ ("frequency_penalty", frequency_penalty),
288
+ ("presence_penalty", presence_penalty),
289
+ ("time_to_first_chunk_ms", time_to_first_chunk_ms),
290
+ ("tool_name", tool_name),
291
+ ("tool_call_id", tool_call_id),
292
+ ("tool_description", tool_description),
293
+ ("agent_name", agent_name),
294
+ ("agent_id", agent_id),
295
+ ("metadata", metadata),
296
+ ("attributes", attributes),
297
+ ("error", error),
298
+ ):
299
+ if value is not None:
300
+ fields[key] = value
301
+ return fields
302
+
303
+
304
+ class SpanHandle:
305
+ """Handle around an OTel span. Use as a context manager to activate the span in the
306
+ current context, or keep it detached and call .end() manually."""
307
+
308
+ def __init__(self, span: Span, client: Client | None, state: _SpanState | None) -> None:
309
+ self.span = span
310
+ self._client = client
311
+ self._state = state
312
+ self._context_token: Token[Context] | None = None
313
+ self._ended = False
314
+
315
+ def _recording(self) -> bool:
316
+ return (
317
+ self._client is not None
318
+ and self._state is not None
319
+ and not self._ended
320
+ and self.span.is_recording()
321
+ )
322
+
323
+ def update(
324
+ self,
325
+ *,
326
+ name: str | None = None,
327
+ input: Any = NOT_GIVEN,
328
+ output: Any = NOT_GIVEN,
329
+ model: str | None = None,
330
+ provider: str | None = None,
331
+ system_instructions: Any = None,
332
+ response_model: str | None = None,
333
+ response_id: str | None = None,
334
+ output_type: str | None = None,
335
+ finish_reason: str | None = None,
336
+ usage: Usage | Mapping[str, int] | None = None,
337
+ cost_usd: float | None = None,
338
+ temperature: float | None = None,
339
+ top_p: float | None = None,
340
+ top_k: float | None = None,
341
+ max_tokens: int | None = None,
342
+ stop_sequences: Sequence[str] | None = None,
343
+ seed: int | None = None,
344
+ frequency_penalty: float | None = None,
345
+ presence_penalty: float | None = None,
346
+ time_to_first_chunk_ms: float | None = None,
347
+ tool_name: str | None = None,
348
+ tool_call_id: str | None = None,
349
+ tool_description: str | None = None,
350
+ agent_name: str | None = None,
351
+ agent_id: str | None = None,
352
+ metadata: Mapping[str, Any] | None = None,
353
+ attributes: Mapping[str, AttributeValue] | None = None,
354
+ error: BaseException | None = None,
355
+ ) -> SpanHandle:
356
+ if not self._recording():
357
+ return self
358
+ assert self._client is not None and self._state is not None
359
+ try:
360
+ fields = _collect_fields(
361
+ name=name,
362
+ input=input,
363
+ output=output,
364
+ model=model,
365
+ provider=provider,
366
+ system_instructions=system_instructions,
367
+ response_model=response_model,
368
+ response_id=response_id,
369
+ output_type=output_type,
370
+ finish_reason=finish_reason,
371
+ usage=usage,
372
+ cost_usd=cost_usd,
373
+ temperature=temperature,
374
+ top_p=top_p,
375
+ top_k=top_k,
376
+ max_tokens=max_tokens,
377
+ stop_sequences=stop_sequences,
378
+ seed=seed,
379
+ frequency_penalty=frequency_penalty,
380
+ presence_penalty=presence_penalty,
381
+ time_to_first_chunk_ms=time_to_first_chunk_ms,
382
+ tool_name=tool_name,
383
+ tool_call_id=tool_call_id,
384
+ tool_description=tool_description,
385
+ agent_name=agent_name,
386
+ agent_id=agent_id,
387
+ metadata=metadata,
388
+ attributes=attributes,
389
+ error=error,
390
+ )
391
+ _apply_fields(self._client, self.span, self._state, fields)
392
+ except BaseException as exc:
393
+ self._client.report("SpanHandle.update failed", exc)
394
+ return self
395
+
396
+ def end(
397
+ self,
398
+ *,
399
+ name: str | None = None,
400
+ input: Any = NOT_GIVEN,
401
+ output: Any = NOT_GIVEN,
402
+ model: str | None = None,
403
+ provider: str | None = None,
404
+ system_instructions: Any = None,
405
+ response_model: str | None = None,
406
+ response_id: str | None = None,
407
+ output_type: str | None = None,
408
+ finish_reason: str | None = None,
409
+ usage: Usage | Mapping[str, int] | None = None,
410
+ cost_usd: float | None = None,
411
+ temperature: float | None = None,
412
+ top_p: float | None = None,
413
+ top_k: float | None = None,
414
+ max_tokens: int | None = None,
415
+ stop_sequences: Sequence[str] | None = None,
416
+ seed: int | None = None,
417
+ frequency_penalty: float | None = None,
418
+ presence_penalty: float | None = None,
419
+ time_to_first_chunk_ms: float | None = None,
420
+ tool_name: str | None = None,
421
+ tool_call_id: str | None = None,
422
+ tool_description: str | None = None,
423
+ agent_name: str | None = None,
424
+ agent_id: str | None = None,
425
+ metadata: Mapping[str, Any] | None = None,
426
+ attributes: Mapping[str, AttributeValue] | None = None,
427
+ error: BaseException | None = None,
428
+ end_time: TimeInput = None,
429
+ ) -> None:
430
+ if not self._recording():
431
+ return
432
+ self.update(
433
+ name=name,
434
+ input=input,
435
+ output=output,
436
+ model=model,
437
+ provider=provider,
438
+ system_instructions=system_instructions,
439
+ response_model=response_model,
440
+ response_id=response_id,
441
+ output_type=output_type,
442
+ finish_reason=finish_reason,
443
+ usage=usage,
444
+ cost_usd=cost_usd,
445
+ temperature=temperature,
446
+ top_p=top_p,
447
+ top_k=top_k,
448
+ max_tokens=max_tokens,
449
+ stop_sequences=stop_sequences,
450
+ seed=seed,
451
+ frequency_penalty=frequency_penalty,
452
+ presence_penalty=presence_penalty,
453
+ time_to_first_chunk_ms=time_to_first_chunk_ms,
454
+ tool_name=tool_name,
455
+ tool_call_id=tool_call_id,
456
+ tool_description=tool_description,
457
+ agent_name=agent_name,
458
+ agent_id=agent_id,
459
+ metadata=metadata,
460
+ attributes=attributes,
461
+ error=error,
462
+ )
463
+ self._ended = True
464
+ self.span.end(_to_ns(end_time))
465
+
466
+ def traceparent(self) -> str | None:
467
+ return traceparent_of(self.span.get_span_context())
468
+
469
+ def __enter__(self) -> SpanHandle:
470
+ if self._client is not None and self._state is not None:
471
+ self._context_token = otel_context.attach(trace.set_span_in_context(self.span))
472
+ return self
473
+
474
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> Literal[False]:
475
+ if self._context_token is not None:
476
+ otel_context.detach(self._context_token)
477
+ self._context_token = None
478
+ if not self._ended and self._recording():
479
+ if isinstance(exc, BaseException):
480
+ self.end(error=exc)
481
+ else:
482
+ self.end()
483
+ return False
484
+
485
+
486
+ def _noop_handle() -> SpanHandle:
487
+ return SpanHandle(trace.INVALID_SPAN, None, None)
488
+
489
+
490
+ def start_span(
491
+ name: str,
492
+ *,
493
+ type: SpanType = "span",
494
+ input: Any = NOT_GIVEN,
495
+ output: Any = NOT_GIVEN,
496
+ model: str | None = None,
497
+ provider: str | None = None,
498
+ system_instructions: Any = None,
499
+ response_model: str | None = None,
500
+ response_id: str | None = None,
501
+ output_type: str | None = None,
502
+ finish_reason: str | None = None,
503
+ usage: Usage | Mapping[str, int] | None = None,
504
+ cost_usd: float | None = None,
505
+ temperature: float | None = None,
506
+ top_p: float | None = None,
507
+ top_k: float | None = None,
508
+ max_tokens: int | None = None,
509
+ stop_sequences: Sequence[str] | None = None,
510
+ seed: int | None = None,
511
+ frequency_penalty: float | None = None,
512
+ presence_penalty: float | None = None,
513
+ time_to_first_chunk_ms: float | None = None,
514
+ tool_name: str | None = None,
515
+ tool_call_id: str | None = None,
516
+ tool_description: str | None = None,
517
+ agent_name: str | None = None,
518
+ agent_id: str | None = None,
519
+ metadata: Mapping[str, Any] | None = None,
520
+ attributes: Mapping[str, AttributeValue] | None = None,
521
+ parent: ParentType = None,
522
+ start_time: TimeInput = None,
523
+ capture_input: bool | None = None,
524
+ capture_output: bool | None = None,
525
+ ) -> SpanHandle:
526
+ """Start a telemetry.dev span. Entering the returned handle (``with``) activates it in the
527
+ current context; without ``with`` it is a detached handle that must be ended via .end()."""
528
+ client = get_client()
529
+ if client is None or not client.enabled or client.tracer is None:
530
+ return _noop_handle()
531
+ try:
532
+ operation = SPAN_TYPE_TO_OPERATION.get(type)
533
+ if operation is None:
534
+ logger.debug("telemetry-dev: unknown span type %r; using 'span'", type)
535
+ operation = SPAN_TYPE_TO_OPERATION["span"]
536
+ state = _SpanState(
537
+ operation=operation,
538
+ capture_input=client.capture_input if capture_input is None else capture_input,
539
+ capture_output=client.capture_output if capture_output is None else capture_output,
540
+ )
541
+ initial: dict[str, otel_types.AttributeValue] = {ATTR_OPERATION: operation}
542
+ if operation == "execute_tool":
543
+ initial[ATTR_TOOL_NAME] = name
544
+ elif operation == "invoke_agent":
545
+ initial[ATTR_AGENT_NAME] = name
546
+ span = client.tracer.start_span(
547
+ name,
548
+ context=context_from_parent(parent),
549
+ attributes=initial,
550
+ start_time=_to_ns(start_time),
551
+ )
552
+ _SPAN_STATES[span] = state
553
+ handle = SpanHandle(span, client, state)
554
+ fields = _collect_fields(
555
+ input=input,
556
+ output=output,
557
+ model=model,
558
+ provider=provider,
559
+ system_instructions=system_instructions,
560
+ response_model=response_model,
561
+ response_id=response_id,
562
+ output_type=output_type,
563
+ finish_reason=finish_reason,
564
+ usage=usage,
565
+ cost_usd=cost_usd,
566
+ temperature=temperature,
567
+ top_p=top_p,
568
+ top_k=top_k,
569
+ max_tokens=max_tokens,
570
+ stop_sequences=stop_sequences,
571
+ seed=seed,
572
+ frequency_penalty=frequency_penalty,
573
+ presence_penalty=presence_penalty,
574
+ time_to_first_chunk_ms=time_to_first_chunk_ms,
575
+ tool_name=tool_name,
576
+ tool_call_id=tool_call_id,
577
+ tool_description=tool_description,
578
+ agent_name=agent_name,
579
+ agent_id=agent_id,
580
+ metadata=metadata,
581
+ attributes=attributes,
582
+ )
583
+ if fields:
584
+ _apply_fields(client, span, state, fields)
585
+ return handle
586
+ except BaseException as exc:
587
+ client.report("start_span failed", exc)
588
+ return _noop_handle()
589
+
590
+
591
+ def update_current_span(
592
+ *,
593
+ name: str | None = None,
594
+ input: Any = NOT_GIVEN,
595
+ output: Any = NOT_GIVEN,
596
+ model: str | None = None,
597
+ provider: str | None = None,
598
+ system_instructions: Any = None,
599
+ response_model: str | None = None,
600
+ response_id: str | None = None,
601
+ output_type: str | None = None,
602
+ finish_reason: str | None = None,
603
+ usage: Usage | Mapping[str, int] | None = None,
604
+ cost_usd: float | None = None,
605
+ temperature: float | None = None,
606
+ top_p: float | None = None,
607
+ top_k: float | None = None,
608
+ max_tokens: int | None = None,
609
+ stop_sequences: Sequence[str] | None = None,
610
+ seed: int | None = None,
611
+ frequency_penalty: float | None = None,
612
+ presence_penalty: float | None = None,
613
+ time_to_first_chunk_ms: float | None = None,
614
+ tool_name: str | None = None,
615
+ tool_call_id: str | None = None,
616
+ tool_description: str | None = None,
617
+ agent_name: str | None = None,
618
+ agent_id: str | None = None,
619
+ metadata: Mapping[str, Any] | None = None,
620
+ attributes: Mapping[str, AttributeValue] | None = None,
621
+ error: BaseException | None = None,
622
+ ) -> None:
623
+ """Apply the update field set to the currently active span; no-ops when there is none."""
624
+ client = get_client()
625
+ if client is None or not client.enabled:
626
+ return
627
+ span = trace.get_current_span()
628
+ if not span.is_recording():
629
+ return
630
+ state = _SPAN_STATES.get(span)
631
+ if state is None:
632
+ operation = "function"
633
+ if isinstance(span, SdkSpan):
634
+ current = (span.attributes or {}).get(ATTR_OPERATION)
635
+ if isinstance(current, str):
636
+ operation = current
637
+ state = _SpanState(
638
+ operation=operation,
639
+ capture_input=client.capture_input,
640
+ capture_output=client.capture_output,
641
+ )
642
+ try:
643
+ fields = _collect_fields(
644
+ name=name,
645
+ input=input,
646
+ output=output,
647
+ model=model,
648
+ provider=provider,
649
+ system_instructions=system_instructions,
650
+ response_model=response_model,
651
+ response_id=response_id,
652
+ output_type=output_type,
653
+ finish_reason=finish_reason,
654
+ usage=usage,
655
+ cost_usd=cost_usd,
656
+ temperature=temperature,
657
+ top_p=top_p,
658
+ top_k=top_k,
659
+ max_tokens=max_tokens,
660
+ stop_sequences=stop_sequences,
661
+ seed=seed,
662
+ frequency_penalty=frequency_penalty,
663
+ presence_penalty=presence_penalty,
664
+ time_to_first_chunk_ms=time_to_first_chunk_ms,
665
+ tool_name=tool_name,
666
+ tool_call_id=tool_call_id,
667
+ tool_description=tool_description,
668
+ agent_name=agent_name,
669
+ agent_id=agent_id,
670
+ metadata=metadata,
671
+ attributes=attributes,
672
+ error=error,
673
+ )
674
+ _apply_fields(client, span, state, fields)
675
+ except BaseException as exc:
676
+ client.report("update_current_span failed", exc)