Skip to content

Commit a55a112

Browse files
committed
Consolidate telemetry provider configuration
1 parent 42ab887 commit a55a112

3 files changed

Lines changed: 378 additions & 121 deletions

File tree

src/mistralai/extra/observability/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from .telemetry import (
88
TelemetryConfigurationError,
99
configure_telemetry,
10-
resolve_telemetry_enabled,
11-
set_tracing_hook_provider,
1210
)
1311

1412
if TYPE_CHECKING:
@@ -31,6 +29,9 @@ def set_tracer_provider(
3129
When set, all SDK spans produced by *client* will be emitted through
3230
*provider* instead of the global TracerProvider.
3331
32+
This helper is kept for compatibility. New code can call
33+
configure_telemetry(client, provider=provider) directly.
34+
3435
Usage::
3536
3637
from opentelemetry.sdk.trace import TracerProvider
@@ -40,13 +41,12 @@ def set_tracer_provider(
4041
client = Mistral(api_key="...")
4142
set_tracer_provider(client, TracerProvider())
4243
"""
43-
set_tracing_hook_provider(client, provider)
44+
configure_telemetry(client, provider=provider)
4445

4546

4647
__all__ = [
4748
"TelemetryConfigurationError",
4849
"configure_telemetry",
49-
"resolve_telemetry_enabled",
5050
"set_tracer_provider",
5151
"trace",
5252
]

src/mistralai/extra/observability/telemetry.py

Lines changed: 147 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
from __future__ import annotations
44

5+
import logging
56
import os
67
import weakref
7-
from typing import TYPE_CHECKING, Any
8+
from typing import TYPE_CHECKING, Any, Final, Literal
89

910
from opentelemetry import trace as otel_trace
1011

@@ -24,51 +25,82 @@
2425
MISTRAL_TELEMETRY_ENDPOINT = "https://api.mistral.ai/telemetry/v1/traces"
2526
OTEL_EXPORTER_OTLP_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT"
2627
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"
28+
TELEMETRY_PROVIDER_DEDICATED: Final[Literal["dedicated"]] = "dedicated"
29+
TELEMETRY_PROVIDER_GLOBAL: Final[Literal["global"]] = "global"
2730

28-
_TRUE_VALUES = {"1", "true", "yes", "on"}
29-
_FALSE_VALUES = {"0", "false", "no", "off"}
31+
_DISABLED_VALUE = "false"
32+
_PROVIDER_VALUES = {TELEMETRY_PROVIDER_DEDICATED, TELEMETRY_PROVIDER_GLOBAL}
33+
34+
TelemetryProviderMode = Literal["dedicated", "global"]
35+
TelemetrySetting = bool | str | None
36+
37+
logger = logging.getLogger(__name__)
3038

3139

3240
class TelemetryConfigurationError(RuntimeError):
3341
"""Raised when opt-in telemetry cannot be configured."""
3442

3543

36-
def resolve_telemetry_enabled(telemetry: bool | None = None) -> bool:
37-
"""Resolve the telemetry opt-in flag from an explicit value or environment."""
38-
return _resolve_telemetry_configuration(telemetry)[0]
44+
def _resolve_telemetry_configuration(
45+
telemetry: TelemetrySetting = None,
46+
) -> tuple[TelemetryProviderMode | None, bool]:
47+
"""Return telemetry provider mode and whether to use OTel env config."""
48+
mode = (
49+
_resolve_telemetry_mode(telemetry)
50+
if telemetry is not None
51+
else _resolve_mistral_telemetry_env()
52+
)
53+
return (
54+
mode,
55+
mode == TELEMETRY_PROVIDER_DEDICATED
56+
and _has_otel_exporter_endpoint_env(),
57+
)
3958

4059

41-
def _resolve_telemetry_configuration(
42-
telemetry: bool | None = None,
43-
) -> tuple[bool, bool]:
44-
"""Return whether telemetry is enabled and whether to use OTel env config."""
45-
use_otel_env_exporter = _has_otel_exporter_endpoint_env()
46-
if telemetry is not None:
47-
return telemetry, telemetry and use_otel_env_exporter
60+
def _resolve_telemetry_mode(value: bool | str) -> TelemetryProviderMode | None:
61+
if isinstance(value, bool):
62+
return TELEMETRY_PROVIDER_DEDICATED if value else None
63+
64+
normalized = value.strip().lower()
65+
if normalized == TELEMETRY_PROVIDER_DEDICATED:
66+
return TELEMETRY_PROVIDER_DEDICATED
67+
if normalized == TELEMETRY_PROVIDER_GLOBAL:
68+
return TELEMETRY_PROVIDER_GLOBAL
69+
if normalized == _DISABLED_VALUE:
70+
return None
71+
72+
accepted_values = ", ".join(sorted(_PROVIDER_VALUES | {_DISABLED_VALUE}))
73+
raise TelemetryConfigurationError(
74+
f"Invalid telemetry setting {value!r}. Expected one of: {accepted_values}."
75+
)
76+
4877

49-
env_telemetry = _resolve_mistral_telemetry_env()
50-
if env_telemetry is not None:
51-
return env_telemetry, env_telemetry and use_otel_env_exporter
78+
def _resolve_provider_mode(value: str) -> TelemetryProviderMode:
79+
normalized = value.strip().lower()
80+
if normalized == TELEMETRY_PROVIDER_DEDICATED:
81+
return TELEMETRY_PROVIDER_DEDICATED
82+
if normalized == TELEMETRY_PROVIDER_GLOBAL:
83+
return TELEMETRY_PROVIDER_GLOBAL
5284

53-
return False, False
85+
accepted_values = ", ".join(sorted(_PROVIDER_VALUES))
86+
raise TelemetryConfigurationError(
87+
f"Invalid telemetry provider {value!r}. Expected one of: {accepted_values}."
88+
)
5489

5590

56-
def _resolve_mistral_telemetry_env() -> bool | None:
91+
def _resolve_mistral_telemetry_env() -> TelemetryProviderMode | None:
5792
env_value = os.getenv(MISTRAL_SDK_TELEMETRY_ENV)
5893
if env_value is None or env_value == "":
5994
return None
6095

61-
normalized = env_value.strip().lower()
62-
if normalized in _TRUE_VALUES:
63-
return True
64-
if normalized in _FALSE_VALUES:
65-
return False
66-
67-
accepted_values = ", ".join(sorted(_TRUE_VALUES | _FALSE_VALUES))
68-
raise TelemetryConfigurationError(
69-
f"Invalid {MISTRAL_SDK_TELEMETRY_ENV}={env_value!r}. "
70-
f"Expected one of: {accepted_values}."
71-
)
96+
try:
97+
return _resolve_telemetry_mode(env_value)
98+
except TelemetryConfigurationError as exc:
99+
accepted_values = ", ".join(sorted(_PROVIDER_VALUES | {_DISABLED_VALUE}))
100+
raise TelemetryConfigurationError(
101+
f"Invalid {MISTRAL_SDK_TELEMETRY_ENV}={env_value!r}. "
102+
f"Expected one of: {accepted_values}."
103+
) from exc
72104

73105

74106
def _has_otel_exporter_endpoint_env() -> bool:
@@ -81,73 +113,104 @@ def _has_otel_exporter_endpoint_env() -> bool:
81113
)
82114

83115

84-
def configure_telemetry(client: "Mistral") -> bool:
85-
"""Configure an isolated telemetry provider for a Mistral client.
86-
87-
Calling configure_telemetry(client) explicitly enables SDK telemetry.
88-
Environment and SDKConfiguration-based auto-resolution are handled by the
89-
request hook.
116+
def configure_telemetry(
117+
client: "Mistral",
118+
provider: str | otel_trace.TracerProvider = TELEMETRY_PROVIDER_DEDICATED,
119+
) -> bool:
120+
"""Configure telemetry provider mode for a Mistral client.
90121
91-
Returns True when telemetry is enabled and a provider is attached. Returns
92-
False when a non-telemetry provider is already set.
122+
By default, this creates an SDK-owned telemetry provider/exporter. Passing
123+
provider="global" clears the per-client provider so SDK spans use the
124+
global OpenTelemetry provider. Passing a TracerProvider attaches it to this
125+
client without taking ownership of its lifecycle.
93126
"""
94127
hooks = getattr(client.sdk_configuration, "_hooks", None)
95128
if hooks is None:
96129
raise ValueError("Cannot configure telemetry: SDK hooks not initialised.")
97130

98-
return configure_telemetry_for_hook(
99-
_get_tracing_hook(hooks),
100-
client.sdk_configuration,
101-
telemetry=True,
102-
finalizer_owner=client,
103-
)
131+
hook = _get_tracing_hook(hooks)
132+
if isinstance(provider, str):
133+
provider_mode = _resolve_provider_mode(provider)
134+
if provider_mode == TELEMETRY_PROVIDER_GLOBAL:
135+
return _use_global_tracer_provider(hook, replace_existing=True)
136+
137+
return configure_telemetry_for_hook(
138+
hook,
139+
client.sdk_configuration,
140+
telemetry=provider_mode,
141+
finalizer_owner=client,
142+
replace_existing=True,
143+
)
144+
145+
if isinstance(provider, bool):
146+
raise TelemetryConfigurationError(
147+
"Invalid telemetry provider bool. Expected 'dedicated', 'global', "
148+
"or an OpenTelemetry TracerProvider."
149+
)
150+
151+
_attach_custom_tracer_provider(hook, provider)
152+
return True
104153

105154

106155
def configure_telemetry_for_hook(
107156
hook: "TracingHook",
108157
sdk_config: "SDKConfiguration",
109-
telemetry: bool | None = None,
158+
telemetry: TelemetrySetting = None,
110159
finalizer_owner: Any | None = None,
111160
respect_global_provider: bool = False,
161+
replace_existing: bool = False,
112162
) -> bool:
113163
"""Configure telemetry for a tracing hook when the user has opted in."""
114164
# Fast path: already resolved and no explicit override requested.
115-
if hook._auto_telemetry_provider is not None and telemetry is not False:
165+
if hook._auto_telemetry_provider is not None and telemetry is None:
116166
return True
117-
if telemetry is False and hook._auto_telemetry_provider is None:
118-
return False
119167
if telemetry is None and hook._telemetry_auto_disabled:
120168
return False
121169

122170
telemetry_setting = telemetry
123171
if telemetry_setting is None:
124172
config_setting = getattr(sdk_config, "telemetry", None)
125-
telemetry_setting = config_setting if isinstance(config_setting, bool) else None
173+
telemetry_setting = (
174+
config_setting if isinstance(config_setting, (bool, str)) else None
175+
)
126176
using_env_setting = telemetry_setting is None
127177

128-
telemetry_enabled, use_otel_env_exporter = _resolve_telemetry_configuration(
178+
provider_mode, use_otel_env_exporter = _resolve_telemetry_configuration(
129179
telemetry_setting
130180
)
131-
if not telemetry_enabled:
132-
if telemetry_setting is False:
133-
_shutdown_telemetry_provider(hook)
134-
hook._telemetry_auto_disabled = False
135-
elif using_env_setting:
136-
hook._telemetry_auto_disabled = True
181+
if provider_mode is None:
182+
_shutdown_telemetry_provider(hook)
183+
hook._telemetry_auto_disabled = True
137184
return False
138185

186+
if provider_mode == TELEMETRY_PROVIDER_GLOBAL:
187+
return _use_global_tracer_provider(
188+
hook,
189+
replace_existing=replace_existing or not using_env_setting,
190+
)
191+
139192
if (
140-
respect_global_provider
193+
provider_mode == TELEMETRY_PROVIDER_DEDICATED
194+
and respect_global_provider
141195
and using_env_setting
142196
and _has_real_global_tracer_provider()
143197
):
198+
logger.debug(
199+
"Skipping Mistral SDK telemetry auto-configuration because a global "
200+
"OpenTelemetry provider is already configured. Call "
201+
"configure_telemetry(client, provider='dedicated') to attach an "
202+
"SDK-owned provider for this client."
203+
)
204+
hook._telemetry_auto_disabled = True
144205
return False
145206

146207
if hook._auto_telemetry_provider is not None:
147208
return True
148209

149210
if hook.tracer_provider is not None:
150-
return False
211+
if not replace_existing:
212+
return False
213+
hook.tracer_provider = None
151214

152215
api_key = (
153216
None
@@ -162,22 +225,6 @@ def configure_telemetry_for_hook(
162225
return True
163226

164227

165-
def set_tracing_hook_provider(
166-
client: "Mistral",
167-
provider: otel_trace.TracerProvider,
168-
) -> None:
169-
"""Attach a provider to the client's tracing hook, replacing auto telemetry."""
170-
hooks = getattr(client.sdk_configuration, "_hooks", None)
171-
if hooks is None:
172-
raise ValueError(
173-
"Cannot set tracer_provider: SDK hooks not initialised on this client."
174-
)
175-
176-
hook = _get_tracing_hook(hooks)
177-
_shutdown_telemetry_provider(hook)
178-
hook.tracer_provider = provider
179-
180-
181228
def _get_tracing_hook(hooks: Any) -> "TracingHook":
182229
from mistralai.client._hooks.tracing import TracingHook
183230

@@ -280,6 +327,33 @@ def _attach_telemetry_provider(
280327
)
281328

282329

330+
def _attach_custom_tracer_provider(
331+
hook: "TracingHook",
332+
provider: otel_trace.TracerProvider,
333+
) -> None:
334+
_shutdown_telemetry_provider(hook)
335+
hook.tracer_provider = provider
336+
hook._telemetry_auto_disabled = False
337+
338+
339+
def _use_global_tracer_provider(
340+
hook: "TracingHook",
341+
*,
342+
replace_existing: bool,
343+
) -> bool:
344+
if (
345+
hook.tracer_provider is not None
346+
and hook._auto_telemetry_provider is None
347+
and not replace_existing
348+
):
349+
return False
350+
351+
_shutdown_telemetry_provider(hook)
352+
hook.tracer_provider = None
353+
hook._telemetry_auto_disabled = True
354+
return True
355+
356+
283357
def _shutdown_telemetry_provider(hook: "TracingHook") -> None:
284358
finalizer = hook._telemetry_finalizer
285359
if finalizer is not None:

0 commit comments

Comments
 (0)