iflytek/astron-agent · error · CustomException

QUESTION_ANSWER_HANDLER_RESPONSE_ERROR

QUESTION_ANSWER_HANDLER_RESPONSE_ERROR

Error message

Parse result: {model_res} is abnormal

What it means

In the prompt-template mode of the question-answer node, the LLM response is expected to be a JSON object (dict) after `json.loads`. If parsing succeeds but yields a non-dict (e.g. a JSON list, string, or number), the node raises QUESTION_ANSWER_HANDLER_RESPONSE_ERROR stating the parsed result is abnormal.

Solutions

  1. Strengthen the prompt template to explicitly demand a single JSON object, e.g. 'Respond with exactly one JSON object of the form {...}', and include a concrete example.
  2. Validate/normalize the LLM output before parsing: if loads() yields a list, wrap or map it into the expected dict shape.
  3. Enable/adjust JSON mode or a structured-output constraint on the model call so the top level is guaranteed to be an object.
  4. Add retries: catch this error in handle_prompt_template_response's retry loop and re-prompt the model (the node already supports max_retries for slot extraction).

Example fix

# before
model_res = json.loads(json_str)
if not isinstance(model_res, dict):
    raise CustomException(...)

# after
model_res = json.loads(json_str)
if isinstance(model_res, list) and len(model_res) == 1:
    model_res = model_res[0]
if not isinstance(model_res, dict):
    raise CustomException(...)
Defensive patterns

Strategy: type-guard

Validate before calling

def parsed_is_dict(raw: str) -> bool:
    try:
        return isinstance(json.loads(raw), dict)
    except Exception:
        return False

Type guard

def is_dict(obj) -> bool:
    return isinstance(obj, dict)

Try / catch

try:
    result = await run_question_answer_node(...)
except CustomException as e:
    if e.err_code == CodeEnum.QUESTION_ANSWER_HANDLER_RESPONSE_ERROR:
        logger.warning(f"non-dict model output, retrying: {e.err_msg}")
        result = await retry_with_stricter_prompt(...)
    else:
        raise

Prevention

When it happens

Trigger: async_execute_prompt -> json.loads(json_str) returns a top-level non-dict value: the LLM produced a JSON array (e.g. `["A","B"]`), a bare string, number, or `null` instead of the expected `{...}` option/parameter object.

Common situations: Prompt template not strict enough about output shape so the model emits a JSON array of options; model wrapped output so the extracted json_str is just a quoted scalar; temperature/top_p changes make output shape unstable across model versions; switching to a model that ignores the JSON-object instruction.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/6c8e6e9a3e7a64f5. Report an issue: GitHub.

Appendix: source

Thrown at core/workflow/engine/nodes/question_answer/question_answer_node.py:562

                event_log_node_trace=event_log_node_trace,
            )
            self.calculate_usage_token(token_usage)
            await span_context.add_info_events_async(
                {"token_usage": json.dumps(self.token_usage, ensure_ascii=False)}
            )
            # 5. Extract JSON block
            json_match = re.search(r"```json\s*\n?(.*?)\n?```", response, re.DOTALL)
            json_str = json_match.group(1).strip() if json_match else response.strip()
            await span_context.add_info_events_async(
                {"llm_result": response, "json_str": json_str}
            )

            # 6. Safely parse JSON
            try:
                model_res = json.loads(json_str)
                if not isinstance(model_res, dict):
                    err_msg = f"Parse result: {model_res} is abnormal"
                    raise CustomException(
                        err_code=CodeEnum.QUESTION_ANSWER_HANDLER_RESPONSE_ERROR,
                        err_msg=err_msg,
                        cause_error=err_msg,
                    )

                # Record parsed content
                await span_context.add_info_events_async(
                    {
                        "extracted_params": json.dumps(model_res, ensure_ascii=False),
                        "token_usage": str(token_usage),
                    }
                )

                # 7. Build return object
                prompt_result = PromptResult(
                    role=model_res.get("role", "assistant"),
                    content=model_res.get("content", ""),
                    complete_data=model_res.get("completed", {}),

View on GitHub (pinned to 5e758547a8)