{"record":{"id":"5b3906d300b7053f","repo":"zylon-ai/private-gpt","slug":"object-schemas-must-define-properties","errorCode":null,"errorMessage":"Object schemas must define 'properties'","messagePattern":"Object schemas must define 'properties'","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/chat/schema_models.py","lineNumber":151,"sourceCode":"    \"\"\"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\"):\n        if composition_key in schema:\n            for sub_schema in schema[composition_key]:\n                _validate_json_schema_item(sub_schema, strict=False)\n            return\n\n    if \"type\" not in schema:\n        raise ValueError(\"Schema must define a 'type' field\")\n\n    if schema[\"type\"] == \"object\":\n        if \"properties\" not in schema:\n            raise ValueError(\"Object schemas must define 'properties'\")\n        for _, prop_schema in schema.get(\"properties\", {}).items():\n            _validate_json_schema_item(prop_schema, strict=False)\n    elif schema[\"type\"] == \"array\":\n        _validate_json_schema_item(schema.get(\"items\", {}))\n    else:\n        pass\n\n\ndef _resolve_field_name_collisions(field_name: str, used_names: set[str]) -> str:\n    \"\"\"Resolve field name collisions by appending counter.\"\"\"\n    if field_name not in used_names:\n        return field_name\n\n    counter = 1\n    while f\"{field_name}_{counter}\" in used_names:\n        counter += 1\n\n    return f\"{field_name}_{counter}\"","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/schema_models.py#L133-L169","documentation":"Raised by the top-level _validate_json_schema when the root schema declares \"type\": \"object\" but contains no \"properties\" key. The dynamic-model builder needs at least a properties map (even empty) to construct fields for the Pydantic model. Only the root object schema requires properties; nested object property schemas are validated non-strictly and may omit them.","triggerScenarios":"Passing {\"type\": \"object\"} alone, or {\"type\": \"object\", \"additionalProperties\": {...}} without a \"properties\" key, as the root schema to create_model_from_json_schema.","commonSituations":"Map/dict-style output schemas where the author expects additionalProperties to define values; minimal placeholder schemas; schemas stripped during serialization (exclude_none dropping an empty properties dict).","solutions":["Add \"properties\": {} (or the real field definitions) to the root object schema.","If the model should capture arbitrary keys, still declare \"properties\" and document that only listed fields become model fields.","Validate with this helper (or jsonschema) client-side before calling the chat API."],"exampleFix":"// before\n{\"type\": \"object\"}\n\n// after\n{\"type\": \"object\", \"properties\": {\"city\": {\"type\": \"string\"}}}","handlingStrategy":"validation","validationCode":"if schema.get(\"type\") == \"object\" and \"properties\" not in schema:\n    schema.setdefault(\"properties\", {})","typeGuard":"def object_schema_has_properties(s: dict) -> bool:\n    return s.get(\"type\") != \"object\" or \"properties\" in s","tryCatchPattern":"try:\n    create_model_from_json_schema(schema)\nexcept ValueError as e:\n    if \"properties\" in str(e):\n        schema.setdefault(\"properties\", {})\n        model = create_model_from_json_schema(schema)\n    else:\n        raise","preventionTips":["Always emit properties (possibly {}) in object schemas.","Watch for exclude_none serialization dropping empty properties dicts.","Validate root object schemas client-side."],"tags":["json-schema","validation","structured-output","pydantic"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}