zylon-ai/private-gpt · error · ValueError
Tool message {j} in user block {i} does not follow an assist
Error message
Tool message {j} in user block {i} does not follow an assistant message: {message} What it means
Tool-message ordering validation inside a user block: for each message in block[1:-1], a 'tool' role message must directly follow an assistant message (the one that issued the tool_call). It raises when a tool message appears at the start of the interior (j==0, i.e. right after the user message) or the preceding block[j] message is not an assistant message.
Source
Thrown at private_gpt/components/chat/processors/chat_history/memory/utils/repairs.py:157
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}"
)
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:
returnView on GitHub (pinned to 4a030776a3)
Solutions
- Reconstruct ordering so each tool message directly follows the assistant message carrying the matching tool_call
- Drop orphan tool messages that have no parent assistant message before repair
- Fix the persistence layer to store assistant/tool messages atomically as a pair
Example fix
# before
repaired = repair_with_tools(chat_history)
# after
# drop orphan tool messages (no preceding assistant)
cleaned = []
for m in chat_history:
if m.role == "tool" and (not cleaned or cleaned[-1].role != "assistant"):
continue
cleaned.append(m)
repaired = repair_with_tools(cleaned) Defensive patterns
Strategy: validation
Validate before calling
def tool_messages_follow_assistant(history: list[ChatMessage]) -> bool:
for i, m in enumerate(history):
if m.role == "tool":
if i == 0 or history[i - 1].role != "assistant":
return False
return True
assert tool_messages_follow_assistant(conversation_messages) Type guard
def is_tool_ordering_valid(history: list[ChatMessage]) -> bool:
return tool_messages_follow_assistant(history) Try / catch
try:
repaired = repair_with_tools(chat_history)
except ValueError as e:
if "does not follow an assistant message" in str(e):
cleaned = [m for i, m in enumerate(chat_history) if not (m.role == "tool" and (i == 0 or chat_history[i-1].role != "assistant"))]
repaired = repair_with_tools(cleaned)
else:
raise Prevention
- Store assistant tool_call and its tool result as an atomic pair
- Never truncate history between an assistant tool_call and its tool result
- Validate tool ordering at persistence time
When it happens
Trigger: Calling repair_with_tools() on history where a tool result appears immediately after the user message, or where ordering was shuffled so a tool message follows another tool/user message instead of its assistant.
Common situations: Persisted tool results without their parent assistant tool_call message; parallel tool calls reordered during storage; history truncation that cut off the assistant message but kept the tool result.
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
- Message {j} in user block {i} is neither an assistant nor a
- Chat history contains a tool message: {message.content}. Rea
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/5d4b0e39b5984e2f.
Report an issue: GitHub.