zylon-ai/private-gpt · error · Errors.InvalidRequest

No user message found in request.

Error message

No user message found in request.

What it means

Raised by the static helper _last_user_message when no message with role USER exists in request.messages. The validator scans messages in reverse and raises Errors.InvalidRequest if none matches. A chat request must contain at least one user message.

Source

Thrown at private_gpt/server/chat/interceptors/validator_request_interceptor.py:183

        return

    @staticmethod
    def _extract_text(message: ChatMessage) -> str:
        """Extract normalized text from message blocks."""
        parts = [
            block.text.strip()
            for block in message.blocks
            if isinstance(block, TextBlock) and block.text and block.text.strip()
        ]
        return "\n".join(parts)

    @staticmethod
    def _last_user_message(messages: list[ChatMessage]) -> ChatMessage:
        """Return the latest user message from request messages."""
        for message in reversed(messages):
            if message.role == MessageRole.USER:
                return message
        raise Errors.InvalidRequest("No user message found in request.")

    @staticmethod
    def _system_message_text(messages: list[ChatMessage]) -> str | None:
        """Return first system message text when present."""
        for message in messages:
            if message.role != MessageRole.SYSTEM:
                continue
            text = ValidatorRequestInterceptor._extract_text(message)
            if text:
                return text
        return None

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Ensure request.messages contains at least one ChatMessage with role == MessageRole.USER.
  2. Append the current user query before sending multi-turn history.
  3. Validate the payload client-side: assert any(m.role == 'user' for m in messages) before the call.

Example fix

# before
messages = [ChatMessage(role=MessageRole.ASSISTANT, ...)]

# after
messages = [ChatMessage(role=MessageRole.USER, ...), ChatMessage(role=MessageRole.ASSISTANT, ...), ChatMessage(role=MessageRole.USER, ...)]  # last user turn present
Defensive patterns

Strategy: validation

Validate before calling

assert any(m.role == MessageRole.USER for m in request.messages), 'chat requires a user message'

Type guard

def has_user_message(messages: list[ChatMessage]) -> bool:
    return any(m.role == MessageRole.USER for m in messages)

Try / catch

try:
    await chat_facade.create_chat_event_generator(request=request)
except Errors.InvalidRequest as e:
    if 'No user message' in str(e):
        request.messages.append(ChatMessage(role=MessageRole.USER, blocks=[TextBlock(text=user_query)]))

Prevention

When it happens

Trigger: POST /v1/chat/completions with only assistant/system/tool messages; empty messages array; role string mismatch (e.g. 'user ' with whitespace or wrong enum) after manual payload construction.

Common situations: Client bugs that build the message list conditionally and skip the user turn; replaying conversation history without appending the new user query; serialization that drops the role field or maps it incorrectly.

Related errors


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