{"record":{"id":"2c90cb34bae28433","repo":"Graphify-Labs/graphify","slug":"label-response-is-not-parseable-json-text-120","errorCode":null,"errorMessage":"label response is not parseable JSON: {text[:120]!r}","messagePattern":"label response is not parseable JSON: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"graphify/llm.py","lineNumber":2935,"sourceCode":"    data: dict | None = None\n    try:\n        parsed = json.loads(cleaned)\n        if isinstance(parsed, dict):\n            data = parsed\n    except (json.JSONDecodeError, ValueError):\n        data = None\n    if data is None:\n        # Salvage: pull the complete \"<cid>\": \"<name>\" pairs directly. A model\n        # can truncate its reply mid-object (a stingy token budget or a preamble\n        # eating the completion), which used to hard-fail the whole batch with\n        # e.g. `Expecting value: line 1 column 6` on a `{\"0\":` fragment (#1690).\n        # Recovering the pairs that DID arrive labels those communities instead\n        # of dropping the entire batch to placeholders.\n        pairs = re.findall(r'\"?(-?\\d+)\"?\\s*:\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"', cleaned)\n        if pairs:\n            data = {k: v for k, v in pairs}\n        else:\n            raise ValueError(f\"label response is not parseable JSON: {text[:120]!r}\")\n    out: dict[int, str] = {}\n    for cid in labeled_cids:\n        name = data.get(str(cid))\n        if name is None:\n            name = data.get(cid)\n        if isinstance(name, str) and name.strip():\n            out[cid] = name.strip()\n    return out\n\n\ndef _label_batch_with_retry(\n    batch_cids: list[int],\n    batch_lines: list[str],\n    *,\n    backend: str,\n    model: str | None,\n    depth: int = 0,\n    max_depth: int = 3,","sourceCodeStart":2917,"sourceCodeEnd":2953,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/llm.py#L2917-L2953","documentation":"ValueError raised when a community-label batch response from the LLM is neither parseable JSON nor salvageable by the regex fallback. The parser first tries full JSON; on failure it recovers complete quoted cid/name pairs (issue #1690: truncated replies mid-object should not kill the whole batch); only when even that finds no pairs does it raise, echoing the first 120 chars of the raw text.","triggerScenarios":"Parsing a label response where json.loads fails AND the pair-regex finds no complete '<cid>': '<name>' structures at all (llm.py:2928-2935) - the reply is pure prose, a fully truncated fragment like '{\"0\":', or HTML/error text from a gateway.","commonSituations":"Reasoning/thinking models answering with prose instead of JSON; tiny max_tokens budgets truncating before the first pair; a misrouted base_url returning an HTML error page; prompts mutated by content filters so the model refuses instead of labeling.","solutions":["Retry the batch - truncation-based failures are often transient; _label_batch_with_retry exists for this.","Increase the LLM max-token budget for labeling so the JSON object fits.","If the echoed text is prose/refusal, tighten the labeling prompt or switch model/backend.","If the echoed text looks like HTML, fix base_url - you are not talking to the model you think."],"exampleFix":"# before\nlabels = label_batch(cids)   # ValueError: label response is not parseable JSON\n\n# after - retry via the library's retry wrapper and a bigger budget\nlabels = _label_batch_with_retry(cids, max_tokens=2048)\n# fallback: accept placeholders for unlabeled communities","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    labels = label_batch(cids)\nexcept ValueError as exc:\n    if \"not parseable JSON\" in str(exc):\n        labels = {cid: f\"community-{cid}\" for cid in cids}  # placeholder fallback\n        log.warning(\"LLM labels unparseable; using placeholders: %s\", exc)\n    else:\n        raise","preventionTips":["Budget max_tokens generously for batch label responses (size the batch times expected name length).","Inspect the echoed 120-char prefix - it distinguishes truncation from refusal from HTML.","Keep batches small so one bad reply costs little and the salvage path (partial pairs) has something to save."],"tags":["llm","json","parsing","labeling"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}