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
@@ -46,6 +46,7 @@ from _tools import (
validate_payment_method,
)
from agent_framework import (
Agent,
AgentExecutorResponse,
AgentResponseUpdate,
Executor,
@@ -56,7 +57,7 @@ from agent_framework import (
executor,
handler,
)
from agent_framework.azure import AzureOpenAIResponsesClient
from agent_framework.foundry import FoundryChatClient
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import DefaultAzureCredential
from dotenv import load_dotenv
@@ -74,9 +75,10 @@ async def start_executor(input: str, ctx: WorkflowContext[list[Message]]) -> Non
class ResearchLead(Executor):
"""Aggregates and summarizes travel planning findings from all specialized agents."""
def __init__(self, client: AzureOpenAIResponsesClient, id: str = "travel-planning-coordinator"):
def __init__(self, client: FoundryChatClient, id: str = "travel-planning-coordinator"):
# Use default_options to persist conversation history for evaluation.
self.agent = client.as_agent(
self.agent = Agent(
client=client,
id="travel-planning-coordinator",
instructions=(
"You are the final coordinator. You will receive responses from multiple agents: "
@@ -143,13 +145,13 @@ class ResearchLead(Executor):
async def run_workflow_with_response_tracking(
query: str, client: AzureOpenAIResponsesClient | None = None, deployment_name: str | None = None
query: str, client: FoundryChatClient | None = None, deployment_name: str | None = None
) -> dict:
"""Run multi-agent workflow and track conversation IDs, response IDs, and interaction sequence.
Args:
query: The user query to process through the multi-agent workflow
client: Optional AzureOpenAIResponsesClient instance
client: Optional FoundryChatClient instance
deployment_name: Optional model deployment name for the workflow agents
Returns:
@@ -159,12 +161,12 @@ async def run_workflow_with_response_tracking(
try:
async with DefaultAzureCredential() as credential:
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=credential,
)
async with project_client:
client = AzureOpenAIResponsesClient(project_client=project_client, deployment_name=deployment_name)
client = FoundryChatClient(project_client=project_client, model=deployment_name)
return await _run_workflow_with_client(query, client)
except Exception as e:
print(f"Error during workflow execution: {e}")
@@ -173,7 +175,7 @@ async def run_workflow_with_response_tracking(
return await _run_workflow_with_client(query, client)
async def _run_workflow_with_client(query: str, client: AzureOpenAIResponsesClient) -> dict:
async def _run_workflow_with_client(query: str, client: FoundryChatClient) -> dict:
"""Execute workflow with given client and track all interactions."""
# Initialize tracking variables - use lists to track multiple responses per agent
@@ -205,16 +207,17 @@ async def _run_workflow_with_client(query: str, client: AzureOpenAIResponsesClie
}
async def _create_workflow(client: AzureOpenAIResponsesClient):
async def _create_workflow(client: FoundryChatClient):
"""Create the multi-agent travel planning workflow with specialized agents.
Uses a single shared AzureOpenAIResponsesClient for all agents.
Uses a single shared FoundryChatClient for all agents.
"""
final_coordinator = ResearchLead(client=client, id="final-coordinator")
# Agent 1: Travel Request Handler (initial coordinator)
travel_request_handler = client.as_agent(
travel_request_handler = Agent(
client=client,
id="travel-request-handler",
instructions=(
"You receive user travel queries and relay them to specialized agents. Extract key information: destination, dates, budget, and preferences. Pass this information forward clearly to the next agents."
@@ -223,7 +226,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 2: Hotel Search Executor
hotel_search_agent = client.as_agent(
hotel_search_agent = Agent(
client=client,
id="hotel-search-agent",
instructions=(
"You are a hotel search specialist. Your task is ONLY to search for and provide hotel information. Use search_hotels to find options, get_hotel_details for specifics, and check_availability to verify rooms. Output format: List hotel names, prices per night, total cost for the stay, locations, ratings, amenities, and addresses. IMPORTANT: Only provide hotel information without additional commentary."
@@ -233,7 +237,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 3: Flight Search Executor
flight_search_agent = client.as_agent(
flight_search_agent = Agent(
client=client,
id="flight-search-agent",
instructions=(
"You are a flight search specialist. Your task is ONLY to search for and provide flight information. Use search_flights to find options, get_flight_details for specifics, and check_availability for seats. Output format: List flight numbers, airlines, departure/arrival times, prices, durations, and cabin class. IMPORTANT: Only provide flight information without additional commentary."
@@ -243,7 +248,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 4: Activity Search Executor
activity_search_agent = client.as_agent(
activity_search_agent = Agent(
client=client,
id="activity-search-agent",
instructions=(
"You are an activities specialist. Your task is ONLY to search for and provide activity information. Use search_activities to find options for activities. Output format: List activity names, descriptions, prices, durations, ratings, and categories. IMPORTANT: Only provide activity information without additional commentary."
@@ -253,7 +259,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 5: Booking Confirmation Executor
booking_confirmation_agent = client.as_agent(
booking_confirmation_agent = Agent(
client=client,
id="booking-confirmation-agent",
instructions=(
"You confirm bookings. Use check_hotel_availability and check_flight_availability to verify slots, then confirm_booking to finalize. Provide ONLY: confirmation numbers, booking references, and confirmation status."
@@ -263,7 +270,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 6: Booking Payment Executor
booking_payment_agent = client.as_agent(
booking_payment_agent = Agent(
client=client,
id="booking-payment-agent",
instructions=(
"You process payments. Use validate_payment_method to verify payment, then process_payment to complete transactions. Provide ONLY: payment confirmation status, transaction IDs, and payment amounts."
@@ -273,7 +281,8 @@ async def _create_workflow(client: AzureOpenAIResponsesClient):
)
# Agent 7: Booking Information Aggregation Executor
booking_info_aggregation_agent = client.as_agent(
booking_info_aggregation_agent = Agent(
client=client,
id="booking-info-aggregation-agent",
instructions=(
"You aggregate hotel and flight search results. Receive options from search agents and organize them. Provide: top 2-3 hotel options with prices and top 2-3 flight options with prices in a structured format."
@@ -356,7 +365,7 @@ async def create_and_run_workflow(deployment_name: str | None = None):
query = example_queries[0]
print(f"Query: {query}\n")
result = await run_workflow_with_response_tracking(query, deployment_name=deployment_name)
result = await run_workflow_with_response_tracking(query, model=deployment_name)
# Create output data structure
output_data = {"agents": {}, "query": result["query"], "output": result.get("output", "")}
@@ -9,7 +9,7 @@ import time
from typing import TYPE_CHECKING, Any
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
from azure.identity import AzureCliCredential
from create_workflow import create_and_run_workflow
from dotenv import load_dotenv
@@ -33,8 +33,8 @@ This script:
def create_openai_client() -> OpenAI:
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=DefaultAzureCredential(),
endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
credential=AzureCliCredential(),
)
return project_client.get_openai_client()
@@ -58,7 +58,7 @@ async def run_workflow(deployment_name: str | None = None) -> dict[str, Any]:
print("Executing multi-agent travel planning workflow...")
print("This may take a few minutes...")
workflow_data = await create_and_run_workflow(deployment_name=deployment_name)
workflow_data = await create_and_run_workflow(model=deployment_name)
print("Workflow execution completed")
return workflow_data
@@ -216,7 +216,7 @@ async def main():
print_section("Travel Planning Workflow Evaluation")
print_section("Step 1: Running Workflow")
workflow_data = await run_workflow(deployment_name=workflow_agent_model)
workflow_data = await run_workflow(model=workflow_agent_model)
print_section("Step 2: Response Data Summary")
display_response_summary(workflow_data)
@@ -225,7 +225,7 @@ async def main():
fetch_agent_responses(openai_client, workflow_data, agents_to_evaluate)
print_section("Step 4: Creating Evaluation")
eval_object = create_evaluation(openai_client, deployment_name=eval_model)
eval_object = create_evaluation(openai_client, model=eval_model)
print_section("Step 5: Running Evaluation")
eval_run = run_evaluation(openai_client, eval_object, workflow_data, agents_to_evaluate)