{"record":{"id":"3faf6053c006f71b","repo":"zylon-ai/private-gpt","slug":"message-j-in-user-block-i-is-neither-an-assist","errorCode":null,"errorMessage":"Message {j} in user block {i} is neither an assistant nor a tool: {message}","messagePattern":"Message (.+?) in user block (.+?) is neither an assistant nor a tool: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py","lineNumber":161,"sourceCode":"        # 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                )\n\n\ndef _assert_repair_without_tools(\n    chat_history: list[ChatMessage],\n) -> None:\n    \"\"\"Assert that the chat history does not contain any tool messages.\n\n    This is used to ensure that the chat history is\n    clean and does not contain any tool calls or tool messages.\n    \"\"\"\n    if not chat_history:\n        return\n\n    def is_a_potential_tool_message(msg: ChatMessage) -> tuple[bool, str | None]:\n        if msg.role == \"tool\":\n            return True, \"Tool message detected\"","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py#L143-L179","documentation":"Interior user-block validation: every message strictly between the first and last of a block must be either 'assistant' or 'tool'. Any other role (in practice a second 'user' message inside the block) fails the check, because the block structure only admits user → (assistant|tool)* → end.","triggerScenarios":"Calling repair_without_tools()/repair_with_tools() on history where a user message appears mid-block — i.e. two user turns with responses missing around them, or a user message sandwiched between assistant messages.","commonSituations":"Consecutive user messages not merged before validation; interleaved transcripts from multi-user sessions stuffed into one history; message ordering corrupted by a custom loader.","solutions":["Apply merge_adjacent_messages (and/or split into proper blocks) before calling repair_*","Split the history at each user message so blocks contain only one user turn each","Remove stray user messages that belong to another session"],"exampleFix":"# before\nrepaired = repair_without_tools(chat_history)\n\n# after\nfrom private_gpt.components.chat.processors.chat_history.memory.utils.merge import merge_adjacent_messages\nrepaired = repair_without_tools(merge_adjacent_messages(chat_history))","handlingStrategy":"validation","validationCode":"def interior_is_assistant_or_tool(history: list[ChatMessage]) -> bool:\n    for b in get_user_blocks(history):\n        if any(m.role not in (\"assistant\", \"tool\") for m in b[1:-1]):\n            return False\n    return True\n\nassert interior_is_assistant_or_tool(history)","typeGuard":"def has_clean_block_interiors(history: list[ChatMessage]) -> bool:\n    return interior_is_assistant_or_tool(history)","tryCatchPattern":"try:\n    repaired = repair_without_tools(chat_history)\nexcept ValueError as e:\n    if \"neither an assistant nor a tool\" in str(e):\n        repaired = repair_without_tools(merge_adjacent_messages(chat_history))\n    else:\n        raise","preventionTips":["Merge adjacent user messages before any repair call","Keep one session per history — don't interleave users","Add schema validation on loaded transcripts"],"tags":["chat-history","validation","repair","message-order"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}