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
- Ensure the strategy output includes the original last user message object (append it explicitly after summarizing).
- Return early for empty/short histories instead of running them through condensing.
- Unit-test custom strategies with single-user-message and assistant-only fixtures to catch pivot loss.
- 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
- Never slice the pivot user message out with [:-1]-style trims.
- Unit-test strategies with one-message and assistant-only histories.
- Explicitly append the captured last_user_message to strategy output.
- Fall back to truncated history that keeps the pivot on invariant failure.
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
- No user messages found after condensation.
- No user messages found in the chat history.
- Maximum number of iterations for condensing exceeded.
- The last user message exceeds the maximum length allowed.
- Condensed chat history exceeds maximum length after applying
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/3bec3f318e1993a7.
Report an issue: GitHub.