Hi All,
I’m implementing a custom graph with dynamic tool binding. To implement ReAct loop, I’m handing messages from LLM in a separate node to execute tool calls. The related code looks like the following:
class SessionState(TypedDict):
messages: List[langchain.messages.AnyMessage]
current_tool_list: List[langchain_core.runnables.base.Runnable]
@tool
async def sample_tool(ordinary_param_1: str, runtime: ToolRuntime) -> int:
"""returns 10"""
return 10
async def _lg_node_tool_call(state: SessionState, runtime: Runtime) -> dict:
tool_result_list = []
for tool_call in state["messages"][-1].tool_calls:
tool_name = tool_call["name"]
tool_name_list = [t.get_name() for t in state["current_tool_list"]]
if tool_name in tool_name_list:
tool_func = cast(
langchain_core.tools.structured.StructuredTool,
state["current_tool_list"][tool_name_list.index(tool_name)],
)
observation = None
call_args = tool_call["args"]
if tool_func.coroutine is not None:
observation = await tool_func.ainvoke(call_args)
if tool_func.func is not None:
observation = tool_func.invoke(call_args)
tool_result_list.append(
langchain.messages.ToolMessage(
content=observation, tool_call_id=tool_call["id"]
)
)
return {"messages": state["messages"] + tool_result_list}
graph_builder = langgraph.graph.StateGraph(
state_schema=SessionState,
)
# other nodes and edges are defined here
self._agent_graph_builder.add_node("tool_node", _lg_node_tool_call)
graph = graph_builder.compile()
graph.invoke(
{
"messages": [],
"current_tool_list": [sample_tool],
}
)
This works good with tools that accept only ordinary parameters. However if tool accepts a runtime, like sample_tool() above, I’m getting the Pydantic exception that runtime is missed:
pydantic_core._pydantic_core.ValidationError: 1 validation error for sample_tool
runtime
Field required [type=missing, input_value={'ordinary_param_1': 'test_input'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.12/v/missing
I tried to create an instance of langchain.tools.ToolRuntime object manually inside _lg_node_tool_call(), but Pydantic starts to complain on its fields (e.g. context must be None and stream_writer must be callable, etc.), so I’m not sure if this manual construction is supported.
Is there a way to construct ToolRuntime properly or at least get all Pydantic rules for it?