{"record":{"id":"c07671619f09f8a9","repo":"datawhalechina/hello-agents","slug":"stage-json-c07671","errorCode":null,"errorMessage":"{stage}失败：工具结果必须是 JSON 对象","messagePattern":"(.+?)失败：工具结果必须是 JSON 对象","errorType":"exception","errorClass":"WorkflowExecutionError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/zenith191-RequirementClarifierAgent/src/workflow.py","lineNumber":155,"sourceCode":"\n    def _run_tool(\n        self, name: str, parameters: dict[str, object], stage: str\n    ) -> dict[str, object]:\n        \"\"\"通过官方 ToolRegistry 获取工具并解析其字符串协议。\"\"\"\n\n        tool = self.tool_registry.get_tool(name)\n        if tool is None:\n            raise WorkflowExecutionError(f\"{stage}失败：工具 {name} 未注册\")\n        try:\n            raw_result = tool.run(parameters)\n        except Exception as exc:\n            raise WorkflowExecutionError(f\"{stage}失败：工具执行异常：{exc}\") from exc\n        try:\n            payload = json.loads(raw_result)\n        except (TypeError, ValueError) as exc:\n            raise WorkflowExecutionError(f\"{stage}失败：工具返回的不是有效 JSON\") from exc\n        if not isinstance(payload, dict):\n            raise WorkflowExecutionError(f\"{stage}失败：工具结果必须是 JSON 对象\")\n        if not payload.get(\"ok\"):\n            raise WorkflowExecutionError(\n                f\"{stage}失败：{payload.get('message', '未知工具错误')}\"\n            )\n        return payload\n\n    def _clear_agent_histories(self) -> None:\n        \"\"\"避免多次运行时把上一条需求带入下一条需求。\"\"\"\n\n        for agent in (\n            self.team.analyst,\n            self.team.architect,\n            self.team.reviewer,\n            self.team.synthesizer,\n        ):\n            clear_history = getattr(agent, \"clear_history\", None)\n            if callable(clear_history):\n                clear_history()","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/zenith191-RequirementClarifierAgent/src/workflow.py#L137-L173","documentation":"Raised by _run_tool when the tool's JSON parses successfully but is not an object — e.g. a list, string, number, or null. The protocol requires a JSON object at top level because the next step reads payload['ok'] and payload['message']. So '[]', '\"ok\"', or 'null' all pass json.loads then fail this check.","triggerScenarios":"A tool returns json.dumps([1,2,3]) (a list) or json.dumps(None); a tool stringifies a scalar like json.dumps('ok'); double-encoding like json.dumps(json.dumps(obj)) yields a string that parses to a string, not an object.","commonSituations":"Tools whose natural output is a list (search results, findings) and the author encoded the bare list; double-serialization bugs after refactoring; returning the result of another json.dumps call directly.","solutions":["Wrap non-object output: return json.dumps({\"ok\": True, \"results\": [...]}) instead of a bare list.","Check for accidental double encoding — one json.dumps only.","Audit each custom tool with: payload = json.loads(t.run(p)); assert isinstance(payload, dict) and 'ok' in payload."],"exampleFix":"# before\ndef run(self, params):\n    return json.dumps(search(params[\"q\"]))  # list\n\n# after\ndef run(self, params):\n    return json.dumps({\"ok\": True, \"results\": search(params[\"q\"])})","handlingStrategy":"type-guard","validationCode":"import json\n\ndef tool_result_is_object(tool, sample_params) -> bool:\n    payload = json.loads(tool.run(sample_params))\n    return isinstance(payload, dict)\n\n# enforce at registration time\nfor t in tools:\n    assert tool_result_is_object(t, t.sample_params), t.name","typeGuard":"import json\n\ndef decodes_to_json_object(raw: str) -> bool:\n    try:\n        return isinstance(json.loads(raw), dict)\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    result = workflow.run(requirement)\nexcept WorkflowExecutionError as e:\n    if \"必须是 JSON 对象\" in str(e):\n        # tool returned a list/scalar — wrap it: {\"ok\": true, \"data\": <that value>}\n        wrap_tool_result_as_object()\n        result = workflow.run(requirement)\n    else:\n        raise","preventionTips":["Always wrap list results: {\"ok\": true, \"results\": [...]} not a bare array.","Watch for double json.dumps — it produces a string, not an object, after one parse.","Include the object-shape requirement in the tool authoring guide/contributing docs."],"tags":["python","json","tools","protocol"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}