mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
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:
committed by
GitHub
Unverified
parent
a5b36dc379
commit
e3eff65a6b
@@ -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())
|
||||
Reference in New Issue
Block a user