22
33from __future__ import annotations
44
5+ import logging
56import os
67import weakref
7- from typing import TYPE_CHECKING , Any
8+ from typing import TYPE_CHECKING , Any , Final , Literal
89
910from opentelemetry import trace as otel_trace
1011
2425MISTRAL_TELEMETRY_ENDPOINT = "https://api.mistral.ai/telemetry/v1/traces"
2526OTEL_EXPORTER_OTLP_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT"
2627OTEL_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
3240class 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
74106def _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
106155def 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-
181228def _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+
283357def _shutdown_telemetry_provider (hook : "TracingHook" ) -> None :
284358 finalizer = hook ._telemetry_finalizer
285359 if finalizer is not None :
0 commit comments