From 14b0432ffc193cd73f762d99d073a76d4298f3a2 Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Wed, 21 Oct 2026 02:53:56 -0400 Subject: [PATCH 1/2] fix(mistral): keep seed=0 and the caller's extra_body ChatMistral translated `seed` to `extra_body={"random_seed": seed}` under a truthiness check, so seed=0 was dropped, and it assigned a fresh extra_body, discarding any extra_body supplied through kwargs. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + chatlas/_provider_mistral.py | 14 +++++++++----- tests/test_provider_mistral.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c7c6110..0dcd0598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes +* `ChatMistral(seed=0)` now sends `random_seed: 0` instead of silently dropping the seed, and a seed no longer replaces an `extra_body` passed through `kwargs`: Mistral's `random_seed` is merged into it. * `content_image_url()` with a `data:` URL no longer silently drops the `detail` argument: the setting is now carried by inline image content and forwarded to OpenAI's Responses and Chat Completions APIs (regular image URLs already forwarded it). Other providers continue to ignore `detail`. (#467) * `ChatBedrockAnthropic()` now uses native structured outputs for `data_model` on Claude 4.5 and later, as `ChatAnthropic()` does. Its Bedrock model IDs (`anthropic.claude-...`, `us.anthropic.claude-...`) weren't recognized, so it fell back to forcing a tool call, which Claude Opus 5.5 and Sonnet 5.5 reject with a 400. * A tool written as a generator (see "Progressive results" in the tool-calling docs) now reports each yielded value in `.chat_async()` and `.stream_async()`, as it does in `.chat()`. A synchronous generator tool used to send the generator object itself (``) as its result. An `async def` generator tool now counts as an async tool, so `.chat()` raises the usual "Cannot use async tools in a synchronous chat" error instead of sending `` as the result. diff --git a/chatlas/_provider_mistral.py b/chatlas/_provider_mistral.py index f7826f17..826bca63 100644 --- a/chatlas/_provider_mistral.py +++ b/chatlas/_provider_mistral.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Optional, cast from ._chat import Chat from ._logging import log_model_default @@ -170,12 +170,16 @@ def _chat_perform_args( del kwargs2["stream_options"] # Mistral wants random_seed, not seed - if seed := kwargs2.pop("seed", None): - if isinstance(seed, int): - kwargs2["extra_body"] = {"random_seed": seed} - elif seed is not None: + seed = kwargs2.pop("seed", None) + if seed is not None: + if not isinstance(seed, int): raise ValueError( "MistralProvider only accepts an integer seed, or None." ) + extra_body: dict[str, Any] = dict( + cast("dict[str, Any]", kwargs2.get("extra_body") or {}) + ) + extra_body["random_seed"] = seed + kwargs2["extra_body"] = extra_body return kwargs2 diff --git a/tests/test_provider_mistral.py b/tests/test_provider_mistral.py index bb3c4a41..f5455209 100644 --- a/tests/test_provider_mistral.py +++ b/tests/test_provider_mistral.py @@ -78,3 +78,18 @@ def test_data_extraction(): @pytest.mark.vcr def test_mistral_model_list(): assert_list_models(ChatMistral) + + +def test_mistral_seed_zero_is_sent_as_random_seed(): + chat = ChatMistral(api_key="key", seed=0) + args = chat.provider._chat_perform_args(False, [], {}) + assert "seed" not in args + assert args["extra_body"] == {"random_seed": 0} + + +def test_mistral_seed_keeps_user_extra_body(): + chat = ChatMistral(api_key="key", seed=7) + args = chat.provider._chat_perform_args( + False, [], {}, kwargs={"extra_body": {"safe_prompt": True}} + ) + assert args["extra_body"] == {"safe_prompt": True, "random_seed": 7} From 8d9afc25c8ef76feb7b304051f866c91da108fe5 Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Wed, 21 Oct 2026 04:22:11 -0400 Subject: [PATCH 2/2] docs: add PR number to the changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dcd0598..cd082d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes -* `ChatMistral(seed=0)` now sends `random_seed: 0` instead of silently dropping the seed, and a seed no longer replaces an `extra_body` passed through `kwargs`: Mistral's `random_seed` is merged into it. +* `ChatMistral(seed=0)` now sends `random_seed: 0` instead of silently dropping the seed, and a seed no longer replaces an `extra_body` passed through `kwargs`: Mistral's `random_seed` is merged into it. (#475) * `content_image_url()` with a `data:` URL no longer silently drops the `detail` argument: the setting is now carried by inline image content and forwarded to OpenAI's Responses and Chat Completions APIs (regular image URLs already forwarded it). Other providers continue to ignore `detail`. (#467) * `ChatBedrockAnthropic()` now uses native structured outputs for `data_model` on Claude 4.5 and later, as `ChatAnthropic()` does. Its Bedrock model IDs (`anthropic.claude-...`, `us.anthropic.claude-...`) weren't recognized, so it fell back to forcing a tool call, which Claude Opus 5.5 and Sonnet 5.5 reject with a 400. * A tool written as a generator (see "Progressive results" in the tool-calling docs) now reports each yielded value in `.chat_async()` and `.stream_async()`, as it does in `.chat()`. A synchronous generator tool used to send the generator object itself (``) as its result. An `async def` generator tool now counts as an async tool, so `.chat()` raises the usual "Cannot use async tools in a synchronous chat" error instead of sending `` as the result.