{"record":{"id":"625b8ccb54b0f2eb","repo":"affaan-m/ECC","slug":"the-vision-model-never-returned-usable-json-after-3-attempts-625b8c","errorCode":null,"errorMessage":"the vision model never returned usable JSON after 3 attempts; detail: {json.dumps(attempts)}","messagePattern":"the vision model never returned usable JSON after 3 attempts; detail: (.+?)","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/distill.py","lineNumber":381,"sourceCode":"        if not hits:\n            return spec, prov\n\n        # Keep the best answer seen so far, so three hedged attempts still\n        # yield the least-hedged one rather than an exception.\n        if best is None or len(hits) < len(banned_hits(best[0])):\n            best = (spec, prov)\n        if attempt < 3:\n            log.warning(\"attempt %d hedged on %s; asking it to commit\",\n                        attempt, \", \".join(sorted(hits)))\n            prompt = _rewrite_prompt(base, hits, spec)\n            continue\n        log.warning(\"still hedging on %s after 3 attempts; keeping best\",\n                    \", \".join(sorted(banned_hits(best[0]))))\n        return best\n\n    if best is not None:\n        return best\n    raise SystemExit(\n        \"the vision model never returned usable JSON after 3 attempts; \"\n        f\"detail: {json.dumps(attempts)}\"\n    )\n\n\ndef mint_prop(sp: pack_mod.StylePack, stills: list[Path]) -> dict | None:\n    \"\"\"Turn the highest-detail still into a GLB and store it in the pack.\"\"\"\n    scored = sorted(((detail_score(p), p) for p in stills), key=lambda t: -t[0])\n    if not scored:\n        return None\n    score, hero = scored[0]\n    print(f\"  prop source    : {hero.name} (detail {score:.1f})\")\n\n    url = falapi.upload(hero)\n    mesh_url = falapi.image_to_3d(url)\n    dest = sp.props_dir / f\"{hero.stem}.glb\"\n    falapi.download(mesh_url, dest)\n    return {","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/distill.py#L363-L399","documentation":"describe() retries the vision-model call up to 3 times, collecting per-attempt failure details in `attempts`. If every attempt failed and no best candidate exists, it raises SystemExit with the JSON-encoded attempt details, telling the operator the vision model never produced usable JSON and why each attempt failed.","triggerScenarios":"describe(genre/paths) calls the vision LLM 3 times; all attempts raise (e.g. extract_json ValueError, empty response, API errors) or return non-dict output, leaving best=None, so the final raise fires after the retry loop.","commonSituations":"Invalid or missing FAL API key causing every call to fail; persistent network outage; model endpoint renamed/unavailable; prompt so malformed the model never emits JSON; rate limiting across all retries.","solutions":["Read the `detail` JSON in the message: it lists each attempt's failure reason; fix the root cause it reports (auth error, HTTP status, parse error).","Verify FAL_KEY is set and valid, or confirm dry-run mode is intentional (falapi.is_dry_run()).","Test the vision endpoint/model name directly with a single request to confirm the model is available.","Improve the prompt to require a bare JSON object, and increase max_tokens to avoid truncation.","If failures are transient (rate limits, network), add backoff between the 3 attempts or raise the retry count."],"exampleFix":"null","handlingStrategy":"fallback","validationCode":"def can_call_vision() -> bool:\n    return bool(os.environ.get(\"FAL_KEY\")) or falapi.is_dry_run()","typeGuard":"null","tryCatchPattern":"try:\n    result = describe(...)\nexcept SystemExit as exc:\n    log.error(\"vision pipeline failed after retries: %s\", exc)\n    result = default_description  # cached or placeholder","preventionTips":["Validate FAL_KEY presence before starting a long distill run","Check the attempts detail JSON on failure to fix the root cause, not just retry","Add exponential backoff between attempts for rate-limit errors","Monitor the vision endpoint's availability before batch runs","Keep prompt instructions strict about emitting bare JSON"],"tags":["llm","retry","api","vision"],"backgroundTag":"api-request-failed","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"}