mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Add Durabletask samples and minor fixes (#3157)
* Add samples and minor fixes * Add redis sample and wait-for-completion * Add wait-for-completion support * ADd missing docs
This commit is contained in:
committed by
GitHub
Unverified
parent
1e36ba33c4
commit
3df916064c
@@ -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 threads.
|
||||
- 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/getting_started/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 thread: <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 thread: <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,116 @@
|
||||
"""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_CHAT_DEPLOYMENT_NAME
|
||||
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
|
||||
- Durable Task Scheduler must be running
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
||||
from agent_framework_durabletask import DurableAIAgentClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
|
||||
|
||||
# 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 DefaultAzureCredential()
|
||||
|
||||
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_thread = weather_agent.get_new_thread()
|
||||
|
||||
logger.debug(f"Created weather conversation thread: {weather_thread.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, thread=weather_thread)
|
||||
logger.info(f"WeatherAgent: {weather_response.text} \n")
|
||||
|
||||
logger.debug("Testing MathAgent")
|
||||
|
||||
# Get reference to MathAgent
|
||||
math_agent = agent_client.get_agent("MathAgent")
|
||||
math_thread = math_agent.get_new_thread()
|
||||
|
||||
logger.debug(f"Created math conversation thread: {math_thread.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, thread=math_thread)
|
||||
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,6 @@
|
||||
# Agent Framework packages (installing from local package until a package is published)
|
||||
-e ../../../../
|
||||
-e ../../../../packages/durabletask
|
||||
|
||||
# Azure authentication
|
||||
azure-identity
|
||||
@@ -0,0 +1,57 @@
|
||||
"""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_CHAT_DEPLOYMENT_NAME
|
||||
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
|
||||
- Durable Task Scheduler must be running (e.g., using Docker)
|
||||
|
||||
To run this sample:
|
||||
python sample.py
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# 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
|
||||
with get_worker(log_handler=silent_handler) as 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,172 @@
|
||||
"""Worker process for hosting multiple 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_CHAT_DEPLOYMENT_NAME
|
||||
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
|
||||
- Start a Durable Task Scheduler (e.g., using Docker)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework_durabletask import DurableAIAgentWorker
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Agent names
|
||||
WEATHER_AGENT_NAME = "WeatherAgent"
|
||||
MATH_AGENT_NAME = "MathAgent"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
AgentProtocol: The configured Weather agent with weather tool
|
||||
"""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
|
||||
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:
|
||||
AgentProtocol: The configured Math agent with calculation tools
|
||||
"""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
|
||||
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 DefaultAzureCredential()
|
||||
|
||||
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())
|
||||
Reference in New Issue
Block a user