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,80 @@
# Multi-Agent
This sample demonstrates how to host multiple AI agents with different tools in a single worker-client setup using the Durable Task Scheduler.
## Key Concepts Demonstrated
- Hosting multiple agents (WeatherAgent and MathAgent) in a single worker process.
- Each agent with its own specialized tools and instructions.
- Interacting with different agents using separate conversation sessions.
- Worker-client architecture for multi-agent systems.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup, you can run the sample using the combined approach or separate worker and client processes:
**Option 1: Combined (Recommended for Testing)**
```bash
cd samples/04-hosting/durabletask/02_multi_agent
python sample.py
```
**Option 2: Separate Processes**
Start the worker in one terminal:
```bash
python worker.py
```
In a new terminal, run the client:
```bash
python client.py
```
The client will interact with both agents:
```
Starting Durable Task Multi-Agent Client...
Using taskhub: default
Using endpoint: http://localhost:8080
================================================================================
Testing WeatherAgent
================================================================================
Created weather conversation session: <guid>
User: What is the weather in Seattle?
🔧 [TOOL CALLED] get_weather(location=Seattle)
✓ [TOOL RESULT] {'location': 'Seattle', 'temperature': 72, 'conditions': 'Sunny', 'humidity': 45}
WeatherAgent: The current weather in Seattle is sunny with a temperature of 72°F and 45% humidity.
================================================================================
Testing MathAgent
================================================================================
Created math conversation session: <guid>
User: Calculate a 20% tip on a $50 bill
🔧 [TOOL CALLED] calculate_tip(bill_amount=50.0, tip_percentage=20.0)
✓ [TOOL RESULT] {'bill_amount': 50.0, 'tip_percentage': 20.0, 'tip_amount': 10.0, 'total': 60.0}
MathAgent: For a $50 bill with a 20% tip, the tip amount is $10.00 and the total is $60.00.
```
## Viewing Agent State
You can view the state of both agents in the Durable Task Scheduler dashboard:
1. Open your browser and navigate to `http://localhost:8082`
2. In the dashboard, you can view:
- The state of both WeatherAgent and MathAgent entities (dafx-WeatherAgent, dafx-MathAgent)
- Each agent's conversation state across multiple interactions
@@ -0,0 +1,120 @@
# Copyright (c) Microsoft. All rights reserved.
"""Client application for interacting with multiple hosted agents.
This client connects to the Durable Task Scheduler and interacts with two different
agents (WeatherAgent and MathAgent), demonstrating how to work with multiple agents
each with their own specialized capabilities and tools.
Prerequisites:
- The worker must be running with both agents registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL when running the worker
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
import asyncio
import logging
import os
from agent_framework.azure import DurableAIAgentClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
# Load environment variables from .env file
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_client(
taskhub: str | None = None, endpoint: str | None = None, log_handler: logging.Handler | None = None
) -> DurableAIAgentClient:
"""Create a configured DurableAIAgentClient.
Args:
taskhub: Task hub name (defaults to TASKHUB env var or "default")
endpoint: Scheduler endpoint (defaults to ENDPOINT env var or "http://localhost:8080")
log_handler: Optional logging handler for client logging
Returns:
Configured DurableAIAgentClient instance
"""
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
dts_client = DurableTaskSchedulerClient(
host_address=endpoint_url,
secure_channel=endpoint_url != "http://localhost:8080",
taskhub=taskhub_name,
token_credential=credential,
log_handler=log_handler,
)
return DurableAIAgentClient(dts_client)
def run_client(agent_client: DurableAIAgentClient) -> None:
"""Run client interactions with both WeatherAgent and MathAgent.
Args:
agent_client: The DurableAIAgentClient instance
"""
logger.debug("Testing WeatherAgent")
# Get reference to WeatherAgent
weather_agent = agent_client.get_agent("WeatherAgent")
weather_session = weather_agent.create_session()
logger.debug(f"Created weather conversation session: {weather_session.session_id}")
# Test WeatherAgent
weather_message = "What is the weather in Seattle?"
logger.info(f"User: {weather_message}")
weather_response = weather_agent.run(weather_message, session=weather_session)
logger.info(f"WeatherAgent: {weather_response.text} \n")
logger.debug("Testing MathAgent")
# Get reference to MathAgent
math_agent = agent_client.get_agent("MathAgent")
math_session = math_agent.create_session()
logger.debug(f"Created math conversation session: {math_session.session_id}")
# Test MathAgent
math_message = "Calculate a 20% tip on a $50 bill"
logger.info(f"User: {math_message}")
math_response = math_agent.run(math_message, session=math_session)
logger.info(f"MathAgent: {math_response.text} \n")
logger.debug("Both agents completed successfully!")
async def main() -> None:
"""Main entry point for the client application."""
logger.debug("Starting Durable Task Multi-Agent Client...")
# Create client using helper function
agent_client = get_client()
try:
run_client(agent_client)
except Exception as e:
logger.exception(f"Error during agent interaction: {e}")
finally:
logger.debug("Client shutting down")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,14 @@
# Agent Framework packages
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
azure-identity
@@ -0,0 +1,51 @@
# Copyright (c) Microsoft. All rights reserved.
"""Multi-Agent Sample - Durable Task Integration (Combined Worker + Client)
This sample demonstrates running both the worker and client in a single process
for multiple agents with different tools. The worker registers two agents
(WeatherAgent and MathAgent), each with their own specialized capabilities.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
python sample.py
"""
import logging
# Import helper functions from worker and client modules
from client import get_client, run_client # pyrefly: ignore[missing-import]
from dotenv import load_dotenv
from worker import get_worker, setup_worker # pyrefly: ignore[missing-import]
# Configure logging
logging.basicConfig(level=logging.INFO, force=True)
logger = logging.getLogger(__name__)
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Durable Task Multi-Agent Sample (Combined Worker + Client)...")
silent_handler = logging.NullHandler()
# Create and start the worker using helper function and context manager
dts_worker = get_worker(log_handler=silent_handler)
with dts_worker:
# Register agents using helper function
setup_worker(dts_worker)
# Start the worker
dts_worker.start()
logger.debug("Worker started and listening for requests...")
# Create the client using helper function
agent_client = get_client(log_handler=silent_handler)
try:
# Run client interactions using helper function
run_client(agent_client)
except Exception as e:
logger.exception(f"Error during agent interaction: {e}")
logger.debug("Sample completed. Worker shutting down...")
if __name__ == "__main__":
load_dotenv()
main()
@@ -0,0 +1,184 @@
# Copyright (c) Microsoft. All rights reserved.
"""Worker process for hosting multiple Azure OpenAI agents with different tools using Durable Task.
This worker registers two agents - a weather assistant and a math assistant - each
with their own specialized tools. This demonstrates how to host multiple agents
with different capabilities in a single worker process.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
import asyncio
import logging
import os
from typing import Any
from agent_framework import Agent, tool
from agent_framework.azure import DurableAIAgentWorker
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
# Load environment variables from .env file
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Agent names
WEATHER_AGENT_NAME = "WeatherAgent"
MATH_AGENT_NAME = "MathAgent"
@tool
def get_weather(location: str) -> dict[str, Any]:
"""Get current weather for a location."""
logger.info(f"🔧 [TOOL CALLED] get_weather(location={location})")
result = {
"location": location,
"temperature": 72,
"conditions": "Sunny",
"humidity": 45,
}
logger.info(f"✓ [TOOL RESULT] {result}")
return result
@tool
def calculate_tip(bill_amount: float, tip_percentage: float = 15.0) -> dict[str, Any]:
"""Calculate tip amount and total bill."""
logger.info(f"🔧 [TOOL CALLED] calculate_tip(bill_amount={bill_amount}, tip_percentage={tip_percentage})")
tip = bill_amount * (tip_percentage / 100)
total = bill_amount + tip
result = {
"bill_amount": bill_amount,
"tip_percentage": tip_percentage,
"tip_amount": round(tip, 2),
"total": round(total, 2),
}
logger.info(f"✓ [TOOL RESULT] {result}")
return result
def create_weather_agent():
"""Create the Weather agent using Azure OpenAI.
Returns:
Agent: The configured Weather agent with weather tool
"""
return Agent(
client=OpenAIChatCompletionClient(
credential=AsyncAzureCliCredential(),
),
name=WEATHER_AGENT_NAME,
instructions="You are a helpful weather assistant. Provide current weather information.",
tools=[get_weather],
)
def create_math_agent():
"""Create the Math agent using Azure OpenAI.
Returns:
Agent: The configured Math agent with calculation tools
"""
return Agent(
client=OpenAIChatCompletionClient(
credential=AsyncAzureCliCredential(),
),
name=MATH_AGENT_NAME,
instructions="You are a helpful math assistant. Help users with calculations like tip calculations.",
tools=[calculate_tip],
)
def get_worker(
taskhub: str | None = None, endpoint: str | None = None, log_handler: logging.Handler | None = None
) -> DurableTaskSchedulerWorker:
"""Create a configured DurableTaskSchedulerWorker.
Args:
taskhub: Task hub name (defaults to TASKHUB env var or "default")
endpoint: Scheduler endpoint (defaults to ENDPOINT env var or "http://localhost:8080")
log_handler: Optional logging handler for worker logging
Returns:
Configured DurableTaskSchedulerWorker instance
"""
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
secure_channel=endpoint_url != "http://localhost:8080",
taskhub=taskhub_name,
token_credential=credential,
log_handler=log_handler,
)
def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
"""Set up the worker with multiple agents registered.
Args:
worker: The DurableTaskSchedulerWorker instance
Returns:
DurableAIAgentWorker with agents registered
"""
# Wrap it with the agent worker
agent_worker = DurableAIAgentWorker(worker)
# Create and register both agents
logger.debug("Creating and registering agents...")
weather_agent = create_weather_agent()
math_agent = create_math_agent()
agent_worker.add_agent(weather_agent)
agent_worker.add_agent(math_agent)
logger.debug(f"✓ Registered agents: {weather_agent.name}, {math_agent.name}")
return agent_worker
async def main():
"""Main entry point for the worker process."""
logger.debug("Starting Durable Task Multi-Agent Worker...")
# Create a worker using the helper function
worker = get_worker()
# Setup worker with agents
setup_worker(worker)
logger.info("Worker is ready and listening for requests...")
logger.info("Press Ctrl+C to stop. \n")
try:
# Start the worker (this blocks until stopped)
worker.start()
# Keep the worker running
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
logger.debug("Worker shutdown initiated")
logger.info("Worker stopped")
if __name__ == "__main__":
asyncio.run(main())