Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Corentin André <corentin.andre@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: MichisGitIsKing <MichisGitIsKing@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-05-19 11:56:25 +02:00 committed by GitHub
parent 626f905186
commit 228f3c65a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
158 changed files with 7235 additions and 916 deletions

View file

@ -11,6 +11,7 @@ import httpx
from vibe import __version__
from vibe.core.config import ProviderConfig, VibeConfig
from vibe.core.llm.format import ResolvedToolCall
from vibe.core.logger import logger
from vibe.core.telemetry.build_metadata import build_base_metadata
from vibe.core.telemetry.types import (
AgentEntrypoint,
@ -30,6 +31,30 @@ _DEFAULT_TELEMETRY_BASE_URL = "https://api.mistral.ai"
_DATALAKE_EVENTS_PATH = "/v1/datalake/events"
def get_mistral_provider_and_api_key(
config: VibeConfig,
) -> tuple[ProviderConfig, str] | None:
"""Resolve a Mistral provider and its API key, or None.
Prefers the active provider when it is a Mistral provider; otherwise
falls back to the first configured Mistral provider. Returns a key
only when a Mistral provider is found, to avoid leaking third-party
credentials to Mistral-controlled endpoints (telemetry, A/B test
evaluation, ...).
"""
try:
provider = config.get_mistral_provider()
except Exception:
return None
if provider is None:
return None
env_var = provider.api_key_env_var
api_key = os.getenv(env_var) if env_var else None
if api_key is None:
return None
return provider, api_key
class TelemetryClient:
def __init__(
self,
@ -38,11 +63,13 @@ class TelemetryClient:
parent_session_id_getter: Callable[[], str | None] | None = None,
entrypoint_metadata_getter: Callable[[], EntrypointMetadata | None]
| None = None,
experiments_getter: Callable[[], dict[str, str]] | None = None,
) -> None:
self._config_getter = config_getter
self._session_id_getter = session_id_getter
self._parent_session_id_getter = parent_session_id_getter
self._entrypoint_metadata_getter = entrypoint_metadata_getter
self._experiments_getter = experiments_getter
self._client: httpx.AsyncClient | None = None
self._pending_tasks: set[asyncio.Task[Any]] = set()
self.last_correlation_id: str | None = None
@ -52,33 +79,13 @@ class TelemetryClient:
return urljoin(base.rstrip("/"), _DATALAKE_EVENTS_PATH)
def _get_mistral_api_key(self) -> str | None:
"""Get the API key from the active provider if it's Mistral,
otherwise the first Mistral provider.
Only returns an API key if the provider is a Mistral provider
to avoid leaking third-party credentials to the telemetry endpoint.
"""
provider_and_api_key = self._get_mistral_provider_and_api_key()
provider_and_api_key = get_mistral_provider_and_api_key(self._config_getter())
if provider_and_api_key is None:
return None
_, api_key = provider_and_api_key
return api_key
def _get_mistral_provider_and_api_key(self) -> tuple[ProviderConfig, str] | None:
try:
provider = self._config_getter().get_mistral_provider()
except Exception:
return None
if provider is None:
return None
env_var = provider.api_key_env_var
api_key = os.getenv(env_var) if env_var else None
if api_key is None:
return None
return provider, api_key
def _is_enabled(self) -> bool:
"""Check if telemetry is enabled in the current config."""
try:
return self._config_getter().enable_telemetry
except Exception:
@ -109,7 +116,10 @@ class TelemetryClient:
return None
return self._parent_session_id_getter()
def build_client_event_metadata(self) -> dict[str, str]:
def build_client_event_metadata(self) -> dict[str, Any]:
experiments = (
self._experiments_getter() if self._experiments_getter is not None else None
)
return build_base_metadata(
entrypoint_metadata=(
self._entrypoint_metadata_getter()
@ -118,6 +128,7 @@ class TelemetryClient:
),
session_id=self.session_id,
parent_session_id=self.parent_session_id,
experiments=experiments,
)
def send_telemetry_event(
@ -129,13 +140,19 @@ class TelemetryClient:
) -> None:
if not self._is_enabled():
return
provider_and_api_key = self._get_mistral_provider_and_api_key()
provider_and_api_key = get_mistral_provider_and_api_key(self._config_getter())
if provider_and_api_key is None:
return
provider, mistral_api_key = provider_and_api_key
telemetry_url = self._get_telemetry_url(provider.api_base)
user_agent = get_user_agent(provider.backend)
properties = self.build_client_event_metadata() | properties
logger.debug(
"telemetry event=%s properties=%s correlation_id=%s",
event_name,
properties,
correlation_id,
)
payload: dict[str, Any] = {"event": event_name, "properties": properties}
if correlation_id: