Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions libs/core/langchain_core/runnables/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ class EventData(TypedDict, total=False):
chunks support addition in general, and adding them up should result
in the output of the `Runnable` that generated the event.
"""
tool_call_id: NotRequired[str | None]
"""The tool call ID associated with the tool execution.

This field is only available for tool-related events (e.g., `on_tool_error`)
and can be used to link errors to specific tool calls in stateless agent
implementations.

!!! version-added "Added in version 1.0.0"
"""


class BaseStreamEvent(TypedDict):
Expand Down
6 changes: 4 additions & 2 deletions libs/core/langchain_core/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ def run(
# but if it is we will send a `None` value to the callback instead
# TODO: will need to address issue via a patch.
inputs=tool_input if isinstance(tool_input, dict) else None,
tool_call_id=tool_call_id,
**kwargs,
)

Expand Down Expand Up @@ -852,7 +853,7 @@ def run(
error_to_raise = e

if error_to_raise:
run_manager.on_tool_error(error_to_raise)
run_manager.on_tool_error(error_to_raise, tool_call_id=tool_call_id)
raise error_to_raise
output = _format_output(content, artifact, tool_call_id, self.name, status)
run_manager.on_tool_end(output, color=color, name=self.name, **kwargs)
Expand Down Expand Up @@ -916,6 +917,7 @@ async def arun(
# but if it is we will send a `None` value to the callback instead
# TODO: will need to address issue via a patch.
inputs=tool_input if isinstance(tool_input, dict) else None,
tool_call_id=tool_call_id,
**kwargs,
)
content = None
Expand Down Expand Up @@ -965,7 +967,7 @@ async def arun(
error_to_raise = e

if error_to_raise:
await run_manager.on_tool_error(error_to_raise)
await run_manager.on_tool_error(error_to_raise, tool_call_id=tool_call_id)
raise error_to_raise

output = _format_output(content, artifact, tool_call_id, self.name, status)
Expand Down
41 changes: 26 additions & 15 deletions libs/core/langchain_core/tracers/event_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class RunInfo(TypedDict):
"""The inputs to the run."""
parent_run_id: UUID | None
"""The ID of the parent run."""
tool_call_id: NotRequired[str | None]
"""The tool call ID associated with the run."""


def _assign_name(name: str | None, serialized: dict[str, Any] | None) -> str:
Expand Down Expand Up @@ -300,6 +302,10 @@ def _write_run_start_info(
# vs. None value.
info["inputs"] = kwargs["inputs"]

if "tool_call_id" in kwargs:
# Store tool_call_id in run info for linking errors to tool calls
info["tool_call_id"] = kwargs["tool_call_id"]

self.run_map[run_id] = info
self.parent_map[run_id] = parent_run_id

Expand Down Expand Up @@ -658,6 +664,7 @@ async def on_tool_start(
name_=name_,
run_type="tool",
inputs=inputs,
tool_call_id=kwargs.get("tool_call_id"),
)

self._send(
Expand Down Expand Up @@ -686,23 +693,27 @@ async def on_tool_error(
**kwargs: Any,
) -> None:
"""Run when tool errors."""
# Extract tool_call_id from kwargs first, fallback to run_info if available
tool_call_id = kwargs.get("tool_call_id")
run_info, inputs = self._get_tool_run_info_with_inputs(run_id)

self._send(
{
"event": "on_tool_error",
"data": {
"error": error,
"input": inputs,
},
"run_id": str(run_id),
"name": run_info["name"],
"tags": run_info["tags"],
"metadata": run_info["metadata"],
"parent_ids": self._get_parent_ids(run_id),
# If not in kwargs, check run_info (fallback for backward compatibility)
if tool_call_id is None:
tool_call_id = run_info.get("tool_call_id")

event: StandardStreamEvent = {
"event": "on_tool_error",
"data": {
"error": error,
"input": inputs,
"tool_call_id": tool_call_id,
},
"tool",
)
"run_id": str(run_id),
"name": run_info["name"],
"tags": run_info["tags"],
"metadata": run_info["metadata"],
"parent_ids": self._get_parent_ids(run_id),
}
self._send(event, "tool")

@override
async def on_tool_end(self, output: Any, *, run_id: UUID, **kwargs: Any) -> None:
Expand Down