Skip to content

Commit 055cccd

Browse files
chore(langchain): allow injection of ToolRuntime and generic ToolRuntime[ContextT, StateT] (#33546)
Adds special private helper to allow direct injection of `ToolRuntime` in tools, plus adding guards for generic annotations w/ `get_origin`. Went w/ the private helper so that we didn't change behavior for other injected types.
1 parent 361514d commit 055cccd

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

libs/core/langchain_core/tools/base.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,26 @@ class InjectedToolArg:
12101210
"""
12111211

12121212

1213+
class _DirectlyInjectedToolArg:
1214+
"""Annotation for tool arguments that are injected at runtime.
1215+
1216+
Injected via direct type annotation, rather than annotated metadata.
1217+
1218+
For example, ToolRuntime is a directly injected argument.
1219+
Note the direct annotation rather than the verbose alternative:
1220+
Annotated[ToolRuntime, InjectedRuntime]
1221+
```python
1222+
from langchain_core.tools import tool, ToolRuntime
1223+
1224+
1225+
@tool
1226+
def foo(x: int, runtime: ToolRuntime) -> str:
1227+
# use runtime.state, runtime.context, runtime.store, etc.
1228+
...
1229+
```
1230+
"""
1231+
1232+
12131233
class InjectedToolCallId(InjectedToolArg):
12141234
"""Annotation for injecting the tool call ID.
12151235
@@ -1237,6 +1257,24 @@ def foo(
12371257
"""
12381258

12391259

1260+
def _is_directly_injected_arg_type(type_: Any) -> bool:
1261+
"""Check if a type annotation indicates a directly injected argument.
1262+
1263+
This is currently only used for ToolRuntime.
1264+
Checks if either the annotation itself is a subclass of _DirectlyInjectedToolArg
1265+
or the origin of the annotation is a subclass of _DirectlyInjectedToolArg.
1266+
1267+
Ex: ToolRuntime or ToolRuntime[ContextT, StateT] would both return True.
1268+
"""
1269+
return (
1270+
isinstance(type_, type) and issubclass(type_, _DirectlyInjectedToolArg)
1271+
) or (
1272+
(origin := get_origin(type_)) is not None
1273+
and isinstance(origin, type)
1274+
and issubclass(origin, _DirectlyInjectedToolArg)
1275+
)
1276+
1277+
12401278
def _is_injected_arg_type(
12411279
type_: type | TypeVar, injected_type: type[InjectedToolArg] | None = None
12421280
) -> bool:
@@ -1249,7 +1287,15 @@ def _is_injected_arg_type(
12491287
Returns:
12501288
`True` if the type is an injected argument, `False` otherwise.
12511289
"""
1252-
injected_type = injected_type or InjectedToolArg
1290+
if injected_type is None:
1291+
# if no injected type is specified,
1292+
# check if the type is a directly injected argument
1293+
if _is_directly_injected_arg_type(type_):
1294+
return True
1295+
injected_type = InjectedToolArg
1296+
1297+
# if the type is an Annotated type, check if annotated metadata
1298+
# is an intance or subclass of the injected type
12531299
return any(
12541300
isinstance(arg, injected_type)
12551301
or (isinstance(arg, type) and issubclass(arg, injected_type))

libs/langchain_v1/langchain/tools/tool_node.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def my_tool(x: int) -> str:
7575
from langchain_core.tools.base import (
7676
TOOL_MESSAGE_BLOCK_TYPES,
7777
ToolException,
78+
_DirectlyInjectedToolArg,
7879
get_all_basemodel_annotations,
7980
)
8081
from langgraph._internal._runnable import RunnableCallable
@@ -1349,7 +1350,7 @@ def custom_condition(state):
13491350

13501351

13511352
@dataclass
1352-
class ToolRuntime(InjectedToolArg, Generic[ContextT, StateT]):
1353+
class ToolRuntime(_DirectlyInjectedToolArg, Generic[ContextT, StateT]):
13531354
"""Runtime context automatically injected into tools.
13541355
13551356
When a tool function has a parameter named 'tool_runtime' with type hint

0 commit comments

Comments
 (0)