datawhalechina/hello-agents · error · WorkflowExecutionError

{stage}失败:工具结果必须是 JSON 对象

Error message

{stage}失败:工具结果必须是 JSON 对象

What it means

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.

Source

Thrown at Co-creation-projects/zenith191-RequirementClarifierAgent/src/workflow.py:155

    def _run_tool(
        self, name: str, parameters: dict[str, object], stage: str
    ) -> dict[str, object]:
        """通过官方 ToolRegistry 获取工具并解析其字符串协议。"""

        tool = self.tool_registry.get_tool(name)
        if tool is None:
            raise WorkflowExecutionError(f"{stage}失败:工具 {name} 未注册")
        try:
            raw_result = tool.run(parameters)
        except Exception as exc:
            raise WorkflowExecutionError(f"{stage}失败:工具执行异常:{exc}") from exc
        try:
            payload = json.loads(raw_result)
        except (TypeError, ValueError) as exc:
            raise WorkflowExecutionError(f"{stage}失败:工具返回的不是有效 JSON") from exc
        if not isinstance(payload, dict):
            raise WorkflowExecutionError(f"{stage}失败:工具结果必须是 JSON 对象")
        if not payload.get("ok"):
            raise WorkflowExecutionError(
                f"{stage}失败:{payload.get('message', '未知工具错误')}"
            )
        return payload

    def _clear_agent_histories(self) -> None:
        """避免多次运行时把上一条需求带入下一条需求。"""

        for agent in (
            self.team.analyst,
            self.team.architect,
            self.team.reviewer,
            self.team.synthesizer,
        ):
            clear_history = getattr(agent, "clear_history", None)
            if callable(clear_history):
                clear_history()

View on GitHub (pinned to 606a07d341)

Solutions

  1. Wrap non-object output: return json.dumps({"ok": True, "results": [...]}) instead of a bare list.
  2. Check for accidental double encoding — one json.dumps only.
  3. Audit each custom tool with: payload = json.loads(t.run(p)); assert isinstance(payload, dict) and 'ok' in payload.

Example fix

# before
def run(self, params):
    return json.dumps(search(params["q"]))  # list

# after
def run(self, params):
    return json.dumps({"ok": True, "results": search(params["q"])})
Defensive patterns

Strategy: type-guard

Validate before calling

import json

def tool_result_is_object(tool, sample_params) -> bool:
    payload = json.loads(tool.run(sample_params))
    return isinstance(payload, dict)

# enforce at registration time
for t in tools:
    assert tool_result_is_object(t, t.sample_params), t.name

Type guard

import json

def decodes_to_json_object(raw: str) -> bool:
    try:
        return isinstance(json.loads(raw), dict)
    except (TypeError, ValueError):
        return False

Try / catch

try:
    result = workflow.run(requirement)
except WorkflowExecutionError as e:
    if "必须是 JSON 对象" in str(e):
        # tool returned a list/scalar — wrap it: {"ok": true, "data": <that value>}
        wrap_tool_result_as_object()
        result = workflow.run(requirement)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/c07671619f09f8a9. Report an issue: GitHub.