zylon-ai/private-gpt · error · ValueError

User block {i} does not start with a user message: {block[0]

Error message

User block {i} does not start with a user message: {block[0]}

What it means

User-block structure validation: each block produced by get_user_blocks must start with a role=='user' message. Because blocks are defined as starting at user messages, a block whose first message is not 'user' means the splitting invariant broke (or the history was mutated between split and validation).

Source

Thrown at private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py:135

def _assert_user_blocks(chat_history: list[ChatMessage], strict: bool) -> None:
    """A user block should follow a regex pattern.

    Strict mode: (user, (assistant, tool)*, assistant)
    Non-strict mode: (user, (assistant, tool)*, assistant?)

    """
    if not chat_history:
        return

    matrix = get_user_blocks(chat_history)
    for i, block in enumerate(matrix):
        if not block:
            raise ValueError(f"User block {i} is empty.")

        # Validate the first and last messages in the block
        if block[0].role != "user":
            raise ValueError(
                f"User block {i} does not start with a user message: {block[0]}"
            )

        if len(block) == 1:
            # No more validation needed for a single user message block
            continue

        # Validate the last message in the block
        if strict and block[-1].role != "assistant":
            raise ValueError(
                f"User block {i} does not end with an assistant message: {block[-1]}"
            )
        elif not strict and block[-1].role not in ["assistant", "tool"]:
            raise ValueError(
                f"User block {i} does not end with an assistant or tool message: {block[-1]}"
            )

        # Validate the tool messages

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Ensure conversations passed to repair_* start with a user message after system messages
  2. Prepend a synthetic user message for assistant-first histories
  3. Fix upstream code that injects assistant/tool messages before the first user turn

Example fix

# before
repaired = repair_with_tools(conversation_messages)

# after
if conversation_messages and conversation_messages[0].role != "user":
    conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages
repaired = repair_with_tools(conversation_messages)
Defensive patterns

Strategy: validation

Validate before calling

if conversation_messages and conversation_messages[0].role != "user":
    conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages

Type guard

def starts_with_user(history: list[ChatMessage]) -> bool:
    return bool(history) and history[0].role == "user"

Try / catch

try:
    repaired = repair_with_tools(conversation_messages)
except ValueError as e:
    if "does not start with a user message" in str(e):
        conversation_messages = [ChatMessage(role=MessageRole.USER, content="(start)")] + conversation_messages
        repaired = repair_with_tools(conversation_messages)
    else:
        raise

Prevention

When it happens

Trigger: Calling repair_without_tools()/repair_with_tools() on a conversation whose first non-system message is not a user message (e.g. assistant-first conversation), so the leading chunk fails the block[0].role != 'user' check.

Common situations: Histories that begin with an assistant greeting; a tool message appearing before any user message; custom history loaders that reorder messages.

Related errors


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