{"record":{"id":"38c18b84603245f8","repo":"zylon-ai/private-gpt","slug":"user-block-i-is-empty","errorCode":null,"errorMessage":"User block {i} is empty.","messagePattern":"User block (.+?) is empty\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py","lineNumber":131,"sourceCode":"        if assistant_is_valid and tool_is_valid\n        else []\n    )\n\n\ndef _assert_user_blocks(chat_history: list[ChatMessage], strict: bool) -> None:\n    \"\"\"A user block should follow a regex pattern.\n\n    Strict mode: (user, (assistant, tool)*, assistant)\n    Non-strict mode: (user, (assistant, tool)*, assistant?)\n\n    \"\"\"\n    if not chat_history:\n        return\n\n    matrix = get_user_blocks(chat_history)\n    for i, block in enumerate(matrix):\n        if not block:\n            raise ValueError(f\"User block {i} is empty.\")\n\n        # Validate the first and last messages in the block\n        if block[0].role != \"user\":\n            raise ValueError(\n                f\"User block {i} does not start with a user message: {block[0]}\"\n            )\n\n        if len(block) == 1:\n            # No more validation needed for a single user message block\n            continue\n\n        # Validate the last message in the block\n        if strict and block[-1].role != \"assistant\":\n            raise ValueError(\n                f\"User block {i} does not end with an assistant message: {block[-1]}\"\n            )\n        elif not strict and block[-1].role not in [\"assistant\", \"tool\"]:\n            raise ValueError(","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py#L113-L149","documentation":"During _assert_user_blocks, get_user_blocks(chat_history) produced a block (list of messages between user-message boundaries) that is empty. An empty block means the splitting logic created a boundary with no messages, which indicates malformed or inconsistent history structure. The repair pipeline treats this as unrecoverable and raises.","triggerScenarios":"Calling repair_without_tools() or repair_with_tools() (which call _assert_user_blocks) on a history where get_user_blocks yields an empty list element — typically degenerate role sequences or empty message lists at block boundaries.","commonSituations":"Histories assembled from multiple sources (DB + cache) with dropped messages; empty-message entries inserted by a buggy producer; history truncated mid-block.","solutions":["Filter out empty/None messages from chat_history before calling repair_*","Inspect get_user_blocks output on the failing history to find which boundary is empty","Fix the producer that persists malformed histories (validate on write)"],"exampleFix":"# before\nrepaired = repair_without_tools(chat_history)\n\n# after\nchat_history = [m for m in chat_history if m is not None and m.content]\nrepaired = repair_without_tools(chat_history)","handlingStrategy":"validation","validationCode":"blocks = get_user_blocks(chat_history)\nassert all(len(b) > 0 for b in blocks), \"empty user block detected\"\nassert chat_history, \"history itself empty\"","typeGuard":"def has_no_empty_blocks(history: list[ChatMessage]) -> bool:\n    return all(len(b) > 0 for b in get_user_blocks(history))","tryCatchPattern":"try:\n    repaired = repair_without_tools(chat_history)\nexcept ValueError as e:\n    if \"is empty\" in str(e):\n        chat_history = [m for m in chat_history if m is not None and m.content]\n        repaired = repair_without_tools(chat_history)\n    else:\n        raise","preventionTips":["Sanitize persisted histories (no None/empty messages) before repair","Validate on write, not only on read","Unit-test repair entry points with degenerate histories"],"tags":["chat-history","validation","repair","user-blocks"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}