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 a list but element ToolMessage.content[{i}] is not a dict and could not be coerced to a string.:\n\n{x} What it means
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.
Source
Thrown at libs/core/langchain_core/messages/tool.py:126
"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"]
if isinstance(tool_call_id, (UUID, int, float)):
values["tool_call_id"] = str(tool_call_id)
return values
@overload
def __init__(
self,
content: str | list[str | dict[Any, Any]],
**kwargs: Any,
) -> None: ...
@overload
def __init__(
self,View on GitHub (pinned to e32fa9a52e)
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
Example fix
# before msg = ToolMessage(content=[row_a, row_b], tool_call_id=call_id) # row_b.__str__ raises # after from dataclasses import asdict msg = ToolMessage(content=[asdict(r) for r in (row_a, row_b)], tool_call_id=call_id)
Defensive patterns
Strategy: validation
Validate before calling
def coercible_list_content(items: list) -> bool:
for x in items:
if isinstance(x, (str, dict)):
continue
try:
str(x)
except Exception:
return False
return True Type guard
def is_safe_tool_content_list(content: list) -> bool:
return all(isinstance(x, (str, dict)) for x in content) Try / catch
try:
msg = ToolMessage(content=items, tool_call_id=cid)
except ValueError as e:
msg = ToolMessage(content=[str(x) if not isinstance(x, (str, dict)) else x for x in items], tool_call_id=cid) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ToolMessage content should be a string or a list of string/d
- invalid IP address
- Failed to resolve hostname '{hostname}': {e}
- Network error while validating URL: {e}
- maxsize must be greater than 0
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/5180f68a765a0e7a.
Report an issue: GitHub.