fix(validator): reject any non-user/tool trailing message, not just assistant - #246
Conversation
…ssistant
`_validate_last_message` (serving/test mode) is meant to require the final
message to be a user or tool message, or an assistant message with `prefix`
/ `continue_final_message` set. But the guard ANDed `bad_role` with an
assistant-only flag:
bad_assistant = isinstance(message, AssistantMessage) and not message.prefix and not continue_final_message
bad_role = message.role not in {Roles.user, Roles.tool}
if bad_assistant and bad_role:
raise ...
Because `bad_assistant` is only ever True for an AssistantMessage, the check
could never fire for any other role. A conversation ending in a SystemMessage
(allowed by `_validate_message_order` as user -> system) therefore passed
validation, despite the error message stating only User/Tool (or prefix/
continue Assistant) are valid as the last message.
The existing tests reveal the intent: every case that builds a conversation
ending in a SystemMessage appends a trailing UserMessage with the comment
"so we don't get an error for ending with a system message" -- i.e. the
maintainers assume a trailing system message is rejected, but it was not.
Fix: reject when the role is not user/tool unless it is a valid trailing
assistant (prefix or continue_final_message). Adds `test_ends_with_system`.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes validation of the last chat message role in “serving” mode so that invalid trailing roles (e.g., system) are properly rejected, and adds regression coverage for the bug.
Changes:
- Corrects the last-message validation logic to reject non-
user/toolroles unless the last message is a continuableassistant. - Adds a regression test to ensure a trailing
systemmessage raises the expected exception.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/validation/test_chat_validation.py | Adds a regression test for trailing system message rejection. |
| src/mistral_common/protocol/instruct/validator.py | Fixes boolean logic so invalid trailing roles are rejected in serving mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| valid_trailing_assistant = isinstance(message, AssistantMessage) and ( | ||
| message.prefix or continue_final_message | ||
| ) |
| match=( | ||
| r"Expected last role User or Tool \(or Assistant with prefix or continue_final_message set to " | ||
| r"True\) for serving but got system" | ||
| ), |
|
Thanks @copilot — two good questions; I looked at both: 1. elif continue_final_message and (last_message_role != Roles.assistant or message.prefix):
raise ... # "Expected last role Assistant with prefix False ... continue_final_message"So with 2. Strict regex in the new test. I matched the full message on purpose, to stay consistent with the existing |
Problem
MistralRequestValidator._validate_last_message(serving / test mode) is meant to require the conversation's last message to be a user or tool message, or an assistant message withprefix/continue_final_messageset — as stated in its own error message and the comment "The last message must be a user or tool message in serving mode...".But the guard ANDs
bad_rolewith an assistant-only flag:bad_assistantis only everTruefor anAssistantMessage, so the condition can never fire for any other role. Since_validate_message_orderpermitsuser → system, a conversation ending in aSystemMessagereaches this check and passes — even though it's exactly what the message says should be rejected.Reproduced (serving mode):
The test suite reveals the intent: every case that ends a conversation with a
SystemMessageappends a trailingUserMessagewith the comment "so we don't get an error for ending with a system message" (tests/validation/test_chat_validation.py:82, :101). The maintainers assume a trailing system message is rejected — but theandbug means it isn't. No test asserts a trailing system message is accepted.Fix
Reject when the role is not user/tool unless the message is a valid trailing assistant (
prefixorcontinue_final_message):Verified against all role × prefix ×
continue_final_messagecombinations — only assistant-without-prefix and system-last now raise; user, tool, assistant-with-prefix, and thecontinue_final_messagepath are unchanged (and the followingelif continue_final_messagebranch still behaves identically). A naivebad_role or bad_assistantwas avoided because it would wrongly reject a validprefix=Trueassistant.Tests
Adds
test_ends_with_systemasserting a trailing system message raises in serving mode. The fulltest_chat_validation.pymessage-structure suite passes (the only failures in my environment are the pre-existingtest_audio_*cases that require the optionalsoundfileextra, unrelated to this change).