{"record":{"id":"6c94b32bdcad4efa","repo":"affaan-m/ECC","slug":"no-json-object-found-in-response-text-200-r-distill","errorCode":null,"errorMessage":"no JSON object found in response: {text[:200]!r}","messagePattern":"no JSON object found in response: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/distill.py","lineNumber":112,"sourceCode":"\n    for chunk in candidates:\n        chunk = chunk.strip()\n        try:\n            obj = json.loads(chunk)\n            if isinstance(obj, dict):\n                return obj\n        except json.JSONDecodeError:\n            pass\n        span = _balanced_object(chunk)\n        if span:\n            try:\n                obj = json.loads(span)\n                if isinstance(obj, dict):\n                    return obj\n            except json.JSONDecodeError:\n                continue\n\n    raise ValueError(f\"no JSON object found in response: {text[:200]!r}\")\n\n\ndef _balanced_object(text: str) -> str | None:\n    start = text.find(\"{\")\n    if start < 0:\n        return None\n    depth = 0\n    in_str = False\n    esc = False\n    for i in range(start, len(text)):\n        ch = text[i]\n        if in_str:\n            if esc:\n                esc = False\n            elif ch == \"\\\\\":\n                esc = True\n            elif ch == '\"':\n                in_str = False","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/distill.py#L94-L130","documentation":"extract_json scans the vision model's raw response text for a balanced JSON object and parses it with json.loads. It raises ValueError when no substring of the response parses to a dict, meaning the model returned prose, a truncated answer, a JSON array, or malformed JSON instead of an object. This is the guard ensuring downstream code only ever handles dict-shaped model output.","triggerScenarios":"extract_json is called by describe() with the LLM response text; it raises when the text contains no '{', contains unbalanced braces, contains a JSON array/scalar only, or all candidate spans fail json.loads (e.g. trailing commas, single quotes, markdown fences wrapping malformed JSON).","commonSituations":"Model returns a conversational refusal or explanation instead of JSON; model wraps JSON in ```json fences with stray text; model returns a JSON array instead of an object; response is truncated by max_tokens leaving unbalanced braces; model uses non-strict JSON (trailing commas, comments).","solutions":["Re-run describe(); LLM output is nondeterministic and a retry often yields parseable JSON (the caller already retries up to 3 attempts in describe).","Inspect the response snippet in the error message (first 200 chars) to see what the model actually returned and adjust the prompt to demand a bare JSON object.","Enable/verify response_format JSON mode or lower temperature on the vision model call so output is constrained to JSON.","Increase max_tokens so the object is not truncated mid-way, and strip markdown code fences before parsing.","If the model consistently returns arrays, adapt the prompt schema or post-process arrays into objects before calling extract_json."],"exampleFix":"// before\nobj = extract_json(raw_text)\n\n// after\nimport json, re\ndef extract_json_safe(raw_text: str) -> dict:\n    stripped = re.sub(r\"^```(?:json)?|```$\", \"\", raw_text.strip(), flags=re.M).strip()\n    try:\n        obj = json.loads(stripped)\n    except json.JSONDecodeError as exc:\n        raise ValueError(f\"model returned non-JSON output: {stripped[:200]!r}\") from exc\n    if not isinstance(obj, dict):\n        raise ValueError(f\"expected a JSON object, got {type(obj).__name__}\")\n    return obj","handlingStrategy":"try-catch","validationCode":"def looks_like_json_object(text: str) -> bool:\n    t = text.strip()\n    return t.startswith(\"{\") and t.endswith(\"}\")","typeGuard":"def is_dict(obj: object) -> TypeGuard[dict]:\n    return isinstance(obj, dict)","tryCatchPattern":"try:\n    obj = extract_json(raw)\nexcept ValueError as exc:\n    log.warning(\"non-JSON model output: %s\", exc)\n    obj = retry_with_stricter_prompt(raw)","preventionTips":["Use the model's JSON/response_format mode when available","Strip markdown code fences from LLM output before parsing","Set max_tokens high enough that the JSON object is never truncated","Log the raw response on parse failure for quick diagnosis","Prompt explicitly: 'respond with a single bare JSON object and nothing else'"],"tags":["json","llm","parsing","response-validation"],"backgroundTag":"json-parse-error","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}