Skip to content

core: inconsistent behavior for BaseTool subclasses that return list from _run/_arun #30578

@menezesandre

Description

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).

Example Code

from langchain_core.tools import BaseTool


class MyTool(BaseTool):
    name: str = "my_tool"
    description: str = "This is a tool that does something"

    def _run(self, n: int) -> list[dict[str, str]]:
        return [{"item": str(i)} for i in range(n)]


my_tool = MyTool()

# In this case, the tool message content is a JSON string.
result = my_tool.run({"n": 1}, tool_call_id="1")
print(result.model_dump(include={"content"}))  # {'content': '[{"item": "0"}]'}

# In this case, the tool message content is an empty list (not a JSON string).
# It will be interpreted as an empty message (0 content blocks) rather than a
# message containing an empty list.
result = my_tool.run({"n": 0}, tool_call_id="2")
print(result.model_dump(include={"content"}))  # {'content': []}

Error Message and Stack Trace (if applicable)

No response

Description

The BaseTool processes the _run output to ensure that a valid message content is returned (a string or a list of message content blocks).
When the tool result is a list, there is an ambiguous scenario when the list is empty. In that case, the result is considered a valid message content (all items in the list are content blocks) and it is not serialized.

if not _is_message_content_type(content):
isinstance(obj, list) and all(_is_message_content_block(e) for e in obj)

This is specially problematic because there are a few tools in langchain_community that return lists. E.g. TavilySearchResults can return an empty list and that can result in errors when using the ToolMessage.

I'm not sure what would be the best solution here, a few alternatives:

  • consider that the ambiguous case (empty list) is not a message content type, i.e. update _is_message_content_type to also check if the list is not empty
  • allow tools to explicitly state whether the result is a list of message content blocks or a generic list that should be serialized (similar to how we define the response_format)
  • forbid the ambiguous scenario (state it in the documentation; have more specific return types for the BaseTool abstract methods; give a warning if the result is a list that does not contain message content blocks)

(this should include reviewing the existing tool integrations)

System Info

System Information

OS: Darwin
OS Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041
Python Version: 3.13.2 (main, Feb 5 2025, 18:58:04) [Clang 19.1.6 ]

Package Information

langchain_core: 0.3.66
langsmith: 0.4.4

Optional packages not installed

langserve

Other Dependencies

httpx: 0.28.1
jsonpatch<2.0,>=1.33: Installed. No version info available.
langsmith-pyo3: Installed. No version info available.
langsmith>=0.3.45: Installed. No version info available.
openai-agents: Installed. No version info available.
opentelemetry-api: Installed. No version info available.
opentelemetry-exporter-otlp-proto-http: Installed. No version info available.
opentelemetry-sdk: Installed. No version info available.
orjson: 3.10.18
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.11.7
pydantic>=2.7.4: Installed. No version info available.
pytest: Installed. No version info available.
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.4
requests-toolbelt: 1.0.0
rich: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
zstandard: 0.23.0

Metadata

Metadata

Labels

bugRelated to a bug, vulnerability, unexpected error with an existing featurecore`langchain-core` package issues & PRsexternalgreat-writeupThis is a stellar example of the types of issues/PRs we're looking forinvestigateFlagged for investigation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions