zylon-ai/private-gpt · error · ValueError
Unknown message role {self.role}. Expected 'system', 'user',
Error message
Unknown message role {self.role}. Expected 'system', 'user', 'assistant', or 'tool'. What it means
Raised by POST /skills/{skill_id}/versions when the target skill does not exist in the specified collection. Version creation is only valid for an existing skill, so the pre-check on get_skill returning None yields 404.
Source
Thrown at private_gpt/chat/input_models.py:875
current_group.append(msg)
if current_group:
groups.append(current_group)
return groups
def _convert_into_llama_index_messages(
self,
tool_uses: dict[str, ToolUseBlock] | None = None,
) -> tuple[list["ChatMessage"], dict[str, ToolUseBlock]]:
tool_uses = tool_uses or {}
if self.role in ["system", "user"]:
return self._convert_message(), tool_uses
elif self.role == "assistant":
return self._convert_assistant_message(tool_uses)
else:
raise ValueError(
f"Unknown message role {self.role}. Expected 'system', 'user', 'assistant', or 'tool'."
)
def _extract_content(
self,
content: str | BaseContentBlock | Sequence[ContentBlockType] | None,
) -> tuple[list[LIContentBlock] | None, dict[str, list[ContentBlockType]] | None]:
"""Extract text content from various content types."""
blocks: list[LIContentBlock] = []
custom_blocks: dict[str, list[ContentBlockType]] = {}
if isinstance(content, str):
blocks.append(LITextBlock(text=content))
content_blocks: Sequence[ContentBlockType] = []
elif isinstance(content, BaseContentBlock):
content_blocks = [cast(ContentBlockType, content)]
else:
content_blocks = content or []
View on GitHub (pinned to 4a030776a3)
Solutions
- GET the skill first to confirm it exists in the collection, then post the version.
- If 404, recreate the skill (POST /skills) instead of adding a version.
- Refresh skill state in the editor before the version upload.
Example fix
// before
await skillsApi.createVersion(id, collection, files);
// after
const existing = await skillsApi.get(id, collection).catch(() => null);
if (!existing) { await skillsApi.create(body); } // recreate instead
else { await skillsApi.createVersion(id, collection, files); } Defensive patterns
Strategy: validation
Validate before calling
const skill = await skillsApi.get(id, collection).catch(() => null);
if (!skill) { await skillsApi.create({...body}); /* create instead of version */ } Type guard
const canVersion = (s) => s != null && !s.readonly;
Try / catch
try { await skillsApi.createVersion(id, collection, files); }
catch (e) {
if (e.status === 404) { await skillsApi.create({...skillBody, files}); } // recreate
else throw e;
} Prevention
- Guard version uploads behind a skill existence check
- Refresh editor state before submitting versioned changes
- Handle 'skill vanished' by offering recreate rather than a dead end
When it happens
Trigger: Posting a new version for a skill id that was deleted, belongs to another collection, or is misspelled; racing with a concurrent delete.
Common situations: Uploading a new version from a stale edit screen after the skill was removed elsewhere; collection switch in the UI not propagated to the version form.
Related errors
- Invalid system item: {item}
- Unsupported content block type: {type(block)}
- Audio size {audio_size} exceeds maximum allowed size of {set
- Invalid tool specification: {tool}
- Invalid system value: {value}
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/4713fecc79c29de9.
Report an issue: GitHub.