{"record":{"id":"2da24b698d9aed77","repo":"zylon-ai/private-gpt","slug":"array-schemas-must-define-items","errorCode":null,"errorMessage":"Array schemas must define 'items'","messagePattern":"Array schemas must define 'items'","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/chat/schema_models.py","lineNumber":122,"sourceCode":"            _validate_json_schema_item(sub_schema, strict=False)\n        return  # anyOf schemas don't need type field\n\n    if \"allOf\" in schema:\n        if not isinstance(schema[\"allOf\"], list):\n            raise ValueError(\"'allOf' must be an array of schemas\")\n        for sub_schema in schema[\"allOf\"]:\n            _validate_json_schema_item(sub_schema, strict=False)\n        return  # allOf schemas don't need type field\n\n    # Regular schema validation\n    if \"type\" not in schema:\n        if strict:\n            raise ValueError(\"Schema must define a 'type' field\")\n        else:\n            return  # Non-strict mode allows missing type\n\n    if schema[\"type\"] == \"array\" and \"items\" not in schema:\n        raise ValueError(\"Array schemas must define 'items'\")\n\n    if schema[\"type\"] == \"array\" and not isinstance(schema.get(\"items\"), dict):\n        raise ValueError(\"Array 'items' must be a dictionary representing JSON Schema\")\n\n    # Recursively validate array items\n    if schema[\"type\"] == \"array\":\n        _validate_json_schema_item(schema.get(\"items\", {}))\n\n\ndef _validate_json_schema(schema: dict[str, Any]) -> None:\n    \"\"\"Validate that the schema is a valid JSON Schema object.\"\"\"\n    if not isinstance(schema, dict):\n        raise ValueError(\"Schema must be a dictionary representing JSON Schema\")\n    if not schema:\n        return\n\n    # Composition keywords at the top level are valid without a \"type\" field.\n    for composition_key in (\"allOf\", \"anyOf\", \"oneOf\"):","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/schema_models.py#L104-L140","documentation":"Raised by _validate_json_schema_item when a schema declares \"type\": \"array\" but has no \"items\" key. The validator requires every array schema to describe its element schema, because create_model_from_json_schema must build a Pydantic model for the item type. It fires both at the top level and inside nested properties/items.","triggerScenarios":"Passing a schema such as {\"type\": \"array\"} or {\"type\": \"object\", \"properties\": {\"tags\": {\"type\": \"array\"}}} to create_model_from_json_schema or any structured-output API that validates schemas with this helper.","commonSituations":"Hand-written or LLM-generated tool schemas that treat arrays as self-describing; porting TypeScript types (string[]) to JSON Schema and forgetting the items mapping; schemas trimmed for brevity before sending to the chat API.","solutions":["Add an \"items\" schema to every \"type\": \"array\" node: {\"type\": \"array\", \"items\": {\"type\": \"string\"}}.","For heterogeneous arrays use {\"items\": {\"anyOf\": [...]}} instead of omitting items.","Run the schema through a JSON Schema linter (or this validator directly) before submitting the request."],"exampleFix":"// before\n{\"type\": \"object\", \"properties\": {\"tags\": {\"type\": \"array\"}}}\n\n// after\n{\"type\": \"object\", \"properties\": {\"tags\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}}","handlingStrategy":"validation","validationCode":"def ensure_array_items(node: dict) -> None:\n    if node.get(\"type\") == \"array\" and \"items\" not in node:\n        node[\"items\"] = {\"type\": \"string\"}  # or reject explicitly\n\ndef walk(schema: dict):\n    ensure_array_items(schema)\n    for key in (\"properties\",):\n        for sub in schema.get(key, {}).values():\n            walk(sub)\n    if isinstance(schema.get(\"items\"), dict):\n        walk(schema[\"items\"])","typeGuard":"def arrays_have_items(s: dict) -> bool:\n    ok = s.get(\"type\") != \"array\" or \"items\" in s\n    return ok and all(arrays_have_items(v) for v in s.get(\"properties\", {}).values()) and (not isinstance(s.get(\"items\"), dict) or arrays_have_items(s[\"items\"]))","tryCatchPattern":"try:\n    create_model_from_json_schema(schema)\nexcept ValueError as e:\n    if \"items\" in str(e):\n        raise HTTPException(400, \"Every array schema must define 'items'\") from e\n    raise","preventionTips":["Never author {\"type\": \"array\"} without items.","Lint schemas with jsonschema meta-validation before submission.","In code generation from types, always emit an items schema for arrays."],"tags":["json-schema","validation","structured-output","pydantic"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}