langchain - ✅(Solved) Fix Non-dict tool arguments should be set invalid [12 pull requests, 8 comments, 5 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#35990Fetched 2026-04-08 00:47:41
View on GitHub
Comments
8
Participants
5
Timeline
36
Reactions
0
Author
Timeline (top)
cross-referenced ×12referenced ×12commented ×8labeled ×3

tool arguments that can be deserialized via json.loads is not necessarily a dict. Other types will trigger ValidationError of AIMessage.

Error Message

error = None error = str(e) if error is None: "error": error,

Error Message and Stack Trace (if applicable)

Root Cause

tool arguments that can be deserialized via json.loads is not necessarily a dict. Other types will trigger ValidationError of AIMessage.

Fix Action

Fix / Workaround

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • 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).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Other Dependencies

httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.0.7 openai: 2.13.0 orjson: 3.11.7 packaging: 25.0 pydantic: 2.12.5 pytest: 9.0.2 pyyaml: 6.0.3 requests: 2.32.5 requests-toolbelt: 1.0.0 rich: 14.3.2 tenacity: 9.1.2 tiktoken: 0.12.0 typing-extensions: 4.15.0 uuid-utils: 0.14.0 xxhash: 3.6.0 zstandard: 0.25.0

PR fix notes

PR #35991: fix(openai): detect invalid tool argument type

Description (problem / solution / changelog)

Fixes #35990

tool arguments that can be deserialized via json.loads is not necessarily a dict. Other types will trigger ValidationError of AIMessage. This PR checks the argument is a dict.

Changed files

  • libs/core/langchain_core/output_parsers/openai_tools.py (modified, +8/-0)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +3/-1)

PR #35993: fix: validate tool arguments are dict after JSON parsing

Description (problem / solution / changelog)

Summary

Currently json.loads can successfully parse non-dict values (lists, strings, numbers), but these will fail validation since ToolCall.args requires dict.

Problem

Added isinstance(args, dict) check after json.loads to catch non-dict cases and properly mark them as invalid_tool_calls with TypeError.

Fix

Added check:

if not isinstance(args, dict):
    raise TypeError("Arguments must be a dictionary")

Fixes #35990

Changed files

  • libs/langchain_v1/langchain/agents/middleware/__init__.py (modified, +2/-0)
  • libs/langchain_v1/langchain/agents/middleware/types.py (modified, +3/-3)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +3/-1)

PR #35994: fix(openai): treat non-dict tool arguments as invalid tool calls

Description (problem / solution / changelog)

Fixes #35990

When parsing tool call arguments from the OpenAI Responses API, json.loads() can succeed but return non-dict types (strings, numbers, lists, null, bools). Previously, these were incorrectly treated as valid tool calls, causing ValidationError when creating AIMessage since ToolCall expects args to be a dict.

This change validates that parsed arguments are a dictionary and treats non-dict results as invalid tool calls, consistent with how malformed JSON is handled.

Changes:

  • Added type check after json.loads() in _construct_lc_result_from_responses_api()
  • Raise TypeError with clear message when args is not a dict
  • Added test covering string, int, list, null, and bool argument types

AI involvement: This PR was created with assistance from ClawOSS, an autonomous codebase helper.

Changed files

  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +4/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +47/-0)

PR #35997: fix(openai): treat non-dict tool arguments as invalid tool calls

Description (problem / solution / changelog)

Fixes #35990

When parsing tool call arguments from JSON in the Responses API, valid JSON that deserializes to non-dict types (e.g., strings, numbers, lists) was causing a ValidationError in AIMessage because ToolCall requires args to be dict[str, Any].

This change ensures non-dict arguments are treated as invalid tool calls with an appropriate error message, consistent with how JSONDecodeError is already handled.

Changes:

  • Added type check after json.loads() to verify args is a dict
  • Raises TypeError with descriptive message if args is not a dict
  • Catches TypeError alongside JSONDecodeError to mark as invalid_tool_call
  • Added unit test for non-dict arguments scenario

Changed files

  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +4/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +35/-0)

PR #36009: fix(openai): non-dict tool arguments should be set invalid

Description (problem / solution / changelog)

Fixes #35990

When tool call arguments are valid JSON but deserialize to non-dict types (e.g., string "abc", list [], number 123), they should be treated as invalid tool calls rather than causing ValidationError on AIMessage.

The ToolCall TypedDict requires args to be dict[str, Any]. Previously, the code only caught JSONDecodeError, allowing non-dict values to pass through and fail validation when creating the AIMessage.

Changes:

  • Added isinstance(args, dict) check after json.loads()
  • Added TypeError to the exception tuple being caught
  • Added test for non-dict arguments case

Changed files

  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +4/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +37/-0)

PR #36017: fix: validate tool arguments are dict after JSON deserialization

Description (problem / solution / changelog)

Summary

When tool arguments are deserialized via json.loads but result in a non-dict type (e.g., list, string), the code previously would create a valid tool_call that would later fail during AIMessage validation.

This fix adds a TypeError check to ensure args is a dict, and if not, treats it as an invalid_tool_call with the appropriate error message.

Fix

In _construct_lc_result_from_responses_api, added validation after JSON parsing:

args = json.loads(output.arguments, strict=False)
if not isinstance(args, dict):
    raise TypeError("Arguments must be a dictionary")

This ensures non-dict arguments are handled as invalid tool calls rather than causing downstream validation errors.

Related Issue

Fixes #35990

Changed files

  • libs/core/langchain_core/utils/_merge.py (modified, +5/-0)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +3/-1)

PR #36043: fix: handle non-dict tool arguments as invalid tool calls

Description (problem / solution / changelog)

Fixed issue #35990: When parsing tool call arguments, if JSON parsing succeeds but result is not dict type (like list or string), the code now correctly marks it as invalid tool call instead of throwing ValidationError.

Changed files

  • libs/core/langchain_core/structured_query.py (modified, +1/-1)
  • libs/core/langchain_core/utils/_merge.py (modified, +17/-7)
  • libs/core/langchain_core/utils/usage.py (modified, +16/-8)
  • libs/core/tests/unit_tests/utils/test_usage.py (modified, +28/-1)
  • libs/core/tests/unit_tests/utils/test_utils.py (modified, +5/-0)
  • libs/langchain_v1/langchain/agents/middleware/types.py (modified, +4/-4)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +3/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +36/-0)

PR #36052: fix(openai): treat non-dict JSON tool arguments as invalid tool calls

Description (problem / solution / changelog)

Fixes #35990

Why

json.loads() successfully parses valid JSON that is not a dict — arrays, strings, numbers, booleans, and null are all valid JSON. The Responses API tool call handler only caught JSONDecodeError, so any of these non-dict results were passed directly as args in a tool_call entry. This causes a ValidationError in AIMessage because ToolCall.args is typed as dict[str, Any].

What changed

langchain_openai/chat_models/base.py — in _construct_lc_result_from_responses_api, after json.loads() succeeds, added an isinstance(args, dict) check that raises TypeError for non-dict results. TypeError is now caught alongside JSONDecodeError, routing the call to invalid_tool_calls with the raw arguments string and a descriptive error message.

Tests

Added test__construct_lc_result_from_responses_api_function_call_non_dict_json with 5 parametrized cases ([1,2,3], "a string", 42, true, null) — all now produce an invalid_tool_call entry instead of raising a ValidationError.


🤖 This PR was developed with assistance from Claude Code (Anthropic).

Changed files

  • libs/partners/anthropic/langchain_anthropic/middleware/anthropic_tools.py (modified, +2/-2)
  • libs/partners/anthropic/tests/unit_tests/middleware/test_anthropic_tools.py (modified, +24/-1)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +18/-12)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +107/-0)

PR #36073: fix: route non-dict tool arguments to invalid_tool_calls

Description (problem / solution / changelog)

Description

Fixes #35990.

json.loads() can successfully parse JSON that deserializes to non-dict types (strings, lists, integers, booleans). When this happens for tool call arguments in the Responses API path, the non-dict value is assigned to ToolCall.args which requires a dict[str, Any], causing a ValidationError on AIMessage construction.

This PR adds an isinstance(args, dict) check after json.loads(). Non-dict results now raise a TypeError that is caught alongside JSONDecodeError, routing the malformed tool call to invalid_tool_calls instead of crashing.

Changes

  • libs/partners/openai/langchain_openai/chat_models/base.py: Added isinstance(args, dict) check after json.loads() in _construct_lc_result_from_responses_api(). Changed except JSONDecodeError to except (JSONDecodeError, TypeError).
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py: Added parameterized test covering string, list, integer, and boolean JSON arguments — all verified to route to invalid_tool_calls.

Changed files

  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +6/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +50/-0)

PR #36074: fix: route non-dict tool arguments to invalid_tool_calls

Description (problem / solution / changelog)

Description

Fixes #35990

json.loads can return valid JSON that isn't a dict (e.g. lists, strings, numbers, booleans, null). When tool call arguments deserialize to a non-dict type, AIMessage raises a ValidationError because ToolCall.args requires dict[str, Any].

This PR adds an isinstance(args, dict) check after json.loads so that non-dict results are routed to invalid_tool_calls with a clear error message, matching the existing behavior for JSONDecodeError.

Changes

langchain-openai (libs/partners/openai/langchain_openai/chat_models/base.py):

  • In the Response API path (_construct_lc_result_from_responses_api), raise TypeError when parsed args are not a dict, caught alongside JSONDecodeError

langchain-core (libs/core/langchain_core/output_parsers/openai_tools.py):

  • In parse_tool_call, raise OutputParserException when parsed args are not a dict (before returning to callers that route exceptions to invalid_tool_calls)

Tests

  • 5 parametrized tests for the Response API path (list, string, number, boolean, null)
  • 5 parametrized tests for parse_tool_call (same types)

All verify that non-dict arguments produce invalid_tool_calls entries (or OutputParserException) instead of ValidationError.

Changed files

  • libs/core/langchain_core/output_parsers/openai_tools.py (modified, +8/-0)
  • libs/core/tests/unit_tests/output_parsers/test_openai_tools.py (modified, +23/-0)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +6/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +46/-0)

PR #36076: fix(core,openai): route non-dict tool arguments to invalid_tool_calls

Description (problem / solution / changelog)

Description

Fixes #35990

json.loads() successfully parses valid JSON that is not a dict — arrays, strings, numbers, booleans, and null are all valid JSON. The current code only catches JSONDecodeError, so any of these non-dict results are passed directly as args in a tool_call entry. This causes a ValidationError on AIMessage construction because ToolCall.args is typed as dict[str, Any].

Changes

Added isinstance(args, dict) checks after json.loads() in three locations:

  1. langchain_core.messages.tool.default_tool_parser — catches TypeError alongside JSONDecodeError, routes non-dict args to invalid_tool_calls
  2. langchain_core.output_parsers.openai_tools.parse_tool_call — raises OutputParserException for non-dict args (consistent with existing JSONDecodeError handling)
  3. langchain_openai.chat_models.base (Responses API handler) — catches TypeError alongside JSONDecodeError, routes non-dict args to invalid_tool_calls

Tests

Added parametrized tests covering all 5 non-dict JSON types (array, string, number, boolean, null) in:

  • libs/core/tests/unit_tests/messages/test_ai.pydefault_tool_parser
  • libs/core/tests/unit_tests/output_parsers/test_openai_tools.pyparse_tool_call
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py — Responses API handler

All 15 new tests pass. Existing tests unaffected.

Changed files

  • libs/core/langchain_core/messages/tool.py (modified, +6/-1)
  • libs/core/langchain_core/output_parsers/openai_tools.py (modified, +7/-0)
  • libs/core/tests/unit_tests/messages/test_ai.py (modified, +31/-0)
  • libs/core/tests/unit_tests/output_parsers/test_openai_tools.py (modified, +22/-0)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +6/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +49/-0)

PR #36089: fix(openai): detect invalid tool argument type

Description (problem / solution / changelog)

Summary

Tool arguments that can be deserialized via json.loads are not necessarily a dict. Other types (lists, strings, numbers) will trigger ValidationError of AIMessage. This PR checks that the argument is a dict and treats non-dict arguments as invalid tool calls.

Changes

  • Added type check after json.loads() to verify arguments is a dict
  • Non-dict arguments are now treated as invalid tool calls with appropriate error message

Testing

  • Added test test__construct_lc_result_from_responses_api_function_call_non_dict_args that verifies the fix handles non-dict JSON types (list, string, number)
  • Existing tests pass

Fixes langchain-ai/langchain#35990

Changed files

  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +14/-3)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +99/-0)

Code Example

The `tool_call` field of AIMessage requires its arguments to be an dict:

class ToolCall(TypedDict):
    """Represents an AI's request to call a tool.

    Example:
        
        {"name": "foo", "args": {"a": 1}, "id": "123"}
        

        This represents a request to call the tool named `'foo'` with arguments
        `{"a": 1}` and an identifier of `'123'`.

    !!! note "Factory function"

        `tool_call` may also be used as a factory to create a `ToolCall`. Benefits
        include:

        * Required arguments strictly validated at creation time
    """

    name: str
    """The name of the tool to be called."""

    args: dict[str, Any]
    """The arguments to the tool call as a dictionary."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """

    type: NotRequired[Literal["tool_call"]]
    """Used for discrimination."""

While the conversion function only treats a tool call triggering JSONDecodeError as invalid tool call. Arguments deserialized as other types will trigger ValidationError of AIMessage:

def __construct_lc_result_from_response_api():
...
            try:
                args = json.loads(output.arguments, strict=False)
                if not isinstance(args, dict):
                    raise TypeError("Arguments must be a dictionary")
                error = None
            except (JSONDecodeError, TypeError) as e:
                args = output.arguments
                error = str(e)
            if error is None:
                tool_call = {
                    "type": "tool_call",
                    "name": output.name,
                    "args": args,
                    "id": output.call_id,
                }
                tool_calls.append(tool_call)
            else:
                tool_call = {
                    "type": "invalid_tool_call",
                    "name": output.name,
                    "args": args,
                    "id": output.call_id,
                    "error": error,
                }
                invalid_tool_calls.append(tool_call)

---
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • 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).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

No response

Reproduction Steps / Example Code (Python)

The `tool_call` field of AIMessage requires its arguments to be an dict:

class ToolCall(TypedDict):
    """Represents an AI's request to call a tool.

    Example:
        
        {"name": "foo", "args": {"a": 1}, "id": "123"}
        

        This represents a request to call the tool named `'foo'` with arguments
        `{"a": 1}` and an identifier of `'123'`.

    !!! note "Factory function"

        `tool_call` may also be used as a factory to create a `ToolCall`. Benefits
        include:

        * Required arguments strictly validated at creation time
    """

    name: str
    """The name of the tool to be called."""

    args: dict[str, Any]
    """The arguments to the tool call as a dictionary."""

    id: str | None
    """An identifier associated with the tool call.

    An identifier is needed to associate a tool call request with a tool
    call result in events when multiple concurrent tool calls are made.
    """

    type: NotRequired[Literal["tool_call"]]
    """Used for discrimination."""

While the conversion function only treats a tool call triggering JSONDecodeError as invalid tool call. Arguments deserialized as other types will trigger ValidationError of AIMessage:

def __construct_lc_result_from_response_api():
...
            try:
                args = json.loads(output.arguments, strict=False)
                if not isinstance(args, dict):
                    raise TypeError("Arguments must be a dictionary")
                error = None
            except (JSONDecodeError, TypeError) as e:
                args = output.arguments
                error = str(e)
            if error is None:
                tool_call = {
                    "type": "tool_call",
                    "name": output.name,
                    "args": args,
                    "id": output.call_id,
                }
                tool_calls.append(tool_call)
            else:
                tool_call = {
                    "type": "invalid_tool_call",
                    "name": output.name,
                    "args": args,
                    "id": output.call_id,
                    "error": error,
                }
                invalid_tool_calls.append(tool_call)

Error Message and Stack Trace (if applicable)

Description

tool arguments that can be deserialized via json.loads is not necessarily a dict. Other types will trigger ValidationError of AIMessage.

System Info

System Information

OS: Linux OS Version: #17~24.04.1-Ubuntu SMP Mon Dec 1 20:10:50 UTC 2025 Python Version: 3.13.11 (main, Dec 9 2025, 19:04:10) [Clang 21.1.4 ]

Package Information

langchain_core: 1.2.8 langchain: 1.2.8 langsmith: 0.6.8 langchain_openai: 1.1.7 langgraph_sdk: 0.3.3

Optional packages not installed

langserve

Other Dependencies

httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.0.7 openai: 2.13.0 orjson: 3.11.7 packaging: 25.0 pydantic: 2.12.5 pytest: 9.0.2 pyyaml: 6.0.3 requests: 2.32.5 requests-toolbelt: 1.0.0 rich: 14.3.2 tenacity: 9.1.2 tiktoken: 0.12.0 typing-extensions: 4.15.0 uuid-utils: 0.14.0 xxhash: 3.6.0 zstandard: 0.25.0

extent analysis

Fix Plan

To fix the issue where tool arguments deserialized via json.loads are not necessarily dictionaries, we need to add a type check for the deserialized arguments. Here are the steps:

  • Check if the deserialized arguments are a dictionary
  • If not, raise a TypeError with a descriptive error message
  • Update the __construct_lc_result_from_response_api function to handle the new type check

Code Changes

def __construct_lc_result_from_response_api():
    ...
    try:
        args = json.loads(output.arguments, strict=False)
        if not isinstance(args, dict):
            raise TypeError("Arguments must be a dictionary")
        error = None
    except (JSONDecodeError, TypeError) as e:
        args = output.arguments
        error = str(e)
    if error is None:
        if not isinstance(args, dict):
            error = "Arguments must be a dictionary"
            tool_call = {
                "type": "invalid_tool_call",
                "name": output.name,
                "args": args,
                "id": output.call_id,
                "error": error,
            }
            invalid_tool_calls.append(tool_call)
        else:
            tool_call = {
                "type": "tool_call",
                "name": output.name,
                "args": args,
                "id": output.call_id,
            }
            tool_calls.append(tool_call)
    else:
        tool_call = {
            "type": "invalid_tool_call",
            "name": output.name,
            "args": args,
            "id": output.call_id,
            "error": error,
        }
        invalid_tool_calls.append(tool_call)

Verification

To verify that the fix worked, you can test the __construct_lc_result_from_response_api function with different types of arguments, including dictionaries and non-dictionaries. Check that the function correctly handles each case and raises a TypeError when the arguments are not a dictionary.

Extra Tips

  • Make sure to test the function thoroughly to ensure that it handles all possible edge cases.
  • Consider adding additional error handling to handle cases where the arguments are not a dictionary, but can be converted to a dictionary (e.g. a list of key-value pairs).
  • Keep in mind that the json.loads function can raise a JSONDecodeError if the input string is not valid JSON. Make sure to handle this case accordingly.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING