langchain-ai/langchain · error · ValueError
ToolMessage content should be a string or a list of string/d
Error message
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. What it means
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.
Source
Thrown at libs/core/langchain_core/messages/tool.py:112
Args:
values: The model arguments.
"""
content = values["content"]
if isinstance(content, tuple):
content = list(content)
if not isinstance(content, (str, list)):
try:
values["content"] = str(content)
except ValueError as e:
msg = (
"ToolMessage content should be a string or a list of string/dicts. "
f"Received:\n\n{content=}\n\n which could not be coerced into a "
"string."
)
raise ValueError(msg) from e
elif isinstance(content, list):
values["content"] = []
for i, x in enumerate(content):
if not isinstance(x, (str, dict)):
try:
values["content"].append(str(x))
except ValueError as e:
msg = (
"ToolMessage content should be a string or a list of "
"string/dicts. Received a list but "
f"element ToolMessage.content[{i}] is not a dict and could "
f"not be coerced to a string.:\n\n{x}"
)
raise ValueError(msg) from e
else:
values["content"].append(x)
tool_call_id = values["tool_call_id"]View on GitHub (pinned to e32fa9a52e)
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
Example fix
# before tool_msg = ToolMessage(content=custom_obj, tool_call_id=call_id) # custom_obj.__str__ raises # after import json tool_msg = ToolMessage(content=json.dumps(custom_obj, default=str), tool_call_id=call_id)
Defensive patterns
Strategy: validation
Validate before calling
def coercible_content(content) -> bool:
if isinstance(content, (str, list)):
return True
try:
str(content)
except Exception:
return False
return True Type guard
def is_tool_message_safe_content(content) -> bool:
if isinstance(content, str):
return True
if isinstance(content, list):
return all(isinstance(x, (str, dict)) for x in content)
return False Try / catch
try:
msg = ToolMessage(content=payload, tool_call_id=cid)
except ValueError:
msg = ToolMessage(content=json.dumps(payload, default=str), tool_call_id=cid) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ToolMessage content should be a string or a list of string/d
- Either data or path must be provided
- If multiple pydantic schemas are provided then args_only sho
- Dict Pydantic schema unsupported with args_only: {self.pydan
- Tool arguments must be specified as a dict, received: {res['
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/dd2cc3c47c36d6b6.
Report an issue: GitHub.