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

  1. Adjust the prompt/schema to require an object root: {'items': [...]} instead of a bare array
  2. Update few-shot examples to show object roots only
  3. If a list output is actually desired, wrap it client-side: parse {'items': [...]} and use value['items']
  4. 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

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


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/1ce85da8f2abe35e. Report an issue: GitHub.