zylon-ai/private-gpt · error · ValueError
Schema must be a dictionary representing JSON Schema
Error message
Schema must be a dictionary representing JSON Schema
What it means
Raised by DELETE /skills/{skill_id}/versions/{version} when the parent skill does not exist in the collection. The route checks the skill first; a miss returns 404 before attempting to delete the version.
Source
Thrown at private_gpt/chat/schema_models.py:87
"""Handle reserved BaseModel attributes - conflicts with Pydantic internals."""
if field_name.lower() in RESERVED_NAMES:
return f"{field_name}_field"
return field_name
# Apply all sanitization steps in order
sanitized = _handle_empty_field_name(field_name)
sanitized = _handle_leading_underscores(sanitized)
sanitized = _handle_dunder_names(sanitized)
sanitized = _handle_python_keywords(sanitized)
sanitized = _handle_reserved_basemodel_attributes(sanitized)
return sanitized
def _validate_json_schema_item(schema: dict[str, Any], strict: bool = True) -> None:
"""Validate that the schema is a valid JSON Schema object."""
if not isinstance(schema, dict):
raise ValueError("Schema must be a dictionary representing JSON Schema")
if schema == {}:
return
# Handle combinators first
if "oneOf" in schema:
if not isinstance(schema["oneOf"], list):
raise ValueError("'oneOf' must be an array of schemas")
for sub_schema in schema["oneOf"]:
_validate_json_schema_item(sub_schema, strict=False)
return # oneOf schemas don't need type field
if "anyOf" in schema:
if not isinstance(schema["anyOf"], list):
raise ValueError("'anyOf' must be an array of schemas")
for sub_schema in schema["anyOf"]:
_validate_json_schema_item(sub_schema, strict=False)
return # anyOf schemas don't need type fieldView on GitHub (pinned to 4a030776a3)
Solutions
- Treat as success if the skill itself is gone — the version is gone with it.
- Refresh the version list after any skill-level delete and drop stale rows.
- Confirm collection matches before issuing the delete.
Example fix
// before
await skillsApi.deleteVersion(id, token, col);
// after
try { await skillsApi.deleteVersion(id, token, col); }
catch (e) {
const skill = await skillsApi.get(id, col).catch(() => null);
if (e.status !== 404 || skill) throw e; // 404 + no skill = nothing to do
} Defensive patterns
Strategy: try-catch
Validate before calling
const skill = await skillsApi.get(id, collection).catch(() => null);
if (!skill) return { deleted: true, viaSkillDeletion: true }; // nothing left Try / catch
try { await skillsApi.deleteVersion(id, token, collection); }
catch (e) {
const skill = await skillsApi.get(id, collection).catch(() => null);
if (e.status === 404 && !skill) return; // skill gone -> version gone
throw e;
} Prevention
- Drop version rows from UI state when the parent skill is deleted
- Make version delete idempotent like skill delete
- Serialize skill-level and version-level deletes
When it happens
Trigger: Deleting a version of a skill that was deleted (whole-skill deletion removes versions); wrong collection parameter; duplicate delete.
Common situations: Deleting the entire skill in one session while another session deletes an individual version; stale version list after skill removal.
Related errors
- Invalid message order: expected {expected_roles} after {prev
- Unknown message role {self.role}. Expected 'system', 'user',
- Audio size {audio_size} exceeds maximum allowed size of {set
- Invalid tool specification: {tool}
- Invalid system item: {item}
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/8e9f747ba21de8b5.
Report an issue: GitHub.