{"record":{"id":"5180f68a765a0e7a","repo":"langchain-ai/langchain","slug":"toolmessage-content-should-be-a-string-or-a-list-o-5180f6","errorCode":null,"errorMessage":"ToolMessage content should be a string or a list of string/dicts. Received a list but element ToolMessage.content[{i}] is not a dict and could not be coerced to a string.:\\n\\n{x}","messagePattern":"ToolMessage content should be a string or a list of string/dicts\\. Received a list but element ToolMessage\\.content\\[(.+?)\\] is not a dict and could not be coerced to a string\\.:\\\\n\\\\n(.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/messages/tool.py","lineNumber":126,"sourceCode":"                    \"ToolMessage content should be a string or a list of string/dicts. \"\n                    f\"Received:\\n\\n{content=}\\n\\n which could not be coerced into a \"\n                    \"string.\"\n                )\n                raise ValueError(msg) from e\n        elif isinstance(content, list):\n            values[\"content\"] = []\n            for i, x in enumerate(content):\n                if not isinstance(x, (str, dict)):\n                    try:\n                        values[\"content\"].append(str(x))\n                    except ValueError as e:\n                        msg = (\n                            \"ToolMessage content should be a string or a list of \"\n                            \"string/dicts. Received a list but \"\n                            f\"element ToolMessage.content[{i}] is not a dict and could \"\n                            f\"not be coerced to a string.:\\n\\n{x}\"\n                        )\n                        raise ValueError(msg) from e\n                else:\n                    values[\"content\"].append(x)\n\n        tool_call_id = values[\"tool_call_id\"]\n        if isinstance(tool_call_id, (UUID, int, float)):\n            values[\"tool_call_id\"] = str(tool_call_id)\n        return values\n\n    @overload\n    def __init__(\n        self,\n        content: str | list[str | dict[Any, Any]],\n        **kwargs: Any,\n    ) -> None: ...\n\n    @overload\n    def __init__(\n        self,","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/messages/tool.py#L108-L144","documentation":"Raised in ToolMessage's validator when `content` is a list containing a non-str/non-dict element whose `str()` conversion raises a ValueError. As with the scalar case, ordinary objects coerce fine; this error means the specific list element has a raising `__str__`, and the message names the failing index.","triggerScenarios":"Passing `content=[obj1, \"text\", obj2]` where `obj2.__str__` raises; lists of ORM/model objects whose repr validates state; mixed payloads where one element is malformed.","commonSituations":"Returning a list of database rows or API objects as tool output; list elements that are partially constructed or whose repr depends on a session that has closed; the index in the message (`content[i]`) identifies which element failed.","solutions":["Pre-serialize list elements: `[str(x) if not isinstance(x, (str, dict)) else x for x in items]` with your own error handling","Fix the raising `__str__`/`__repr__` on the offending element class","Convert elements to dicts (`x.model_dump()` / `dataclasses.asdict(x)`) since dicts pass through untouched"],"exampleFix":"# before\nmsg = ToolMessage(content=[row_a, row_b], tool_call_id=call_id)  # row_b.__str__ raises\n\n# after\nfrom dataclasses import asdict\nmsg = ToolMessage(content=[asdict(r) for r in (row_a, row_b)], tool_call_id=call_id)","handlingStrategy":"validation","validationCode":"def coercible_list_content(items: list) -> bool:\n    for x in items:\n        if isinstance(x, (str, dict)):\n            continue\n        try:\n            str(x)\n        except Exception:\n            return False\n    return True","typeGuard":"def is_safe_tool_content_list(content: list) -> bool:\n    return all(isinstance(x, (str, dict)) for x in content)","tryCatchPattern":"try:\n    msg = ToolMessage(content=items, tool_call_id=cid)\nexcept ValueError as e:\n    msg = ToolMessage(content=[str(x) if not isinstance(x, (str, dict)) else x for x in items], tool_call_id=cid)","preventionTips":["Map ORM/model objects to dicts (asdict/model_dump) before returning tool output","Use the failing index in the message to pinpoint the bad element fast","Avoid letting objects with stateful __repr__ into message content"],"tags":["tool-message","validation","serialization","list-content"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}