zylon-ai/private-gpt · error · ValueError
User block {i} does not end with an assistant message: {bloc
Error message
User block {i} does not end with an assistant message: {block[-1]} What it means
Strict-mode user-block validation: every block with more than one message must end with an assistant message, enforcing the (user, (assistant, tool)*, assistant) shape. A block ending in a user message (consecutive user turns) or a tool message (unanswered tool call) fails this check in strict mode.
Source
Thrown at private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py:145
matrix = get_user_blocks(chat_history)
for i, block in enumerate(matrix):
if not block:
raise ValueError(f"User block {i} is empty.")
# Validate the first and last messages in the block
if block[0].role != "user":
raise ValueError(
f"User block {i} does not start with a user message: {block[0]}"
)
if len(block) == 1:
# No more validation needed for a single user message block
continue
# Validate the last message in the block
if strict and block[-1].role != "assistant":
raise ValueError(
f"User block {i} does not end with an assistant message: {block[-1]}"
)
elif not strict and block[-1].role not in ["assistant", "tool"]:
raise ValueError(
f"User block {i} does not end with an assistant or tool message: {block[-1]}"
)
# Validate the tool messages
for j, message in enumerate(block[1:-1]):
if message.role == "tool":
if j == 0 or block[j].role != "assistant":
raise ValueError(
f"Tool message {j} in user block {i} does not follow an assistant message: {message}"
)
elif message.role != "assistant":
raise ValueError(
f"Message {j} in user block {i} is neither an assistant nor a tool: {message}"
)View on GitHub (pinned to 4a030776a3)
Solutions
- Pass strict=False to allow blocks ending with assistant OR tool messages
- Repair the history first: merge adjacent user messages or append a placeholder assistant reply
- Fix persistence so interrupted tool flows are not saved half-complete
Example fix
# before repaired = repair_with_tools(chat_history, strict=True) # after repaired = repair_with_tools(chat_history, strict=False)
Defensive patterns
Strategy: fallback
Validate before calling
def blocks_end_with_assistant(history: list[ChatMessage]) -> bool:
return all(b[-1].role == "assistant" for b in get_user_blocks(history) if len(b) > 1) Type guard
def is_strict_repairable(history: list[ChatMessage]) -> bool:
for b in get_user_blocks(history):
if not b or b[0].role != "user":
return False
if len(b) > 1 and b[-1].role != "assistant":
return False
return True Try / catch
try:
repaired = repair_with_tools(chat_history, strict=True)
except ValueError as e:
if "does not end with an assistant message" in str(e):
repaired = repair_with_tools(chat_history, strict=False) # tolerate tool-ending blocks
else:
raise Prevention
- Choose strict mode deliberately per pipeline, not by default
- Don't persist conversations mid-tool-flow
- Append a placeholder assistant message when a user turn was never answered
When it happens
Trigger: Calling repair_without_tools(strict=True) or repair_with_tools(strict=True) on history where a user block ends with a user message (two user turns in a row) or with a tool message (assistant issued tool_call but no tool result / final assistant reply recorded).
Common situations: User sent follow-up messages before the assistant responded (multi-turn spamming); tool-call flows interrupted mid-execution and persisted; histories replayed from logs missing the final assistant answer.
Related errors
- User block {i} does not start with a user message: {block[0]
- User block {i} does not end with an assistant or tool messag
- Tool message {j} in user block {i} does not follow an assist
- Message {j} in user block {i} is neither an assistant nor a
- User block {i} is empty.
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/d215cba02157e59b.
Report an issue: GitHub.