{"record":{"id":"dd2cc3c47c36d6b6","repo":"langchain-ai/langchain","slug":"toolmessage-content-should-be-a-string-or-a-list-o","errorCode":null,"errorMessage":"ToolMessage content should be a string or a list of string/dicts. Received:\\n\\n{content=}\\n\\n which could not be coerced into a string.","messagePattern":"ToolMessage content should be a string or a list of string/dicts\\. Received:\\\\n\\\\n(.+?)\\\\n\\\\n which could not be coerced into a string\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/messages/tool.py","lineNumber":112,"sourceCode":"\n        Args:\n            values: The model arguments.\n\n        \"\"\"\n        content = values[\"content\"]\n        if isinstance(content, tuple):\n            content = list(content)\n\n        if not isinstance(content, (str, list)):\n            try:\n                values[\"content\"] = str(content)\n            except ValueError as e:\n                msg = (\n                    \"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\"]","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/messages/tool.py#L94-L130","documentation":"Raised in ToolMessage's Pydantic validator when `content` is not a str or list and calling `str(content)` on it itself raises a ValueError. This only happens when the object has a broken `__str__`/`__repr__` that throws, so the failure points at the payload object, not at ToolMessage usage.","triggerScenarios":"Passing a custom object as ToolMessage content whose `__str__` raises ValueError (or raises inside `__repr__` used by `__str__`); objects whose string conversion depends on state that is missing at construction time.","commonSituations":"Wrapping SDK response objects or ORM rows in ToolMessage without first serializing them; custom data classes with strict `__str__` implementations that validate fields; partially-initialized objects escaping into message content.","solutions":["Serialize before constructing: pass `json.dumps(obj, default=str)` or `str(obj)` yourself in a context where you control errors","Fix or relax the custom object's `__str__`/`__repr__` so it cannot raise","Pass a list of dicts (`[{\"result\": data}]`) which ToolMessage accepts without coercion"],"exampleFix":"# before\ntool_msg = ToolMessage(content=custom_obj, tool_call_id=call_id)  # custom_obj.__str__ raises\n\n# after\nimport json\ntool_msg = ToolMessage(content=json.dumps(custom_obj, default=str), tool_call_id=call_id)","handlingStrategy":"validation","validationCode":"def coercible_content(content) -> bool:\n    if isinstance(content, (str, list)):\n        return True\n    try:\n        str(content)\n    except Exception:\n        return False\n    return True","typeGuard":"def is_tool_message_safe_content(content) -> bool:\n    if isinstance(content, str):\n        return True\n    if isinstance(content, list):\n        return all(isinstance(x, (str, dict)) for x in content)\n    return False","tryCatchPattern":"try:\n    msg = ToolMessage(content=payload, tool_call_id=cid)\nexcept ValueError:\n    msg = ToolMessage(content=json.dumps(payload, default=str), tool_call_id=cid)","preventionTips":["Serialize tool outputs to str/dict/list before building ToolMessage","Make custom objects' __str__ total (never raise)","Prefer json.dumps(..., default=str) for arbitrary payloads"],"tags":["tool-message","validation","serialization","pydantic"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}