telemetry-dev 0.2.5__tar.gz → 0.2.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telemetry-dev
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics
5
5
  Keywords: telemetry,opentelemetry,llm,genai,tracing,observability
6
6
  Author: telemetry.dev
@@ -14,9 +14,9 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Typing :: Typed
17
- Requires-Dist: opentelemetry-api>=1.35.0,<2
18
- Requires-Dist: opentelemetry-sdk>=1.35.0,<2
19
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0,<2
17
+ Requires-Dist: opentelemetry-api>=1.39.0,<2
18
+ Requires-Dist: opentelemetry-sdk>=1.39.0,<2
19
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.39.0,<2
20
20
  Requires-Python: >=3.10
21
21
  Project-URL: Homepage, https://telemetry.dev
22
22
  Project-URL: Repository, https://github.com/telemetry-dev/telemetry
@@ -135,9 +135,12 @@ the core SDK with provider integrations to enable the new metric.
135
135
  ### Span fields (start/update/end)
136
136
 
137
137
  `input`, `output`, `model`, `provider`, `system_instructions`, `response_model`, `response_id`,
138
- `output_type`, `finish_reason`, `usage` (dict with exactly `input_tokens`, `output_tokens`,
138
+ `output_type`, `finish_reason`, `usage` (dict with `input_tokens`, `output_tokens`,
139
139
  `total_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
140
- `reasoning_output_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
140
+ `reasoning_output_tokens`, `text_input_tokens`, `text_output_tokens`, `image_input_tokens`,
141
+ `image_output_tokens`, `audio_input_tokens`, `audio_output_tokens`,
142
+ `text_cache_read_input_tokens`, `image_cache_read_input_tokens`, and
143
+ `audio_cache_read_input_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
141
144
  `stop_sequences`, `seed`, `frequency_penalty`, `presence_penalty`, `time_to_first_chunk_ms`,
142
145
  `tool_name`, `tool_call_id`, `tool_description`, `agent_name`, `agent_id`, `metadata`
143
146
  (→ `td.metadata.*`, this span only), `attributes` (raw escape hatch, merged last), `error`.
@@ -111,9 +111,12 @@ the core SDK with provider integrations to enable the new metric.
111
111
  ### Span fields (start/update/end)
112
112
 
113
113
  `input`, `output`, `model`, `provider`, `system_instructions`, `response_model`, `response_id`,
114
- `output_type`, `finish_reason`, `usage` (dict with exactly `input_tokens`, `output_tokens`,
114
+ `output_type`, `finish_reason`, `usage` (dict with `input_tokens`, `output_tokens`,
115
115
  `total_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`,
116
- `reasoning_output_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
116
+ `reasoning_output_tokens`, `text_input_tokens`, `text_output_tokens`, `image_input_tokens`,
117
+ `image_output_tokens`, `audio_input_tokens`, `audio_output_tokens`,
118
+ `text_cache_read_input_tokens`, `image_cache_read_input_tokens`, and
119
+ `audio_cache_read_input_tokens`), `cost_usd`, `temperature`, `top_p`, `top_k`, `max_tokens`,
117
120
  `stop_sequences`, `seed`, `frequency_penalty`, `presence_penalty`, `time_to_first_chunk_ms`,
118
121
  `tool_name`, `tool_call_id`, `tool_description`, `agent_name`, `agent_id`, `metadata`
119
122
  (→ `td.metadata.*`, this span only), `attributes` (raw escape hatch, merged last), `error`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.5"
3
+ version = "0.2.7"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -25,9 +25,9 @@ classifiers = [
25
25
  "Typing :: Typed",
26
26
  ]
27
27
  dependencies = [
28
- "opentelemetry-api>=1.35.0,<2",
29
- "opentelemetry-sdk>=1.35.0,<2",
30
- "opentelemetry-exporter-otlp-proto-http>=1.35.0,<2",
28
+ "opentelemetry-api>=1.39.0,<2",
29
+ "opentelemetry-sdk>=1.39.0,<2",
30
+ "opentelemetry-exporter-otlp-proto-http>=1.39.0,<2",
31
31
  ]
32
32
 
33
33
  [[project.authors]]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telemetry-dev"
3
- version = "0.2.5"
3
+ version = "0.2.7"
4
4
  description = "telemetry.dev SDK for Python — OpenTelemetry-native GenAI tracing, logs, and metrics"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -19,9 +19,9 @@ classifiers = [
19
19
  "Typing :: Typed",
20
20
  ]
21
21
  dependencies = [
22
- "opentelemetry-api>=1.35.0,<2",
23
- "opentelemetry-sdk>=1.35.0,<2",
24
- "opentelemetry-exporter-otlp-proto-http>=1.35.0,<2",
22
+ "opentelemetry-api>=1.39.0,<2",
23
+ "opentelemetry-sdk>=1.39.0,<2",
24
+ "opentelemetry-exporter-otlp-proto-http>=1.39.0,<2",
25
25
  ]
26
26
 
27
27
  [project.urls]
@@ -122,31 +122,34 @@ class CaptureBudget:
122
122
  if value_id in seen:
123
123
  return byte_count, item_count
124
124
  seen.add(value_id)
125
- mapping: dict[object, object] | None = None
126
- children: Iterable[object] = ()
127
- if isinstance(value, dict):
128
- mapping = cast("dict[object, object]", value)
129
- elif isinstance(value, list | tuple | set | frozenset):
130
- children = cast("Iterable[object]", value)
131
- else:
132
- attributes = getattr(value, "__dict__", None)
133
- if isinstance(attributes, dict):
134
- mapping = cast("dict[object, object]", attributes)
135
-
136
- if mapping is not None:
137
- children = chain.from_iterable(mapping.items())
138
-
139
- for child in children:
140
- measured = cls._measure(
141
- child,
142
- remaining_bytes=remaining_bytes - byte_count,
143
- remaining_items=remaining_items - item_count,
144
- depth=depth + 1,
145
- seen=seen,
146
- )
147
- if measured is None:
148
- return None
149
- child_bytes, child_items = measured
150
- byte_count += child_bytes
151
- item_count += child_items
152
- return byte_count, item_count
125
+ try:
126
+ mapping: dict[object, object] | None = None
127
+ children: Iterable[object] = ()
128
+ if isinstance(value, dict):
129
+ mapping = cast("dict[object, object]", value)
130
+ elif isinstance(value, list | tuple | set | frozenset):
131
+ children = cast("Iterable[object]", value)
132
+ else:
133
+ attributes = getattr(value, "__dict__", None)
134
+ if isinstance(attributes, dict):
135
+ mapping = cast("dict[object, object]", attributes)
136
+
137
+ if mapping is not None:
138
+ children = chain.from_iterable(mapping.items())
139
+
140
+ for child in children:
141
+ measured = cls._measure(
142
+ child,
143
+ remaining_bytes=remaining_bytes - byte_count,
144
+ remaining_items=remaining_items - item_count,
145
+ depth=depth + 1,
146
+ seen=seen,
147
+ )
148
+ if measured is None:
149
+ return None
150
+ child_bytes, child_items = measured
151
+ byte_count += child_bytes
152
+ item_count += child_items
153
+ return byte_count, item_count
154
+ finally:
155
+ seen.remove(value_id)
@@ -41,7 +41,12 @@ from ._config import (
41
41
  resolve_config,
42
42
  )
43
43
  from ._context import SessionSampler
44
- from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder, OutputChunkAggregation
44
+ from ._metrics import (
45
+ GuardedOTLPMetricExporter,
46
+ MetricsRecorder,
47
+ OutputChunkAggregation,
48
+ sdk_meter_provider,
49
+ )
45
50
  from ._processor import ExportMode, StampingSpanProcessor
46
51
  from ._semconv import SCOPE_NAME
47
52
  from ._serialize import Mask, serialize_content
@@ -207,9 +212,7 @@ class Client:
207
212
  )
208
213
  metrics_recorder: MetricsRecorder | None = None
209
214
  if metric_reader is not None:
210
- self._meter_provider = MeterProvider(
211
- metric_readers=[metric_reader], resource=resource, shutdown_on_exit=False
212
- )
215
+ self._meter_provider = sdk_meter_provider(metric_reader, resource)
213
216
  meter = self._meter_provider.get_meter(SCOPE_NAME, SDK_VERSION)
214
217
  metrics_recorder = MetricsRecorder(
215
218
  meter, on_error=on_error, output_chunks=self._output_chunks
@@ -9,16 +9,20 @@ from typing import Any
9
9
 
10
10
  from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
11
11
  from opentelemetry.metrics import Meter
12
+ from opentelemetry.sdk.metrics import MeterProvider
12
13
  from opentelemetry.sdk.metrics.export import (
13
14
  AggregationTemporality,
14
15
  Histogram,
15
16
  HistogramDataPoint,
16
17
  Metric,
17
18
  MetricExportResult,
19
+ MetricReader,
18
20
  MetricsData,
19
21
  ResourceMetrics,
20
22
  ScopeMetrics,
21
23
  )
24
+ from opentelemetry.sdk.metrics.view import DropAggregation, View
25
+ from opentelemetry.sdk.resources import Resource
22
26
  from opentelemetry.sdk.trace import ReadableSpan
23
27
  from opentelemetry.trace import INVALID_SPAN_CONTEXT, NonRecordingSpan, set_span_in_context
24
28
 
@@ -41,6 +45,23 @@ _MAX_CHUNK_ATTRIBUTE_SETS = 2000
41
45
  _CHUNK_METRIC_NAME = "gen_ai.client.operation.time_per_output_chunk"
42
46
 
43
47
 
48
+ def sdk_meter_provider(reader: MetricReader, resource: Resource) -> MeterProvider:
49
+ return MeterProvider(
50
+ metric_readers=[reader],
51
+ resource=resource,
52
+ shutdown_on_exit=False,
53
+ # OpenTelemetry records metric-reader metrics on the reader's own provider (always in
54
+ # 1.41, opt-in from 1.42); they must not be exported to telemetry.dev.
55
+ views=[
56
+ View(
57
+ meter_name="opentelemetry-sdk",
58
+ instrument_name="otel.sdk.*",
59
+ aggregation=DropAggregation(),
60
+ )
61
+ ],
62
+ )
63
+
64
+
44
65
  @dataclass
45
66
  class _ChunkAggregate:
46
67
  count: int
@@ -91,6 +91,15 @@ USAGE_ATTRS: dict[str, str] = {
91
91
  "cache_read_input_tokens": "gen_ai.usage.cache_read.input_tokens",
92
92
  "cache_creation_input_tokens": "gen_ai.usage.cache_creation.input_tokens",
93
93
  "reasoning_output_tokens": "gen_ai.usage.reasoning.output_tokens",
94
+ "text_input_tokens": "gen_ai.usage.text.input_tokens",
95
+ "text_output_tokens": "gen_ai.usage.text.output_tokens",
96
+ "text_cache_read_input_tokens": "gen_ai.usage.text.cache_read.input_tokens",
97
+ "image_input_tokens": "gen_ai.usage.image.input_tokens",
98
+ "image_output_tokens": "gen_ai.usage.image.output_tokens",
99
+ "image_cache_read_input_tokens": "gen_ai.usage.image.cache_read.input_tokens",
100
+ "audio_input_tokens": "gen_ai.usage.audio.input_tokens",
101
+ "audio_output_tokens": "gen_ai.usage.audio.output_tokens",
102
+ "audio_cache_read_input_tokens": "gen_ai.usage.audio.cache_read.input_tokens",
94
103
  }
95
104
 
96
105
  SAMPLING_ATTRS: dict[str, str] = {
@@ -72,7 +72,7 @@ NOT_GIVEN: Any = _NotGiven()
72
72
 
73
73
 
74
74
  class Usage(TypedDict, total=False):
75
- """Token usage — exactly these six fields are emitted; unknown keys are dropped."""
75
+ """Token usage; unknown keys are dropped."""
76
76
 
77
77
  input_tokens: int
78
78
  output_tokens: int
@@ -80,6 +80,15 @@ class Usage(TypedDict, total=False):
80
80
  cache_read_input_tokens: int
81
81
  cache_creation_input_tokens: int
82
82
  reasoning_output_tokens: int
83
+ text_input_tokens: int
84
+ text_output_tokens: int
85
+ text_cache_read_input_tokens: int
86
+ image_input_tokens: int
87
+ image_output_tokens: int
88
+ image_cache_read_input_tokens: int
89
+ audio_input_tokens: int
90
+ audio_output_tokens: int
91
+ audio_cache_read_input_tokens: int
83
92
 
84
93
 
85
94
  ParentType = str | Context | SpanContext | None
@@ -22,7 +22,7 @@ from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor
22
22
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
23
23
 
24
24
  from ._config import SDK_VERSION, logger, report_error, resolve_config
25
- from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder
25
+ from ._metrics import GuardedOTLPMetricExporter, MetricsRecorder, sdk_meter_provider
26
26
  from ._processor import ExportMode, StampingSpanProcessor
27
27
  from ._semconv import SCOPE_NAME
28
28
 
@@ -137,9 +137,7 @@ class TelemetrySpanProcessor(SpanProcessor):
137
137
  else _DORMANT_METRIC_INTERVAL_MILLIS
138
138
  ),
139
139
  )
140
- self._meter_provider = MeterProvider(
141
- metric_readers=[reader], resource=resource, shutdown_on_exit=False
142
- )
140
+ self._meter_provider = sdk_meter_provider(reader, resource)
143
141
  meter = self._meter_provider.get_meter(SCOPE_NAME, SDK_VERSION)
144
142
  metrics_recorder = MetricsRecorder(meter, on_error=on_error)
145
143
 
File without changes