{"record":{"id":"d000758cf33f221b","repo":"affaan-m/ECC","slug":"empty-response-distill","errorCode":null,"errorMessage":"empty response","messagePattern":"empty response","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/distill.py","lineNumber":88,"sourceCode":"    \"'}'. Do not wrap it in a code fence. Do not add commentary. Every key \"\n    \"listed must be present; use a short string (or list of strings) for each.\"\n)\n\n\n# ---------------------------------------------------------------------------\n# JSON extraction / repair\n# ---------------------------------------------------------------------------\n\n\ndef extract_json(text: str) -> dict:\n    \"\"\"Pull a JSON object out of a model reply.\n\n    Models wrap JSON in code fences and preambles even when told not to, so a\n    bare ``json.loads`` fails on output that is otherwise perfectly good.\n    Fenced content is tried first, then the outermost balanced ``{...}``.\n    \"\"\"\n    if not text or not text.strip():\n        raise ValueError(\"empty response\")\n\n    candidates: list[str] = []\n    for m in re.finditer(r\"```(?:json)?\\s*(.+?)```\", text, re.DOTALL | re.IGNORECASE):\n        candidates.append(m.group(1))\n    candidates.append(text)\n\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)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/distill.py#L70-L106","documentation":"extract_json in the distillation script parses model output into JSON. It raises ValueError('empty response') immediately when the model returned an empty or whitespace-only string, before attempting fence or brace extraction.","triggerScenarios":"The LLM call returned an empty completion (empty string or only whitespace/newlines), which is passed to extract_json from describe().","commonSituations":"Provider returning empty completions due to content filters, max_tokens=0 or exhausted quota, network truncation, or a misconfigured prompt yielding no output; retrying against an unreachable endpoint that returns empty bodies.","solutions":["Retry the model call; if the provider is intermittently returning empty completions, add a retry with backoff before parsing","Check the completion's finish_reason / stop reason and token limits (max_tokens) that may have suppressed output","Log the raw response (length, finish reason) to distinguish filtering, truncation, and auth/quota issues","Guard the call site: only call extract_json after verifying the response text is non-empty"],"exampleFix":"// before\ntext = client.complete(prompt)\nresult = extract_json(text)\n// after\ntext = client.complete(prompt)\nif not text or not text.strip():\n    raise RuntimeError(f'model returned empty completion (finish_reason={text.finish_reason})')\nresult = extract_json(text)","handlingStrategy":"retry","validationCode":"if not completion or not completion.text.strip():\n    raise RuntimeError('model returned empty completion; skipping parse')","typeGuard":"def has_text(completion):\n    return bool(completion and completion.text and completion.text.strip())","tryCatchPattern":"for attempt in range(3):\n    text = client.complete(prompt)\n    if has_text(text):\n        break\n    time.sleep(2 ** attempt)\nelse:\n    raise RuntimeError('empty completion after 3 attempts')\nresult = extract_json(text)","preventionTips":["Set a sane max_tokens and check finish_reason on every completion","Log response length and provider stop reasons for every LLM call","Add a non-empty check before any JSON parsing of model output","Retry with backoff on empty completions before failing the pipeline"],"tags":["llm","json-parsing","empty-input"],"backgroundTag":"empty-api-response","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"}