Python: Complete durableagent package (#3058)

* Add worker and clients

* Clean code and refactor common code

* Implement sample

* Add sample

* Update readmes

* Fix tests

* Fix tests

* Update requirements

* Fix typo

* Address comments

* use response.text
This commit is contained in:
Laveesh Rohra
2026-01-07 13:53:21 -08:00
committed by GitHub
Unverified
parent a5b36dc379
commit e3eff65a6b
46 changed files with 4477 additions and 1644 deletions
@@ -0,0 +1,66 @@
# Single Agent Sample
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.
## Key Concepts Demonstrated
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
- Registering durable agents with the worker and interacting with them via a client.
- Conversation management (via threads) for isolated interactions.
- Worker-client architecture for distributed agent execution.
## 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 separate worker and client processes:
**Start the worker:**
```bash
cd samples/getting_started/durabletask/01_single_agent
python worker.py
```
The worker will register the Joker agent and listen for requests.
**In a new terminal, run the client:**
```bash
python client.py
```
The client will interact with the Joker agent:
```
Starting Durable Task Agent Client...
Using taskhub: default
Using endpoint: http://localhost:8080
Getting reference to Joker agent...
Created conversation thread: a1b2c3d4-e5f6-7890-abcd-ef1234567890
User: Tell me a short joke about cloud computing.
Joker: Why did the cloud break up with the server?
Because it found someone more "uplifting"!
User: Now tell me one about Python programming.
Joker: Why do Python programmers prefer dark mode?
Because light attracts bugs!
```
## Viewing Agent State
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.
@@ -0,0 +1,92 @@
"""Client application for interacting with a Durable Task hosted agent.
This client connects to the Durable Task Scheduler and sends requests to
registered agents, demonstrating how to interact with agents from external processes.
Prerequisites:
- The worker must be running with the agent 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__)
async def main() -> None:
"""Main entry point for the client application."""
logger.info("Starting Durable Task Agent Client...")
# 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()
# Create a client using Azure Managed Durable Task
client = DurableTaskSchedulerClient(
host_address=endpoint,
secure_channel=endpoint != "http://localhost:8080",
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("")
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}")
except Exception as e:
logger.exception(f"Error during agent interaction: {e}")
finally:
logger.info("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,137 @@
"""Single Agent Sample - Durable Task Integration (Combined Worker + Client)
This sample demonstrates running both the worker and client in a single process.
The worker is started first to register the agent, then client operations are
performed against the running worker.
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
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)
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.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"
# 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("")
# Start the worker
worker.start()
logger.info("Worker started and listening for requests...")
logger.info("")
# 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("")
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}")
except Exception as e:
logger.exception(f"Error during agent interaction: {e}")
logger.info("")
logger.info("Sample completed. Worker shutting down...")
if __name__ == "__main__":
load_dotenv()
main()
@@ -0,0 +1,89 @@
"""Worker process for hosting a single Azure OpenAI-powered agent using Durable Task.
This worker registers agents as durable entities and continuously listens for requests.
The worker should run as a background service, processing incoming agent requests.
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 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__)
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.",
)
async def main():
"""Main entry point for the worker process."""
logger.info("Starting Durable Task Agent Worker...")
# 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()
# Create a worker using Azure Managed Durable Task
worker = DurableTaskSchedulerWorker(
host_address=endpoint,
secure_channel=endpoint != "http://localhost:8080",
taskhub=taskhub_name,
token_credential=credential
)
# Wrap it 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("")
logger.info("Worker is ready and listening for requests...")
logger.info("Press Ctrl+C to stop.")
logger.info("")
try:
# Start the worker (this blocks until stopped)
worker.start()
# Keep the worker running
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
logger.info("Worker shutdown initiated")
logger.info("Worker stopped")
if __name__ == "__main__":
asyncio.run(main())