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

  1. Reorder so all system messages come first and the conversation ends with the user's (or assistant's) message.
  2. In tool loops, append a final user nudge (e.g. 'continue') after tool results, or ensure the assistant turn closes the loop.
  3. 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

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


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/c149a761c1926a31. Report an issue: GitHub.