{"record":{"id":"34a6884dc0135c9b","repo":"zylon-ai/private-gpt","slug":"no-user-messages-found-in-the-chat-history","errorCode":null,"errorMessage":"No user messages found in the chat history.","messagePattern":"No user messages found in the chat history\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py","lineNumber":82,"sourceCode":"        self.message_to_input = message_to_input\n        self.builder = summarize_workflow_builder or get_global_injector().get(\n            SummarizeWorkflowBuilder\n        )\n        self.llm_component = llm_component or get_global_injector().get(LLMComponent)\n        self.prompt_builder_service = (\n            prompt_builder_service or get_global_injector().get(PromptBuilderService)\n        )\n\n    def _split_conversation(\n        self,\n        chat_history: list[ChatMessage],\n    ) -> tuple[list[ChatMessage], ChatMessage, list[ChatMessage]]:\n        \"\"\"Split conversation into left and right parts.\"\"\"\n        for i in range(len(chat_history) - 1, -1, -1):\n            if chat_history[i].role == \"user\":\n                return chat_history[:i], chat_history[i], chat_history[i + 1 :]\n\n        raise ValueError(\"No user messages found in the chat history.\")\n\n    async def _get_messages_tokens(\n        self,\n        messages: ChatMessage | list[ChatMessage],\n        tokenizer_fn: TokenizerFn | None = None,\n    ) -> int:\n        if not messages:\n            return 0\n        if isinstance(messages, ChatMessage):\n            messages = [messages]\n\n        return await estimate_token_count(\n            messages,\n            tokenizer_fn=tokenizer_fn,\n            message_to_input=self.message_to_input,\n        )\n\n    async def _summarize(","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py#L64-L100","documentation":"ValueError from ConversationCondenser._split_conversation. It scans chat_history from the end backwards for the last message with role 'user' and splits into (left, last_user, right); if no message has role 'user' it cannot split and raises. It is the prerequisite invariant for condensing: the last user message is always preserved.","triggerScenarios":"Invoking the condenser on a history consisting only of assistant/system/tool messages; a degenerate single-message history with role != 'user'; tests constructing histories without a user turn; upstream steps dropping or reassigning the user role.","commonSituations":"Programmatic chat pipelines that seed history with prior assistant output; role mapped to 'human' or 'USER' (case/alias mismatch) when building ChatMessage objects; empty conversations guarded incorrectly.","solutions":["Ensure the chat history passed to condensing contains at least one ChatMessage with role exactly 'user'.","Normalize role names when constructing history (map 'human' -> 'user') before invoking the condenser.","Skip condensing for histories with no user turn (they are already assistant-only; nothing to preserve).","In tests, always append a user message to fixtures."],"exampleFix":"// before\nhistory = [ChatMessage(role=\"assistant\", content=\"hi\")]\ncondensed = await condenser.condense(history)  # ValueError\n\n// after\nhistory = [ChatMessage(role=\"user\", content=\"hi\")]\ncondensed = await condenser.condense(history)","handlingStrategy":"validation","validationCode":"if not any(m.role == \"user\" for m in chat_history):\n    # nothing to preserve; skip condensing entirely\n    return chat_history","typeGuard":"def history_has_user(messages: list[ChatMessage]) -> bool:\n    return any(m.role == \"user\" for m in messages)","tryCatchPattern":"try:\n    condensed = await condenser.condense(history)\nexcept ValueError as e:\n    if \"No user messages\" in str(e):\n        return history  # assistant-only history, return as-is\n    raise","preventionTips":["Always include a user turn in histories passed to the condenser.","Normalize role aliases ('human' -> 'user') when building ChatMessage lists.","Guard with a has-user check before calling condense.","In tests, add user messages to fixtures."],"tags":["chat-history","condensation","invariant","roles"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}