zylon-ai/private-gpt · error · ValueError

No user messages found after condensation.

Error message

No user messages found after condensation.

What it means

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.

Source

Thrown at private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py:818

                left_tokens=left_tokens,
                last_user_tokens=last_user_message_tokens,
                right_tokens=right_tokens,
            )
        else:
            chat_history = await self._condense_from_right(
                llm=llm,
                tokenizer_fn=tokenizer,
                chat_history=chat_history,
                max_length=max_length,
                left_tokens=left_tokens,
                last_user_tokens=last_user_message_tokens,
                right_tokens=right_tokens,
            )

        # After condensation, we ensure that the last user message is preserved
        # and the chat history is within the maximum length
        if not any(msg.role == "user" for msg in chat_history):
            raise ValueError("No user messages found after condensation.")

        if not last_user_message:
            raise ValueError("No last user message found after condensation.")

        tokens = await self._get_messages_tokens(chat_history, tokenizer_fn=tokenizer)
        if tokens > max_length:
            raise ValueError(
                "Condensed chat history exceeds maximum length after applying condensation strategy."
            )

        return chat_history

View on GitHub (pinned to 4a030776a3)

Solutions

  1. If using a custom strategy, re-append the original last user message to the condensed result before returning it.
  2. Check the summarizer prompt: the summary must be returned as assistant/system messages with the user turn retained.
  3. Inspect the condensed history in a debug hook to see where the user role disappeared.
  4. Catch the ValueError and fall back to returning the uncondensed (possibly truncated) history to keep the chat alive.

Example fix

// before
def condense(history):
    return [ChatMessage(role="assistant", content=summarize(history))]  # loses user turn

// after
def condense(history):
    last_user = [m for m in history if m.role == "user"][-1]
    return [ChatMessage(role="assistant", content=summarize(history)), last_user]
Defensive patterns

Strategy: validation

Validate before calling

condensed = await strategy.condense(history)
if not any(m.role == "user" for m in condensed):
    condensed = condensed + [last_user_message]  # re-assert pivot

Type guard

def retains_user_turn(messages: list[ChatMessage]) -> bool:
    return any(m.role == "user" for m in messages)

Try / catch

try:
    return await condenser.condense(history)
except ValueError as e:
    if "No user messages found after" in str(e):
        return history[-keep_n:]  # fallback preserving the last user turn
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/8651e9b83bf92efb. Report an issue: GitHub.