{"record":{"id":"a8c0838c9c2545b8","repo":"zylon-ai/private-gpt","slug":"invalid-tool-specification-tool","errorCode":null,"errorMessage":"Invalid tool specification: {tool}","messagePattern":"Invalid tool specification: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/chat/input_models.py","lineNumber":1153,"sourceCode":"            tool_context = tool.get(\"context\")\n            if not isinstance(tool_context, list):\n                tool_context = None\n        # Try to get the custom name\n        name: str = \"\"\n        if isinstance(tool, dict) and tool.get(\"name\"):\n            name = tool.get(\"name\") or \"\"\n\n        return ToolSpecBody(\n            name=name or internal_tool_name,\n            type=tool_type,\n            context=tool_context,\n        )\n    else:\n        # Tools provided (and executed) by the caller\n        try:\n            return ToolSpecBody.model_validate(tool)\n        except Exception as e:\n            raise ValueError(f\"Invalid tool specification: {tool}\") from e\n\n\nToolSpecOrString = Annotated[ToolSpecBody, BeforeValidator(validate_tool_spec)]\n\n\nclass CompletionMetadata(BaseModel):\n    user_id: str | None = Field(\n        default=None,\n        description=\"Opaque user identifier for request attribution.\",\n        max_length=512,\n    )\n\n    model_config = ConfigDict(extra=\"allow\")\n\n\nclass MessagesInputBase(BaseModel):\n    \"\"\"Shared Anthropic-compatible input shape for message-based endpoints.\"\"\"\n","sourceCodeStart":1135,"sourceCodeEnd":1171,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/input_models.py#L1135-L1171","documentation":"Raised by GET /skills/{skill_id}/versions when the skill itself does not exist in the collection. Versions are only addressable under an existing skill, so the skill-level pre-check fails with 404 before pagination of versions begins.","triggerScenarios":"Listing versions for a deleted skill, an id from a different collection, or a malformed id; navigating to a versions screen from a stale link.","commonSituations":"Deep links to version history of removed skills; collection context switching in the UI; bookmarks surviving a skill purge.","solutions":["Validate the skill exists (GET /skills/{id}) before navigating to its versions screen.","Clear deep links/caches when a skill 404s and route back to the skill list.","Confirm the collection query parameter matches."],"exampleFix":"// before\nrouter.push(`/skills/${id}/versions`);\n\n// after\nconst ok = await skillsApi.exists(id, collection);\nrouter.push(ok ? `/skills/${id}/versions` : '/skills');","handlingStrategy":"validation","validationCode":"const skill = await skillsApi.get(id, collection).catch(() => null);\nif (!skill) redirect('/skills'); // don't even fetch versions","typeGuard":null,"tryCatchPattern":"try { return await skillsApi.listVersions(id, collection, { limit, page }); }\ncatch (e) { if (e.status === 404) return { data: [], hasMore: false }; throw e; }","preventionTips":["Validate the parent skill before rendering version UI","Expire deep links to versions when the skill 404s","Include collection in all version URLs"],"tags":["fastapi","http-404","skills","versioning","pagination"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}