affaan-m/ECC · error · SystemExit

the vision model never returned usable JSON after 3…

Error message

the vision model never returned usable JSON after 3 attempts; detail: {json.dumps(attempts)}

What it means

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.

Solutions

  1. 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).
  2. Verify FAL_KEY is set and valid, or confirm dry-run mode is intentional (falapi.is_dry_run()).
  3. Test the vision endpoint/model name directly with a single request to confirm the model is available.
  4. Improve the prompt to require a bare JSON object, and increase max_tokens to avoid truncation.
  5. If failures are transient (rate limits, network), add backoff between the 3 attempts or raise the retry count.

Example fix

null
Defensive patterns

Strategy: fallback

Validate before calling

def can_call_vision() -> bool:
    return bool(os.environ.get("FAL_KEY")) or falapi.is_dry_run()

Type guard

null

Try / catch

try:
    result = describe(...)
except SystemExit as exc:
    log.error("vision pipeline failed after retries: %s", exc)
    result = default_description  # cached or placeholder

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/625b8ccb54b0f2eb. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-distillation/scripts/distill.py:381

        if not hits:
            return spec, prov

        # Keep the best answer seen so far, so three hedged attempts still
        # yield the least-hedged one rather than an exception.
        if best is None or len(hits) < len(banned_hits(best[0])):
            best = (spec, prov)
        if attempt < 3:
            log.warning("attempt %d hedged on %s; asking it to commit",
                        attempt, ", ".join(sorted(hits)))
            prompt = _rewrite_prompt(base, hits, spec)
            continue
        log.warning("still hedging on %s after 3 attempts; keeping best",
                    ", ".join(sorted(banned_hits(best[0]))))
        return best

    if best is not None:
        return best
    raise SystemExit(
        "the vision model never returned usable JSON after 3 attempts; "
        f"detail: {json.dumps(attempts)}"
    )


def mint_prop(sp: pack_mod.StylePack, stills: list[Path]) -> dict | None:
    """Turn the highest-detail still into a GLB and store it in the pack."""
    scored = sorted(((detail_score(p), p) for p in stills), key=lambda t: -t[0])
    if not scored:
        return None
    score, hero = scored[0]
    print(f"  prop source    : {hero.name} (detail {score:.1f})")

    url = falapi.upload(hero)
    mesh_url = falapi.image_to_3d(url)
    dest = sp.props_dir / f"{hero.stem}.glb"
    falapi.download(mesh_url, dest)
    return {

View on GitHub (pinned to 8321021c54)