Python: Add second approval-required tool (set_stop_loss) to concurrent_builder_tool_approval sample#4875
Python: Add second approval-required tool (set_stop_loss) to concurrent_builder_tool_approval sample#4875moonbox3 wants to merge 4 commits intomicrosoft:mainfrom
Conversation
Add a second approval-gated tool (set_stop_loss) to the concurrent workflow tool approval sample to demonstrate handling approval requests for different tools in the same concurrent workflow. Changes: - Add set_stop_loss(symbol, stop_price) with approval_mode='always_require' - Include new tool in both agents' tool lists - Update agent instructions and prompt to encourage stop-loss usage - Update docstring to reflect two approval-gated tools - Update sample output to show mixed approval requests Fixes microsoft#4874 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 91%
✓ Correctness
This PR adds a new
set_stop_losstool withapproval_mode="always_require"to the concurrent builder tool approval sample. The new tool follows the same patterns as the existingexecute_tradetool, uses validAnnotated[float, ...]parameter typing, and is correctly wired into both agents' tool lists. The instructions, prompts, and sample output documentation are all updated consistently. No logical, API usage, or correctness issues found.
✓ Security Reliability
This is a straightforward sample file change that adds a new
set_stop_lossmock tool function withapproval_mode="always_require"to demonstrate multiple tools requiring approval in a concurrent workflow. The new tool follows the same patterns as the existingexecute_tradetool. All functions are mocks returning formatted strings with no real side effects. The approval flow inprocess_event_streamhandles the new tool generically via the existingfunction_approval_requesttype check. No security or reliability issues found.
✓ Test Coverage
This PR modifies only a sample file (not library code) to add a second approval-required tool (
set_stop_loss) to demonstrate handling approval requests for different tools in concurrent workflows. The newset_stop_losstool function is trivial (string formatting), and the underlying framework behaviors it exercises—``@tool(approval_mode="always_require")and `ConcurrentBuilder` concurrent approval handling—are already covered by existing unit tests in `test_concurrent.py`, `test_tools.py`, `test_function_invocation_logic.py`, and others. No new library-level behavior is introduced, so no new tests are needed.
✓ Design Approach
This PR adds a second approval-required tool (
set_stop_loss) to the concurrent workflow sample to better demonstrate multiple approval-required tools working in parallel. The change is purely additive and well-scoped: the new tool is correctly decorated withapproval_mode="always_require", added to both agents, and the documentation/comments are updated consistently. There is one pre-existing inconsistency (not introduced here) worth noting: the sample output comment shows"Approval requested for tool: ..."and"Arguments: ..."lines, butprocess_event_streamin this file does not actually print those messages (it only prints"Simulating human approval for: .."). The PR updates the sample output comment to add more of these phantom lines without fixing the underlying gap between documented output and actual code behavior, which could mislead developers trying to understand the sample.
Suggestions
- The sample output comment (lines 200–228) shows
"Approval requested for tool:"and"Arguments: ..."lines thatprocess_event_streamdoes not actually print — only"Simulating human approval for:"is emitted. The sequential sample (sequential_builder_tool_approval.py) correctly prints[APPROVAL REQUIRED], tool name, and arguments before the simulated approval. Consider aligningprocess_event_streamin this file to also print the tool name and arguments when collecting approval requests, so the sample output comment matches the real output.
Automated review by moonbox3's agents
There was a problem hiding this comment.
Pull request overview
Enhances the Python concurrent_builder_tool_approval workflow sample to demonstrate concurrent approval handling across multiple distinct approval-required tools, making the scenario closer to real trading/risk-management flows.
Changes:
- Added a new approval-required tool,
set_stop_loss, alongsideexecute_trade. - Updated both agents’ instructions and tool lists to include stop-loss usage.
- Updated the task prompt and the sample’s expected output narrative to reflect stop-loss approval requests.
…ream (microsoft#4874) Align process_event_stream in concurrent_builder_tool_approval.py to print the tool name and arguments when collecting approval requests, matching the sample output comment and the sequential_builder_tool_approval.py pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
This is a straightforward sample code enhancement that adds informative print statements when tool approval requests are received during concurrent workflow processing. The access pattern (
event.data.function_call.nameandevent.data.function_call.arguments) is correct: whenContent.type == "function_approval_request", thefunction_callattribute is always aContentobject of typefunction_callwhich hasnameandargumentsattributes. The# type: ignorecomments are appropriate since the type checker can't narrowfunction_callfromContent | Nonebased on the runtime type check. The existing code at lines 138-139 already uses the same access pattern without issue.
✓ Security Reliability
The change adds two print statements to a sample script that display the tool name and arguments from a function approval request as it arrives in the event stream. This is purely a sample/demo enhancement for better user experience during the approval flow. The
# type: ignorecomments are consistent with the existing pattern at lines 139 (already present in the file). Thefunction_callattribute onContentis a well-defined field (line 508/561 in_types.py), and the guardevent.data.type == "function_approval_request"ensures the field is populated before access. No security, reliability, or correctness issues are present.
✓ Test Coverage
This PR adds three lines of print output to a sample script (concurrent_builder_tool_approval.py) to display tool name and arguments when an approval request is received. The change is cosmetic console output in a sample file, not in library/package code. No behavioral logic is introduced or modified — only additional print statements inside an existing conditional branch. Sample files in this repo do not have unit tests, and adding tests for print-statement-only changes in samples would provide negligible value. The sample output docstring at the bottom of the file has already been updated in a prior version to reflect this output, so the code and documentation are consistent.
✓ Design Approach
The diff adds two print statements inside the event-stream processor to surface tool name and arguments at the moment an approval request is received, rather than only at the later 'simulating approval' step. This is a sample file, and the output matches what the existing docstring already documents. The approach is correct and consistent with the surrounding code. The
# type: ignorecomments are consistent with the existing pattern at line 139 whererequest.function_call.nameis accessed the same way. Sincefrom_function_approval_requestalways setsfunction_callas a non-optional parameter, the runtime risk is low. No fundamental design issues exist.
Suggestions
- The
function_callfield is typed asContent | None, so accessing.nameand.argumentswithout a None-guard could raiseAttributeErrorif the content were constructed via a path other thanfrom_function_approval_request. Addingif event.data.function_call is not None:around lines 130-131 (and removing the# type: ignorecomments) would be safer, more idiomatic, and consistent with what the type system expects.
Automated review by moonbox3's agents
python/samples/03-workflows/tool-approval/concurrent_builder_tool_approval.py
Outdated
Show resolved
Hide resolved
…osoft#4874) Add explicit None-checks before accessing function_call.name and function_call.arguments in concurrent_builder_tool_approval.py. The function_call field is typed Content | None, so direct attribute access without a guard could raise AttributeError and required type: ignore comments. The None-guard is consistent with the pattern used in _agent_run.py and removes the suppression comments. Also add a regression test verifying that function_call defaults to None and that the None-guard pattern is safe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…icrosoft#4874) Apply the same fix to sequential_builder_tool_approval.py and group_chat_builder_tool_approval.py, which had the identical pattern of accessing function_call.name/arguments without a None-guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 89%
✓ Correctness
Clean, correct change. The added
request.function_call is not Noneguard narows the type fromContent | NonetoContent, which properly eliminates the need for the# type: ignorecomments. The factory methodfrom_function_approval_requestalways setsfunction_callto a non-None value, so the guard won't change runtime behavior in practice, but it's more defensive and type-safe. No correctness issues found.
✓ Security Reliability
The diff adds a defensive
Nonecheck onrequest.function_callbefore accessing its attributes, replacing# type: ignoresuppressions with a proper runtime guard. This is a sound reliability improvement that preventsAttributeErrorif a malformedfunction_approval_requestarrives with aNonefunction_call. The underlyingto_function_approval_responsemethod in the core library also passesself.function_callthrough with# type: ignore, meaning theNonewould propagate silently without this guard. However, whenfunction_call is None, the sample now silently skips the approval request without producing a response entry, which could cause the workflow to hang waiting for a response that never comes. This is a minor concern for sample code but worth noting.
✓ Test Coverage
This PR adds a None-guard (
and request.function_call is not None) to two sample files before accessingfunction_callattributes, and removes the now-unnecessary# type: ignorecomments. The underlyingContent.function_callfield is typed asContent | None, so this change is correct for type safety. A corresponding unit test (test_function_approval_request_function_call_none_guardin test_types.py) already exists that validates the None-guard pattern, covering both the None case and the normal case with attributes. Since these are sample files (not library code), the existing library-level tests adequately cover the underlying behavior. No new tests are needed for this change.
✓ Design Approach
The PR removes
# type: ignoresuppressors by adding anis not Noneguard before accessingrequest.function_call. This is a valid fix:Content.__init__typesfunction_callasOptional[Content], so the type-checker correctly flags unguarded access. The concurrent sample already uses this exact pattern, confirming this is the intended idiom. Thefrom_function_approval_requestfactory does require a non-Nonefunction_call, so this state should never occur via the public API. The only design concern is that whentype == 'function_approval_request'butfunction_call is None(e.g., from a deserialization edge case), both samples silently skip the request, leaving it unanswered inresponses— which could hang the workflow. A warning log in anelsebranch would surface this as a framework bug rather than a silent no-op. That said, since this is sample/demo code and not framework logic, this is a minor concern rather than a blocking issue.
Suggestions
- When
type == 'function_approval_request'butfunction_call is None, both samples silently skip the request without adding a response entry, which could hang the workflow. Consider adding anelsebranch with a warning log (e.g.,print(f'WARNING: function_approval_request {request_id} has no function_call, skipping')) so this edge case is detectable rather than a silent no-op. - Longer-term, the root cause is that
Contentuses a monolithic class with a singlefunction_call: Content | Nonefield for all type variants (the recurring# type: ignoreinto_function_approval_responseat line 1238 of_types.pyexposes the same gap). A discriminated union or per-type dataclasses would eliminate this class of type suppressions entirely.
Automated review by moonbox3's agents
Motivation and Context
The concurrent_builder_tool_approval sample only demonstrated a single tool requiring approval (
execute_trade), which didn't fully showcase how concurrent workflows handle approval requests for multiple different tools simultaneously. Adding a second approval-required tool makes the sample more realistic and informative.Fixes #4874
Description
A new
set_stop_losstool withapproval_mode="always_require"is added to the sample alongside the existingexecute_tradetool. Both agents' instructions and tool lists are updated to include the new tool, and the task prompt now requests stop-loss orders. The expected output comments are updated to reflect the additional approval requests, demonstrating that concurrent workflows can independently handle multiple approval-requiring tools across parallel agents.Contribution Checklist