fix(moonshot): stop mutating caller messages on tool_choice='required' (#31060)

_add_tool_choice_required_message appended the "select a tool" prompt to
the caller's messages list in place, so transform_request corrupted the
caller's conversation history and appended a duplicate prompt on every
retry. Build and return a new list instead so the call stays idempotent.

Adds a regression test asserting the input messages list is unchanged
across repeated transform_request calls.

Co-authored-by: Wassbdr <wassim.badraoui07@gmail.com>
This commit is contained in:
Wassim Badraoui 2026-06-24 12:40:52 +02:00 • committed by GitHub
parent 6893df16fb
commit e71d6ef8ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 5 deletions

View file

@ -238,11 +238,11 @@ class MoonshotChatConfig(OpenAIGPTConfig):
https://platform.moonshot.ai/docs/guide/migrating-from-openai-to-kimi#about-tool_choice
"""
messages.append(
optional_params.pop("tool_choice")
return [
*messages,
{
"role": "user",
"content": "Please select a tool to handle the current issue.", # Usually, the Kimi large language model understands the intention to invoke a tool and selects one for invocation
}
)
optional_params.pop("tool_choice")
return messages
},
]

View file

@ -305,6 +305,34 @@ class TestMoonshotConfig:
assert len(result["messages"]) == 2
assert result["messages"][1]["content"] == "Please select a tool to handle the current issue."
def test_tool_choice_required_does_not_mutate_input_messages(self):
"""tool_choice='required' must not mutate the caller's messages list.
The handling appends a "select a tool" user message; building it in
place corrupts the caller's conversation history and makes
transform_request non-idempotent across retries.
"""
config = MoonshotChatConfig()
messages = [{"role": "user", "content": "What's the weather like?"}]
for _ in range(2):
optional_params = {
"tool_choice": "required",
"tools": [{"type": "function", "function": {"name": "get_weather"}}],
}
result = config.transform_request(
model="moonshot-v1-8k",
messages=messages,
optional_params=optional_params,
litellm_params={},
headers={},
)
# The returned request carries the extra message.
assert len(result["messages"]) == 2
# The caller's list is untouched, so repeated calls stay idempotent.
assert messages == [{"role": "user", "content": "What's the weather like?"}]
def test_tool_choice_non_required_preserved(self):
"""Test that non-'required' tool_choice values are preserved"""
config = MoonshotChatConfig()