diff --git a/src/praisonai-agents/praisonaiagents/agent/chat_mixin.py b/src/praisonai-agents/praisonaiagents/agent/chat_mixin.py index a5643af7b1..544c908f37 100644 --- a/src/praisonai-agents/praisonaiagents/agent/chat_mixin.py +++ b/src/praisonai-agents/praisonaiagents/agent/chat_mixin.py @@ -4439,7 +4439,12 @@ async def _achat_completion(self, response, tools, reasoning_steps=False): # after-context aggregation) are fired once, guarded, inside # execute_tool_async — no inline duplicate dispatch here. # Pass the tools list to honor task-scoped tools - result = await self.execute_tool_async(function_name, arguments, tools_override=tools) + result = await self.execute_tool_async( + function_name, + arguments, + tool_call_id=getattr(tool_call, "id", None), + tools_override=tools, + ) results.append(result) except Exception as e: diff --git a/src/praisonai-agents/praisonaiagents/agent/execution_mixin.py b/src/praisonai-agents/praisonaiagents/agent/execution_mixin.py index e7b76e5bfd..f3d03085a5 100644 --- a/src/praisonai-agents/praisonaiagents/agent/execution_mixin.py +++ b/src/praisonai-agents/praisonaiagents/agent/execution_mixin.py @@ -1519,6 +1519,7 @@ async def _execute_tool_async_via_middleware( run_id=getattr(self, '_current_run_id', 'unknown'), session_id=getattr(self, '_session_id', None) or 'default', tool_name=function_name, + metadata={"tool_call_id": tool_call_id}, ), ) diff --git a/src/praisonai-agents/praisonaiagents/agent/tool_execution.py b/src/praisonai-agents/praisonaiagents/agent/tool_execution.py index 4a9bf3b0af..8db7652735 100644 --- a/src/praisonai-agents/praisonaiagents/agent/tool_execution.py +++ b/src/praisonai-agents/praisonaiagents/agent/tool_execution.py @@ -489,6 +489,7 @@ def execute_tool(self, function_name: str, arguments: Dict[str, Any], tool_call_ run_id=getattr(self, '_current_run_id', 'unknown'), session_id=getattr(self, '_session_id', None) or 'default', tool_name=function_name, + metadata={"tool_call_id": tool_call_id}, ), ) diff --git a/src/praisonai-agents/tests/unit/agent/test_achat_unified_dispatch.py b/src/praisonai-agents/tests/unit/agent/test_achat_unified_dispatch.py index 438fc94207..9295cb062a 100644 --- a/src/praisonai-agents/tests/unit/agent/test_achat_unified_dispatch.py +++ b/src/praisonai-agents/tests/unit/agent/test_achat_unified_dispatch.py @@ -104,6 +104,7 @@ def run(self, **kwargs): assert not hasattr(browser_tool, "__name__") tool_call = SimpleNamespace( + id="legacy-tool-call-001", function=SimpleNamespace(name="browserbase", arguments="{}") ) response = SimpleNamespace( @@ -117,6 +118,7 @@ def run(self, **kwargs): mock_exec.assert_awaited_once() assert mock_exec.await_args[0][0] == "browserbase" + assert mock_exec.await_args.kwargs["tool_call_id"] == "legacy-tool-call-001" assert results is not None diff --git a/src/praisonai-agents/tests/unit/hooks/test_tool_call_identity_context.py b/src/praisonai-agents/tests/unit/hooks/test_tool_call_identity_context.py new file mode 100644 index 0000000000..26cfa608f7 --- /dev/null +++ b/src/praisonai-agents/tests/unit/hooks/test_tool_call_identity_context.py @@ -0,0 +1,68 @@ +"""Tests for tool-call identity exposed through middleware context.""" + +from __future__ import annotations + +import pytest +from praisonaiagents import Agent +from praisonaiagents.hooks import wrap_tool_call + + +def test_sync_tool_middleware_receives_original_tool_call_id(): + observed_ids: list[str | None] = [] + handler_calls: list[str] = [] + + @wrap_tool_call + def capture_identity(request, call_next): + observed_ids.append(request.context.metadata.get("tool_call_id")) + return call_next(request) + + def inert_tool(value: str) -> str: + handler_calls.append(value) + return "completed" + + agent = Agent( + name="sync-middleware-identity", + instructions="Exercise one inert test tool.", + tools=[inert_tool], + hooks=[capture_identity], + approval=True, + ) + + result = agent.execute_tool( + "inert_tool", {"value": "synthetic-value"}, "sync-tool-call-001" + ) + + assert result == "completed" + assert handler_calls == ["synthetic-value"] + assert observed_ids == ["sync-tool-call-001"] + + +@pytest.mark.asyncio +async def test_async_tool_middleware_receives_original_tool_call_id(): + observed_ids: list[str | None] = [] + handler_calls: list[str] = [] + + @wrap_tool_call + def capture_identity(request, call_next): + observed_ids.append(request.context.metadata.get("tool_call_id")) + return call_next(request) + + async def inert_tool(value: str) -> str: + handler_calls.append(value) + return "completed" + + agent = Agent( + name="async-middleware-identity", + instructions="Exercise one inert test tool.", + tools=[inert_tool], + hooks=[capture_identity], + approval=True, + ) + + result = await agent.execute_tool_async( + "inert_tool", {"value": "synthetic-value"}, "async-tool-call-001" + ) + + assert result == "completed" + assert handler_calls == ["synthetic-value"] + assert observed_ids == ["async-tool-call-001"]