{"record":{"id":"ed93c845861ff977","repo":"BerriAI/litellm","slug":"litellm-jsonschemavalidationerror-model-model","errorCode":null,"errorMessage":"litellm.JSONSchemaValidationError: model={model}, returned an invalid response={raw_response}, for schema={schema}.\\nAccess raw response with `e.raw_response`","messagePattern":"litellm\\.JSONSchemaValidationError: model=(.+?), returned an invalid response=(.+?), for schema=(.+?)\\.\\\\nAccess raw response with `e\\.raw_response`","errorType":"exception","errorClass":"JSONSchemaValidationError","httpStatus":500,"severity":"error","filePath":"litellm/litellm_core_utils/json_validation_rule.py","lineNumber":114,"sourceCode":"    return normalized_tool\n\n\ndef validate_schema(schema: dict, response: str):\n    \"\"\"\n    Validate if the returned json response follows the schema.\n\n    Params:\n    - schema - dict: JSON schema\n    - response - str: Received json response as string.\n    \"\"\"\n    from jsonschema import ValidationError, validate\n\n    from litellm import JSONSchemaValidationError\n\n    try:\n        response_dict: Final = json.loads(response)\n    except json.JSONDecodeError:\n        raise JSONSchemaValidationError(model=\"\", llm_provider=\"\", raw_response=response, schema=json.dumps(schema))\n\n    try:\n        validate(response_dict, schema=schema)\n    except ValidationError:\n        raise JSONSchemaValidationError(model=\"\", llm_provider=\"\", raw_response=response, schema=json.dumps(schema))\n","sourceCodeStart":96,"sourceCodeEnd":120,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/json_validation_rule.py#L96-L120","documentation":"Raised inside LiteLLM's JSON-validation rule when the model's response cannot even be parsed as JSON (json.JSONDecodeError) while validating against a response_format JSON schema. LiteLLM wraps this into JSONSchemaValidationError with model='', the raw response and schema attached, so callers can inspect e.raw_response. It means the LLM returned non-JSON output (prose, markdown fences, or an empty/Partial response) for a request expecting structured JSON.","triggerScenarios":"Calling completion with response_format={'type':'json_schema', ...} (or the json_validation enforce rule) where the model's reply is not parseable by json.loads — e.g. '```json\\n{...}\\n```' with fences, truncated output hitting max_tokens, or a model that ignores the schema.","commonSituations":"Using weaker models that wrap JSON in markdown; low max_tokens cutting off the JSON; streaming responses validated before completion; schemas without strict mode on providers that don't enforce it natively.","solutions":["Catch JSONSchemaValidationError and retry the request (optionally with the error appended as corrective feedback) — transient non-JSON replies are common","Use a model/provider combination with native structured-output support (OpenAI json_schema strict mode) instead of client-side validation only","Strip markdown fences / re-parse e.raw_response yourself before giving up","Increase max_tokens so the JSON is not truncated, and simplify the schema"],"exampleFix":"# before\nresp = litellm.completion(model='gpt-4o', messages=msgs,\n    response_format={'type':'json_schema','json_schema':{'name':'out','schema':S,'strict':True}})\n\n# after: retry once on invalid JSON\nfrom litellm import JSONSchemaValidationError\ntry:\n    resp = litellm.completion(...)\nexcept JSONSchemaValidationError as e:\n    raw = e.raw_response.strip().removeprefix('```json').removesuffix('```')\n    resp = litellm.completion(model='gpt-4o', messages=msgs + [\n        {'role':'assistant','content':e.raw_response},\n        {'role':'user','content':'Return ONLY valid JSON matching the schema.'}],\n        response_format={'type':'json_schema','json_schema':{'name':'out','schema':S,'strict':True}})","handlingStrategy":"retry","validationCode":"import json\n\ndef parse_model_json(raw: str) -> dict | None:\n    try:\n        return json.loads(raw)\n    except json.JSONDecodeError:\n        stripped = raw.strip().removeprefix('```json').removeprefix('```').removesuffix('```').strip()\n        try:\n            return json.loads(stripped)\n        except json.JSONDecodeError:\n            return None","typeGuard":"def is_parseable_json(raw: str) -> bool:\n    try:\n        json.loads(raw)\n        return True\n    except (json.JSONDecodeError, TypeError):\n        return False","tryCatchPattern":"from litellm import JSONSchemaValidationError\n\nfor attempt in range(2):\n    try:\n        resp = litellm.completion(model=m, messages=msgs, response_format=rf)\n        break\n    except JSONSchemaValidationError as e:\n        if attempt == 1:\n            raise\n        msgs = msgs + [{'role': 'user', 'content': 'Your previous reply was not valid JSON. Return ONLY valid JSON.'}]","preventionTips":["Prefer providers with native strict json_schema enforcement","Set max_tokens generously to avoid truncated JSON","Always catch JSONSchemaValidationError and use e.raw_response rather than re-calling blindly"],"tags":["litellm","json-schema","response-format","validation","structured-output"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}