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.

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.

Appendix: 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)