{"record":{"id":"6c8e6e9a3e7a64f5","repo":"iflytek/astron-agent","slug":"question-answer-handler-response-error","errorCode":"QUESTION_ANSWER_HANDLER_RESPONSE_ERROR","errorMessage":"Parse result: {model_res} is abnormal","messagePattern":"Parse result: (.+?) is abnormal","errorType":"error_code","errorClass":"CustomException","httpStatus":null,"severity":"error","filePath":"core/workflow/engine/nodes/question_answer/question_answer_node.py","lineNumber":562,"sourceCode":"                event_log_node_trace=event_log_node_trace,\n            )\n            self.calculate_usage_token(token_usage)\n            await span_context.add_info_events_async(\n                {\"token_usage\": json.dumps(self.token_usage, ensure_ascii=False)}\n            )\n            # 5. Extract JSON block\n            json_match = re.search(r\"```json\\s*\\n?(.*?)\\n?```\", response, re.DOTALL)\n            json_str = json_match.group(1).strip() if json_match else response.strip()\n            await span_context.add_info_events_async(\n                {\"llm_result\": response, \"json_str\": json_str}\n            )\n\n            # 6. Safely parse JSON\n            try:\n                model_res = json.loads(json_str)\n                if not isinstance(model_res, dict):\n                    err_msg = f\"Parse result: {model_res} is abnormal\"\n                    raise CustomException(\n                        err_code=CodeEnum.QUESTION_ANSWER_HANDLER_RESPONSE_ERROR,\n                        err_msg=err_msg,\n                        cause_error=err_msg,\n                    )\n\n                # Record parsed content\n                await span_context.add_info_events_async(\n                    {\n                        \"extracted_params\": json.dumps(model_res, ensure_ascii=False),\n                        \"token_usage\": str(token_usage),\n                    }\n                )\n\n                # 7. Build return object\n                prompt_result = PromptResult(\n                    role=model_res.get(\"role\", \"assistant\"),\n                    content=model_res.get(\"content\", \"\"),\n                    complete_data=model_res.get(\"completed\", {}),","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/workflow/engine/nodes/question_answer/question_answer_node.py#L544-L580","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Validate/normalize the LLM output before parsing: if loads() yields a list, wrap or map it into the expected dict shape.","Enable/adjust JSON mode or a structured-output constraint on the model call so the top level is guaranteed to be an object.","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)."],"exampleFix":"# before\nmodel_res = json.loads(json_str)\nif not isinstance(model_res, dict):\n    raise CustomException(...)\n\n# after\nmodel_res = json.loads(json_str)\nif isinstance(model_res, list) and len(model_res) == 1:\n    model_res = model_res[0]\nif not isinstance(model_res, dict):\n    raise CustomException(...)","handlingStrategy":"type-guard","validationCode":"def parsed_is_dict(raw: str) -> bool:\n    try:\n        return isinstance(json.loads(raw), dict)\n    except Exception:\n        return False","typeGuard":"def is_dict(obj) -> bool:\n    return isinstance(obj, dict)","tryCatchPattern":"try:\n    result = await run_question_answer_node(...)\nexcept CustomException as e:\n    if e.err_code == CodeEnum.QUESTION_ANSWER_HANDLER_RESPONSE_ERROR:\n        logger.warning(f\"non-dict model output, retrying: {e.err_msg}\")\n        result = await retry_with_stricter_prompt(...)\n    else:\n        raise","preventionTips":["Include an exact JSON-object example in the extraction prompt.","Prefer the model's native JSON/structured-output mode.","Keep temperature low for extraction tasks.","Always assert the parsed type immediately after json.loads."],"tags":["llm","json","question-answer","response-shape"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}