{"record":{"id":"0b88d82575d88f30","repo":"datawhalechina/hello-agents","slug":"json-0b88d8","errorCode":null,"errorMessage":"模型响应中没有完整的 JSON 对象","messagePattern":"模型响应中没有完整的 JSON 对象","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/Henry2513-MeetingActionAgent/main.ipynb","lineNumber":147,"sourceCode":"   \"source\": [\n    \"## 2. 解析 Agent 返回的 JSON\\n\",\n    \"\\n\",\n    \"模型有时会把 JSON 包在 Markdown 代码围栏中，或者在前后添加一句解释。下面的函数先提取最外层 JSON 对象，再交给 Pydantic 校验。\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"id\": \"fb68843b\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# 从模型响应中截取并解析 JSON 对象。\\n\",\n    \"def extract_json_object(text: str) -> dict:\\n\",\n    \"    start = text.find(\\\"{\\\")\\n\",\n    \"    end = text.rfind(\\\"}\\\")\\n\",\n    \"    if start == -1 or end == -1 or end < start:\\n\",\n    \"        raise ValueError(\\\"模型响应中没有完整的 JSON 对象\\\")\\n\",\n    \"    return json.loads(text[start : end + 1])\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# 将模型响应解析并验证为指定的 Pydantic 模型。\\n\",\n    \"def parse_model_response(text: str, model_type: type[BaseModel]) -> BaseModel:\\n\",\n    \"    return model_type.model_validate(extract_json_object(text))\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"ad5a8562\",\n   \"metadata\": {},\n   \"source\": [\n    \"## 3. 把结构化结果转换为 Markdown\\n\",\n    \"\\n\",\n    \"Markdown 由普通 Python 生成，避免让模型重复改写已经审核过的内容。\\n\"\n   ]\n  },","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/Henry2513-MeetingActionAgent/main.ipynb#L129-L165","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure prompts include the schema and an explicit 'respond with JSON only' instruction (as run_structured in the same notebook does).","Increase max_tokens / request a compact schema so the answer is not truncated before the closing brace.","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.","For robustness, use the provider's structured-output / JSON mode instead of substring slicing when available."],"exampleFix":"# before\nstart = text.find(\"{\"); end = text.rfind(\"}\")\nif start == -1 or end == -1 or end < start:\n    raise ValueError(\"模型响应中没有完整的 JSON 对象\")\n\n# after\nclean = text.strip().removeprefix(\"```json\").removeprefix(\"```\").removesuffix(\"```\").strip()\nstart = clean.find(\"{\"); end = clean.rfind(\"}\")\nif start == -1 or end == -1 or end < start:\n    raise ValueError(\"模型响应中没有完整的 JSON 对象\")\nreturn json.loads(clean[start:end + 1])","handlingStrategy":"retry","validationCode":"def looks_like_json_object(text: str) -> bool:\n    return text.find(\"{\") != -1 and text.rfind(\"}\") != -1 and text.rfind(\"}\") > text.find(\"{\")","typeGuard":"null","tryCatchPattern":"try:\n    data = parse_model_response(raw, Model)\nexcept ValueError as err:\n    if budget.remaining:\n        data = parse_model_response(repair(raw, err), Model)\n    else:\n        raise","preventionTips":["Always include the target JSON Schema in the prompt and demand raw JSON only.","Strip markdown fences before slicing.","Set max_tokens large enough to avoid truncation before the closing brace.","Use provider JSON/structured-output mode when available instead of substring extraction."],"tags":["json","llm-output","parsing","valueerror"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}