zylon-ai/private-gpt · error · ValueError
No user messages found in the chat history.
Error message
No user messages found in the chat history.
What it means
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.
Source
Thrown at private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py:82
self.message_to_input = message_to_input
self.builder = summarize_workflow_builder or get_global_injector().get(
SummarizeWorkflowBuilder
)
self.llm_component = llm_component or get_global_injector().get(LLMComponent)
self.prompt_builder_service = (
prompt_builder_service or get_global_injector().get(PromptBuilderService)
)
def _split_conversation(
self,
chat_history: list[ChatMessage],
) -> tuple[list[ChatMessage], ChatMessage, list[ChatMessage]]:
"""Split conversation into left and right parts."""
for i in range(len(chat_history) - 1, -1, -1):
if chat_history[i].role == "user":
return chat_history[:i], chat_history[i], chat_history[i + 1 :]
raise ValueError("No user messages found in the chat history.")
async def _get_messages_tokens(
self,
messages: ChatMessage | list[ChatMessage],
tokenizer_fn: TokenizerFn | None = None,
) -> int:
if not messages:
return 0
if isinstance(messages, ChatMessage):
messages = [messages]
return await estimate_token_count(
messages,
tokenizer_fn=tokenizer_fn,
message_to_input=self.message_to_input,
)
async def _summarize(View on GitHub (pinned to 4a030776a3)
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.
Example fix
// before history = [ChatMessage(role="assistant", content="hi")] condensed = await condenser.condense(history) # ValueError // after history = [ChatMessage(role="user", content="hi")] condensed = await condenser.condense(history)
Defensive patterns
Strategy: validation
Validate before calling
if not any(m.role == "user" for m in chat_history):
# nothing to preserve; skip condensing entirely
return chat_history Type guard
def history_has_user(messages: list[ChatMessage]) -> bool:
return any(m.role == "user" for m in messages) Try / catch
try:
condensed = await condenser.condense(history)
except ValueError as e:
if "No user messages" in str(e):
return history # assistant-only history, return as-is
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No user messages found after condensation.
- No last user message found after condensation.
- 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/34a6884dc0135c9b.
Report an issue: GitHub.