zylon-ai/private-gpt · error · ValueError
Message {j} in user block {i} is neither an assistant nor a
Error message
Message {j} in user block {i} is neither an assistant nor a tool: {message} What it means
Interior user-block validation: every message strictly between the first and last of a block must be either 'assistant' or 'tool'. Any other role (in practice a second 'user' message inside the block) fails the check, because the block structure only admits user → (assistant|tool)* → end.
Source
Thrown at private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py:161
# 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}"
)
def _assert_repair_without_tools(
chat_history: list[ChatMessage],
) -> None:
"""Assert that the chat history does not contain any tool messages.
This is used to ensure that the chat history is
clean and does not contain any tool calls or tool messages.
"""
if not chat_history:
return
def is_a_potential_tool_message(msg: ChatMessage) -> tuple[bool, str | None]:
if msg.role == "tool":
return True, "Tool message detected"View on GitHub (pinned to 4a030776a3)
Solutions
- Apply merge_adjacent_messages (and/or split into proper blocks) before calling repair_*
- Split the history at each user message so blocks contain only one user turn each
- Remove stray user messages that belong to another session
Example fix
# before repaired = repair_without_tools(chat_history) # after from private_gpt.components.chat.processors.chat_history.memory.utils.merge import merge_adjacent_messages repaired = repair_without_tools(merge_adjacent_messages(chat_history))
Defensive patterns
Strategy: validation
Validate before calling
def interior_is_assistant_or_tool(history: list[ChatMessage]) -> bool:
for b in get_user_blocks(history):
if any(m.role not in ("assistant", "tool") for m in b[1:-1]):
return False
return True
assert interior_is_assistant_or_tool(history) Type guard
def has_clean_block_interiors(history: list[ChatMessage]) -> bool:
return interior_is_assistant_or_tool(history) Try / catch
try:
repaired = repair_without_tools(chat_history)
except ValueError as e:
if "neither an assistant nor a tool" in str(e):
repaired = repair_without_tools(merge_adjacent_messages(chat_history))
else:
raise Prevention
- Merge adjacent user messages before any repair call
- Keep one session per history — don't interleave users
- Add schema validation on loaded transcripts
When it happens
Trigger: Calling repair_without_tools()/repair_with_tools() on history where a user message appears mid-block — i.e. two user turns with responses missing around them, or a user message sandwiched between assistant messages.
Common situations: Consecutive user messages not merged before validation; interleaved transcripts from multi-user sessions stuffed into one history; message ordering corrupted by a custom loader.
Related errors
- User block {i} does not start with a user message: {block[0]
- User block {i} does not end with an assistant message: {bloc
- User block {i} does not end with an assistant or tool messag
- Tool message {j} in user block {i} does not follow an assist
- User block {i} is empty.
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/3faf6053c006f71b.
Report an issue: GitHub.