Skip to content

Structured Tool with strict args_schema prevents injected arguments #34246

@andreitava-uip

Description

@andreitava-uip

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-cli
  • 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-perplexity
  • langchain-prompty
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Example Code (Python)

from typing import Any

from langchain.tools import ToolRuntime
from langchain_core.tools import StructuredTool
from pydantic import BaseModel


class ArgsSchema(BaseModel):
    str_value: str
    int_value: int

    model_config = {"extra": "forbid"}


def tool_func(runtime: ToolRuntime, **kwargs: Any) -> Any:
    return {"input_kwargs": kwargs, "runtime": runtime}


tool = StructuredTool(
    name="example_tool",
    description="An example tool that echoes its input and runtime.",
    func=tool_func,
    args_schema=ArgsSchema,
)

dummy_runtime: ToolRuntime = ToolRuntime(
    state={},
    context=None,
    config={},
    stream_writer=lambda x: None,
    tool_call_id=None,
    store=None,
)
input_kwargs = {"str_value": "hello", "int_value": 42, "runtime": dummy_runtime}
result = tool.invoke(input_kwargs)
print(result)

Error Message and Stack Trace (if applicable)

Traceback (most recent call last):
  File "/home/andrei/python-sdks/coded-playground/.stuffs/example.py", line 35, in <module>
    result = tool.invoke(input_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/langchain_core/tools/base.py", line 605, in invoke
    return self.run(tool_input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/langchain_core/tools/base.py", line 933, in run
    raise error_to_raise
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/langchain_core/tools/base.py", line 892, in run
    tool_args, tool_kwargs = self._to_args_and_kwargs(
                             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/langchain_core/tools/base.py", line 795, in _to_args_and_kwargs
    tool_input = self._parse_input(tool_input, tool_call_id)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/langchain_core/tools/base.py", line 672, in _parse_input
    result = input_args.model_validate(tool_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/andrei/python-sdks/coded-playground/.venv/lib/python3.11/site-packages/pydantic/main.py", line 716, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for ArgsSchema
runtime
  Extra inputs are not permitted [type=extra_forbidden, input_value=ToolRuntime(state={}, con...all_id=None, store=None), input_type=ToolRuntime]
    For further information visit https://errors.pydantic.dev/2.12/v/extra_forbidden

Description

Trying to inject ToolRuntime into a func wrapped by StructuredTool which uses a pydantic model with model_config = {"extra": "forbid"}.

I expect it to work since injected args should not need to be specified in the schema to prevent them being exposed to the llm.
Instead it results in an extra inputs error because the entire payload is being validated.

This has the same cause as #31688, but it seems the fix was to re-add the injected args to the payload after the validation removes them. In the case of model_config = {"extra": "forbid"} however, an unhandled exception is raised.

I can work around it by defining the schema to contain the runtime and skip json schema serialization, but it would be nice not to have to resort to this.

class ArgsSchema(BaseModel):
    str_value: str
    int_value: int

    model_config = {"extra": "forbid"}
    runtime: Annotated[Any, SkipJsonSchema()]

System Info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025
Python Version: 3.11.14 (main, Oct 14 2025, 21:26:53) [Clang 20.1.4 ]

Package Information

langchain_core: 1.1.0
langchain: 1.1.2
langsmith: 0.4.47
langgraph_sdk: 0.2.10

Optional packages not installed

langserve

Other Dependencies

httpx: 0.28.1
jsonpatch: 1.33
langgraph: 1.0.4
orjson: 3.11.4
packaging: 25.0
pydantic: 2.12.5
pyyaml: 6.0.3
requests: 2.32.5
requests-toolbelt: 1.0.0
tenacity: 9.1.2
typing-extensions: 4.15.0
zstandard: 0.25.0

Metadata

Metadata

Assignees

Labels

bugRelated to a bug, vulnerability, unexpected error with an existing featurecore`langchain-core` package issues & PRs

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions