From a0331285d714ab37738ada934cc040dfd13f1d5e Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Thu, 14 Aug 2025 16:28:36 -0400 Subject: [PATCH] fix(core): Support no-args tools by defaulting args to empty dict (#32530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes #32408 Description: This PR ensures that tool calls without explicitly provided `args` will default to an empty dictionary (`{}`), allowing tools with no parameters (e.g. `def foo() -> str`) to be registered and invoked without validation errors. This change improves compatibility with agent frameworks that may omit the `args` field when generating tool calls. Issue: See [langgraph#5722](https://github.com/langchain-ai/langgraph/issues/5722) – LangGraph currently emits tool calls without `args`, which leads to validation errors when tools with no parameters are invoked. This PR ensures compatibility by defaulting `args` to `{}` when missing. Dependencies: None --------- Thank you for contributing to LangChain! Follow these steps to mark your pull request as ready for review. **If any of these steps are not completed, your PR will not be considered for review.** - [ ] **PR title**: Follows the format: {TYPE}({SCOPE}): {DESCRIPTION} - Examples: - feat(core): add multi-tenant support - fix(cli): resolve flag parsing error - docs(openai): update API usage examples - Allowed `{TYPE}` values: - feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert, release - Allowed `{SCOPE}` values (optional): - core, cli, langchain, standard-tests, docs, anthropic, chroma, deepseek, exa, fireworks, groq, huggingface, mistralai, nomic, ollama, openai, perplexity, prompty, qdrant, xai - Note: the `{DESCRIPTION}` must not start with an uppercase letter. - Once you've written the title, please delete this checklist item; do not include it in the PR. - [ ] **PR message**: ***Delete this entire checklist*** and replace with - **Description:** a description of the change. Include a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) if applicable to a relevant issue. - **Issue:** the issue # it fixes, if applicable (e.g. Fixes #123) - **Dependencies:** any dependencies required for this change - **Twitter handle:** if your PR gets announced, and you'd like a mention, we'll gladly shout you out! - [ ] **Add tests and docs**: If you're adding a new integration, you must include: 1. A test for the integration, preferably unit tests that do not rely on network access, 2. An example notebook showing its use. It lives in `docs/docs/integrations` directory. - [ ] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. **We will not consider a PR unless these three are passing in CI.** See [contribution guidelines](https://python.langchain.com/docs/contributing/) for more. Additional guidelines: - Make sure optional dependencies are imported within a function. - Please do not add dependencies to `pyproject.toml` files (even optional ones) unless they are **required** for unit tests. - Most PRs should not touch more than one package. - Changes should be backwards compatible. --------- Signed-off-by: jitokim Co-authored-by: jito Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libs/core/langchain_core/messages/ai.py | 5 ++++- libs/core/tests/unit_tests/test_messages.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/core/langchain_core/messages/ai.py b/libs/core/langchain_core/messages/ai.py index c81187dc3f6..27ae49dca9f 100644 --- a/libs/core/langchain_core/messages/ai.py +++ b/libs/core/langchain_core/messages/ai.py @@ -358,7 +358,10 @@ class AIMessageChunk(AIMessage, BaseMessageChunk): for chunk in self.tool_call_chunks: try: - args_ = parse_partial_json(chunk["args"]) if chunk["args"] != "" else {} # type: ignore[arg-type] + if chunk["args"] is not None and chunk["args"] != "": + args_ = parse_partial_json(chunk["args"]) + else: + args_ = {} if isinstance(args_, dict): tool_calls.append( create_tool_call( diff --git a/libs/core/tests/unit_tests/test_messages.py b/libs/core/tests/unit_tests/test_messages.py index 0656a2f2e97..807f52ae10d 100644 --- a/libs/core/tests/unit_tests/test_messages.py +++ b/libs/core/tests/unit_tests/test_messages.py @@ -455,9 +455,9 @@ def test_message_chunk_to_message() -> None: tool_calls=[ create_tool_call(name="tool1", args={"a": 1}, id="1"), create_tool_call(name="tool2", args={}, id="2"), + create_tool_call(name="tool3", args={}, id="3"), ], invalid_tool_calls=[ - create_invalid_tool_call(name="tool3", args=None, id="3", error=None), create_invalid_tool_call(name="tool4", args="abc", id="4", error=None), ], )