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:
Laveesh Rohra
2026-01-14 10:56:11 -08:00
committed by GitHub
Unverified
parent 1e36ba33c4
commit 3df916064c
48 changed files with 4221 additions and 1153 deletions
@@ -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__":