From 4fc01aea2de392251b3d0f25e6d5d33d20d387ad Mon Sep 17 00:00:00 2001 From: atty57 <99388680+atty57@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:11:36 -0400 Subject: [PATCH] Python: Forward function_invocation_kwargs through DevUI to agent.run() (#7344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevUI built the agent run kwargs by hand and forwarded only `stream` and `session`, so tools that read request-scoped values via FunctionInvocationContext never received them. Read `function_invocation_kwargs` from the request — `extra_body` (the same channel as response_id/checkpoint_id) or a top-level field — and merge it into the kwargs passed to `agent.run()`. Adds a parametrized test covering both channels. --- .../devui/agent_framework_devui/_executor.py | 11 ++++ .../devui/tests/devui/test_execution.py | 50 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/python/packages/devui/agent_framework_devui/_executor.py b/python/packages/devui/agent_framework_devui/_executor.py index e217341511c..c31f0fdec89 100644 --- a/python/packages/devui/agent_framework_devui/_executor.py +++ b/python/packages/devui/agent_framework_devui/_executor.py @@ -376,6 +376,17 @@ async def _execute_agent( if session: run_kwargs["session"] = session + # Forward request-scoped function_invocation_kwargs so tools can read them + # via FunctionInvocationContext. Accept them in extra_body (same channel as + # response_id/checkpoint_id) or as a top-level field (model_extra). + fik = None + if request.extra_body: + fik = request.extra_body.get("function_invocation_kwargs") + if fik is None and request.model_extra: + fik = request.model_extra.get("function_invocation_kwargs") + if isinstance(fik, dict): + run_kwargs["function_invocation_kwargs"] = fik + stream = cast(Any, agent.run(user_message, **run_kwargs)) async for update in stream: for trace_event in trace_collector.get_pending_events(): diff --git a/python/packages/devui/tests/devui/test_execution.py b/python/packages/devui/tests/devui/test_execution.py index f1c03c1728e..b84e8a7c608 100644 --- a/python/packages/devui/tests/devui/test_execution.py +++ b/python/packages/devui/tests/devui/test_execution.py @@ -596,6 +596,56 @@ def create_session(self, **kwargs): assert "Processed: hello" in text_events[0].delta +@pytest.mark.parametrize( + "request_extras", + [ + {"extra_body": {"function_invocation_kwargs": {"tenantId": "abc123"}}}, # documented channel + {"function_invocation_kwargs": {"tenantId": "abc123"}}, # top-level (model_extra), as issue #7344 tried + ], +) +async def test_agent_execution_forwards_function_invocation_kwargs(request_extras): + """DevUI forwards request function_invocation_kwargs into agent.run() (issue #7344).""" + from agent_framework import AgentResponseUpdate, AgentSession, Content + + captured: dict[str, Any] = {} + + class RecordingAgent: + """Agent that records the kwargs its run() is called with.""" + + id = "kwargs_test" + name = "Kwargs Test Agent" + description = "Records run() kwargs" + + def run(self, messages=None, *, stream=False, session=None, **kwargs): + captured.update(kwargs) + return self._stream_impl(messages) + + async def _stream_impl(self, messages): + yield AgentResponseUpdate(contents=[Content.from_text(text="ok")], role="assistant") + + def create_session(self, **kwargs): + return AgentSession() + + discovery = EntityDiscovery(None) + executor = AgentFrameworkExecutor(discovery, MessageMapper()) + + agent = RecordingAgent() + entity_info = await discovery.create_entity_info_from_object(agent, source="test") + discovery.register_entity(entity_info.id, entity_info, agent) + + request = AgentFrameworkRequest( + metadata={"entity_id": entity_info.id}, + input="hello", + stream=True, + **request_extras, + ) + + async for _event in executor.execute_streaming(request): + pass + + assert captured.get("function_invocation_kwargs") == {"tenantId": "abc123"} + + # ============================================================================= # Full Pipeline Tests for SequentialBuilder # =============================================================================