666ghj/MiroFish · error · LLMResponseError
LLM JSON response must be a top-level JSON object
Error message
LLM JSON response must be a top-level JSON object
What it means
LLMResponseError raised after successful parsing when the decoded value is not a dict — e.g. the model returned a JSON array, string, number, or null. The client's contract is a top-level JSON object (it returns Dict[str, Any]), so non-object roots are rejected even though they are valid JSON.
Source
Thrown at backend/app/utils/llm_client.py:285
value, end = json.JSONDecoder().raw_decode(content)
except json.JSONDecodeError:
raise LLMResponseError(
"LLM returned invalid JSON "
f"(line {strict_error.lineno}, column {strict_error.colno})",
finish_reason=finish_reason,
) from strict_error
trailing = content[end:].strip()
if trailing:
if _contains_additional_json_container(trailing):
raise LLMResponseError(
"LLM returned multiple JSON values",
finish_reason=finish_reason,
)
logger.warning("Ignoring text after a complete LLM JSON object")
if not isinstance(value, dict):
raise LLMResponseError(
"LLM JSON response must be a top-level JSON object",
finish_reason=finish_reason,
)
return value
View on GitHub (pinned to b5b53acc57)
Solutions
- Adjust the prompt/schema to require an object root: {'items': [...]} instead of a bare array
- Update few-shot examples to show object roots only
- If a list output is actually desired, wrap it client-side: parse {'items': [...]} and use value['items']
- Use response_format json_object plus an explicit schema description of the object shape
Example fix
# before
prompt = "Return a JSON list of sub-questions."
# after
prompt = "Return a JSON object like {\"sub_questions\": [\"...\", \"...\"]}." Defensive patterns
Strategy: type-guard
Type guard
def is_json_object_response(content: str) -> bool:
try:
return isinstance(json.loads(content), dict)
except json.JSONDecodeError:
return False Try / catch
try:
value = LLMClient._parse_json_response(resp)
except LLMResponseError as e:
if "top-level JSON object" in str(e):
value = client.generate_json(object_root_prompt())
else:
raise Prevention
- Specify the exact object shape in the prompt, including array-valued keys
- Never prompt with 'return a list' when the client contract is a dict
- Validate model-facing schemas against the parser's dict requirement
When it happens
Trigger: Prompt/schema leads the model to answer with '[...]' (a list of items) or a bare scalar; json.loads succeeds, isinstance(value, dict) fails.
Common situations: Prompts that say 'return a list of entities' (model naturally emits a top-level array), or few-shot examples showing array roots.
Related errors
- Ontology result must be an object
- LLM JSON output was truncated at the token limit
- LLM JSON generation stopped unexpectedly ({finish_reason})
- LLM returned empty JSON content
- LLM returned invalid JSON (line {strict_error.lineno}, colum
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/1ce85da8f2abe35e.
Report an issue: GitHub.