langchain - ✅(Solved) Fix Runtime is not injected when using dynamic tools [2 pull requests, 3 comments, 3 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#35305Fetched 2026-04-08 00:26:46
View on GitHub
Comments
3
Participants
3
Timeline
19
Reactions
0
Author
Assignees
Timeline (top)
referenced ×5commented ×3labeled ×3mentioned ×2

I've been testing dynamic tool registration via a middleware. I've noticed that runtime is not injected into dynamic tools. This is because _inject_tool_args in ToolNode is skipping injection since it does not know about the dynamic tool.

Error Message

Error Message and Stack Trace (if applicable)

calculate_tip call will result in an error because runtime wont be injected

Root Cause

calculate_tip call will result in an error because runtime wont be injected

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.27.2 jsonpatch: 1.33 langgraph: 1.0.8 openai: 2.21.0 opentelemetry-api: 1.31.1 opentelemetry-exporter-otlp-proto-http: 1.31.1 opentelemetry-sdk: 1.31.1 orjson: 3.11.3 packaging: 24.2 pydantic: 2.11.7 pytest: 8.3.5 pyyaml: 6.0.2 requests: 2.32.3 requests-toolbelt: 1.0.0 rich: 14.0.0 tenacity: 9.1.2 tiktoken: 0.9.0 typing-extensions: 4.13.0 uuid-utils: 0.12.0 zstandard: 0.25.0

PR fix notes

PR #34842: feat: dynamic tool registration via middleware

Description (problem / solution / changelog)

dependent upon https://github.com/langchain-ai/langgraph/pull/6711

  1. relax constraint in factory.py to allow for tools not pre-registered in the ModelRequest.tools list
  2. always add tool node if wrap_tool_call or awrap_tool_call is implemented
  3. add tests confirming you can register new tools at runtime in wrap_model_call and execute them via wrap_tool_call

allows for the following pattern

from langchain_core.messages import HumanMessage, ToolMessage
from langchain_core.tools import tool

from libs.langchain_v1.langchain.agents.factory import create_agent
from libs.langchain_v1.langchain.agents.middleware.types import (
    AgentMiddleware,
    ModelRequest,
    ToolCallRequest,
)


@tool
def get_weather(location: str) -> str:
    """Get the current weather for a location."""
    return f"The weather in {location} is sunny and 72°F."


@tool
def calculate_tip(bill_amount: float, tip_percentage: float = 20.0) -> str:
    """Calculate the tip amount for a bill."""
    tip = bill_amount * (tip_percentage / 100)
    return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"

class DynamicToolMiddleware(AgentMiddleware):
    """Middleware that adds and handles a dynamic tool."""

    def wrap_model_call(self, request: ModelRequest, handler):
        updated = request.override(tools=[*request.tools, calculate_tip])
        return handler(updated)

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        if request.tool_call["name"] == "calculate_tip":
            return handler(request.override(tool=calculate_tip))
        return handler(request)


agent = create_agent(model="openai:gpt-4o-mini", tools=[get_weather], middleware=[DynamicToolMiddleware()])
result = agent.invoke({
    "messages": [HumanMessage("What's the weather in NYC? Also calculate a 20% tip on a $85 bill")]
})
for msg in result["messages"]:
    msg.pretty_print()

Changed files

  • libs/langchain_v1/langchain/agents/factory.py (modified, +50/-25)
  • libs/langchain_v1/pyproject.toml (modified, +1/-1)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/core/test_dynamic_tools.py (added, +407/-0)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/core/test_tools.py (modified, +4/-1)
  • libs/langchain_v1/uv.lock (modified, +13/-13)

Code Example

from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ToolCallRequest
from langchain.tools import ToolRuntime, tool


@dataclass
class Context:
    user_id: str


# This tool will be added dynamically at runtime
@tool
def calculate_tip(bill_amount: float, tip_percentage: float, runtime: ToolRuntime[Context]) -> str:
    """Calculate the tip amount for a bill."""
    tip = bill_amount * (tip_percentage / 100)
    return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"


# This tool is added at compile time
@tool
def get_weather(location: str) -> str:
    """Get the weather in a given location."""
    return f"The weather in {location} is sunny."


class DynamicToolMiddleware(AgentMiddleware):
    """Middleware that registers and handles dynamic tools."""

    def wrap_model_call(self, request: ModelRequest, handler):
        # Add dynamic tool to the request
        # This could be loaded from an MCP server, database, etc.
        updated = request.override(tools=[*request.tools, calculate_tip])
        return handler(updated)

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        # Handle execution of the dynamic tool
        if request.tool_call["name"] == "calculate_tip":
            return handler(
                request.override(tool=calculate_tip)
            )
        return handler(request)


agent = create_agent(
    model="gpt-4o",
    tools=[get_weather],  # Only static tools registered here
    middleware=[DynamicToolMiddleware()],
)

# The agent can now use both get_weather AND calculate_tip
result = agent.invoke({"messages": [{"role": "user", "content": "Calculate a 20% tip on $85"}]})

---

calculate_tip call will result in an error because runtime wont be injected
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

https://github.com/langchain-ai/langchain/pull/34842

Reproduction Steps / Example Code (Python)

from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ToolCallRequest
from langchain.tools import ToolRuntime, tool


@dataclass
class Context:
    user_id: str


# This tool will be added dynamically at runtime
@tool
def calculate_tip(bill_amount: float, tip_percentage: float, runtime: ToolRuntime[Context]) -> str:
    """Calculate the tip amount for a bill."""
    tip = bill_amount * (tip_percentage / 100)
    return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"


# This tool is added at compile time
@tool
def get_weather(location: str) -> str:
    """Get the weather in a given location."""
    return f"The weather in {location} is sunny."


class DynamicToolMiddleware(AgentMiddleware):
    """Middleware that registers and handles dynamic tools."""

    def wrap_model_call(self, request: ModelRequest, handler):
        # Add dynamic tool to the request
        # This could be loaded from an MCP server, database, etc.
        updated = request.override(tools=[*request.tools, calculate_tip])
        return handler(updated)

    def wrap_tool_call(self, request: ToolCallRequest, handler):
        # Handle execution of the dynamic tool
        if request.tool_call["name"] == "calculate_tip":
            return handler(
                request.override(tool=calculate_tip)
            )
        return handler(request)


agent = create_agent(
    model="gpt-4o",
    tools=[get_weather],  # Only static tools registered here
    middleware=[DynamicToolMiddleware()],
)

# The agent can now use both get_weather AND calculate_tip
result = agent.invoke({"messages": [{"role": "user", "content": "Calculate a 20% tip on $85"}]})

Error Message and Stack Trace (if applicable)

calculate_tip call will result in an error because runtime wont be injected

Description

I've been testing dynamic tool registration via a middleware. I've noticed that runtime is not injected into dynamic tools. This is because _inject_tool_args in ToolNode is skipping injection since it does not know about the dynamic tool.

System Info

System Information

OS: Linux OS Version: #1 SMP Tue Nov 5 00:21:55 UTC 2024 Python Version: 3.12.11 (main, Aug 8 2025, 17:06:48) [Clang 20.1.4 ]

Package Information

langchain_core: 1.2.13 langchain: 1.2.10 langsmith: 0.4.31 langchain_openai: 1.1.10 langchain_text_splitters: 1.1.0 langgraph_sdk: 0.3.6

Optional packages not installed

langserve

Other Dependencies

httpx: 0.27.2 jsonpatch: 1.33 langgraph: 1.0.8 openai: 2.21.0 opentelemetry-api: 1.31.1 opentelemetry-exporter-otlp-proto-http: 1.31.1 opentelemetry-sdk: 1.31.1 orjson: 3.11.3 packaging: 24.2 pydantic: 2.11.7 pytest: 8.3.5 pyyaml: 6.0.2 requests: 2.32.3 requests-toolbelt: 1.0.0 rich: 14.0.0 tenacity: 9.1.2 tiktoken: 0.9.0 typing-extensions: 4.13.0 uuid-utils: 0.12.0 zstandard: 0.25.0

extent analysis

Fix Plan

Step 1: Update ToolNode to inject runtime for dynamic tools

Update the inject_tool_args method in ToolNode to include dynamic tools.

# langgraph/libs/prebuilt/langgraph/prebuilt/tool_node.py
class ToolNode(Node):
    # ...

    def inject_tool_args(self, tool, tool_args):
        # ...
        if isinstance(tool, DynamicTool):
            tool_args['runtime'] = self.runtime
        # ...

Step 2: Update DynamicToolMiddleware to handle dynamic tool injection

Update the wrap_model_call method in DynamicToolMiddleware to inject runtime for dynamic tools.

# langchain/middleware.py
class DynamicToolMiddleware(AgentMiddleware):
    # ...

    def wrap_model_call(self, request: ModelRequest, handler):
        # ...
        if request.tool_call["name"] == "calculate_tip":
            request.tool_call["args"]["runtime"] = self.runtime
        # ...

Step 3: Update calculate_tip tool to accept runtime argument

Update the calculate_tip tool to accept the runtime argument.

# tools.py
@tool
def calculate_tip(bill_amount: float, tip_percentage: float, runtime: ToolRuntime[Context]) -> str:
    # ...

Verification

  1. Run the example code with the updated ToolNode and DynamicToolMiddleware.
  2. Verify that the calculate_tip tool receives the runtime argument.
  3. Verify that the tool executes correctly with the injected runtime.

Extra Tips

  • Make sure to update the langgraph and langchain packages to the latest versions.
  • Test the updated code with different dynamic tools to ensure the fix works as expected.
  • Consider adding additional logging or debugging statements to help diagnose any issues that may arise.

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