{"record":{"id":"d215cba02157e59b","repo":"zylon-ai/private-gpt","slug":"user-block-i-does-not-end-with-an-assistant-mess","errorCode":null,"errorMessage":"User block {i} does not end with an assistant message: {block[-1]}","messagePattern":"User block (.+?) does not end with an assistant message: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py","lineNumber":145,"sourceCode":"\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(\n                f\"User block {i} does not end with an assistant or tool message: {block[-1]}\"\n            )\n\n        # Validate the tool messages\n        for j, message in enumerate(block[1:-1]):\n            if message.role == \"tool\":\n                if j == 0 or block[j].role != \"assistant\":\n                    raise ValueError(\n                        f\"Tool message {j} in user block {i} does not follow an assistant message: {message}\"\n                    )\n            elif message.role != \"assistant\":\n                raise ValueError(\n                    f\"Message {j} in user block {i} is neither an assistant nor a tool: {message}\"\n                )","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py#L127-L163","documentation":"Strict-mode user-block validation: every block with more than one message must end with an assistant message, enforcing the (user, (assistant, tool)*, assistant) shape. A block ending in a user message (consecutive user turns) or a tool message (unanswered tool call) fails this check in strict mode.","triggerScenarios":"Calling repair_without_tools(strict=True) or repair_with_tools(strict=True) on history where a user block ends with a user message (two user turns in a row) or with a tool message (assistant issued tool_call but no tool result / final assistant reply recorded).","commonSituations":"User sent follow-up messages before the assistant responded (multi-turn spamming); tool-call flows interrupted mid-execution and persisted; histories replayed from logs missing the final assistant answer.","solutions":["Pass strict=False to allow blocks ending with assistant OR tool messages","Repair the history first: merge adjacent user messages or append a placeholder assistant reply","Fix persistence so interrupted tool flows are not saved half-complete"],"exampleFix":"# before\nrepaired = repair_with_tools(chat_history, strict=True)\n\n# after\nrepaired = repair_with_tools(chat_history, strict=False)","handlingStrategy":"fallback","validationCode":"def blocks_end_with_assistant(history: list[ChatMessage]) -> bool:\n    return all(b[-1].role == \"assistant\" for b in get_user_blocks(history) if len(b) > 1)","typeGuard":"def is_strict_repairable(history: list[ChatMessage]) -> bool:\n    for b in get_user_blocks(history):\n        if not b or b[0].role != \"user\":\n            return False\n        if len(b) > 1 and b[-1].role != \"assistant\":\n            return False\n    return True","tryCatchPattern":"try:\n    repaired = repair_with_tools(chat_history, strict=True)\nexcept ValueError as e:\n    if \"does not end with an assistant message\" in str(e):\n        repaired = repair_with_tools(chat_history, strict=False)  # tolerate tool-ending blocks\n    else:\n        raise","preventionTips":["Choose strict mode deliberately per pipeline, not by default","Don't persist conversations mid-tool-flow","Append a placeholder assistant message when a user turn was never answered"],"tags":["chat-history","validation","repair","strict-mode","message-order"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}