mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
@@ -27,18 +27,14 @@ from openai.types.responses import (
|
||||
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
|
||||
from openai.types.shared import Metadata, ResponsesModel
|
||||
|
||||
from ._discovery_models import Deployment, DeploymentConfig, DeploymentEvent, DiscoveryResponse, EntityInfo
|
||||
from ._discovery_models import DiscoveryResponse, EntityInfo
|
||||
from ._openai_custom import (
|
||||
AgentFrameworkRequest,
|
||||
CustomResponseOutputItemAddedEvent,
|
||||
CustomResponseOutputItemDoneEvent,
|
||||
ExecutorActionItem,
|
||||
MetaResponse,
|
||||
OpenAIError,
|
||||
ResponseFunctionResultComplete,
|
||||
ResponseOutputData,
|
||||
ResponseOutputFile,
|
||||
ResponseOutputImage,
|
||||
ResponseTraceEvent,
|
||||
ResponseTraceEventComplete,
|
||||
ResponseWorkflowEventComplete,
|
||||
@@ -55,14 +51,10 @@ __all__ = [
|
||||
"ConversationItem",
|
||||
"CustomResponseOutputItemAddedEvent",
|
||||
"CustomResponseOutputItemDoneEvent",
|
||||
"Deployment",
|
||||
"DeploymentConfig",
|
||||
"DeploymentEvent",
|
||||
"DiscoveryResponse",
|
||||
"EntityInfo",
|
||||
"ExecutorActionItem",
|
||||
"InputTokensDetails",
|
||||
"MetaResponse",
|
||||
"Metadata",
|
||||
"OpenAIError",
|
||||
"OpenAIResponse",
|
||||
@@ -75,9 +67,6 @@ __all__ = [
|
||||
"ResponseFunctionToolCall",
|
||||
"ResponseFunctionToolCallOutputItem",
|
||||
"ResponseInputParam",
|
||||
"ResponseOutputData",
|
||||
"ResponseOutputFile",
|
||||
"ResponseOutputImage",
|
||||
"ResponseOutputItemAddedEvent",
|
||||
"ResponseOutputItemDoneEvent",
|
||||
"ResponseOutputMessage",
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EnvVarRequirement(BaseModel):
|
||||
@@ -37,10 +36,6 @@ class EntityInfo(BaseModel):
|
||||
# Environment variable requirements
|
||||
required_env_vars: list[EnvVarRequirement] | None = None
|
||||
|
||||
# Deployment support
|
||||
deployment_supported: bool = False # Whether entity can be deployed
|
||||
deployment_reason: str | None = None # Explanation of why/why not entity can be deployed
|
||||
|
||||
# Agent-specific fields (optional, populated when available)
|
||||
instructions: str | None = None
|
||||
model_id: str | None = None
|
||||
@@ -60,144 +55,3 @@ class DiscoveryResponse(BaseModel):
|
||||
"""Response model for entity discovery."""
|
||||
|
||||
entities: list[EntityInfo] = Field(default_factory=list)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Deployment Models
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class DeploymentConfig(BaseModel):
|
||||
"""Configuration for deploying an entity."""
|
||||
|
||||
entity_id: str = Field(description="Entity ID to deploy")
|
||||
resource_group: str = Field(description="Azure resource group name")
|
||||
app_name: str = Field(description="Azure Container App name")
|
||||
region: str = Field(default="eastus", description="Azure region")
|
||||
ui_mode: str = Field(default="user", description="UI mode (user or developer)")
|
||||
ui_enabled: bool = Field(default=True, description="Whether to enable web interface")
|
||||
stream: bool = Field(default=True, description="Stream deployment events")
|
||||
|
||||
@field_validator("app_name")
|
||||
@classmethod
|
||||
def validate_app_name(cls, v: str) -> str:
|
||||
"""Validate Azure Container App name format.
|
||||
|
||||
Azure Container App names must:
|
||||
- Be 3-32 characters long
|
||||
- Contain only lowercase letters, numbers, and hyphens
|
||||
- Start with a lowercase letter
|
||||
- End with a lowercase letter or number
|
||||
- Not contain consecutive hyphens
|
||||
"""
|
||||
if not v:
|
||||
raise ValueError("app_name cannot be empty")
|
||||
|
||||
if len(v) < 3 or len(v) > 32:
|
||||
raise ValueError("app_name must be between 3 and 32 characters")
|
||||
|
||||
if not re.match(r"^[a-z][a-z0-9-]*[a-z0-9]$", v):
|
||||
raise ValueError(
|
||||
"app_name must start with a lowercase letter, "
|
||||
"end with a letter or number, and contain only lowercase letters, numbers, and hyphens"
|
||||
)
|
||||
|
||||
if "--" in v:
|
||||
raise ValueError("app_name cannot contain consecutive hyphens")
|
||||
|
||||
return v
|
||||
|
||||
@field_validator("resource_group")
|
||||
@classmethod
|
||||
def validate_resource_group(cls, v: str) -> str:
|
||||
"""Validate Azure resource group name format.
|
||||
|
||||
Azure resource group names must:
|
||||
- Be 1-90 characters long
|
||||
- Contain only alphanumeric, underscore, parentheses, hyphen, period (except at end)
|
||||
- Not end with a period
|
||||
"""
|
||||
if not v:
|
||||
raise ValueError("resource_group cannot be empty")
|
||||
|
||||
if len(v) > 90:
|
||||
raise ValueError("resource_group must be 90 characters or less")
|
||||
|
||||
if not re.match(r"^[a-zA-Z0-9._()-]+$", v):
|
||||
raise ValueError(
|
||||
"resource_group can only contain alphanumeric characters, "
|
||||
"underscores, hyphens, periods, and parentheses"
|
||||
)
|
||||
|
||||
if v.endswith("."):
|
||||
raise ValueError("resource_group cannot end with a period")
|
||||
|
||||
return v
|
||||
|
||||
@field_validator("region")
|
||||
@classmethod
|
||||
def validate_region(cls, v: str) -> str:
|
||||
"""Validate Azure region format.
|
||||
|
||||
Validates that the region string is a reasonable format.
|
||||
Does not validate against the full list of Azure regions (which changes).
|
||||
"""
|
||||
if not v:
|
||||
raise ValueError("region cannot be empty")
|
||||
|
||||
if len(v) > 50:
|
||||
raise ValueError("region name too long")
|
||||
|
||||
# Azure regions are typically lowercase with no spaces (e.g., eastus, westeurope)
|
||||
if not re.match(r"^[a-z0-9]+$", v):
|
||||
raise ValueError("region must contain only lowercase letters and numbers (e.g., eastus, westeurope)")
|
||||
|
||||
return v
|
||||
|
||||
@field_validator("entity_id")
|
||||
@classmethod
|
||||
def validate_entity_id(cls, v: str) -> str:
|
||||
"""Validate entity_id format to prevent injection attacks."""
|
||||
if not v:
|
||||
raise ValueError("entity_id cannot be empty")
|
||||
|
||||
if len(v) > 256:
|
||||
raise ValueError("entity_id too long")
|
||||
|
||||
# Allow alphanumeric, hyphens, underscores, and periods
|
||||
if not re.match(r"^[a-zA-Z0-9._-]+$", v):
|
||||
raise ValueError("entity_id contains invalid characters")
|
||||
|
||||
return v
|
||||
|
||||
@field_validator("ui_mode")
|
||||
@classmethod
|
||||
def validate_ui_mode(cls, v: str) -> str:
|
||||
"""Validate ui_mode is one of the allowed values."""
|
||||
if v not in ("user", "developer"):
|
||||
raise ValueError("ui_mode must be 'user' or 'developer'")
|
||||
|
||||
return v
|
||||
|
||||
|
||||
class DeploymentEvent(BaseModel):
|
||||
"""Real-time deployment event (SSE)."""
|
||||
|
||||
type: str = Field(description="Event type (e.g., deploy.validating, deploy.building)")
|
||||
message: str = Field(description="Human-readable message")
|
||||
url: str | None = Field(default=None, description="Deployment URL (on completion)")
|
||||
auth_token: str | None = Field(default=None, description="Auth token (on completion, shown once)")
|
||||
|
||||
|
||||
class Deployment(BaseModel):
|
||||
"""Deployment record."""
|
||||
|
||||
id: str = Field(description="Deployment ID (UUID)")
|
||||
entity_id: str = Field(description="Entity ID that was deployed")
|
||||
resource_group: str = Field(description="Azure resource group")
|
||||
app_name: str = Field(description="Azure Container App name")
|
||||
region: str = Field(description="Azure region")
|
||||
url: str = Field(description="Deployment URL")
|
||||
status: str = Field(description="Deployment status (deploying, deployed, failed)")
|
||||
created_at: str = Field(description="ISO 8601 timestamp")
|
||||
error: str | None = Field(default=None, description="Error message if failed")
|
||||
|
||||
@@ -80,16 +80,9 @@ class CustomResponseOutputItemDoneEvent(BaseModel):
|
||||
|
||||
|
||||
class ResponseWorkflowEventComplete(BaseModel):
|
||||
"""Complete workflow event data.
|
||||
"""Complete workflow event data."""
|
||||
|
||||
DevUI extension for workflow execution events (debugging/observability).
|
||||
Uses past-tense 'completed' to follow OpenAI's event naming pattern.
|
||||
|
||||
Workflow events are shown in the debug panel for monitoring execution flow,
|
||||
not in main chat. Use response.output_item.added for user-facing content.
|
||||
"""
|
||||
|
||||
type: Literal["response.workflow_event.completed"] = "response.workflow_event.completed"
|
||||
type: Literal["response.workflow_event.complete"] = "response.workflow_event.complete"
|
||||
data: dict[str, Any] # Complete event data, not delta
|
||||
executor_id: str | None = None
|
||||
item_id: str
|
||||
@@ -98,17 +91,9 @@ class ResponseWorkflowEventComplete(BaseModel):
|
||||
|
||||
|
||||
class ResponseTraceEventComplete(BaseModel):
|
||||
"""Complete trace event data.
|
||||
"""Complete trace event data."""
|
||||
|
||||
DevUI extension for non-displayable debugging/metadata events.
|
||||
Uses past-tense 'completed' to follow OpenAI's event naming pattern
|
||||
(e.g., response.completed, response.output_item.added).
|
||||
|
||||
Trace events are shown in the Traces debug panel, not in main chat.
|
||||
Use response.output_item.added for user-facing content.
|
||||
"""
|
||||
|
||||
type: Literal["response.trace.completed"] = "response.trace.completed"
|
||||
type: Literal["response.trace.complete"] = "response.trace.complete"
|
||||
data: dict[str, Any] # Complete trace data, not delta
|
||||
span_id: str | None = None
|
||||
item_id: str
|
||||
@@ -139,139 +124,6 @@ class ResponseFunctionResultComplete(BaseModel):
|
||||
timestamp: str | None = None # Optional timestamp for UI display
|
||||
|
||||
|
||||
class ResponseRequestInfoEvent(BaseModel):
|
||||
"""DevUI extension: Workflow requests human input.
|
||||
|
||||
This is a DevUI extension because:
|
||||
- OpenAI Responses API doesn't have a concept of workflow human-in-the-loop pausing
|
||||
- Agent Framework workflows can pause via RequestInfoExecutor to collect external information
|
||||
- Clients need to render forms and submit responses to continue workflow execution
|
||||
|
||||
When a workflow emits this event, it enters IDLE_WITH_PENDING_REQUESTS state.
|
||||
Client should render a form based on request_schema and submit responses via
|
||||
a new request with workflow_hil_response content type.
|
||||
"""
|
||||
|
||||
type: Literal["response.request_info.requested"] = "response.request_info.requested"
|
||||
request_id: str
|
||||
"""Unique identifier for correlating this request with the response."""
|
||||
|
||||
source_executor_id: str
|
||||
"""ID of the executor that is waiting for this response."""
|
||||
|
||||
request_type: str
|
||||
"""Fully qualified type name of the request (e.g., 'module.path:ClassName')."""
|
||||
|
||||
request_data: dict[str, Any]
|
||||
"""Current data from the RequestInfoMessage (may contain defaults/context)."""
|
||||
|
||||
request_schema: dict[str, Any]
|
||||
"""JSON schema describing the request data structure (what the workflow is asking about)."""
|
||||
|
||||
response_schema: dict[str, Any] | None = None
|
||||
"""JSON schema describing the expected response structure for form rendering (what user should provide)."""
|
||||
|
||||
item_id: str
|
||||
"""OpenAI item ID for correlation."""
|
||||
|
||||
output_index: int = 0
|
||||
"""Output index for OpenAI compatibility."""
|
||||
|
||||
sequence_number: int
|
||||
"""Sequence number for ordering events."""
|
||||
|
||||
timestamp: str
|
||||
"""ISO timestamp when the request was made."""
|
||||
|
||||
|
||||
# DevUI Output Content Types - for agent-generated media/data
|
||||
# These extend ResponseOutputItem to support rich content outputs that OpenAI's API doesn't natively support
|
||||
|
||||
|
||||
class ResponseOutputImage(BaseModel):
|
||||
"""DevUI extension: Agent-generated image output.
|
||||
|
||||
This is a DevUI extension because:
|
||||
- OpenAI Responses API only supports text output in ResponseOutputMessage.content
|
||||
- ImageGenerationCall exists but is for tool calls (generating images), not returning existing images
|
||||
- Agent Framework agents can return images via DataContent/UriContent that need proper display
|
||||
|
||||
This type allows images to be displayed inline in chat rather than hidden in trace logs.
|
||||
"""
|
||||
|
||||
id: str
|
||||
"""The unique ID of the image output."""
|
||||
|
||||
image_url: str
|
||||
"""The URL or data URI of the image (e.g., data:image/png;base64,...)"""
|
||||
|
||||
type: Literal["output_image"] = "output_image"
|
||||
"""The type of the output. Always `output_image`."""
|
||||
|
||||
alt_text: str | None = None
|
||||
"""Optional alt text for accessibility."""
|
||||
|
||||
mime_type: str = "image/png"
|
||||
"""The MIME type of the image (e.g., image/png, image/jpeg)."""
|
||||
|
||||
|
||||
class ResponseOutputFile(BaseModel):
|
||||
"""DevUI extension: Agent-generated file output.
|
||||
|
||||
This is a DevUI extension because:
|
||||
- OpenAI Responses API only supports text output in ResponseOutputMessage.content
|
||||
- Agent Framework agents can return files via DataContent/UriContent that need proper display
|
||||
- Supports PDFs, audio files, and other media types
|
||||
|
||||
This type allows files to be displayed inline in chat with appropriate renderers.
|
||||
"""
|
||||
|
||||
id: str
|
||||
"""The unique ID of the file output."""
|
||||
|
||||
filename: str
|
||||
"""The filename (used to determine rendering and download)."""
|
||||
|
||||
type: Literal["output_file"] = "output_file"
|
||||
"""The type of the output. Always `output_file`."""
|
||||
|
||||
file_url: str | None = None
|
||||
"""Optional URL to the file."""
|
||||
|
||||
file_data: str | None = None
|
||||
"""Optional base64-encoded file data."""
|
||||
|
||||
mime_type: str = "application/octet-stream"
|
||||
"""The MIME type of the file (e.g., application/pdf, audio/mp3)."""
|
||||
|
||||
|
||||
class ResponseOutputData(BaseModel):
|
||||
"""DevUI extension: Agent-generated generic data output.
|
||||
|
||||
This is a DevUI extension because:
|
||||
- OpenAI Responses API only supports text output in ResponseOutputMessage.content
|
||||
- Agent Framework agents can return arbitrary structured data that needs display
|
||||
- Useful for debugging and displaying non-text content
|
||||
|
||||
This type allows generic data to be displayed inline in chat.
|
||||
"""
|
||||
|
||||
id: str
|
||||
"""The unique ID of the data output."""
|
||||
|
||||
data: str
|
||||
"""The data payload (string representation)."""
|
||||
|
||||
type: Literal["output_data"] = "output_data"
|
||||
"""The type of the output. Always `output_data`."""
|
||||
|
||||
mime_type: str
|
||||
"""The MIME type of the data."""
|
||||
|
||||
description: str | None = None
|
||||
"""Optional description of the data."""
|
||||
|
||||
|
||||
# Agent Framework extension fields
|
||||
class AgentFrameworkExtraBody(BaseModel):
|
||||
"""Agent Framework specific routing fields for OpenAI requests."""
|
||||
@@ -292,7 +144,7 @@ class AgentFrameworkRequest(BaseModel):
|
||||
"""
|
||||
|
||||
# All OpenAI fields from ResponseCreateParams
|
||||
model: str | None = None
|
||||
model: str # Used as entity_id in DevUI!
|
||||
input: str | list[Any] | dict[str, Any] # ResponseInputParam + dict for workflow structured input
|
||||
stream: bool | None = False
|
||||
|
||||
@@ -304,25 +156,20 @@ class AgentFrameworkRequest(BaseModel):
|
||||
metadata: dict[str, Any] | None = None
|
||||
temperature: float | None = None
|
||||
max_output_tokens: int | None = None
|
||||
top_p: float | None = None
|
||||
tools: list[dict[str, Any]] | None = None
|
||||
|
||||
# Reasoning parameters (for o-series models)
|
||||
reasoning: dict[str, Any] | None = None # {"effort": "low" | "medium" | "high" | "minimal"}
|
||||
|
||||
# Optional extra_body for advanced use cases
|
||||
extra_body: dict[str, Any] | None = None
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
def get_entity_id(self) -> str | None:
|
||||
"""Get entity_id from metadata.entity_id.
|
||||
def get_entity_id(self) -> str:
|
||||
"""Get entity_id from model field.
|
||||
|
||||
In DevUI, entity_id is specified in metadata for routing.
|
||||
In DevUI, model IS the entity_id (agent/workflow name).
|
||||
Simple and clean!
|
||||
"""
|
||||
if self.metadata:
|
||||
return self.metadata.get("entity_id")
|
||||
return None
|
||||
return self.model
|
||||
|
||||
def get_conversation_id(self) -> str | None:
|
||||
"""Extract conversation_id from conversation parameter.
|
||||
@@ -371,40 +218,11 @@ class OpenAIError(BaseModel):
|
||||
return self.model_dump_json()
|
||||
|
||||
|
||||
class MetaResponse(BaseModel):
|
||||
"""Server metadata response for /meta endpoint.
|
||||
|
||||
Provides information about the DevUI server configuration and capabilities.
|
||||
"""
|
||||
|
||||
ui_mode: Literal["developer", "user"] = "developer"
|
||||
"""UI interface mode - 'developer' shows debug tools, 'user' shows simplified interface."""
|
||||
|
||||
version: str
|
||||
"""DevUI version string."""
|
||||
|
||||
framework: str = "agent_framework"
|
||||
"""Backend framework identifier."""
|
||||
|
||||
runtime: Literal["python", "dotnet"] = "python"
|
||||
"""Backend runtime/language - 'python' or 'dotnet' for deployment guides and feature availability."""
|
||||
|
||||
capabilities: dict[str, bool] = {}
|
||||
"""Server capabilities (e.g., tracing, openai_proxy)."""
|
||||
|
||||
auth_required: bool = False
|
||||
"""Whether the server requires Bearer token authentication."""
|
||||
|
||||
|
||||
# Export all custom types
|
||||
__all__ = [
|
||||
"AgentFrameworkRequest",
|
||||
"MetaResponse",
|
||||
"OpenAIError",
|
||||
"ResponseFunctionResultComplete",
|
||||
"ResponseOutputData",
|
||||
"ResponseOutputFile",
|
||||
"ResponseOutputImage",
|
||||
"ResponseTraceEvent",
|
||||
"ResponseTraceEventComplete",
|
||||
"ResponseWorkflowEventComplete",
|
||||
|
||||
Reference in New Issue
Block a user