Python: DevUI - Internal Refactor, Conversations API support, and per… (#1235)

* Python: DevUI - Internal Refactor, Conversations API support, and performance improvements

Comprehensive refactor of DevUI package including samples relocation,
frontend reorganization, OpenAI Conversations API support, and critical
performance and code quality improvements.

Key Changes:

Architecture & Organization
- Moved DevUI samples to python/samples/getting_started/devui/
- Consolidated with other framework samples for better discoverability
- Added .env.example files and comprehensive README
- Restructured frontend components into feature-based folders (agent, workflow, gallery, layout)
- Created new OpenAI-compliant message renderers (devui should render oai responses types primarily)

New Features
- Added _conversations.py (467 lines) - Full conversation storage abstraction, replaces the /threads endpoint to better match oai conversations api
- Implements OpenAI Conversations API for thread management, Supports in-memory and extensible storage backends

API Simplification
- Use 'model' field as entity_id (agent/workflow name) instead of extra_body
- Use standard OpenAI 'conversation' field for conversation context.

Performance & Quality Improvements
- Improved context management in MessageMapper with bounded memory (~500KB max)
- Implemented hybrid LRU + cleanup approach to prevent unbounded memory growth
- General QOL improvement - Eliminated ~150 lines of dead/duplicate code, Consolidated helper functions into _utils.py, Extracted magic numbers to module-level constants, Optimized conversation item lookups with index-based approach

Testing
- Added test_conversations.py (13 tests)
- Added test_performance_fixes.py (9 tests)
- Updated existing tests for code consolidation
- 53 tests passing

Impact: 76 files changed: +4,106 insertions, -2,373 deletions
All linting and formatting checks passing. No breaking changes - backward compatible.

Migration: Samples moved to python/samples/getting_started/devui/

* readme lint fixes

* initial support for function approval and minor ui fixes
This commit is contained in:
Victor Dibia
2025-10-08 12:34:30 -07:00
committed by GitHub
Unverified
parent f5abbc67ae
commit c341ee7ed2
75 changed files with 4605 additions and 2646 deletions
@@ -0,0 +1,6 @@
# Azure OpenAI API Configuration
# Get your credentials from Azure Portal
AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=gpt-4o
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
"""Weather agent sample for DevUI testing."""
from .agent import agent
__all__ = ["agent"]
@@ -0,0 +1,133 @@
# Copyright (c) Microsoft. All rights reserved.
"""Sample weather agent for Agent Framework Debug UI."""
import os
from collections.abc import Awaitable, Callable
from typing import Annotated
from agent_framework import (
ChatAgent,
ChatContext,
ChatMessage,
ChatResponse,
FunctionInvocationContext,
Role,
chat_middleware,
function_middleware,
)
from agent_framework.azure import AzureOpenAIChatClient
@chat_middleware
async def security_filter_middleware(
context: ChatContext,
next: Callable[[ChatContext], Awaitable[None]],
) -> None:
"""Chat middleware that blocks requests containing sensitive information."""
# Block requests with sensitive information
blocked_terms = ["password", "secret", "api_key", "token"]
for message in context.messages:
if message.text:
message_lower = message.text.lower()
for term in blocked_terms:
if term in message_lower:
# Override the response without calling the LLM
context.result = ChatResponse(
messages=[
ChatMessage(
role=Role.ASSISTANT,
text=(
"I cannot process requests containing sensitive information. "
"Please rephrase your question without including passwords, secrets, "
"or other sensitive data."
),
)
]
)
return
await next(context)
@function_middleware
async def atlantis_location_filter_middleware(
context: FunctionInvocationContext,
next: Callable[[FunctionInvocationContext], Awaitable[None]],
) -> None:
"""Function middleware that blocks weather requests for Atlantis."""
# Check if location parameter is "atlantis"
location = getattr(context.arguments, "location", None)
if location and location.lower() == "atlantis":
context.result = (
"Blocked! Hold up right there!! Tell the user that "
"'Atlantis is a special place, we must never ask about the weather there!!'"
)
context.terminate = True
return
await next(context)
def get_weather(
location: Annotated[str, "The location to get the weather for."],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
temperature = 53
return f"The weather in {location} is {conditions[0]} with a high of {temperature}°C."
def get_forecast(
location: Annotated[str, "The location to get the forecast for."],
days: Annotated[int, "Number of days for forecast"] = 3,
) -> str:
"""Get weather forecast for multiple days."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
forecast: list[str] = []
for day in range(1, days + 1):
condition = conditions[0]
temp = 53
forecast.append(f"Day {day}: {condition}, {temp}°C")
return f"Weather forecast for {location}:\n" + "\n".join(forecast)
# Agent instance following Agent Framework conventions
agent = ChatAgent(
name="AzureWeatherAgent",
description="A helpful agent that provides weather information and forecasts",
instructions="""
You are a weather assistant. You can provide current weather information
and forecasts for any location. Always be helpful and provide detailed
weather information when asked.
""",
chat_client=AzureOpenAIChatClient(
api_key=os.environ.get("AZURE_OPENAI_API_KEY", ""),
),
tools=[get_weather, get_forecast],
middleware=[security_filter_middleware, atlantis_location_filter_middleware],
)
def main():
"""Launch the Azure weather agent in DevUI."""
import logging
from agent_framework.devui import serve
# Setup logging
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger(__name__)
logger.info("Starting Azure Weather Agent")
logger.info("Available at: http://localhost:8090")
logger.info("Entity ID: agent_AzureWeatherAgent")
# Launch server with the agent
serve(entities=[agent], port=8090, auto_open=True)
if __name__ == "__main__":
main()