Python: [BREAKING] Python: Provider-leading client design & OpenAI package extraction (#4818)

* Python: Provider-leading client design & OpenAI package extraction

Major refactoring of the Python Agent Framework client architecture:

- Extract OpenAI clients into new `agent-framework-openai` package
- Core package no longer depends on openai, azure-identity, azure-ai-projects
- Rename clients for discoverability: OpenAIResponsesClient → OpenAIChatClient,
  OpenAIChatClient → OpenAIChatCompletionClient
- Unify `model_id`/`deployment_name`/`model_deployment_name` → `model` param
- New FoundryChatClient for Azure AI Foundry Responses API
- New FoundryAgent/FoundryAgentClient for connecting to pre-configured Foundry agents
- Remove OpenAIBase/OpenAIConfigMixin from non-deprecated client MRO
- Deprecate AzureOpenAI* clients, AzureAIClient, OpenAIAssistantsClient
- Reorganize samples: azure_openai+azure_ai+azure_ai_agent → azure/
- ADR-0020: Provider-Leading Client Design

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: missing Agent imports in samples, .model_id → .model in foundry_local sample

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: CI failures — mypy errors, coverage targets, sample imports

- azure-ai mypy: add type ignores for TypedDict total=, model arg, forward ref
- Coverage: replace core.azure/openai targets with openai package target
- project_provider: add type annotation for opts dict

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: populate openai .pyi stub, fix broken README links, coverage targets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fixes

* updated observabilitty

* reset azure init.pyi

* fix errors

* updated adr number

* fix foundry local

* fixed not renamed docstrings and comments, and added deprecated markers to old classes

* fix tests and pyprojects

* fix test vars

* updated function tests

* update durable

* updated test setup for functions

* Fix Foundry auth in workflow samples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Stabilize Python integration workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update hosting samples for Foundry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Trigger full CI rerun

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Trigger CI rerun again

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* trigger rerun

* trigger rerun

* fix for litellm

* undo durabletask changes

* Move Foundry APIs into foundry namespace

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix Foundry pyproject formatting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Split provider samples by Foundry surface

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore hosting sample requirements

Also fix the Foundry Local sample link after the provider sample move.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated tests

* udpated foundry integration tests

* removed dist from azurefunctions tests

* Use separate Foundry clients for concurrent agents

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix client setup in azfunc and durable

* disabled two tests

* updated setup for some function and durable tests

* improved azure openai setup with new clients

* ignore deprecated

* fixes

* skip 11

* remove openai assistants int tests

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-03-25 10:56:29 +01:00
committed by GitHub
Unverified
parent 4b533608b6
commit 5e056b672e
485 changed files with 9784 additions and 12084 deletions
@@ -7,8 +7,8 @@ registered agents, demonstrating how to interact with agents from external proce
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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -17,7 +17,7 @@ import logging
import os
from agent_framework.azure import DurableAIAgentClient
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
@@ -48,7 +48,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
dts_client = DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -1,16 +1,13 @@
# Copyright (c) Microsoft. All rights reserved.
"""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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
python sample.py
"""
@@ -30,27 +27,21 @@ logger = logging.getLogger(__name__)
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Durable Task 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...")
@@ -6,8 +6,8 @@ This worker registers agents as durable entities and continuously listens for re
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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -16,8 +16,10 @@ import logging
import os
from agent_framework import Agent
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.azure import DurableAIAgentWorker
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
@@ -35,7 +37,12 @@ def create_joker_agent() -> Agent:
Returns:
Agent: The configured Joker agent
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
return Agent(
client=FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
),
name="Joker",
instructions="You are good at telling jokes.",
)
@@ -60,7 +67,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -8,8 +8,8 @@ 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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -18,7 +18,7 @@ import logging
import os
from agent_framework.azure import DurableAIAgentClient
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
@@ -49,7 +49,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
dts_client = DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -1,16 +1,13 @@
# Copyright (c) Microsoft. All rights reserved.
"""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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
python sample.py
"""
@@ -30,26 +27,21 @@ 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...")
@@ -7,8 +7,8 @@ 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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -17,9 +17,11 @@ import logging
import os
from typing import Any
from agent_framework import tool
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework import Agent, tool
from agent_framework.azure import DurableAIAgentWorker
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
@@ -71,7 +73,13 @@ def create_weather_agent():
Returns:
Agent: The configured Weather agent with weather tool
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=WEATHER_AGENT_NAME,
instructions="You are a helpful weather assistant. Provide current weather information.",
tools=[get_weather],
@@ -84,7 +92,13 @@ def create_math_agent():
Returns:
Agent: The configured Math agent with calculation tools
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=MATH_AGENT_NAME,
instructions="You are a helpful math assistant. Help users with calculations like tip calculations.",
tools=[calculate_tip],
@@ -110,7 +124,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -9,7 +9,7 @@ This client demonstrates:
Prerequisites:
- The worker must be running with the TravelPlanner agent registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Redis must be running
- Durable Task Scheduler must be running
"""
@@ -21,7 +21,7 @@ from datetime import timedelta
import redis.asyncio as aioredis
from agent_framework.azure import DurableAIAgentClient
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
from redis_stream_response_handler import RedisStreamResponseHandler
@@ -76,7 +76,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
dts_client = DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,15 +1,14 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
azure-identity
# Redis client
redis
@@ -1,19 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
"""Single Agent Streaming Sample - Durable Task Integration (Combined Worker + Client)
This sample demonstrates running both the worker and client in a single process
with reliable Redis-based streaming for agent responses.
The worker is started first to register the TravelPlanner agent with Redis streaming
callback, 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)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
- Redis must be running (e.g., docker run -d --name redis -p 6379:6379 redis:latest)
To run this sample:
python sample.py
"""
@@ -33,27 +29,21 @@ logger = logging.getLogger(__name__)
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Durable Task Agent Sample with Redis Streaming...")
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 and callbacks 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...")
@@ -6,8 +6,8 @@ This worker registers the TravelPlanner agent with the Durable Task Scheduler
and uses RedisStreamCallback to persist streaming responses to Redis for reliable delivery.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
- Start Redis (e.g., docker run -d --name redis -p 6379:6379 redis:latest)
"""
@@ -22,10 +22,11 @@ from agent_framework import Agent, AgentResponseUpdate
from agent_framework.azure import (
AgentCallbackContext,
AgentResponseCallbackProtocol,
AzureOpenAIChatClient,
DurableAIAgentWorker,
)
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
from redis_stream_response_handler import RedisStreamResponseHandler
@@ -153,7 +154,13 @@ def create_travel_agent() -> "Agent":
Returns:
Agent: The configured TravelPlanner agent with travel planning tools.
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name="TravelPlanner",
instructions="""You are an expert travel planner who creates detailed, personalized travel itineraries.
When asked to plan a trip, you should:
@@ -191,7 +198,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -8,8 +8,8 @@ how conversation context is maintained across multiple agent invocations.
Prerequisites:
- The worker must be running with the writer agent and orchestration registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -18,7 +18,7 @@ import json
import logging
import os
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
# Configure logging
@@ -45,7 +45,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -1,22 +1,18 @@
# Copyright (c) Microsoft. All rights reserved.
"""Single Agent Orchestration Chaining Sample - Durable Task Integration
This sample demonstrates chaining two invocations of the same agent inside a Durable Task
orchestration while preserving the conversation state between runs. The orchestration
runs the writer agent sequentially on a shared thread to refine text iteratively.
Components used:
- AzureOpenAIChatClient to construct the writer agent
- FoundryChatClient to construct the writer agent
- DurableTaskSchedulerWorker and DurableAIAgentWorker for agent hosting
- DurableTaskSchedulerClient and orchestration for sequential agent invocations
- Thread management to maintain conversation context across invocations
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker emulator)
To run this sample:
python sample.py
"""
@@ -36,22 +32,17 @@ logger = logging.getLogger(__name__)
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Single Agent Orchestration Chaining Sample...")
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 and orchestrations 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
client = get_client(log_handler=silent_handler)
logger.debug("CLIENT: Starting orchestration...")
# Run the client in the same process
try:
run_client(client)
@@ -61,7 +52,6 @@ def main():
logger.exception(f"Error during orchestration: {e}")
finally:
logger.debug("Worker stopping...")
logger.debug("")
logger.debug("Sample completed")
@@ -7,8 +7,8 @@ chaining behavior by running the agent twice sequentially on the same thread,
preserving conversation context between invocations.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -18,8 +18,10 @@ import os
from collections.abc import Generator
from agent_framework import Agent, AgentResponse
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.azure import DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
from durabletask.task import OrchestrationContext, Task
@@ -49,7 +51,13 @@ def create_writer_agent() -> "Agent":
"when given an improved sentence you polish it further."
)
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=WRITER_AGENT_NAME,
instructions=instructions,
)
@@ -139,7 +147,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -8,8 +8,8 @@ displays the aggregated results.
Prerequisites:
- The worker must be running with both agents and orchestration registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -18,7 +18,7 @@ import json
import logging
import os
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
# Configure logging
@@ -45,7 +45,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -1,19 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.
"""Multi-Agent Orchestration Sample - Durable Task Integration (Combined Worker + Client)
This sample demonstrates running both the worker and client in a single process for
concurrent multi-agent orchestration. The worker registers two domain-specific agents
(physicist and chemist) and an orchestration function that runs them in parallel.
The orchestration uses OrchestrationAgentExecutor to execute agents concurrently
and aggregate their responses.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
python sample.py
"""
@@ -33,30 +29,24 @@ logger = logging.getLogger(__name__)
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Durable Task Multi-Agent Orchestration 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 and orchestrations 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
client = get_client(log_handler=silent_handler)
# Define the prompt
prompt = "What is temperature?"
logger.debug("CLIENT: Starting orchestration...")
try:
# Run the client to start the orchestration
run_client(client, prompt)
except Exception as e:
logger.exception(f"Error during sample execution: {e}")
logger.debug("Sample completed. Worker shutting down...")
@@ -7,8 +7,8 @@ function that runs them concurrently. The orchestration uses OrchestrationAgentE
to execute agents in parallel and aggregate their responses.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -19,8 +19,10 @@ from collections.abc import Generator
from typing import Any
from agent_framework import Agent, AgentResponse
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.azure import DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
from durabletask.task import OrchestrationContext, Task, when_all
@@ -43,7 +45,13 @@ def create_physicist_agent() -> "Agent":
Returns:
Agent: The configured Physicist agent
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=PHYSICIST_AGENT_NAME,
instructions="You are an expert in physics. You answer questions from a physics perspective.",
)
@@ -55,7 +63,13 @@ def create_chemist_agent() -> "Agent":
Returns:
Agent: The configured Chemist agent
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=CHEMIST_AGENT_NAME,
instructions="You are an expert in chemistry. You answer questions from a chemistry perspective.",
)
@@ -138,7 +152,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -14,6 +14,11 @@ This sample demonstrates conditional orchestration logic with two agents that an
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.
This sample uses Azure OpenAI credentials:
- `AZURE_OPENAI_ENDPOINT`
- `AZURE_OPENAI_DEPLOYMENT_NAME`
## Running the Sample
With the environment setup, you can run the sample using the combined approach or separate worker and client processes:
@@ -7,8 +7,8 @@ that uses conditional logic to either handle spam emails or draft professional r
Prerequisites:
- The worker must be running with both agents, orchestration, and activities registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -16,7 +16,7 @@ import asyncio
import logging
import os
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
# Configure logging
@@ -43,7 +43,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-openai
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/openai # OpenAI support - dependency for Azure OpenAI chat samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -10,8 +10,8 @@ The orchestration branches based on spam detection results, calling different
activity functions to handle spam or send legitimate email responses.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
@@ -7,8 +7,8 @@ orchestration function that routes execution based on spam detection results. Ac
handle side effects (spam handling and email sending).
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_DEPLOYMENT_NAME
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -19,8 +19,11 @@ from collections.abc import Generator
from typing import Any, cast
from agent_framework import Agent, AgentResponse
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.azure import DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from azure.identity.aio import get_bearer_token_provider as get_async_bearer_token_provider
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
from durabletask.task import ActivityContext, OrchestrationContext, Task
@@ -64,7 +67,13 @@ def create_spam_agent() -> "Agent":
Returns:
Agent: The configured Spam Detection agent
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
return Agent(
client=OpenAIChatCompletionClient(
model=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
api_key=get_async_bearer_token_provider(
AsyncAzureCliCredential(), "https://cognitiveservices.azure.com/.default"
),
),
name=SPAM_AGENT_NAME,
instructions="You are a spam detection assistant that identifies spam emails.",
)
@@ -76,7 +85,13 @@ def create_email_agent() -> "Agent":
Returns:
Agent: The configured Email Assistant agent
"""
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
return Agent(
client=OpenAIChatCompletionClient(
model=os.environ["AZURE_OPENAI_DEPLOYMENT_NAME"],
api_key=get_async_bearer_token_provider(
AsyncAzureCliCredential(), "https://cognitiveservices.azure.com/.default"
),
),
name=EMAIL_AGENT_NAME,
instructions="You are an email assistant that helps users draft responses to emails with professionalism.",
)
@@ -220,7 +235,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -7,8 +7,8 @@ by starting an orchestration, sending approval/rejection events, and monitoring
Prerequisites:
- The worker must be running with the agent, orchestration, and activities registered
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running
"""
@@ -18,7 +18,7 @@ import logging
import os
import time
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
from durabletask.client import OrchestrationState
@@ -49,7 +49,7 @@ def get_client(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerClient(
host_address=endpoint_url,
@@ -1,11 +1,13 @@
# Agent Framework packages
# To use the deployed version, uncomment the line below and comment out the local installation lines
# To use the deployed version, uncomment the lines below and comment out the local installation lines
# agent-framework-foundry
# agent-framework-durabletask
# Local installation (for development and testing)
# Each package must be listed explicitly because pip doesn't resolve uv workspace sources.
# Without explicit entries, pip would fetch transitive dependencies from PyPI instead of local source.
-e ../../../../packages/core # Core framework - base dependency for all packages
-e ../../../../packages/foundry # Foundry support - dependency for hosted chat/agent samples
-e ../../../../packages/durabletask # Durable Task support - the main package for this sample
# Azure authentication
@@ -1,19 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.
"""Human-in-the-Loop Orchestration Sample - Durable Task Integration
This sample demonstrates the HITL pattern with a WriterAgent that generates content
and waits for human approval. The orchestration handles:
- External event waiting (approval/rejection)
- Timeout handling
- Iterative refinement based on feedback
- Activity functions for notifications and publishing
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Durable Task Scheduler must be running (e.g., using Docker)
To run this sample:
python sample.py
"""
@@ -32,28 +29,21 @@ logger = logging.getLogger()
def main():
"""Main entry point - runs both worker and client in single process."""
logger.debug("Starting Durable Task HITL Content Generation 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 agent, orchestration, and activities 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
client = get_client(log_handler=silent_handler)
try:
logger.debug("CLIENT: Starting orchestration tests...")
run_interactive_client(client)
except Exception as e:
logger.exception(f"Error during sample execution: {e}")
logger.debug("Sample completed. Worker shutting down...")
@@ -7,8 +7,8 @@ a human-in-the-loop review workflow. The orchestration pauses for external event
(human approval/rejection) with timeout handling, and iterates based on feedback.
Prerequisites:
- Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
(plus AZURE_OPENAI_API_KEY or Azure CLI authentication)
- Set FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL
- Sign in with Azure CLI for AzureCliCredential authentication
- Start a Durable Task Scheduler (e.g., using Docker)
"""
@@ -20,8 +20,10 @@ from datetime import timedelta
from typing import Any, cast
from agent_framework import Agent, AgentResponse
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from azure.identity import AzureCliCredential, DefaultAzureCredential
from agent_framework.azure import DurableAIAgentOrchestrationContext, DurableAIAgentWorker
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
from dotenv import load_dotenv
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
from durabletask.task import ActivityContext, OrchestrationContext, Task, when_any # type: ignore
@@ -74,7 +76,13 @@ def create_writer_agent() -> "Agent":
"Limit response to 300 words or less."
)
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
_client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL"],
credential=AsyncAzureCliCredential(),
)
return Agent(
client=_client,
name=WRITER_AGENT_NAME,
instructions=instructions,
)
@@ -298,7 +306,7 @@ def get_worker(
logger.debug(f"Using taskhub: {taskhub_name}")
logger.debug(f"Using endpoint: {endpoint_url}")
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
credential = None if endpoint_url == "http://localhost:8080" else AzureCliCredential()
return DurableTaskSchedulerWorker(
host_address=endpoint_url,
@@ -55,22 +55,6 @@ az role assignment create `
More information on how to configure RBAC permissions for Azure OpenAI can be found in the [Azure OpenAI documentation](https://learn.microsoft.com/azure/ai-services/openai/how-to/create-resource?pivots=cli).
### Setting an API key for the Azure OpenAI service
As an alternative to configuring Azure RBAC permissions, you can set an API key for the Azure OpenAI service by setting the `AZURE_OPENAI_API_KEY` environment variable.
Bash (Linux/macOS/WSL):
```bash
export AZURE_OPENAI_API_KEY="your-api-key"
```
PowerShell:
```powershell
$env:AZURE_OPENAI_API_KEY="your-api-key"
```
### Start Durable Task Scheduler
Most samples use the Durable Task Scheduler (DTS) to support hosted agents and durable orchestrations. DTS also allows you to view the status of orchestrations and their inputs and outputs from a web UI.
@@ -90,15 +74,15 @@ Each sample reads configuration from environment variables. You'll need to set t
Bash (Linux/macOS/WSL):
```bash
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
export AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="your-deployment-name"
export FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project"
export FOUNDRY_MODEL="your-deployment-name"
```
PowerShell:
```powershell
$env:AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
$env:AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="your-deployment-name"
$env:FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com/api/projects/your-project"
$env:FOUNDRY_MODEL="your-deployment-name"
```
### Installing Dependencies