zylon-ai/private-gpt · error · ValueError
Last message role must be one of {self._valid_last_message_r
Error message
Last message role must be one of {self._valid_last_message_roles}, but got {self.messages[-1].role} What it means
ChatBody requires the final message role to be 'user' or 'assistant' (see _valid_last_message_roles). Ending on a 'system' or 'tool' message is rejected because the LLM call needs the last turn to be one the model responds to. This mirrors OpenAI-compatible API behavior enforced server-side via the model_validator.
Source
Thrown at private_gpt/server/chat/chat_models.py:324
if self.seed and self.seed < 0:
self.seed = None
if self.max_tokens is not None and self.max_tokens <= 0:
self.max_tokens = None
if not self.messages:
raise ValueError("Messages cannot be empty")
for message in self.messages:
if not message.content:
raise ValueError(f"Message content cannot be empty: {message}")
if isinstance(message.content, list):
for block in message.content:
if block is None:
raise ValueError(f"Block cannot be None: {message}")
if self.messages[-1].role not in self._valid_last_message_roles:
raise ValueError(
f"Last message role must be one of {self._valid_last_message_roles}, but got {self.messages[-1].role}"
)
# Check tools and tool choice
if self.tools and self.tool_choice and self.tool_choice.type == "tool":
if not self.tools:
raise ValueError("Tool choice is set, but no tools are provided.")
if self.tool_choice.name not in [tool.name for tool in self.tools]:
raise ValueError(
f"Tool choice '{self.tool_choice}' is not in the provided tools."
)
if not self.tools and self.tool_context:
raise ValueError(
"Tool context is provided, but no tools are specified. "
"Please provide tools to use with the tool context."
)
View on GitHub (pinned to 4a030776a3)
Solutions
- Reorder so all system messages come first and the conversation ends with the user's (or assistant's) message.
- In tool loops, append a final user nudge (e.g. 'continue') after tool results, or ensure the assistant turn closes the loop.
- Check for role casing/typos — roles must be exactly 'user' or 'assistant'.
Example fix
# before
messages=[{"role":"user","content":"hi"},{"role":"system","content":"be brief"}]
# after
messages=[{"role":"system","content":"be brief"},{"role":"user","content":"hi"}] Defensive patterns
Strategy: validation
Validate before calling
assert messages[-1]['role'] in ('user', 'assistant'), 'conversation must end with user/assistant' Type guard
def ends_with_valid_role(msgs: list[dict]) -> bool:
return msgs and msgs[-1].get('role') in {'user', 'assistant'} Try / catch
except ValidationError as e:
if 'Last message role' in str(e):
messages = [m for m in messages if m['role'] != 'system'] + system_first(messages)
retry()
else:
raise Prevention
- Put system messages first in request builders
- Close tool loops with an assistant or user turn
- Validate role ordering client-side before each send
When it happens
Trigger: Sending a conversation that ends with {"role":"system", ...} (system prompt appended last) or a trailing tool-result message not followed by a user/assistant turn; request builders that concatenate system messages at the end of history.
Common situations: Prompt templates that append instructions after the user message; multi-step tool loops that send the tool output as the final message without an assistant acknowledgement; role typos like 'Tool' or 'USER'.
Related errors
- Messages cannot be empty
- Message content cannot be empty: {message}
- Block cannot be None: {message}
- Tool choice '{self.tool_choice}' is not in the provided tool
- Tool context is provided, but no tools are specified. Please
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/c149a761c1926a31.
Report an issue: GitHub.