chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,239 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from collections.abc import AsyncIterable
from typing import Annotated
from agent_framework import (
Agent,
Content,
Message,
WorkflowEvent,
tool,
)
from agent_framework.foundry import FoundryChatClient
from agent_framework.orchestrations import ConcurrentBuilder
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
Sample: Concurrent Workflow with Tool Approval Requests
This sample demonstrates how to use ConcurrentBuilder with tools that require human
approval before execution. Multiple agents run in parallel, and any tool requiring
approval will pause the workflow until the human responds.
This sample works as follows:
1. A ConcurrentBuilder workflow is created with two agents running in parallel.
2. Both agents have the same tools, including two requiring approval (execute_trade, set_stop_loss).
3. Both agents receive the same task and work concurrently on their respective stocks.
4. When either agent tries to execute a trade or set a stop-loss, it triggers an approval request.
5. The sample simulates human approval and the workflow completes.
6. Results from both agents are aggregated and output.
Purpose:
Show how tool call approvals work in parallel execution scenarios where multiple
agents may independently trigger approval requests for different tools.
Demonstrate:
- Handling multiple approval requests from different agents in concurrent workflows.
- Handling approval requests for different tools during concurrent agent execution.
- Understanding that approval pauses only the agent that triggered it, not all agents.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL must be set to your Azure OpenAI model deployment name.
- Basic familiarity with ConcurrentBuilder and streaming workflow events.
"""
# 1. Define market data tools (no approval required)
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# See:
# samples/02-agents/tools/function_tool_with_approval.py
# samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_stock_price(symbol: Annotated[str, "The stock ticker symbol"]) -> str:
"""Get the current stock price for a given symbol."""
# Mock data for demonstration
prices = {"AAPL": 175.50, "GOOGL": 140.25, "MSFT": 378.90, "AMZN": 178.75}
price = prices.get(symbol.upper(), 100.00)
return f"{symbol.upper()}: ${price:.2f}"
@tool(approval_mode="never_require")
def get_market_sentiment(symbol: Annotated[str, "The stock ticker symbol"]) -> str:
"""Get market sentiment analysis for a stock."""
# Mock sentiment data
mock_data = {
"AAPL": "Market sentiment for AAPL: Bullish (68% positive mentions in last 24h)",
"GOOGL": "Market sentiment for GOOGL: Neutral (50% positive mentions in last 24h)",
"MSFT": "Market sentiment for MSFT: Bullish (72% positive mentions in last 24h)",
"AMZN": "Market sentiment for AMZN: Bearish (40% positive mentions in last 24h)",
}
return mock_data.get(symbol.upper(), f"Market sentiment for {symbol.upper()}: Unknown")
# 2. Define trading tools (approval required)
@tool(approval_mode="always_require")
def execute_trade(
symbol: Annotated[str, "The stock ticker symbol"],
action: Annotated[str, "Either 'buy' or 'sell'"],
quantity: Annotated[int, "Number of shares to trade"],
) -> str:
"""Execute a stock trade. Requires human approval due to financial impact."""
return f"Trade executed: {action.upper()} {quantity} shares of {symbol.upper()}"
@tool(approval_mode="always_require")
def set_stop_loss(
symbol: Annotated[str, "The stock ticker symbol"],
stop_price: Annotated[float, "The stop-loss price"],
) -> str:
"""Set a stop-loss order for a stock. Requires human approval due to financial impact."""
return f"Stop-loss set for {symbol.upper()} at ${stop_price:.2f}"
@tool(approval_mode="never_require")
def get_portfolio_balance() -> str:
"""Get current portfolio balance and available funds."""
return "Portfolio: $50,000 invested, $10,000 cash available. Holdings: AAPL, GOOGL, MSFT."
def _print_output(event: WorkflowEvent) -> None:
if not event.data:
raise ValueError("WorkflowEvent has no data")
if not isinstance(event.data, list) and not all(isinstance(msg, Message) for msg in event.data):
raise ValueError("WorkflowEvent data is not a list of Message")
messages: list[Message] = event.data # type: ignore
print("\n" + "-" * 60)
print("Workflow completed. Aggregated results from both agents:")
for msg in messages:
if msg.text:
print(f"- {msg.author_name or msg.role}: {msg.text}")
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, Content] | None:
"""Process events from the workflow stream to capture human feedback requests."""
requests: dict[str, Content] = {}
async for event in stream:
if event.type == "request_info" and isinstance(event.data, Content):
# We are only expecting tool approval requests in this sample
requests[event.request_id] = event.data
if event.data.type == "function_approval_request" and event.data.function_call is not None:
print(f"\nApproval requested for tool: {event.data.function_call.name}")
print(f"Arguments: {event.data.function_call.arguments}")
elif event.type == "output":
_print_output(event)
responses: dict[str, Content] = {}
if requests:
for request_id, request in requests.items():
if request.type == "function_approval_request" and request.function_call is not None:
print(f"\nSimulating human approval for: {request.function_call.name}")
# Create approval response
responses[request_id] = request.to_function_approval_response(approved=True)
return responses if responses else None
async def main() -> None:
# 3. Create two agents focused on different stocks but with the same tool sets
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)
microsoft_agent = Agent(
client=client,
name="MicrosoftAgent",
instructions=(
"You are a personal trading assistant focused on Microsoft (MSFT). "
"You manage my portfolio and take actions based on market data. "
"Use stop-loss orders to manage risk."
),
tools=[get_stock_price, get_market_sentiment, get_portfolio_balance, execute_trade, set_stop_loss],
)
google_agent = Agent(
client=client,
name="GoogleAgent",
instructions=(
"You are a personal trading assistant focused on Google (GOOGL). "
"You manage my trades and portfolio based on market conditions. "
"Use stop-loss orders to manage risk."
),
tools=[get_stock_price, get_market_sentiment, get_portfolio_balance, execute_trade, set_stop_loss],
)
# 4. Build a concurrent workflow with both agents
# ConcurrentBuilder requires at least 2 participants for fan-out
workflow = ConcurrentBuilder(participants=[microsoft_agent, google_agent]).build()
# 5. Start the workflow - both agents will process the same task in parallel
print("Starting concurrent workflow with tool approval...")
print("-" * 60)
# Initiate the first run of the workflow.
# Runs are not isolated; state is preserved across multiple calls to run.
stream = workflow.run(
"Manage my portfolio. Use a max of 5000 dollars to adjust my position using "
"your best judgment based on market sentiment. Set stop-loss orders to manage risk. "
"No need to confirm trades with me.",
stream=True,
)
pending_responses = await process_event_stream(stream)
while pending_responses is not None:
# Run the workflow until there is no more human feedback to provide,
# in which case this workflow completes.
stream = workflow.run(stream=True, responses=pending_responses)
pending_responses = await process_event_stream(stream)
"""
Sample Output:
Starting concurrent workflow with tool approval...
------------------------------------------------------------
Approval requested for tool: execute_trade
Arguments: {"symbol":"MSFT","action":"buy","quantity":13}
Approval requested for tool: set_stop_loss
Arguments: {"symbol":"MSFT","stop_price":340.0}
Approval requested for tool: execute_trade
Arguments: {"symbol":"GOOGL","action":"buy","quantity":35}
Approval requested for tool: set_stop_loss
Arguments: {"symbol":"GOOGL","stop_price":126.0}
Simulating human approval for: execute_trade
Simulating human approval for: set_stop_loss
Simulating human approval for: execute_trade
Simulating human approval for: set_stop_loss
------------------------------------------------------------
Workflow completed. Aggregated results from both agents:
- user: Manage my portfolio. Use a max of 5000 dollars to adjust my position using your best judgment based on
market sentiment. Set stop-loss orders to manage risk. No need to confirm trades with me.
- MicrosoftAgent: I have successfully purchased 13 shares of Microsoft (MSFT) and set a stop-loss at $340.00.
This action was based on the positive market sentiment and available funds within the
specified limit. Your portfolio has been adjusted accordingly.
- GoogleAgent: I have successfully purchased 35 shares of GOOGL and set a stop-loss at $126.00. If you need
further assistance or any adjustments, feel free to ask!
"""
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,230 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from collections.abc import AsyncIterable
from typing import Annotated, cast
from agent_framework import (
Agent,
Content,
Message,
WorkflowEvent,
tool,
)
from agent_framework.foundry import FoundryChatClient
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
Sample: Group Chat Workflow with Tool Approval Requests
This sample demonstrates how to use GroupChatBuilder with tools that require human
approval before execution. A group of specialized agents collaborate on a task, and
sensitive tool calls trigger human-in-the-loop approval.
This sample works as follows:
1. A GroupChatBuilder workflow is created with multiple specialized agents.
2. A selector function determines which agent speaks next based on conversation state.
3. Agents collaborate on a software deployment task.
4. When the deployment agent tries to deploy to production, it triggers an approval request.
5. The sample simulates human approval and the workflow completes.
Purpose:
Show how tool call approvals integrate with multi-agent group chat workflows where
different agents have different levels of tool access.
Demonstrate:
- Using set_select_speakers_func with agents that have approval-required tools.
- Handling request_info events (type='request_info') in group chat scenarios.
- Multi-round group chat with tool approval interruption and resumption.
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL must be set to your Azure OpenAI model deployment name.
- Basic familiarity with GroupChatBuilder and streaming workflow events.
"""
# 1. Define tools for different agents
# NOTE: approval_mode="never_require" is for sample brevity.
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def run_tests(test_suite: Annotated[str, "Name of the test suite to run"]) -> str:
"""Run automated tests for the application."""
return f"Test suite '{test_suite}' completed: 47 passed, 0 failed, 0 skipped"
@tool(approval_mode="never_require")
def check_staging_status() -> str:
"""Check the current status of the staging environment."""
return "Staging environment: Healthy, Version 2.3.0 deployed, All services running"
@tool(approval_mode="always_require")
def deploy_to_production(
version: Annotated[str, "The version to deploy"],
components: Annotated[str, "Comma-separated list of components to deploy"],
) -> str:
"""Deploy specified components to production. Requires human approval."""
return f"Production deployment complete: Version {version}, Components: {components}"
@tool(approval_mode="never_require")
def create_rollback_plan(version: Annotated[str, "The version being deployed"]) -> str:
"""Create a rollback plan for the deployment."""
return (
f"Rollback plan created for version {version}: "
"Automated rollback to v2.2.0 if health checks fail within 5 minutes"
)
# 2. Define the speaker selector function
def select_next_speaker(state: GroupChatState) -> str:
"""Select the next speaker based on the conversation flow.
This simple selector follows a predefined flow:
1. QA Engineer runs tests
2. DevOps Engineer checks staging and creates rollback plan
3. DevOps Engineer deploys to production (triggers approval)
"""
if not state.conversation:
raise RuntimeError("Conversation is empty; cannot select next speaker.")
if len(state.conversation) == 1:
return "QAEngineer" # First speaker
return "DevOpsEngineer" # Subsequent speakers
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, Content] | None:
"""Process events from the workflow stream to capture human feedback requests."""
requests: dict[str, Content] = {}
async for event in stream:
if event.type == "request_info" and isinstance(event.data, Content):
# We are only expecting tool approval requests in this sample
requests[event.request_id] = event.data
elif event.type == "output":
# The output of the workflow comes from the orchestrator and it's a list of messages
print("\n" + "=" * 60)
print("Workflow summary:")
outputs = cast(list[Message], event.data)
for msg in outputs:
speaker = msg.author_name or msg.role
print(f"[{speaker}]: {msg.text}")
responses: dict[str, Content] = {}
if requests:
for request_id, request in requests.items():
if request.type == "function_approval_request" and request.function_call is not None:
print("\n[APPROVAL REQUIRED]")
print(f" Tool: {request.function_call.name}")
print(f" Arguments: {request.function_call.arguments}")
print(f"Simulating human approval for: {request.function_call.name}")
# Create approval response
responses[request_id] = request.to_function_approval_response(approved=True)
return responses if responses else None
async def main() -> None:
# 3. Create specialized agents
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)
qa_engineer = Agent(
client=client,
name="QAEngineer",
instructions=(
"You are a QA engineer responsible for running tests before deployment. "
"Run the appropriate test suites and report results clearly."
),
tools=[run_tests],
)
devops_engineer = Agent(
client=client,
name="DevOpsEngineer",
instructions=(
"You are a DevOps engineer responsible for deployments. First check staging "
"status and create a rollback plan, then proceed with production deployment "
"without the need for further instructions."
),
tools=[check_staging_status, create_rollback_plan, deploy_to_production],
)
# 4. Build a group chat workflow with the selector function
# max_rounds=2: Set a hard limit to 2 rounds
# First round: QAEngineer speaks
# Second round: DevOpsEngineer speaks
# If the round limit is larger than 2, the selector will keep selecting DevOpsEngineer,
# which could result in empty messages sent to the DevOpsEngineer after the second round
# since there is no more input from the QAEngineer. This could lead to error from some LLMs
# if they do not accept empty input. Setting max_rounds=2 prevents this issue.
workflow = GroupChatBuilder(
participants=[qa_engineer, devops_engineer],
max_rounds=2,
selection_func=select_next_speaker,
).build()
# 5. Start the workflow
print("Starting group chat workflow for software deployment...")
print(f"Agents: {[qa_engineer.name, devops_engineer.name]}")
print("-" * 60)
# Initiate the first run of the workflow.
# Runs are not isolated; state is preserved across multiple calls to run.
stream = workflow.run(
"We need to deploy version 2.4.0 to production. Please coordinate the deployment.", stream=True
)
pending_responses = await process_event_stream(stream)
while pending_responses is not None:
# Run the workflow until there is no more human feedback to provide,
# in which case this workflow completes.
stream = workflow.run(stream=True, responses=pending_responses)
pending_responses = await process_event_stream(stream)
"""
Sample Output:
Starting group chat workflow for software deployment...
Agents: QA Engineer, DevOps Engineer
------------------------------------------------------------
[QAEngineer]: Running the integration test suite to verify the application
before deployment... Test suite 'integration' completed: 47 passed, 0 failed.
All tests passing - ready for deployment.
[DevOpsEngineer]: Checking staging environment status... Staging is healthy
with version 2.3.0. Creating rollback plan for version 2.4.0... Rollback plan
created with automated rollback to v2.2.0 if health checks fail.
[APPROVAL REQUIRED]
Tool: deploy_to_production
Arguments: {"version": "2.4.0", "components": "api,web,worker"}
============================================================
Human review required for production deployment!
In a real scenario, you would review the deployment details here.
Simulating approval for demo purposes...
============================================================
[DevOpsEngineer]: Production deployment complete! Version 2.4.0 has been
successfully deployed with components: api, web, worker.
------------------------------------------------------------
Deployment workflow completed successfully!
All agents have finished their tasks.
"""
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,166 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from collections.abc import AsyncIterable
from typing import Annotated, cast
from agent_framework import (
Agent,
Content,
Message,
WorkflowEvent,
tool,
)
from agent_framework.foundry import FoundryChatClient
from agent_framework.orchestrations import SequentialBuilder
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
Sample: Sequential Workflow with Tool Approval Requests
This sample demonstrates how to use SequentialBuilder with tools that require human
approval before execution. The approval flow uses the existing @tool decorator
with approval_mode="always_require" to trigger human-in-the-loop interactions.
This sample works as follows:
1. A SequentialBuilder workflow is created with a single agent that has tools requiring approval.
2. The agent receives a user task and determines it needs to call a sensitive tool.
3. The tool call triggers a function_approval_request Content, pausing the workflow.
4. The sample simulates human approval by responding to the .
5. Once approved, the tool executes and the agent completes its response.
6. The workflow outputs the final conversation with all messages.
Purpose:
Show how tool call approvals integrate seamlessly with SequentialBuilder without
requiring any additional builder configuration.
Demonstrate:
- Using @tool(approval_mode="always_require") for sensitive operations.
- Handling request_info events with function_approval_request Content in sequential workflows.
- Resuming workflow execution after approval via run(responses=..., stream=True).
Prerequisites:
- FOUNDRY_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- FOUNDRY_MODEL must be set to your Azure OpenAI model deployment name.
- Basic familiarity with SequentialBuilder and streaming workflow events.
"""
# 1. Define tools - one requiring approval, one that doesn't
@tool(approval_mode="always_require")
def execute_database_query(
query: Annotated[str, "The SQL query to execute against the production database"],
) -> str:
"""Execute a SQL query against the production database. Requires human approval."""
# In a real implementation, this would execute the query
return f"Query executed successfully. Results: 3 rows affected by '{query}'"
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py and
# samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_database_schema() -> str:
"""Get the current database schema. Does not require approval."""
return """
Tables:
- users (id, name, email, created_at)
- orders (id, user_id, total, status, created_at)
- products (id, name, price, stock)
"""
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, Content] | None:
"""Process events from the workflow stream to capture human feedback requests."""
requests: dict[str, Content] = {}
async for event in stream:
if event.type == "request_info" and isinstance(event.data, Content):
# We are only expecting tool approval requests in this sample
requests[event.request_id] = event.data
elif event.type == "output":
# The output of the workflow comes from the orchestrator and it's a list of messages
print("\n" + "=" * 60)
print("Workflow summary:")
outputs = cast(list[Message], event.data)
for msg in outputs:
speaker = msg.author_name or msg.role
print(f"[{speaker}]: {msg.text}")
responses: dict[str, Content] = {}
if requests:
for request_id, request in requests.items():
if request.type == "function_approval_request" and request.function_call is not None:
print("\n[APPROVAL REQUIRED]")
print(f" Tool: {request.function_call.name}")
print(f" Arguments: {request.function_call.arguments}")
print(f"Simulating human approval for: {request.function_call.name}")
# Create approval response
responses[request_id] = request.to_function_approval_response(approved=True)
return responses if responses else None
async def main() -> None:
# 2. Create the agent with tools (approval mode is set per-tool via decorator)
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AzureCliCredential(),
)
database_agent = Agent(
client=client,
name="DatabaseAgent",
instructions=(
"You are a database assistant. You can view the database schema and execute "
"queries. Always check the schema before running queries. Be careful with "
"queries that modify data."
),
tools=[get_database_schema, execute_database_query],
)
# 3. Build a sequential workflow with the agent
workflow = SequentialBuilder(participants=[database_agent]).build()
# 4. Start the workflow with a user task
print("Starting sequential workflow with tool approval...")
print("-" * 60)
# Initiate the first run of the workflow.
# Runs are not isolated; state is preserved across multiple calls to run.
stream = workflow.run(
"Check the schema and then update all orders with status 'pending' to 'processing'", stream=True
)
pending_responses = await process_event_stream(stream)
while pending_responses is not None:
# Run the workflow until there is no more human feedback to provide,
# in which case this workflow completes.
stream = workflow.run(stream=True, responses=pending_responses)
pending_responses = await process_event_stream(stream)
"""
Sample Output:
Starting sequential workflow with tool approval...
------------------------------------------------------------
Approval requested for tool: execute_database_query
Arguments: {"query": "UPDATE orders SET status = 'processing' WHERE status = 'pending'"}
Simulating human approval (auto-approving for demo)...
------------------------------------------------------------
Workflow completed. Final conversation:
[user]: Check the schema and then update all orders with status 'pending' to 'processing'
[assistant]: I've checked the schema and executed the update query. The query
"UPDATE orders SET status = 'processing' WHERE status = 'pending'"
was executed successfully, affecting 3 rows.
"""
if __name__ == "__main__":
asyncio.run(main())