Skip to content

[BUG CLIENT]: open-enum fields (e.g. reasoning_effort) are silently dropped from the request body for values outside their Literals #564

@bilelomrani1

Description

@bilelomrani1

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions