zylon-ai/private-gpt · error · ValueError
Invalid message order: expected {expected_roles} after {prev
Error message
Invalid message order: expected {expected_roles} after {previous_role}, but got {current_role}.Check ToolUseBlock and ToolResultBlock order. What it means
Raised by the DELETE /skills/{skill_id} route when the skill does not exist in the given collection (get_skill returned None). Deletion requires the skill to be resolvable first; a miss short-circuits to 404 before any mutation is attempted.
Source
Thrown at private_gpt/chat/input_models.py:531
expected_roles = {
MessageRole.ASSISTANT,
MessageRole.USER,
}
elif previous_role == MessageRole.ASSISTANT:
expected_roles = {
MessageRole.USER,
MessageRole.TOOL,
}
elif previous_role == MessageRole.TOOL:
expected_roles = {
MessageRole.ASSISTANT,
# Mistral doesn't support user after tool
# Fixed in the tokenizer
MessageRole.USER,
}
if current_role not in expected_roles:
raise ValueError(
f"Invalid message order: expected {expected_roles} after {previous_role}, but got {current_role}."
"Check ToolUseBlock and ToolResultBlock order."
if current_role == MessageRole.TOOL
else ""
)
previous_role = current_role
@classmethod
def _merge_messages(cls, messages: list["MessageInput"]) -> list["MessageInput"]:
"""Merge consecutive messages with the same role."""
if not messages:
return []
merged_messages: list[MessageInput] = []
current_message = messages[0]
for msg in messages[1:]:View on GitHub (pinned to 4a030776a3)
Solutions
- Treat 404 on delete as already-deleted and refresh the list.
- Confirm the collection parameter matches where the skill was created.
- Disable duplicate delete actions while a request is in flight.
Example fix
// before
await skillsApi.delete(id, collection); // second call 404s
// after
try { await skillsApi.delete(id, collection); }
catch (e) { if (e.status !== 404) throw e; } // 404 = already gone Defensive patterns
Strategy: try-catch
Validate before calling
const exists = (await skillsApi.list(collection)).data.some((s) => s.id === id); if (!exists) return; // nothing to delete
Try / catch
try { await skillsApi.delete(id, collection); }
catch (e) { if (e.status === 404) return; /* already deleted */ throw e; } Prevention
- Treat skill delete as idempotent
- Lock the delete button during in-flight requests
- Verify collection context before issuing deletes
When it happens
Trigger: DELETE /skills/{id} with an id from another collection, a deleted skill, or after the collection was cleared; duplicate delete requests.
Common situations: Two clients racing to delete the same skill; stale UI state; deleting in the wrong collection context.
Related errors
- Schema must be a dictionary representing JSON Schema
- Handler must define function handle(input, context).
- Unsupported content block type: {type(block)}
- Unknown message role {self.role}. Expected 'system', 'user',
- Audio size {audio_size} exceeds maximum allowed size of {set
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/5dcdb9671275ac37.
Report an issue: GitHub.