diff --git a/libs/core/langchain_core/tools/base.py b/libs/core/langchain_core/tools/base.py index ff62f3091d4..998490c45df 100644 --- a/libs/core/langchain_core/tools/base.py +++ b/libs/core/langchain_core/tools/base.py @@ -934,6 +934,13 @@ def _prep_run_args( config = ensure_config(config) if _is_tool_call(input): tool_call_id: Optional[str] = cast(ToolCall, input)["id"] + if not tool_call_id: + msg = ( + "Tool call ID must be a non-empty string. " + f"Got '{tool_call_id}' for tool call '{input}'." + ) + raise ValueError(msg) + tool_input: Union[str, dict] = cast(ToolCall, input)["args"].copy() else: tool_call_id = None diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index afe8c09113a..e7c429a9819 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -2457,3 +2457,22 @@ def test_simple_tool_args_schema_dict() -> None: assert tool.args == { "a": {"title": "A", "type": "integer"}, } + + +def test_empty_tool_call_id() -> None: + @tool + def foo(x: int) -> str: + """Foo.""" + return "hi" + + for empty_tool_call_id in (None, ""): + with pytest.raises( + ValueError, + match=( + "Tool call ID must be a non-empty string. " + f"Got '{empty_tool_call_id}' for tool call '.*'." + ), + ): + foo.invoke( + {"type": "tool_call", "args": {"x": 0}, "id": empty_tool_call_id} + )