diff --git a/python/packages/mistral/agent_framework_mistral/_chat_client.py b/python/packages/mistral/agent_framework_mistral/_chat_client.py index b97fb8f072d..d70b6a61f13 100644 --- a/python/packages/mistral/agent_framework_mistral/_chat_client.py +++ b/python/packages/mistral/agent_framework_mistral/_chat_client.py @@ -357,7 +357,7 @@ def __init__( Can also be set via environment variable ``MISTRAL_CHAT_MODEL``. api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable. server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL`` - environment variable, or the Mistral default. + environment variable, or the Mistral default. Ignored when a Mistral SDK ``client`` is provided. client: Optional pre-configured ``mistralai.client.Mistral``. http_client: Optional pre-configured ``httpx.AsyncClient``. When provided, api_key is not required and the client is expected to carry its own auth headers. @@ -383,6 +383,8 @@ def __init__( if client is not None and http_client is not None: raise ValueError("Provide either 'client' or 'http_client', not both.") + # An injected SDK client owns its credentials and endpoint, so api_key and server_url are not required + # and their loaded values are ignored below. mistral_settings = load_settings( MistralSettings, env_prefix="MISTRAL_", @@ -401,8 +403,8 @@ def __init__( if isinstance(client, Mistral): self.client = client - if self.server_url is None: - self.server_url = client.sdk_configuration.get_server_details()[0] + # The injected SDK client sends the requests, so its endpoint wins over configured settings. + self.server_url = client.sdk_configuration.get_server_details()[0] else: client_kwargs: dict[str, Any] = {"timeout_ms": _DEFAULT_TIMEOUT_MS} if resolved_api_key := mistral_settings.get("api_key"): @@ -990,7 +992,7 @@ def __init__( Can also be set via environment variable ``MISTRAL_CHAT_MODEL``. api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable. server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL`` - environment variable, or the Mistral default. + environment variable, or the Mistral default. Ignored when a Mistral SDK ``client`` is provided. client: Optional pre-configured ``mistralai.client.Mistral``. http_client: Optional pre-configured ``httpx.AsyncClient``. When provided, api_key is not required and the client is expected to carry its own auth headers. diff --git a/python/packages/mistral/agent_framework_mistral/_embedding_client.py b/python/packages/mistral/agent_framework_mistral/_embedding_client.py index 29b0107c22e..a097bc3c58a 100644 --- a/python/packages/mistral/agent_framework_mistral/_embedding_client.py +++ b/python/packages/mistral/agent_framework_mistral/_embedding_client.py @@ -96,7 +96,7 @@ class RawMistralEmbeddingClient( Can also be set via environment variable ``MISTRAL_EMBEDDING_MODEL``. api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable. server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL`` - environment variable, or the Mistral default. + environment variable, or the Mistral default. Ignored when a Mistral SDK ``client`` is provided. http_client: Optional pre-configured ``httpx.AsyncClient``. When provided, api_key is not required and the client is expected to carry its own auth headers and base URL. client: Optional pre-configured ``mistralai.client.Mistral``. Passing an HTTP client via @@ -138,6 +138,8 @@ def __init__( if client is not None and http_client is not None: raise ValueError("Provide either 'client' or 'http_client', not both.") + # An injected SDK client owns its credentials and endpoint, so api_key and server_url are not required + # and their loaded values are ignored below. injected = client is not None or http_client is not None required_fields = ["embedding_model"] if injected else ["embedding_model", "api_key"] mistral_settings = load_settings( @@ -157,8 +159,8 @@ def __init__( if isinstance(client, Mistral): self.client = client - if self.server_url is None: - self.server_url = client.sdk_configuration.get_server_details()[0] + # The injected SDK client sends the requests, so its endpoint wins over configured settings. + self.server_url = client.sdk_configuration.get_server_details()[0] else: client_kwargs: dict[str, Any] = {"timeout_ms": _DEFAULT_TIMEOUT_MS} if resolved_api_key := mistral_settings.get("api_key"): @@ -293,7 +295,7 @@ class MistralEmbeddingClient( Can also be set via environment variable ``MISTRAL_EMBEDDING_MODEL``. api_key: Mistral API key. Defaults to ``MISTRAL_API_KEY`` environment variable. server_url: Optional server URL override. Defaults to ``MISTRAL_SERVER_URL`` - environment variable, or the Mistral default. + environment variable, or the Mistral default. Ignored when a Mistral SDK ``client`` is provided. http_client: Optional pre-configured ``httpx.AsyncClient``. client: Optional pre-configured ``mistralai.client.Mistral``. Passing an HTTP client via this parameter remains supported but is deprecated; use ``http_client`` instead. diff --git a/python/packages/mistral/tests/mistral/test_mistral_chat_client.py b/python/packages/mistral/tests/mistral/test_mistral_chat_client.py index 9430f61bb01..80da9f619ce 100644 --- a/python/packages/mistral/tests/mistral/test_mistral_chat_client.py +++ b/python/packages/mistral/tests/mistral/test_mistral_chat_client.py @@ -533,6 +533,23 @@ async def test_get_response_rejects_transport_arguments_split_across_options_and await client.close() +@pytest.mark.parametrize("client_type", [RawMistralChatClient, MistralChatClient]) +@pytest.mark.parametrize("server_url", [None, "https://explicit.example"], ids=["env", "explicit"]) +def test_injected_sdk_client_endpoint_wins_over_configured_server_url( + client_type: type[RawMistralChatClient], + server_url: str | None, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """An injected SDK client defines the endpoint, so MISTRAL_SERVER_URL or server_url should not change it.""" + monkeypatch.setenv("MISTRAL_SERVER_URL", "https://env.example") + sdk_client = Mistral(api_key="test-key", server_url="https://client.example") + + client = client_type(model="mistral-small-latest", client=sdk_client, server_url=server_url) + + assert client.service_url() == "https://client.example" + assert client.client is sdk_client + + @pytest.mark.parametrize("client_type", [RawMistralChatClient, MistralChatClient]) @pytest.mark.parametrize("stream", [False, True]) @pytest.mark.parametrize("configuration", ["server_url", "http_client", "sdk"]) diff --git a/python/packages/mistral/tests/mistral/test_mistral_embedding_client.py b/python/packages/mistral/tests/mistral/test_mistral_embedding_client.py index eb6851e803b..8471d10e39c 100644 --- a/python/packages/mistral/tests/mistral/test_mistral_embedding_client.py +++ b/python/packages/mistral/tests/mistral/test_mistral_embedding_client.py @@ -211,6 +211,21 @@ def test_mistral_embedding_service_url_custom() -> None: assert client.service_url() == "https://custom.mistral.ai" +@pytest.mark.parametrize("server_url", [None, "https://explicit.example"], ids=["env", "explicit"]) +def test_mistral_embedding_injected_sdk_client_endpoint_wins_over_configured_server_url( + server_url: str | None, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """An injected SDK client defines the endpoint, so MISTRAL_SERVER_URL or server_url should not change it.""" + monkeypatch.setenv("MISTRAL_SERVER_URL", "https://env.example") + sdk_client = Mistral(api_key="test-key", server_url="https://client.example") + + client = MistralEmbeddingClient(model="mistral-embed", client=sdk_client, server_url=server_url) + + assert client.service_url() == "https://client.example" + assert client.client is sdk_client + + async def test_mistral_embedding_close_only_closes_owned_client() -> None: owned = MistralEmbeddingClient(model="mistral-embed", api_key="test-key") owned_http_client = owned.client.sdk_configuration.async_client