datawhalechina/hello-agents · error · ValueError

模型响应中没有完整的 JSON 对象

Error message

模型响应中没有完整的 JSON 对象

What it means

A ValueError raised by extract_json_object() when the model response contains no balanced-looking JSON object: it locates the first '{' and the last '}' and slices between them, raising if either is absent or the braces are inverted (end < start). It is the cheap pre-check before json.loads; even when it passes, json.loads can still raise JSONDecodeError on malformed interiors — and the caller treats any ValueError (both this and decode errors) as 'unparseable response'.

Solutions

  1. Ensure prompts include the schema and an explicit 'respond with JSON only' instruction (as run_structured in the same notebook does).
  2. Increase max_tokens / request a compact schema so the answer is not truncated before the closing brace.
  3. If extraction must be lenient, strip markdown fences first and, on failure, retry with a repair prompt — the notebook's run_structured implements exactly this budgeted repair loop.
  4. For robustness, use the provider's structured-output / JSON mode instead of substring slicing when available.

Example fix

# before
start = text.find("{"); end = text.rfind("}")
if start == -1 or end == -1 or end < start:
    raise ValueError("模型响应中没有完整的 JSON 对象")

# after
clean = text.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip()
start = clean.find("{"); end = clean.rfind("}")
if start == -1 or end == -1 or end < start:
    raise ValueError("模型响应中没有完整的 JSON 对象")
return json.loads(clean[start:end + 1])
Defensive patterns

Strategy: retry

Validate before calling

def looks_like_json_object(text: str) -> bool:
    return text.find("{") != -1 and text.rfind("}") != -1 and text.rfind("}") > text.find("{")

Type guard

null

Try / catch

try:
    data = parse_model_response(raw, Model)
except ValueError as err:
    if budget.remaining:
        data = parse_model_response(repair(raw, err), Model)
    else:
        raise

Prevention

When it happens

Trigger: An LLM answer with no braces at all (plain prose, markdown table, or the model apologizing/refusing); a response truncated by max_tokens cutting off before the closing '}' so rfind returns -1; a fenced answer like "no JSON needed"; or stray '}' before any '{' making end < start. It also passes through schema-invalid JSON, which then fails in Pydantic validation one layer up.

Common situations: Forgetting to include the JSON Schema in the prompt so the model answers freely; max_tokens too small for the schema-heavy answer; models that wrap JSON in prose with unbalanced braces; refusal/safety messages instead of data; temperature too high producing creative formats.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/0b88d82575d88f30. Report an issue: GitHub.

Appendix: source

Thrown at Co-creation-projects/Henry2513-MeetingActionAgent/main.ipynb:147

   "source": [
    "## 2. 解析 Agent 返回的 JSON\n",
    "\n",
    "模型有时会把 JSON 包在 Markdown 代码围栏中,或者在前后添加一句解释。下面的函数先提取最外层 JSON 对象,再交给 Pydantic 校验。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb68843b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 从模型响应中截取并解析 JSON 对象。\n",
    "def extract_json_object(text: str) -> dict:\n",
    "    start = text.find(\"{\")\n",
    "    end = text.rfind(\"}\")\n",
    "    if start == -1 or end == -1 or end < start:\n",
    "        raise ValueError(\"模型响应中没有完整的 JSON 对象\")\n",
    "    return json.loads(text[start : end + 1])\n",
    "\n",
    "\n",
    "# 将模型响应解析并验证为指定的 Pydantic 模型。\n",
    "def parse_model_response(text: str, model_type: type[BaseModel]) -> BaseModel:\n",
    "    return model_type.model_validate(extract_json_object(text))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad5a8562",
   "metadata": {},
   "source": [
    "## 3. 把结构化结果转换为 Markdown\n",
    "\n",
    "Markdown 由普通 Python 生成,避免让模型重复改写已经审核过的内容。\n"
   ]
  },

View on GitHub (pinned to 606a07d341)