fix and unify devui samples (#5025)

This commit is contained in:
Eduard van Valkenburg
2026-04-01 15:47:20 +02:00
committed by GitHub
Unverified
parent cee0a458fe
commit 2cb78ea12e
28 changed files with 169 additions and 217 deletions
@@ -0,0 +1,15 @@
# Shared configuration for samples/02-agents/devui
# Used by in_memory_mode.py, main.py, and as a fallback for discovered samples.
# Run `az login` before starting Azure-backed samples.
# Microsoft Foundry samples
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
# Azure OpenAI workflow sample
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=gpt-4o
# Optional fallback env name also supported by workflow_with_agents/workflow.py:
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
# Optional if you need to override the default API version:
AZURE_OPENAI_API_VERSION=2024-10-21
+78 -30
View File
@@ -16,76 +16,124 @@ DevUI is a sample application that provides:
## Quick Start
### Option 1: In-Memory Mode (Simplest)
### Option 1: In-Memory Mode (Programmatic Registration)
Run a single sample directly. This demonstrates how to wrap agents and workflows programmatically without needing a directory structure:
Run a single sample directly. This demonstrates how to register agents and workflows in code without using DevUI's directory discovery.
This sample uses Azure AI Foundry. Before running it:
1. Copy `.env.example` in this folder to `.env`, or export the same values in your shell
2. Set `FOUNDRY_PROJECT_ENDPOINT` and `FOUNDRY_MODEL`
3. Run `az login`
Then start the sample:
```bash
cd python/samples/02-agents/devui
python in_memory_mode.py
```
This opens your browser at http://localhost:8090 with pre-configured agents and a basic workflow.
This opens your browser at http://localhost:8090 with two Foundry-backed agents and a simple text transformation workflow.
### Option 2: Directory Discovery
### Option 2: Directory Discovery with Shared Root `.env`
Launch DevUI to discover all samples in this folder:
Run the folder-level launcher to load `samples/02-agents/devui/.env` and then start DevUI with directory discovery for this folder:
```bash
cd python/samples/02-agents/devui
devui
python main.py
```
This starts the server at http://localhost:8080 with all agents and workflows available.
This starts the server at http://localhost:8080 with all discoverable agents and workflows available. The root `.env` acts as shared fallback configuration for discovered samples.
### Option 3: Directory Discovery with the `devui` CLI
If you prefer the CLI directly, you can still launch DevUI from this folder:
```bash
cd python/samples/02-agents/devui
devui .
```
DevUI discovery checks for a sample-specific `.env` first and then falls back to `.env` in `samples/02-agents/devui/`.
## Sample Structure
Each agent/workflow follows a strict structure required by DevUI's discovery system:
DevUI discovers samples from Python packages that export either `agent` or `workflow`.
Typical agent layout:
```
agent_name/
├── __init__.py # Must export: agent = Agent(...)
├── __init__.py # Must export: agent = ...
├── agent.py # Agent implementation
└── .env.example # Example environment variables
└── .env.example # Optional example environment variables
```
Typical workflow layout:
```
workflow_name/
├── __init__.py # Must export: workflow = ...
├── workflow.py # Workflow implementation
├── workflow.yaml # Optional declarative definition
└── .env.example # Optional example environment variables
```
## Available Samples
### Agents
| Sample | Description | Features | Required Environment Variables |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [**weather_agent_azure/**](weather_agent_azure/) | Weather agent using Azure OpenAI with API key authentication | Azure OpenAI integration, function calling, mock weather tools | `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_ENDPOINT` |
| [**foundry_agent/**](foundry_agent/) | Weather agent using Azure AI Agent (Foundry) with Azure CLI authentication (run `az login` first) | Azure AI Agent integration, Azure CLI authentication, mock weather tools | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL` |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**agent_weather/**](agent_weather/) | A richer Foundry-backed weather agent that shows chat middleware, function middleware, tool calling, and an approval-required tool alongside auto-approved tools. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
| [**agent_foundry/**](agent_foundry/) | A minimal Foundry-backed weather agent with current weather and forecast tools. Use this when you want the smallest possible directory-discovered agent sample. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
### Workflows
| Sample | Description | Features | Required Environment Variables |
| -------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [**declarative/**](declarative/) | Declarative YAML workflow with conditional branching | YAML-based workflow definition, conditional logic, no Python code required | None - uses mock data |
| [**workflow_agents/**](workflow_agents/) | Content review workflow with agents as executors | Agents as workflow nodes, conditional routing based on structured outputs, quality-based paths (Writer -> Reviewer -> Editor/Publisher) | `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_ENDPOINT` |
| [**spam_workflow/**](spam_workflow/) | 5-step email spam detection workflow with branching logic | Sequential execution, conditional branching (spam vs. legitimate), multiple executors, mock spam detection | None - uses mock data |
| [**fanout_workflow/**](fanout_workflow/) | Advanced data processing workflow with parallel execution | Fan-out/fan-in patterns, complex state management, multi-stage processing (validation -> transformation -> quality assurance) | None - uses mock data |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**workflow_declarative/**](workflow_declarative/) | A YAML-defined workflow loaded through `WorkflowFactory`, with nested age-based branching and no model client code. | None |
| [**workflow_with_agents/**](workflow_with_agents/) | A content review workflow that uses agents as executors and routes based on structured review output (`Writer -> Reviewer -> Editor/Publisher -> Summarizer`). | `AZURE_OPENAI_ENDPOINT`, plus `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` or `AZURE_OPENAI_DEPLOYMENT_NAME`; Azure CLI auth via `az login`; `AZURE_OPENAI_API_VERSION` is optional |
| [**workflow_spam/**](workflow_spam/) | A multi-step spam detection workflow with human-in-the-loop approval, branching for spam vs. legitimate messages, and a final reporting step. | None |
| [**workflow_fanout/**](workflow_fanout/) | A larger fan-out/fan-in data processing workflow with parallel validation, multiple transformations, QA, aggregation, and demo failure toggles. | None |
### Standalone Examples
| Sample | Description | Features |
| ------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| [**in_memory_mode.py**](in_memory_mode.py) | Demonstrates programmatic entity registration without directory structure | In-memory agent and workflow registration, multiple entities served from a single file, includes basic workflow, simplest way to get started |
| Sample | What it demonstrates | Required keys / auth |
| ------ | -------------------- | -------------------- |
| [**in_memory_mode.py**](in_memory_mode.py) | Registers multiple entities directly in Python: two Foundry-backed agents plus a simple workflow, all served from one file without directory discovery. | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, plus Azure CLI auth via `az login` |
## Environment Variables
Each sample that requires API keys includes a `.env.example` file. To use:
For samples that require external services:
1. Copy `.env.example` to `.env` in the same directory
2. Fill in your actual API keys
3. DevUI automatically loads `.env` files from entity directories
1. Copy `.env.example` to `.env`
2. Fill in the required values
3. Run `az login` for samples that use Azure CLI authentication
Directory discovery checks `.env` files in this order:
1. The entity directory itself, for example `agent_weather/.env`
2. The root DevUI samples folder, `samples/02-agents/devui/.env`
That means the root `.env.example` can hold shared defaults for multiple samples, while a sample-specific `.env` can override those values when needed.
`in_memory_mode.py` and `main.py` both load `.env` from `samples/02-agents/devui/`, so the root `.env.example` in this folder is the right starting point for both commands.
Alternatively, set environment variables globally:
```bash
export OPENAI_API_KEY="your-key-here"
export OPENAI_CHAT_MODEL="gpt-4o"
# Foundry-backed samples
export FOUNDRY_PROJECT_ENDPOINT="https://your-project.services.ai.azure.com"
export FOUNDRY_MODEL="gpt-4o"
# Azure OpenAI workflow_with_agents sample
export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com"
export AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME="gpt-4o"
export AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o"
az login
```
## Using DevUI with Your Own Agents
@@ -145,7 +193,7 @@ curl http://localhost:8080/v1/entities
## Troubleshooting
**Missing API keys**: Check your `.env` files or environment variables.
**Missing credentials or settings**: Check your `.env` files, confirm the required variables for the sample you are running, and make sure `az login` has completed for Azure-authenticated samples.
**Import errors**: Make sure you've installed the devui package:
@@ -0,0 +1,5 @@
# Azure AI Foundry Configuration
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
@@ -53,7 +53,7 @@ agent = Agent(
name="FoundryWeatherAgent",
client=FoundryChatClient(
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
model_model=os.environ.get("FOUNDRY_MODEL"),
model=os.environ.get("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
instructions="""
@@ -0,0 +1,5 @@
# Azure AI Foundry Configuration
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.services.ai.azure.com
FOUNDRY_MODEL=gpt-4o
@@ -22,6 +22,7 @@ from agent_framework import (
)
from agent_framework.foundry import FoundryChatClient
from agent_framework_devui import register_cleanup
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
@@ -145,7 +146,7 @@ def send_email(
# Agent instance following Agent Framework conventions
agent = Agent(
name="AzureWeatherAgent",
name="WeatherAgent",
description="A helpful agent that provides weather information and forecasts",
instructions="""
You are a weather assistant. You can provide current weather information
@@ -153,7 +154,9 @@ agent = Agent(
weather information when asked.
""",
client=FoundryChatClient(
api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""),
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
model=os.environ.get("FOUNDRY_MODEL"),
credential=AzureCliCredential(),
),
tools=[get_weather, get_forecast, send_email],
middleware=[security_filter_middleware, atlantis_location_filter_middleware],
@@ -164,7 +167,7 @@ register_cleanup(agent, cleanup_resources)
def main():
"""Launch the Azure weather agent in DevUI."""
"""Launch the Weather Agent in DevUI."""
import logging
from agent_framework.devui import serve
@@ -173,9 +176,9 @@ def main():
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)
logger.info("Starting Azure Weather Agent")
logger.info("Starting Weather Agent")
logger.info("Available at: http://localhost:8090")
logger.info("Entity ID: agent_AzureWeatherAgent")
logger.info("Entity ID: agent_WeatherAgent")
# Launch server with the agent
serve(entities=[agent], port=8090, auto_open=True)
@@ -1,15 +0,0 @@
# Azure OpenAI Responses API Configuration
# The Responses API supports PDF uploads, images, and other multimodal content.
# Requires api-version 2025-03-01-preview or later.
# Option 1: Use API key authentication
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
# Option 2: Use Azure CLI authentication (run 'az login' first)
# No API key needed - just leave AZURE_OPENAI_API_KEY unset
# Required: Azure OpenAI endpoint with Responses API support
AZURE_OPENAI_ENDPOINT=https://your-resource.cognitiveservices.azure.com/
# Required: Deployment name (must support Responses API)
FOUNDRY_MODEL=gpt-4.1-mini
@@ -1,6 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
"""Azure Responses Agent sample for DevUI."""
from .agent import agent
__all__ = ["agent"]
@@ -1,128 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
"""Sample agent using Azure OpenAI Responses API for Agent Framework DevUI.
This agent uses the Responses API which supports:
- PDF file uploads
- Image uploads
- Audio inputs
- And other multimodal content
The Chat Completions API (FoundryChatClient) does NOT support PDF uploads.
Use this agent when you need to process documents or other file types.
Required environment variables:
- AZURE_OPENAI_ENDPOINT: Your Azure OpenAI endpoint
- FOUNDRY_MODEL: Deployment name for Responses API
(falls back to FOUNDRY_MODEL if not set)
- AZURE_OPENAI_API_KEY: Your API key (or use Azure CLI auth)
"""
import logging
import os
from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.foundry import FoundryChatClient
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logger = logging.getLogger(__name__)
# Get deployment name - try responses-specific env var first, fall back to chat deployment
_deployment_name = os.environ.get(
"FOUNDRY_MODEL",
os.environ.get("FOUNDRY_MODEL", ""),
)
# Get endpoint - try responses-specific env var first, fall back to default
_endpoint = os.environ.get(
"AZURE_OPENAI_RESPONSES_ENDPOINT",
os.environ.get("AZURE_OPENAI_ENDPOINT", ""),
)
def analyze_content(
query: Annotated[str, "What to analyze or extract from the uploaded content"],
) -> str:
"""Analyze uploaded content based on the user's query.
This is a placeholder - the actual analysis is done by the model
when processing the uploaded files.
"""
return f"Analyzing content for: {query}"
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def summarize_document(
length: Annotated[str, "Desired summary length: 'brief', 'medium', or 'detailed'"] = "medium",
) -> str:
"""Generate a summary of the uploaded document."""
return f"Generating {length} summary of the document..."
@tool(approval_mode="never_require")
def extract_key_points(
max_points: Annotated[int, "Maximum number of key points to extract"] = 5,
) -> str:
"""Extract key points from the uploaded document."""
return f"Extracting up to {max_points} key points..."
# Agent using Azure OpenAI Responses API (supports PDF uploads!)
agent = Agent(
name="AzureResponsesAgent",
description="An agent that can analyze PDFs, images, and other documents using Azure OpenAI Responses API",
instructions="""
You are a helpful document analysis assistant. You can:
1. Analyze uploaded PDF documents and extract information
2. Summarize document contents
3. Answer questions about uploaded files
4. Extract key points and insights
When a user uploads a file, carefully analyze its contents and provide
helpful, accurate information based on what you find.
For PDFs, you can read and understand the text, tables, and structure.
For images, you can describe what you see and extract any text.
""",
client=FoundryChatClient(
model=_deployment_name,
endpoint=_endpoint,
api_version="2025-03-01-preview", # Required for Responses API
),
tools=[summarize_document, extract_key_points],
)
def main():
"""Launch the Azure Responses agent in DevUI."""
from agent_framework_devui import serve
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger.info("=" * 60)
logger.info("Starting Azure Responses Agent")
logger.info("=" * 60)
logger.info("")
logger.info("This agent uses the Azure OpenAI Responses API which supports:")
logger.info(" - PDF file uploads")
logger.info(" - Image uploads")
logger.info(" - Audio inputs")
logger.info("")
logger.info("Try uploading a PDF and asking questions about it!")
logger.info("")
logger.info("Required environment variables:")
logger.info(" - AZURE_OPENAI_ENDPOINT")
logger.info(" - FOUNDRY_MODEL")
logger.info(" - AZURE_OPENAI_API_KEY (or use Azure CLI auth)")
logger.info("")
serve(entities=[agent], port=8090, auto_open=True)
if __name__ == "__main__":
main()
@@ -1,6 +0,0 @@
# Azure AI Foundry Configuration
# Get your credentials from Azure AI Foundry portal
# Make sure to run 'az login' before starting devui
FOUNDRY_PROJECT_ENDPOINT=https://your-project.api.azureml.ms
FOUNDRY_MODEL=gpt-4o
@@ -20,6 +20,7 @@ from agent_framework import (
)
from agent_framework.devui import serve
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from dotenv import load_dotenv
from typing_extensions import Never
@@ -80,14 +81,13 @@ def main():
# Create Azure OpenAI chat client
client = FoundryChatClient(
api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
model=os.environ["FOUNDRY_MODEL"],
endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_version=os.environ.get("AZURE_OPENAI_API_VERSION", "2024-10-21"),
project_endpoint=os.environ.get("FOUNDRY_PROJECT_ENDPOINT"),
credential=AzureCliCredential(),
)
# Create agents
weather_agent = Agent(
weather_assistant = Agent(
name="weather-assistant",
description="Provides weather information and time",
instructions=(
@@ -120,7 +120,7 @@ def main():
)
# Collect entities for serving
entities = [weather_agent, simple_agent, basic_workflow]
entities = [weather_assistant, simple_agent, basic_workflow]
logger.info("Starting DevUI on http://localhost:8090")
logger.info("Entities available:")
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) Microsoft. All rights reserved.
"""Launch DevUI with folder discovery for the samples in this directory.
This sample demonstrates:
- Loading a shared root `.env` file for the DevUI samples folder
- Starting DevUI in directory discovery mode for this folder
- Using root-level settings as fallbacks for discovered samples
"""
from pathlib import Path
from agent_framework.devui import serve
from dotenv import load_dotenv
def main() -> None:
"""Load the root .env file and launch DevUI with folder discovery."""
samples_dir = Path(__file__).resolve().parent
# 1. Load shared defaults for the samples in this folder.
load_dotenv(samples_dir / ".env")
# 2. Start DevUI and discover entities from this directory.
serve(entities_dir=str(samples_dir), auto_open=True)
if __name__ == "__main__":
main()
# Sample output:
# Starting Agent Framework DevUI on 127.0.0.1:8080
@@ -1,6 +0,0 @@
# Azure OpenAI API Configuration
# Get your credentials from Azure Portal
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
@@ -1,7 +0,0 @@
# Azure OpenAI API Configuration
# Get your credentials from Azure Portal
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_VERSION=2024-10-21
@@ -0,0 +1,9 @@
# Azure OpenAI configuration for the Responses-based workflow sample
# This sample uses Azure CLI auth, so run `az login` before starting DevUI.
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME=gpt-4o
# Optional fallback env name also supported by the client:
# AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o
# Optional if you need to override the default API version:
AZURE_OPENAI_API_VERSION=2024-10-21
@@ -18,7 +18,8 @@ import os
from typing import Any
from agent_framework import Agent, AgentExecutorResponse, WorkflowBuilder
from agent_framework.foundry import FoundryChatClient
from agent_framework.openai import OpenAIChatClient
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
from pydantic import BaseModel
@@ -62,8 +63,13 @@ def is_approved(message: Any) -> bool:
return True
# Create Azure OpenAI chat client
client = FoundryChatClient(api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""))
# Create Azure OpenAI Responses chat client
client = OpenAIChatClient(
model=os.environ.get("AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME") or os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME"),
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_version=os.environ.get("AZURE_OPENAI_API_VERSION"),
credential=AzureCliCredential(),
)
# Create Writer agent - generates content
writer = Agent(
@@ -48,9 +48,7 @@ async def main() -> None:
project_endpoint = os.getenv("FOUNDRY_PROJECT_ENDPOINT")
model = os.getenv("FOUNDRY_MODEL")
if not project_endpoint or not model:
raise ValueError(
"FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL must be set"
)
raise ValueError("FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL must be set")
print(f"Connecting to A2A agent at: {a2a_agent_host}")
@@ -48,8 +48,7 @@ async def main() -> None:
client=chat_client,
name="travel-assistant",
instructions=(
"You are a helpful travel assistant. "
"Use your tools to answer questions about weather and flights."
"You are a helpful travel assistant. Use your tools to answer questions about weather and flights."
),
tools=[get_weather, get_flight_price],
)