-
Notifications
You must be signed in to change notification settings - Fork 171
[BUG CLIENT]: SDK overrides httpx client timeout by passing timeout=None to build_request #449
Description
There are two compounding issues here:
- [BUG MODEL] Certain API calls hang indefinitely on the server side (e.g.
tool_choice="required"withmagistral-small-2509and other models, also reproduced withmistral-small-latest). The server never sends a response. - [BUG CLIENT] The SDK overrides httpx client-level timeouts by passing
timeout=Nonetobuild_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.1Reproduction Steps
- Create a
Mistralclient with a customhttpx.AsyncClientthat 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)- 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",
)- The request hangs indefinitely despite the 30s
readtimeout 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 raisehttpx.ReadTimeoutafter 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)