mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: (samples): adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs (#3873)
* adopt AzureOpenAIResponsesClient, reorganize orchestration examples, and fix workflow/orchestration bugs * Updates * add comment
This commit is contained in:
committed by
GitHub
Unverified
parent
8457533c69
commit
1b10b051fd
@@ -14,7 +14,7 @@ from agent_framework import ( # Core chat primitives used to build requests
|
||||
WorkflowContext, # Per-run context and event bus
|
||||
executor, # Decorator to declare a Python function as a workflow executor
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient # Thin client wrapper for Azure OpenAI chat models
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient # Thin client wrapper for Azure OpenAI chat models
|
||||
from azure.identity import AzureCliCredential # Uses your az CLI login for credentials
|
||||
from pydantic import BaseModel # Structured outputs for safer parsing
|
||||
from typing_extensions import Never
|
||||
@@ -32,10 +32,11 @@ Purpose:
|
||||
- Illustrate how to transform one agent's structured result into a new AgentExecutorRequest for a downstream agent.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- You understand the basics of WorkflowBuilder, executors, and events in this framework.
|
||||
- You know the concept of edge conditions and how they gate routes using a predicate function.
|
||||
- Azure OpenAI access is configured for AzureOpenAIChatClient. You should be logged in with Azure CLI (AzureCliCredential)
|
||||
and have the Azure OpenAI environment variables set as documented in the getting started chat client README.
|
||||
- Azure OpenAI access is configured for AzureOpenAIResponsesClient. You should be logged in with Azure CLI (AzureCliCredential)
|
||||
and have the Foundry V2 Project environment variables set as documented in the getting started chat client README.
|
||||
- The sample email resource file exists at workflow/resources/email.txt.
|
||||
|
||||
High level flow:
|
||||
@@ -131,7 +132,11 @@ async def to_email_assistant_request(
|
||||
def create_spam_detector_agent() -> Agent:
|
||||
"""Helper to create a spam detection agent."""
|
||||
# AzureCliCredential uses your current az login. This avoids embedding secrets in code.
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Always return JSON with fields is_spam (bool), reason (string), and email_content (string). "
|
||||
@@ -145,7 +150,11 @@ def create_spam_detector_agent() -> Agent:
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Helper to create an email assistant agent."""
|
||||
# AzureCliCredential uses your current az login. This avoids embedding secrets in code.
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are an email assistant that helps users draft professional responses to emails. "
|
||||
"Your input may be a JSON object that includes 'email_content'; base your reply on that content. "
|
||||
@@ -178,7 +187,7 @@ async def main() -> None:
|
||||
|
||||
# Read Email content from the sample resource file.
|
||||
# This keeps the sample deterministic since the model sees the same email every run.
|
||||
email_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "email.txt")
|
||||
email_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "email.txt") # noqa: ASYNC240
|
||||
|
||||
with open(email_path) as email_file: # noqa: ASYNC230
|
||||
email = email_file.read()
|
||||
|
||||
+23
-5
@@ -13,13 +13,14 @@ from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponseUpdate,
|
||||
Message,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowEvent,
|
||||
executor,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Never
|
||||
@@ -42,6 +43,7 @@ Show how to:
|
||||
- Apply conditional persistence logic (short vs long emails).
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Familiarity with WorkflowBuilder, executors, edges, and events.
|
||||
- Understanding of multi-selection edge groups and how their selection function maps to target ids.
|
||||
- Experience with workflow state for persisting and reusing objects.
|
||||
@@ -177,12 +179,16 @@ async def handle_uncertain(analysis: AnalysisResult, ctx: WorkflowContext[Never,
|
||||
async def database_access(analysis: AnalysisResult, ctx: WorkflowContext[Never, str]) -> None:
|
||||
# Simulate DB writes for email and analysis (and summary if present)
|
||||
await asyncio.sleep(0.05)
|
||||
await ctx.add_event(DatabaseEvent(f"Email {analysis.email_id} saved to database."))
|
||||
await ctx.add_event(DatabaseEvent(type="database_event", data=f"Email {analysis.email_id} saved to database.")) # type: ignore
|
||||
|
||||
|
||||
def create_email_analysis_agent() -> Agent:
|
||||
"""Creates the email analysis agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Always return JSON with fields 'spam_decision' (one of NotSpam, Spam, Uncertain) "
|
||||
@@ -195,7 +201,11 @@ def create_email_analysis_agent() -> Agent:
|
||||
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Creates the email assistant agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an email assistant that helps users draft responses to emails with professionalism."),
|
||||
name="email_assistant_agent",
|
||||
default_options={"response_format": EmailResponse},
|
||||
@@ -204,7 +214,11 @@ def create_email_assistant_agent() -> Agent:
|
||||
|
||||
def create_email_summary_agent() -> Agent:
|
||||
"""Creates the email summary agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an assistant that helps users summarize emails."),
|
||||
name="email_summary_agent",
|
||||
default_options={"response_format": EmailSummaryModel},
|
||||
@@ -267,6 +281,10 @@ async def main() -> None:
|
||||
if isinstance(event, DatabaseEvent):
|
||||
print(f"{event}")
|
||||
elif event.type == "output":
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
# Agent executors stream token-level updates. Skip these to keep sample
|
||||
# output focused on final workflow results.
|
||||
continue
|
||||
print(f"Workflow output: {event.data}")
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from enum import Enum
|
||||
|
||||
from agent_framework import (
|
||||
@@ -8,13 +9,14 @@ from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponseUpdate,
|
||||
Executor,
|
||||
Message,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
@@ -26,7 +28,8 @@ What it does:
|
||||
- The workflow completes when the correct number is guessed.
|
||||
|
||||
Prerequisites:
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIChatClient` agent.
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Azure AI/ Azure OpenAI for `AzureOpenAIResponsesClient` agent.
|
||||
- Authentication via `azure-identity` — uses `AzureCliCredential()` (run `az login`).
|
||||
"""
|
||||
|
||||
@@ -116,7 +119,11 @@ class ParseJudgeResponse(Executor):
|
||||
|
||||
def create_judge_agent() -> Agent:
|
||||
"""Create a judge agent that evaluates guesses."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You strictly respond with one of: MATCHED, ABOVE, BELOW based on the given target and guess."),
|
||||
name="judge_agent",
|
||||
)
|
||||
@@ -140,12 +147,16 @@ async def main():
|
||||
.build()
|
||||
)
|
||||
|
||||
# Step 2: Run the workflow and print the events.
|
||||
# Step 2: Run the workflow with concise streaming output.
|
||||
iterations = 0
|
||||
async for event in workflow.run(NumberSignal.INIT, stream=True):
|
||||
if event.type == "executor_completed" and event.executor_id == "guess_number":
|
||||
iterations += 1
|
||||
print(f"Event: {event}")
|
||||
elif event.type == "output":
|
||||
if isinstance(event.data, AgentResponseUpdate):
|
||||
# Agent executor streams token-level updates; skip to avoid noisy logs.
|
||||
continue
|
||||
print(f"Workflow output: {event.data}")
|
||||
|
||||
# This is essentially a binary search, so the number of iterations should be logarithmic.
|
||||
# The maximum number of iterations is [log2(range size)]. For a range of 1 to 100, this is log2(100) which is 7.
|
||||
|
||||
@@ -18,7 +18,7 @@ from agent_framework import ( # Core chat primitives used to form LLM requests
|
||||
WorkflowContext, # Per-run context and event bus
|
||||
executor, # Decorator to turn a function into a workflow executor
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient # Thin client for Azure OpenAI chat models
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient # Thin client for Azure OpenAI chat models
|
||||
from azure.identity import AzureCliCredential # Uses your az CLI login for credentials
|
||||
from pydantic import BaseModel # Structured outputs with validation
|
||||
from typing_extensions import Never
|
||||
@@ -39,9 +39,10 @@ on that type.
|
||||
- Use ctx.yield_output() to provide workflow results - the workflow completes when idle with no pending work.
|
||||
|
||||
Prerequisites:
|
||||
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
|
||||
- Familiarity with WorkflowBuilder, executors, edges, and events.
|
||||
- Understanding of switch-case edge groups and how Case and Default are evaluated in order.
|
||||
- Working Azure OpenAI configuration for AzureOpenAIChatClient, with Azure CLI login and required environment variables.
|
||||
- Working Azure OpenAI configuration for AzureOpenAIResponsesClient, with Azure CLI login and required environment variables.
|
||||
- Access to workflow/resources/ambiguous_email.txt, or accept the inline fallback string.
|
||||
"""
|
||||
|
||||
@@ -154,7 +155,11 @@ async def handle_uncertain(detection: DetectionResult, ctx: WorkflowContext[Neve
|
||||
|
||||
def create_spam_detection_agent() -> Agent:
|
||||
"""Create and return the spam detection agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=(
|
||||
"You are a spam detection assistant that identifies spam emails. "
|
||||
"Be less confident in your assessments. "
|
||||
@@ -168,7 +173,11 @@ def create_spam_detection_agent() -> Agent:
|
||||
|
||||
def create_email_assistant_agent() -> Agent:
|
||||
"""Create and return the email assistant agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
).as_agent(
|
||||
instructions=("You are an email assistant that helps users draft responses to emails with professionalism."),
|
||||
name="email_assistant_agent",
|
||||
default_options={"response_format": EmailResponse},
|
||||
|
||||
Reference in New Issue
Block a user