zylon-ai/private-gpt · error · ValueError

No last user message found after condensation.

Error message

No last user message found after condensation.

What it means

Post-condensation invariant check in ConversationCondenser.condense, adjacent to the user-message check: after condensing, last_user_message must still be non-empty. The algorithm captures the last user message before condensing and the result must retain it; an empty/falsy last_user_message means the strategy dropped the pivot message the condenser exists to protect, so it raises.

Source

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

            )
        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. Ensure the strategy output includes the original last user message object (append it explicitly after summarizing).
  2. Return early for empty/short histories instead of running them through condensing.
  3. Unit-test custom strategies with single-user-message and assistant-only fixtures to catch pivot loss.
  4. Wrap condense() calls in try/except ValueError and fall back to naive truncation that keeps the last user turn.

Example fix

// before
return left_summary + right_messages[:-1]   # pivot user message dropped

// after
return left_summary + [last_user_message] + right_messages
Defensive patterns

Strategy: validation

Validate before calling

condensed = await strategy.condense(history)
if last_user_message not in condensed:
    condensed.append(last_user_message)

Type guard

def retains_last_user(condensed: list[ChatMessage], pivot: ChatMessage) -> bool:
    return any(m is pivot or m == pivot for m in condensed)

Try / catch

try:
    return await condenser.condense(history)
except ValueError as e:
    if "last user message" in str(e):
        return [summary_of(history), pivot] if history else history
    raise

Prevention

When it happens

Trigger: A condense branch that returns an empty list; a custom strategy that filters out the last user message; code paths where the right-segment trim removes the captured user message and nothing re-inserts it; empty input history reaching condense despite upstream checks.

Common situations: Custom registered condense strategies with off-by-one slicing; strategies returning chat_history[:] vs chat_history[:-1] mistakes; tests with single-message fixtures where the pivot lands in a trimmed segment.

Related errors


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