Python -VV
Python 3.12.12 (main, Nov 19 2025, 22:30:44) [Clang 21.1.4 ]
Pip Freeze
mistralai==2.4.5
pydantic==2.13.4
pydantic-core==2.46.4
Reproduction Steps
Some fields are dropped at serialization, before any request is sent:
from mistralai.client.models.chatcompletionrequest import ChatCompletionRequest
for value in ["none", "high", "medium", "low", "minimal", "xhigh", "auto"]:
req = ChatCompletionRequest(
model="m",
messages=[{"role": "user", "content": "x"}],
reasoning_effort=value,
)
in_dump = "reasoning_effort" in req.model_dump(mode="json")
print(f"input={value!r:10} stored={req.reasoning_effort!r:10} in_json_dump={in_dump}")
input='none' stored='none' in_json_dump=True
input='high' stored='high' in_json_dump=True
input='medium' stored='medium' in_json_dump=False
input='low' stored='low' in_json_dump=False
input='minimal' stored='minimal' in_json_dump=False
input='xhigh' stored='xhigh' in_json_dump=False
input='auto' stored='auto' in_json_dump=False
Only "none" and "high" survive. Everything else is accepted at construction but dropped from the dump, and therefore from the wire.
End-to-end, capturing the outbound body with an httpx hook:
import httpx
from mistralai.client import Mistral
bodies = []
hook = lambda req: bodies.append(req.read()) if "chat/completions" in str(req.url) else None
async with Mistral(api_key=KEY, async_client=httpx.AsyncClient(event_hooks={"request": [hook]})) as client:
for effort in ["high", "medium"]:
bodies.clear()
resp = await client.chat.complete_async(
model="magistral-small-latest",
messages=[{"role": "user", "content": "What is 17*23? Think step by step."}],
reasoning_effort=effort,
)
on_wire = b'"reasoning_effort"' in bodies[0]
blocks = [getattr(c, "type", "text") for c in resp.choices[0].message.content] \
if isinstance(resp.choices[0].message.content, list) else ["text"]
print(f"effort={effort!r:8} on_wire={on_wire} response_blocks={blocks}")
effort='high' on_wire=True response_blocks=['thinking', 'text']
effort='medium' on_wire=False response_blocks=['text']
For "medium" the body carries only model / messages / stream but no reasoning_effort so the server returns a normal non-reasoning response and the drop is invisible to the caller.
Expected Behavior
2 possible behaviors:
- fail-fast and reject the value at construction with a
ValidationError
- or pass it through to the API and let the API returns a 400 status code if the enum is truly unsupported.
The current behavior, accepting silently then dropping silently at serialization, is strictly worse: no error, but the parameter never reaches the server.
Additional Context
No response
Suggested Solutions
reasoning_effort is an open enum, ReasoningEffort = Union[Literal["none", "high"], UnrecognizedStr], and UnrecognizedStr sets strict_schema=core_schema.none_schema(), always failing in strict mode. model_dump(mode="json") serializes the union strictly, so a value like "medium" matches neither branch and is omitted; "none"/"high" survive only because they hit the Literal.
The same Union[Literal[...], UnrecognizedStr] shape is generated for ~50 model types, so every open-enum field drops the same way.
I suggest making the strict branch of UnrecognizedStr (and UnrecognizedInt) pass the underlying value through instead of none_schema(), so model_dump() emits it, and let the API reject unsupported values with status code 400. One fix covers all ~50 fields.
Python -VV
Pip Freeze
Reproduction Steps
Some fields are dropped at serialization, before any request is sent:
Only
"none"and"high"survive. Everything else is accepted at construction but dropped from the dump, and therefore from the wire.End-to-end, capturing the outbound body with an
httpxhook:For
"medium"the body carries onlymodel / messages / streambut noreasoning_effortso the server returns a normal non-reasoning response and the drop is invisible to the caller.Expected Behavior
2 possible behaviors:
ValidationErrorThe current behavior, accepting silently then dropping silently at serialization, is strictly worse: no error, but the parameter never reaches the server.
Additional Context
No response
Suggested Solutions
reasoning_effortis an open enum,ReasoningEffort = Union[Literal["none", "high"], UnrecognizedStr], andUnrecognizedStrsetsstrict_schema=core_schema.none_schema(), always failing in strict mode.model_dump(mode="json")serializes the union strictly, so a value like"medium"matches neither branch and is omitted;"none"/"high"survive only because they hit theLiteral.The same
Union[Literal[...], UnrecognizedStr]shape is generated for ~50 model types, so every open-enum field drops the same way.I suggest making the strict branch of
UnrecognizedStr(andUnrecognizedInt) pass the underlying value through instead ofnone_schema(), somodel_dump()emits it, and let the API reject unsupported values with status code 400. One fix covers all ~50 fields.