{"record":{"id":"8651e9b83bf92efb","repo":"zylon-ai/private-gpt","slug":"no-user-messages-found-after-condensation","errorCode":null,"errorMessage":"No user messages found after condensation.","messagePattern":"No user messages found after condensation\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py","lineNumber":818,"sourceCode":"                left_tokens=left_tokens,\n                last_user_tokens=last_user_message_tokens,\n                right_tokens=right_tokens,\n            )\n        else:\n            chat_history = await self._condense_from_right(\n                llm=llm,\n                tokenizer_fn=tokenizer,\n                chat_history=chat_history,\n                max_length=max_length,\n                left_tokens=left_tokens,\n                last_user_tokens=last_user_message_tokens,\n                right_tokens=right_tokens,\n            )\n\n        # After condensation, we ensure that the last user message is preserved\n        # and the chat history is within the maximum length\n        if not any(msg.role == \"user\" for msg in chat_history):\n            raise ValueError(\"No user messages found after condensation.\")\n\n        if not last_user_message:\n            raise ValueError(\"No last user message found after condensation.\")\n\n        tokens = await self._get_messages_tokens(chat_history, tokenizer_fn=tokenizer)\n        if tokens > max_length:\n            raise ValueError(\n                \"Condensed chat history exceeds maximum length after applying condensation strategy.\"\n            )\n\n        return chat_history\n","sourceCodeStart":800,"sourceCodeEnd":830,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py#L800-L830","documentation":"Post-condensation invariant check in ConversationCondenser.condense: after the chosen strategy runs, it verifies the result still contains at least one message with role 'user'. The condenser's contract is to always preserve the last user message; if the condensed output lost all user messages (e.g. a summarizer replaced them or the right-side trim removed them), it raises rather than returning a userless history.","triggerScenarios":"LLM summarization of the left side producing messages all labelled assistant/system; a condense strategy bug returning only summarized content; histories where the last user message was in the right segment and got trimmed; empty result from the condense strategy.","commonSituations":"Custom condense strategies that rebuild history without role preservation; prompt templates for summarization instructing a single assistant summary; upstream tests feeding role-less or assistant-only histories; strategy output parsing dropping roles.","solutions":["If using a custom strategy, re-append the original last user message to the condensed result before returning it.","Check the summarizer prompt: the summary must be returned as assistant/system messages with the user turn retained.","Inspect the condensed history in a debug hook to see where the user role disappeared.","Catch the ValueError and fall back to returning the uncondensed (possibly truncated) history to keep the chat alive."],"exampleFix":"// before\ndef condense(history):\n    return [ChatMessage(role=\"assistant\", content=summarize(history))]  # loses user turn\n\n// after\ndef condense(history):\n    last_user = [m for m in history if m.role == \"user\"][-1]\n    return [ChatMessage(role=\"assistant\", content=summarize(history)), last_user]","handlingStrategy":"validation","validationCode":"condensed = await strategy.condense(history)\nif not any(m.role == \"user\" for m in condensed):\n    condensed = condensed + [last_user_message]  # re-assert pivot","typeGuard":"def retains_user_turn(messages: list[ChatMessage]) -> bool:\n    return any(m.role == \"user\" for m in messages)","tryCatchPattern":"try:\n    return await condenser.condense(history)\nexcept ValueError as e:\n    if \"No user messages found after\" in str(e):\n        return history[-keep_n:]  # fallback preserving the last user turn\n    raise","preventionTips":["Custom strategies must re-append the last user message to their output.","Test strategies with single-user-message fixtures.","Summaries must be assistant/system role; never replace the user turn.","Validate condensed output with a has-user check before returning."],"tags":["condensation","invariant","chat-history","roles","post-validation"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}