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
@@ -1,6 +1,6 @@
|
||||
# Single Agent Sample
|
||||
# Single Agent
|
||||
|
||||
This sample demonstrates how to use the durable agents extension to create a worker-client setup that hosts a single AI agent and provides interactive conversation via the Durable Task Scheduler.
|
||||
This sample demonstrates how to create a worker-client setup that hosts a single AI agent and provides interactive conversation via the Durable Task Scheduler.
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
@@ -15,18 +15,24 @@ See the [README.md](../README.md) file in the parent directory for more informat
|
||||
|
||||
## Running the Sample
|
||||
|
||||
With the environment setup, you can run the sample using separate worker and client processes:
|
||||
With the environment setup, you can run the sample using the combined approach or separate worker and client processes:
|
||||
|
||||
**Start the worker:**
|
||||
**Option 1: Combined (Recommended for Testing)**
|
||||
|
||||
```bash
|
||||
cd samples/getting_started/durabletask/01_single_agent
|
||||
python sample.py
|
||||
```
|
||||
|
||||
**Option 2: Separate Processes**
|
||||
|
||||
Start the worker in one terminal:
|
||||
|
||||
```bash
|
||||
python worker.py
|
||||
```
|
||||
|
||||
The worker will register the Joker agent and listen for requests.
|
||||
|
||||
**In a new terminal, run the client:**
|
||||
In a new terminal, run the client:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
@@ -58,9 +64,10 @@ Because light attracts bugs!
|
||||
You can view the state of the agent 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 the Joker agent, including its conversation history and current state
|
||||
|
||||
The agent maintains conversation state across multiple interactions, and you can inspect this state in the dashboard to understand how the durable agents extension manages conversation context.
|
||||
2. In the dashboard, you can view:
|
||||
- The state of the Joker agent entity (dafx-Joker)
|
||||
- Conversation history and current state
|
||||
- How the durable agents extension manages conversation context
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,69 +23,96 @@ logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.info("Starting Durable Task Agent Client...")
|
||||
def get_client(
|
||||
taskhub: str | None = None,
|
||||
endpoint: str | None = None,
|
||||
log_handler: logging.Handler | None = None
|
||||
) -> DurableAIAgentClient:
|
||||
"""Create a configured DurableAIAgentClient.
|
||||
|
||||
# Get environment variables for taskhub and endpoint with defaults
|
||||
taskhub_name = os.getenv("TASKHUB", "default")
|
||||
endpoint = os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
logger.info(f"Using taskhub: {taskhub_name}")
|
||||
logger.info(f"Using endpoint: {endpoint}")
|
||||
logger.info("")
|
||||
|
||||
# Set credential to None for emulator, or DefaultAzureCredential for Azure
|
||||
credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential()
|
||||
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")
|
||||
|
||||
# Create a client using Azure Managed Durable Task
|
||||
client = DurableTaskSchedulerClient(
|
||||
host_address=endpoint,
|
||||
secure_channel=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
|
||||
token_credential=credential,
|
||||
log_handler=log_handler
|
||||
)
|
||||
|
||||
# Wrap it with the agent client
|
||||
agent_client = DurableAIAgentClient(client)
|
||||
return DurableAIAgentClient(dts_client)
|
||||
|
||||
|
||||
def run_client(agent_client: DurableAIAgentClient) -> None:
|
||||
"""Run client interactions with the Joker agent.
|
||||
|
||||
Args:
|
||||
agent_client: The DurableAIAgentClient instance
|
||||
"""
|
||||
# Get a reference to the Joker agent
|
||||
logger.info("Getting reference to Joker agent...")
|
||||
logger.debug("Getting reference to Joker agent...")
|
||||
joker = agent_client.get_agent("Joker")
|
||||
|
||||
# Create a new thread for the conversation
|
||||
thread = joker.get_new_thread()
|
||||
logger.debug(f"Thread ID: {thread.session_id}")
|
||||
logger.info("Start chatting with the Joker agent! (Type 'exit' to quit)")
|
||||
|
||||
logger.info(f"Created conversation thread: {thread.session_id}")
|
||||
logger.info("")
|
||||
# Interactive conversation loop
|
||||
while True:
|
||||
# Get user input
|
||||
try:
|
||||
user_message = input("You: ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
logger.info("\nExiting...")
|
||||
break
|
||||
|
||||
# Check for exit command
|
||||
if user_message.lower() == "exit":
|
||||
logger.info("Goodbye!")
|
||||
break
|
||||
|
||||
# Skip empty messages
|
||||
if not user_message:
|
||||
continue
|
||||
|
||||
# Send message to agent and get response
|
||||
try:
|
||||
response = joker.run(user_message, thread=thread)
|
||||
logger.info(f"Joker: {response.text} \n")
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting response: {e}")
|
||||
|
||||
logger.info("Conversation completed.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Agent Client...")
|
||||
|
||||
# Create client using helper function
|
||||
agent_client = get_client()
|
||||
|
||||
try:
|
||||
# First message
|
||||
message1 = "Tell me a short joke about cloud computing."
|
||||
logger.info(f"User: {message1}")
|
||||
logger.info("")
|
||||
|
||||
# Run the agent - this blocks until the response is ready
|
||||
response1 = joker.run(message1, thread=thread)
|
||||
logger.info(f"Agent: {response1.text}")
|
||||
logger.info("")
|
||||
|
||||
# Second message - continuing the conversation
|
||||
message2 = "Now tell me one about Python programming."
|
||||
logger.info(f"User: {message2}")
|
||||
logger.info("")
|
||||
|
||||
response2 = joker.run(message2, thread=thread)
|
||||
logger.info(f"Agent: {response2.text}")
|
||||
logger.info("")
|
||||
|
||||
logger.info(f"Conversation completed successfully!")
|
||||
logger.info(f"Thread ID: {thread.session_id}")
|
||||
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during agent interaction: {e}")
|
||||
finally:
|
||||
logger.info("Client shutting down")
|
||||
logger.debug("Client shutting down")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -14,122 +14,42 @@ To run this sample:
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework_durabletask import DurableAIAgentClient, DurableAIAgentWorker
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from dotenv import load_dotenv
|
||||
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging (must be after imports to override their basicConfig)
|
||||
logging.basicConfig(level=logging.INFO, force=True)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_joker_agent():
|
||||
"""Create the Joker agent using Azure OpenAI.
|
||||
|
||||
Returns:
|
||||
AgentProtocol: The configured Joker agent
|
||||
"""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
|
||||
name="Joker",
|
||||
instructions="You are good at telling jokes.",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.info("Starting Durable Task Agent Sample (Combined Worker + Client)...")
|
||||
|
||||
# Get environment variables for taskhub and endpoint with defaults
|
||||
taskhub_name = os.getenv("TASKHUB", "default")
|
||||
endpoint = os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
logger.debug("Starting Durable Task Agent Sample (Combined Worker + Client)...")
|
||||
|
||||
logger.info(f"Using taskhub: {taskhub_name}")
|
||||
logger.info(f"Using endpoint: {endpoint}")
|
||||
logger.info("")
|
||||
|
||||
# Set credential to None for emulator, or DefaultAzureCredential for Azure
|
||||
credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential()
|
||||
secure_channel = endpoint != "http://localhost:8080"
|
||||
silent_handler = logging.NullHandler()
|
||||
|
||||
# Create and start the worker using a context manager
|
||||
with DurableTaskSchedulerWorker(
|
||||
host_address=endpoint,
|
||||
secure_channel=secure_channel,
|
||||
taskhub=taskhub_name,
|
||||
token_credential=credential
|
||||
) as worker:
|
||||
|
||||
# Wrap with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
# Create and register the Joker agent
|
||||
logger.info("Creating and registering Joker agent...")
|
||||
joker_agent = create_joker_agent()
|
||||
agent_worker.add_agent(joker_agent)
|
||||
|
||||
logger.info(f"✓ Registered agent: {joker_agent.name}")
|
||||
logger.info(f" Entity name: dafx-{joker_agent.name}")
|
||||
logger.info("")
|
||||
# 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
|
||||
worker.start()
|
||||
logger.info("Worker started and listening for requests...")
|
||||
logger.info("")
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
# Create the client
|
||||
client = DurableTaskSchedulerClient(
|
||||
host_address=endpoint,
|
||||
secure_channel=secure_channel,
|
||||
taskhub=taskhub_name,
|
||||
token_credential=credential
|
||||
)
|
||||
|
||||
# Wrap it with the agent client
|
||||
agent_client = DurableAIAgentClient(client)
|
||||
|
||||
# Get a reference to the Joker agent
|
||||
logger.info("Getting reference to Joker agent...")
|
||||
joker = agent_client.get_agent("Joker")
|
||||
|
||||
# Create a new thread for the conversation
|
||||
thread = joker.get_new_thread()
|
||||
|
||||
logger.info(f"Created conversation thread: {thread.session_id}")
|
||||
logger.info("")
|
||||
# Create the client using helper function
|
||||
agent_client = get_client(log_handler=silent_handler)
|
||||
|
||||
try:
|
||||
# First message
|
||||
message1 = "Tell me a short joke about cloud computing."
|
||||
logger.info(f"User: {message1}")
|
||||
logger.info("")
|
||||
|
||||
# Run the agent - this blocks until the response is ready
|
||||
response1 = joker.run(message1, thread=thread)
|
||||
logger.info(f"Agent: {response1.text}; {response1}")
|
||||
logger.info("")
|
||||
|
||||
# Second message - continuing the conversation
|
||||
message2 = "Now tell me one about Python programming."
|
||||
logger.info(f"User: {message2}")
|
||||
logger.info("")
|
||||
|
||||
response2 = joker.run(message2, thread=thread)
|
||||
logger.info(f"Agent: {response2.text}; {response2}")
|
||||
logger.info("")
|
||||
|
||||
logger.info(f"Conversation completed successfully!")
|
||||
logger.info(f"Thread ID: {thread.session_id}")
|
||||
|
||||
# Run client interactions using helper function
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during agent interaction: {e}")
|
||||
|
||||
logger.info("")
|
||||
logger.info("Sample completed. Worker shutting down...")
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -19,7 +19,7 @@ from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -35,39 +35,71 @@ def create_joker_agent():
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.info("Starting Durable Task Agent Worker...")
|
||||
def get_worker(
|
||||
taskhub: str | None = None,
|
||||
endpoint: str | None = None,
|
||||
log_handler: logging.Handler | None = None
|
||||
) -> DurableTaskSchedulerWorker:
|
||||
"""Create a configured DurableTaskSchedulerWorker.
|
||||
|
||||
# Get environment variables for taskhub and endpoint with defaults
|
||||
taskhub_name = os.getenv("TASKHUB", "default")
|
||||
endpoint = os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
logger.info(f"Using taskhub: {taskhub_name}")
|
||||
logger.info(f"Using endpoint: {endpoint}")
|
||||
|
||||
# Set credential to None for emulator, or DefaultAzureCredential for Azure
|
||||
credential = None if endpoint == "http://localhost:8080" else DefaultAzureCredential()
|
||||
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")
|
||||
|
||||
# Create a worker using Azure Managed Durable Task
|
||||
worker = DurableTaskSchedulerWorker(
|
||||
host_address=endpoint,
|
||||
secure_channel=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
|
||||
token_credential=credential,
|
||||
log_handler=log_handler
|
||||
)
|
||||
|
||||
|
||||
def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""Set up the worker with 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 the Joker agent
|
||||
logger.info("Creating and registering Joker agent...")
|
||||
logger.debug("Creating and registering Joker agent...")
|
||||
joker_agent = create_joker_agent()
|
||||
agent_worker.add_agent(joker_agent)
|
||||
|
||||
logger.info(f"✓ Registered agent: {joker_agent.name}")
|
||||
logger.info(f" Entity name: dafx-{joker_agent.name}")
|
||||
logger.info("")
|
||||
logger.debug(f"✓ Registered agent: {joker_agent.name}")
|
||||
logger.debug(f" Entity name: dafx-{joker_agent.name}")
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task 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.")
|
||||
logger.info("")
|
||||
@@ -80,9 +112,9 @@ async def main():
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Worker shutdown initiated")
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
logger.info("Worker stopped")
|
||||
logger.debug("Worker stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user