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'.

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)

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.