ToolRuntime not supported in LangGraph's built-in ToolNode #1012

Closed
opened 2026-02-20 17:42:44 -05:00 by yindo · 12 comments
Owner

Originally created by @TBice123123 on GitHub (Oct 21, 2025).

Originally assigned to: @sydney-runkle on GitHub.

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

import datetime
from langchain_core.tools import tool
from langchain.tools import ToolRuntime
from langgraph.prebuilt import ToolNode

@tool
def get_current_time(runtime: ToolRuntime) -> str:
    """Get the current time."""
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

# Create a ToolNode with the tool
tool_node = ToolNode([get_current_time])

Error Message and Stack Trace (if applicable)


Description

I'm encountering an issue when trying to use the newly introduced ToolRuntime from LangChain within a LangGraph workflow.

In LangChain's create_agent, ToolRuntime works as expected and integrates seamlessly with tools that require runtime context. However, when I switch to using LangGraph and its pre-built ToolNode, I get an error indicating that ToolRuntime is not being passed to the tool invocation.

As a workaround, I'm currently forced to use LangChain’s internal ToolNode, which works but feels inelegant—especially since it’s marked as a private implementation (leading underscore) and not part of the public LangGraph API.

It would be great if LangGraph’s official ToolNode could support ToolRuntime out of the box, ensuring consistency between create_agent and LangGraph-based agents.

Expected behavior:
LangGraph’s ToolNode should accept and propagate ToolRuntime to compatible tools, just like create_agent does.

Actual behavior:
Using ToolRuntime with LangGraph’s ToolNode raises an error about missing runtime context.

Environment:

  • langchain version: [1.0]
  • langgraph version: [1.0]
  • Python version: [3.12]

Thanks for your work on these great libraries!


System Info

langchain version 1.0
langgraph version 1.0

Originally created by @TBice123123 on GitHub (Oct 21, 2025). Originally assigned to: @sydney-runkle on GitHub. ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/). - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python import datetime from langchain_core.tools import tool from langchain.tools import ToolRuntime from langgraph.prebuilt import ToolNode @tool def get_current_time(runtime: ToolRuntime) -> str: """Get the current time.""" return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Create a ToolNode with the tool tool_node = ToolNode([get_current_time]) ``` ### Error Message and Stack Trace (if applicable) ```shell ``` ### Description I'm encountering an issue when trying to use the newly introduced `ToolRuntime` from LangChain within a LangGraph workflow. In LangChain's `create_agent`, `ToolRuntime` works as expected and integrates seamlessly with tools that require runtime context. However, when I switch to using LangGraph and its pre-built `ToolNode`, I get an error indicating that `ToolRuntime` is not being passed to the tool invocation. As a workaround, I'm currently forced to use LangChain’s internal `ToolNode`, which works but feels inelegant—especially since it’s marked as a private implementation (leading underscore) and not part of the public LangGraph API. It would be great if LangGraph’s official `ToolNode` could support `ToolRuntime` out of the box, ensuring consistency between `create_agent` and LangGraph-based agents. **Expected behavior:** LangGraph’s `ToolNode` should accept and propagate `ToolRuntime` to compatible tools, just like `create_agent` does. **Actual behavior:** Using `ToolRuntime` with LangGraph’s `ToolNode` raises an error about missing runtime context. **Environment:** - langchain version: [1.0] - langgraph version: [1.0] - Python version: [3.12] Thanks for your work on these great libraries! --- ### System Info langchain version 1.0 langgraph version 1.0
yindo added the bugpending labels 2026-02-20 17:42:44 -05:00
yindo closed this issue 2026-02-20 17:42:44 -05:00
Author
Owner

@sydney-runkle commented on GitHub (Oct 21, 2025):

Thanks for the callout, we're going to port this over shortly 👍

@sydney-runkle commented on GitHub (Oct 21, 2025): Thanks for the callout, we're going to port this over shortly 👍
Author
Owner

@balalofernandez commented on GitHub (Oct 21, 2025):

How are you supposed to call a tool with runtime?

@tool
def tool_name(param1:str, runtime: ToolRuntime) -> Dict:
    pass

doing

def tool_node(state: dict, runtime: Runtime[ContextSchema]):
    """Performs the tool call"""

    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"], runtime=runtime)
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result}

raises an error as well

@balalofernandez commented on GitHub (Oct 21, 2025): How are you supposed to call a tool with runtime? ```python @tool def tool_name(param1:str, runtime: ToolRuntime) -> Dict: pass ``` doing ```python def tool_node(state: dict, runtime: Runtime[ContextSchema]): """Performs the tool call""" result = [] for tool_call in state["messages"][-1].tool_calls: tool = tools_by_name[tool_call["name"]] observation = tool.invoke(tool_call["args"], runtime=runtime) result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"])) return {"messages": result} ``` raises an error as well
Author
Owner

@TBice123123 commented on GitHub (Oct 22, 2025):

How are you supposed to call a tool with runtime?

@tool
def tool_name(param1:str, runtime: ToolRuntime) -> Dict:
pass
doing

def tool_node(state: dict, runtime: Runtime[ContextSchema]):
"""Performs the tool call"""

result = []
for tool_call in state["messages"][-1].tool_calls:
    tool = tools_by_name[tool_call["name"]]
    observation = tool.invoke(tool_call["args"], runtime=runtime)
    result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
return {"messages": result}

raises an error as well

In langchain,Runtime and ToolRuntime are not the same thing—ToolRuntime is currently only effective within the langchain's Agent and _ToolNode.Even not supported in langgraph'ToolNode.

@TBice123123 commented on GitHub (Oct 22, 2025): > How are you supposed to call a tool with runtime? > > @tool > def tool_name(param1:str, runtime: ToolRuntime) -> Dict: > pass > doing > > def tool_node(state: dict, runtime: Runtime[ContextSchema]): > """Performs the tool call""" > > result = [] > for tool_call in state["messages"][-1].tool_calls: > tool = tools_by_name[tool_call["name"]] > observation = tool.invoke(tool_call["args"], runtime=runtime) > result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"])) > return {"messages": result} > raises an error as well In **langchain**,**Runtime** and **ToolRuntime** are not the same thing—**ToolRuntime** is currently only effective within the **langchain**'s Agent and _ToolNode.Even not supported in **langgraph**'ToolNode.
Author
Owner

@sydney-runkle commented on GitHub (Oct 22, 2025):

It will be soon! We're porting support back this week

@sydney-runkle commented on GitHub (Oct 22, 2025): It will be soon! We're porting support back this week
Author
Owner

@balalofernandez commented on GitHub (Oct 22, 2025):

Should I use InjectedToolArg then? Or is there a simpler way to pass the context to the function?

@balalofernandez commented on GitHub (Oct 22, 2025): Should I use `InjectedToolArg` then? Or is there a simpler way to pass the context to the function?
Author
Owner

@ScarletMercy commented on GitHub (Oct 22, 2025):

如何调用具有运行时的工具?

@tool
def tool_name(param1:str, runtime: ToolRuntime) -> Dict:
pass

def tool_node(state: dict, runtime: Runtime[ContextSchema]):
"""Performs the tool call"""

result = []
for tool_call in state["messages"][-1].tool_calls:
    tool = tools_by_name[tool_call["name"]]
    observation = tool.invoke(tool_call["args"], runtime=runtime)
    result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
return {"messages": result}

同样引发错误

I also encountered this problem. Tools with ToolRuntime can only be called after the ToolRuntime parameter is automatically injected through the context, which has already been implemented inside langchain's create_agent. But in langgraph, it is obviously not implemented, so for now we just have to wait for the bug to be fixed.

@ScarletMercy commented on GitHub (Oct 22, 2025): > 如何调用具有运行时的工具? > > @tool > def tool_name(param1:str, runtime: ToolRuntime) -> Dict: > pass > 做 > > def tool_node(state: dict, runtime: Runtime[ContextSchema]): > """Performs the tool call""" > > result = [] > for tool_call in state["messages"][-1].tool_calls: > tool = tools_by_name[tool_call["name"]] > observation = tool.invoke(tool_call["args"], runtime=runtime) > result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"])) > return {"messages": result} > 同样引发错误 I also encountered this problem. Tools with ToolRuntime can only be called after the ToolRuntime parameter is automatically injected through the context, which has already been implemented inside langchain's create_agent. But in langgraph, it is obviously not implemented, so for now we just have to wait for the bug to be fixed.
Author
Owner

@ecosys-technology commented on GitHub (Oct 25, 2025):

Here is a work-around that we use to get the tools to be called in LangGraph.

First, define the tool with the runtime this way :

@tool
def get_weather(
    city: str
    runtime: Annotated[ToolRuntime, InjectedToolArg],
) -> str:
    """Gets the weather in the specified city."""
    
    # Access state through runtime
    if runtime and runtime.state:
        messages = runtime.state.get("messages", [])
    
    return "It is sunny"

Then, create a custom Tool Node :

def create_tool_node_with_runtime(tools: list):
    """
    Create a custom tool node that properly handles ToolRuntime.
    
    This is a workaround for LangGraph's ToolNode not supporting ToolRuntime.
    See: https://github.com/langchain-ai/langgraph/issues/6318
    """
    from langchain_core.runnables import RunnableConfig
    
    def tool_node(state: Dict[str, Any], config: RunnableConfig = None) -> Dict[str, Any]:
        """Execute tools with runtime context."""
        messages = state.get("messages", [])
        if not messages:
            return state
        
        last_message = messages[-1]
        if not isinstance(last_message, AIMessage) or not hasattr(last_message, 'tool_calls'):
            return state
        
        tool_calls = last_message.tool_calls
        if not tool_calls:
            return state
        
        # Create a mapping of tool names to tool functions
        tool_map = {tool.name: tool for tool in tools}
        
        # Execute each tool call
        tool_messages = []
        for tool_call in tool_calls:
            tool_name = tool_call["name"]
            tool_args = tool_call.get("args", {})
            tool_id = tool_call.get("id", "")
            
            if tool_name not in tool_map:
                error_msg = f"Tool '{tool_name}' not found"
                tool_messages.append(
                    ToolMessage(content=error_msg, tool_call_id=tool_id)
                )
                continue
            
            tool_func = tool_map[tool_name]
            
            try:
                # Check if tool expects ToolRuntime
                import inspect
                sig = inspect.signature(tool_func.func if hasattr(tool_func, 'func') else tool_func)
                
                # Create a copy of tool_args for invocation
                invoke_args = tool_args.copy()
                
                # If tool has a 'runtime' parameter, inject it
                if 'runtime' in sig.parameters:
                    # Create ToolRuntime with required fields
                    runtime = ToolRuntime(
                        state=state,
                        config=config or {},
                        tool_call_id=tool_id,
                        store=None,  # Can be added if needed
                        context=None,  # Can be added if needed
                        stream_writer=None  # Can be added if needed
                    )
                    invoke_args['runtime'] = runtime
                
                # Invoke the tool with the modified args
                result = tool_func.invoke(invoke_args, config=config)
                tool_messages.append(
                    ToolMessage(content=str(result), tool_call_id=tool_id)
                )
            except Exception as e:
                error_msg = f"Error executing tool '{tool_name}': {str(e)}"
                print(f"{error_msg}")
                tool_messages.append(
                    ToolMessage(content=error_msg, tool_call_id=tool_id)
                )
        
        return {
            **state,
            "messages": messages + tool_messages
        }
    
    return tool_node

Finally, use this custom function this way :

# Before
workflow.add_node("tools", ToolNode([get_weather, tool2_func]))

# After
workflow.add_node("tools", create_tool_node_with_runtime([get_weather, tool2_func]))
@ecosys-technology commented on GitHub (Oct 25, 2025): Here is a work-around that we use to get the tools to be called in LangGraph. First, define the tool with the runtime this way : ```python @tool def get_weather( city: str runtime: Annotated[ToolRuntime, InjectedToolArg], ) -> str: """Gets the weather in the specified city.""" # Access state through runtime if runtime and runtime.state: messages = runtime.state.get("messages", []) return "It is sunny" ``` Then, create a custom Tool Node : ```python def create_tool_node_with_runtime(tools: list): """ Create a custom tool node that properly handles ToolRuntime. This is a workaround for LangGraph's ToolNode not supporting ToolRuntime. See: https://github.com/langchain-ai/langgraph/issues/6318 """ from langchain_core.runnables import RunnableConfig def tool_node(state: Dict[str, Any], config: RunnableConfig = None) -> Dict[str, Any]: """Execute tools with runtime context.""" messages = state.get("messages", []) if not messages: return state last_message = messages[-1] if not isinstance(last_message, AIMessage) or not hasattr(last_message, 'tool_calls'): return state tool_calls = last_message.tool_calls if not tool_calls: return state # Create a mapping of tool names to tool functions tool_map = {tool.name: tool for tool in tools} # Execute each tool call tool_messages = [] for tool_call in tool_calls: tool_name = tool_call["name"] tool_args = tool_call.get("args", {}) tool_id = tool_call.get("id", "") if tool_name not in tool_map: error_msg = f"Tool '{tool_name}' not found" tool_messages.append( ToolMessage(content=error_msg, tool_call_id=tool_id) ) continue tool_func = tool_map[tool_name] try: # Check if tool expects ToolRuntime import inspect sig = inspect.signature(tool_func.func if hasattr(tool_func, 'func') else tool_func) # Create a copy of tool_args for invocation invoke_args = tool_args.copy() # If tool has a 'runtime' parameter, inject it if 'runtime' in sig.parameters: # Create ToolRuntime with required fields runtime = ToolRuntime( state=state, config=config or {}, tool_call_id=tool_id, store=None, # Can be added if needed context=None, # Can be added if needed stream_writer=None # Can be added if needed ) invoke_args['runtime'] = runtime # Invoke the tool with the modified args result = tool_func.invoke(invoke_args, config=config) tool_messages.append( ToolMessage(content=str(result), tool_call_id=tool_id) ) except Exception as e: error_msg = f"Error executing tool '{tool_name}': {str(e)}" print(f"{error_msg}") tool_messages.append( ToolMessage(content=error_msg, tool_call_id=tool_id) ) return { **state, "messages": messages + tool_messages } return tool_node ``` Finally, use this custom function this way : ```python # Before workflow.add_node("tools", ToolNode([get_weather, tool2_func])) # After workflow.add_node("tools", create_tool_node_with_runtime([get_weather, tool2_func])) ```
Author
Owner

@sydney-runkle commented on GitHub (Oct 27, 2025):

It will be soon! We're porting support back this week

Sorry for the delay folks, will do this week

You can use get_runtime in the meantime

@sydney-runkle commented on GitHub (Oct 27, 2025): > It will be soon! We're porting support back this week Sorry for the delay folks, will do this week You can use `get_runtime` in the meantime
Author
Owner

@sydney-runkle commented on GitHub (Oct 29, 2025):

Just cut a release w/ this ported over!

@sydney-runkle commented on GitHub (Oct 29, 2025): Just cut a release w/ this ported over!
Author
Owner

@gcalabria commented on GitHub (Oct 30, 2025):

thank you, Sydney 🙏

@gcalabria commented on GitHub (Oct 30, 2025): thank you, Sydney 🙏
Author
Owner

@peanutsee commented on GitHub (Nov 12, 2025):

Which release was this part of? 1.0.3?

@peanutsee commented on GitHub (Nov 12, 2025): Which release was this part of? 1.0.3?
Author
Owner

@juanneilson commented on GitHub (Nov 25, 2025):

Works for me with langchain 1.0.5 and langgraph 1.0.3

@juanneilson commented on GitHub (Nov 25, 2025): Works for me with langchain 1.0.5 and langgraph 1.0.3
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#1012