Skip to content

[BUG CLIENT]: SDK overrides httpx client timeout by passing timeout=None to build_request #449

@bilelomrani1

Description

@bilelomrani1

There are two compounding issues here:

  1. [BUG MODEL] Certain API calls hang indefinitely on the server side (e.g. tool_choice="required" with magistral-small-2509 and other models, also reproduced with mistral-small-latest). The server never sends a response.
  2. [BUG CLIENT] The SDK overrides httpx client-level timeouts by passing timeout=None to build_request, so users cannot protect themselves from (1) with a client-level timeout.

Either bug alone would be manageable, a hanging API call would be caught by a client timeout, and a disabled timeout is harmless if the server always responds. Together they cause requests to hang indefinitely with no way to recover via the httpx client.

Python -VV

Python 3.12.12 (main, Nov 19 2025, 22:30:44) [Clang 21.1.4 ]

Pip Freeze

mistralai==2.1.3
httpx==0.28.1

Reproduction Steps

  1. Create a Mistral client with a custom httpx.AsyncClient that has a timeout configured:
import httpx
from mistralai.client import Mistral

async_client = httpx.AsyncClient(
    timeout=httpx.Timeout(None, connect=10.0, read=30.0),
)
client = Mistral(api_key="...", async_client=async_client)
  1. Make a request that hangs (e.g. tool_choice="required" on certain models):
await client.chat.complete_async(
    model="magistral-small-2509",
    messages=[{"role": "user", "content": "Say hello."}],
    tools=[{"type": "function", "function": {"name": "f", "parameters": {"type": "object"}}}],
    tool_choice="required",
)
  1. The request hangs indefinitely despite the 30s read timeout configured on the httpx client.

This is not specific to magistral-small-2509 — the same hang occurs with mistral-small-latest and likely other models. The server never responds to this particular call pattern.

Expected Behavior

  • API: The server should return a response (or an error) for all valid requests. tool_choice="required" should not cause the server to hang.
  • Client: The httpx client-level timeout should be respected when the SDK does not specify its own timeout_ms. If the server hangs, the request should raise httpx.ReadTimeout after 30 seconds.

Additional Context

The issue is in basesdk.py, _build_request_with_client:

timeout = timeout_ms / 1000 if timeout_ms is not None else None

return client.build_request(
    method,
    url,
    ...,
    timeout=timeout,
)

When timeout_ms is None (the default), the SDK passes timeout=None to httpx.Client.build_request(). In httpx, timeout=None means no timeout at all — it does NOT fall back to the client's configured timeout. The httpx default parameter is timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, and only USE_CLIENT_DEFAULT triggers the fallback to self.timeout.

By explicitly passing None, the SDK actively overrides and disables any timeout the user configured on their custom httpx client.

Verified by reading httpx._client.AsyncClient.build_request:

def build_request(self, ..., timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT):
    ...
    timeout = (
        self.timeout
        if isinstance(timeout, UseClientDefault)
        else Timeout(timeout)  # Timeout(None) = no timeout
    )

Suggested Solutions

Don't pass timeout to build_request when timeout_ms is None, so httpx uses the client's configured timeout:

# In _build_request_with_client:
kwargs = dict(
    ...,
    content=serialized_request_body.content,
    data=serialized_request_body.data,
    files=serialized_request_body.files,
    headers=headers,
)
if timeout_ms is not None:
    kwargs["timeout"] = timeout_ms / 1000
return client.build_request(method, url, params=query_params, **kwargs)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions