Skip to content

fix(core): stringify empty list from tool return instead of passing as content blocks#35635

Open
Giulio Leone (giulio-leone) wants to merge 1 commit intolangchain-ai:masterfrom
giulio-leone:fix/empty-list-tool-return-30578
Open

fix(core): stringify empty list from tool return instead of passing as content blocks#35635
Giulio Leone (giulio-leone) wants to merge 1 commit intolangchain-ai:masterfrom
giulio-leone:fix/empty-list-tool-return-30578

Conversation

@giulio-leone
Copy link
Contributor

Problem

BaseTool subclasses that return an empty list from _run/_arun get inconsistent behavior compared to non-empty lists:

class MyTool(BaseTool):
    name = "my_tool"
    description = "Returns a list"

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

tool = MyTool()

# Non-empty list → correctly JSON-stringified
tool.run({"n": 1}, tool_call_id="1").content  # '[{"item": "0"}]'

# Empty list → passed as raw [] (0 content blocks, not an empty list value)
tool.run({"n": 0}, tool_call_id="2").content  # []  ← BUG

This causes downstream failures because [] is interpreted as zero content blocks rather than an empty list result.

Root Cause

_is_message_content_type() uses all() to check if every item in a list is a valid content block. For empty lists, all([]) returns True (vacuous truth), so the empty list passes the content-type check and is used directly instead of being stringified.

Fix

Add bool(obj) check to short-circuit on empty lists:

def _is_message_content_type(obj: Any) -> bool:
    return isinstance(obj, str) or (
        isinstance(obj, list)
        and bool(obj)  # ← empty lists are not content blocks
        and all(_is_message_content_block(e) for e in obj)
    )

Tests

  • Extended parametrized test__is_message_content_type with empty list case
  • Added regression test test_tool_returning_empty_list_is_stringified that verifies both n=1 (non-empty → JSON string) and n=0 (empty → "[]") return consistent string content

Fixes #30578

@github-actions github-actions bot added core `langchain-core` package issues & PRs external fix For PRs that implement a fix labels Mar 7, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Mar 7, 2026

Merging this PR will degrade performance by 11.58%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

❌ 1 regressed benchmark
✅ 12 untouched benchmarks
⏩ 23 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime test_import_time[RunnableLambda] 521.7 ms 590 ms -11.58%

Comparing giulio-leone:fix/empty-list-tool-return-30578 (70a3fce) with master (527fc02)

Open in CodSpeed

Footnotes

  1. 23 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@giulio-leone Giulio Leone (giulio-leone) force-pushed the fix/empty-list-tool-return-30578 branch from 2d68341 to 2b88c5a Compare March 9, 2026 10:46
…s content blocks

_is_message_content_type() returns True for empty lists because all() on
an empty iterable evaluates to True (vacuous truth). This causes [] returned
by a tool to be passed directly as ToolMessage.content instead of being
JSON-stringified to '[]', leading to inconsistent behavior where empty
lists are treated as zero content blocks rather than empty list values.

Add bool(obj) check to short-circuit on empty lists.

Fixes langchain-ai#30578
@giulio-leone Giulio Leone (giulio-leone) force-pushed the fix/empty-list-tool-return-30578 branch from 2b88c5a to 70a3fce Compare March 9, 2026 14:19
@github-actions github-actions bot added the size: XS < 50 LOC label Mar 9, 2026
@giulio-leone
Copy link
Contributor Author

Friendly ping — CI is green, tests pass, rebased on latest. Ready for review whenever convenient. Happy to address any feedback. 🙏

@giulio-leone
Copy link
Contributor Author

Friendly ping — rebased on latest and ready for review. Happy to address any feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core `langchain-core` package issues & PRs external fix For PRs that implement a fix size: XS < 50 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant