Skip to content

Python: Fix as_tool() swallowing user_input_request events#4568

Open
giles17 wants to merge 4 commits intomicrosoft:mainfrom
giles17:oauth-as-tool
Open

Python: Fix as_tool() swallowing user_input_request events#4568
giles17 wants to merge 4 commits intomicrosoft:mainfrom
giles17:oauth-as-tool

Conversation

@giles17
Copy link
Contributor

@giles17 giles17 commented Mar 9, 2026

Motivation and Context

Fixes #4499.

When a sub-agent wrapped with as_tool() emits user_input_request content (e.g. oauth_consent_request from PR #4197), the event is silently dropped. Both code paths in as_tool() collapse the response to .text, so the caller receives an empty string with no indication that user action is required.

Description

This fix propagates user_input_request Content items from sub-agents through the tool invocation pipeline to the parent agent's response, leveraging the existing user_input_requestAgentExecutorctx.request_info() mechanism.

New exception: UserInputRequiredException (exceptions.py)

  • Extends ToolException; carries contents: list[Any] from the sub-agent
  • Follows the same pattern as MiddlewareTermination — a structured exception that escapes the generic except Exception catch-all to alter control flow

as_tool() changes (_agents.py)

  • Both streaming and non-streaming paths now check response.user_input_requests before returning .text
  • Raises UserInputRequiredException if non-empty

Tool invocation pipeline changes (_tools.py)

  • Two except UserInputRequiredException: raise guards in _auto_invoke_function (direct + middleware paths) prevent the generic except Exception from swallowing the signal
  • invoke_with_termination_handling catches the exception and extracts the Content items
  • _handle_function_call_results expanded to detect any Content with user_input_request=True, returning "action": "return" to stop the function invocation loop and surface the Content in the parent response

Event flow after fix:

Sub-agent → oauth_consent_request Content (user_input_request=True)
  → as_tool() raises UserInputRequiredException
  → _auto_invoke_function re-raises (not swallowed)
  → invoke_with_termination_handling catches, returns Content
  → _handle_function_call_results → "action": "return"
  → Content in parent response.messages
  → AgentExecutor → ctx.request_info() → pauses workflow

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

…#4499)

When a sub-agent wrapped with as_tool() emits user_input_request content
(e.g. oauth_consent_request), the event was silently dropped because
as_tool() only returns .text. This fix propagates user_input_request
Content through the tool invocation pipeline to the parent response.

Changes:
- Add UserInputRequiredException to bridge the str-return boundary
- Check response.user_input_requests in as_tool() before returning .text
- Add re-raise guards in _auto_invoke_function to prevent the generic
  except Exception from swallowing the exception
- Catch in invoke_with_termination_handling to extract Content items
- Expand _handle_function_call_results to detect user_input_request Content

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 9, 2026 19:49
@markwallace-microsoft
Copy link
Member

markwallace-microsoft commented Mar 9, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _agents.py3584288%444, 448, 510, 894, 930, 946, 1036–1040, 1106, 1231, 1247, 1249, 1262, 1268, 1304, 1306, 1315–1320, 1325, 1327, 1333–1334, 1341, 1343–1344, 1352–1353, 1356–1358, 1366–1367, 1369, 1374, 1376
   _tools.py8008389%168–169, 328, 330, 348–350, 358, 376, 390, 397, 404, 420, 422, 429, 437, 469, 494, 498, 515–517, 564–566, 588, 644, 666, 733–739, 775, 786–797, 816–818, 822, 826, 840–842, 1181, 1201, 1279–1285, 1409, 1413, 1426, 1461, 1579, 1609, 1629, 1631, 1687, 1750, 1941–1942, 1993, 2062–2063, 2123, 2128, 2135
   exceptions.py670100% 
TOTAL22713260188% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
4952 20 💤 0 ❌ 0 🔥 1m 21s ⏱️

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a gap in the Python tool-wrapping pipeline where Agent.as_tool() would collapse sub-agent output to .text and silently drop user_input_request content (e.g., oauth_consent_request), preventing parent agents/workflows from pausing for required user action.

Changes:

  • Introduces UserInputRequiredException to carry sub-agent user_input_request contents through tool invocation.
  • Updates Agent.as_tool() (streaming + non-streaming) to raise UserInputRequiredException when user_input_requests are present.
  • Updates tool invocation handling to re-raise/catch UserInputRequiredException, surface the request Content in the parent response, and stop the function loop; adds tests for both as_tool and end-to-end invocation behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
python/packages/core/agent_framework/exceptions.py Adds UserInputRequiredException to represent “user action required” as a structured control-flow signal.
python/packages/core/agent_framework/_agents.py Changes as_tool() to detect user_input_requests and raise instead of returning collapsed .text.
python/packages/core/agent_framework/_tools.py Ensures the exception isn’t swallowed, converts it into surfaced Content, and stops tool-loop processing when user input is required.
python/packages/core/agent_framework/init.py Exports the new exception from the package surface.
python/packages/core/tests/core/test_agents.py Adds coverage for as_tool() raising in streaming/non-streaming and normal text behavior.
python/packages/core/tests/core/test_function_invocation_logic.py Adds an end-to-end test ensuring user_input_request content reaches the parent response via tool invocation.

You can also share your feedback on Copilot code review. Take the survey.

…t.id, suppress logging

- Propagate all Content items from UserInputRequiredException, not just the first
- Set content.id for AgentExecutor request tracking
- Change log_level to None (control-flow signal, not an error)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@giles17 giles17 changed the title Python: Fix as_tool() swallowing user_input_request events Python: Fix as_tool() swallowing user_input_request events Mar 9, 2026
@giles17 giles17 changed the title Python: Fix as_tool() swallowing user_input_request events Python: Fix as_tool() swallowing user_input_request events Mar 9, 2026
eavanvalkenburg added a commit to eavanvalkenburg/agent-framework that referenced this pull request Mar 10, 2026
…ons (microsoft#3642)

Audit and refactor public **kwargs usage across core agents, chat clients,
tools, sessions, and provider packages per the migration strategy codified
in CODING_STANDARD.md.

Key changes:
- Add explicit runtime buckets: function_invocation_kwargs and client_kwargs
  on RawAgent.run() and chat client get_response() layers.
- Refactor FunctionTool to prefer explicit ctx: FunctionInvocationContext
  injection; legacy **kwargs tools still work via _forward_runtime_kwargs.
- Refactor Agent.as_tool() to use direct JSON schema, always-streaming
  wrapper, approval_mode parameter, and UserInputRequiredException
  propagation (integrates PR microsoft#4568 behavior).
- Remove implicit session bleeding into FunctionInvocationContext; tools
  that need a session must receive it via function_invocation_kwargs.
- Lower chat-client layers after FunctionInvocationLayer accept only
  compatibility **kwargs (client_kwargs flattened, function_invocation_kwargs
  ignored).
- Add layered docstring composition from Raw... implementations via
  _docstrings.py helper.
- Clean up provider constructors to use explicit additional_properties.
- Deprecation warnings on legacy direct kwargs paths.
- Update samples, tests, and typing across all 23 packages.

Resolves microsoft#3642

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg added a commit to eavanvalkenburg/agent-framework that referenced this pull request Mar 10, 2026
…ons (microsoft#3642)

Audit and refactor public **kwargs usage across core agents, chat clients,
tools, sessions, and provider packages per the migration strategy codified
in CODING_STANDARD.md.

Key changes:
- Add explicit runtime buckets: function_invocation_kwargs and client_kwargs
  on RawAgent.run() and chat client get_response() layers.
- Refactor FunctionTool to prefer explicit ctx: FunctionInvocationContext
  injection; legacy **kwargs tools still work via _forward_runtime_kwargs.
- Refactor Agent.as_tool() to use direct JSON schema, always-streaming
  wrapper, approval_mode parameter, and UserInputRequiredException
  propagation (integrates PR microsoft#4568 behavior).
- Remove implicit session bleeding into FunctionInvocationContext; tools
  that need a session must receive it via function_invocation_kwargs.
- Lower chat-client layers after FunctionInvocationLayer accept only
  compatibility **kwargs (client_kwargs flattened, function_invocation_kwargs
  ignored).
- Add layered docstring composition from Raw... implementations via
  _docstrings.py helper.
- Clean up provider constructors to use explicit additional_properties.
- Deprecation warnings on legacy direct kwargs paths.
- Update samples, tests, and typing across all 23 packages.

Resolves microsoft#3642

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg added a commit to eavanvalkenburg/agent-framework that referenced this pull request Mar 10, 2026
…ons (microsoft#3642)

Audit and refactor public **kwargs usage across core agents, chat clients,
tools, sessions, and provider packages per the migration strategy codified
in CODING_STANDARD.md.

Key changes:
- Add explicit runtime buckets: function_invocation_kwargs and client_kwargs
  on RawAgent.run() and chat client get_response() layers.
- Refactor FunctionTool to prefer explicit ctx: FunctionInvocationContext
  injection; legacy **kwargs tools still work via _forward_runtime_kwargs.
- Refactor Agent.as_tool() to use direct JSON schema, always-streaming
  wrapper, approval_mode parameter, and UserInputRequiredException
  propagation (integrates PR microsoft#4568 behavior).
- Remove implicit session bleeding into FunctionInvocationContext; tools
  that need a session must receive it via function_invocation_kwargs.
- Lower chat-client layers after FunctionInvocationLayer accept only
  compatibility **kwargs (client_kwargs flattened, function_invocation_kwargs
  ignored).
- Add layered docstring composition from Raw... implementations via
  _docstrings.py helper.
- Clean up provider constructors to use explicit additional_properties.
- Deprecation warnings on legacy direct kwargs paths.
- Update samples, tests, and typing across all 23 packages.

Resolves microsoft#3642

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 19:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


You can also share your feedback on Copilot code review. Take the survey.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: as_tool() swallows CUSTOM(oauth_consent_request) events — no way to surface consent flow to caller

3 participants