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

  1. Pre-serialize list elements: `[str(x) if not isinstance(x, (str, dict)) else x for x in items]` with your own error handling
  2. Fix the raising `__str__`/`__repr__` on the offending element class
  3. 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

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


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/5180f68a765a0e7a. Report an issue: GitHub.