Skip to content

Commit 1ef24d3

Browse files
victordibiacrickmanekzhu
authored
Python: Add DevUI to AgentFramework (microsoft#781)
* add initial backend service code for devui * add tests * add frontendcode * ui updates * update readme * ui updates and tweaks * update ui bundle * improve ui, add react flow base * add react flow ui, fix background * update ui, fix introspection bug * update readme * update ui build * add support for multimodal input - both backend and frontend * update ui build * refactor as main framework package * backend and tests refactor * ui build update * ui build update and refactor * update pyproject.toml, update uv.lock * update ui build * ui update to fit oai responses types * add backend updat and readme update * mypy and other fixes * add intial dev guide * update ui and fix workflow bug * update ui build, add thread support * type fixes * update workflow view * update uv.lock * fix workflow iport errors * lint and other fixes * mypy fixes * minor update * update ui build * refactor to use oai dependencies directly, update examples to samples, improve typing * readme update * update ui and ui build * fix workflow pyright error * update ui, fix issues with run workflow placement, miniamp menu, etc * make samples integrate serve --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 parent adb6dcd commit 1ef24d3

98 files changed

Lines changed: 18045 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

python/packages/devui/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Test artifacts
2+
tests/captured_messages/
3+
4+
# Python cache
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
9+
# Local development files
10+
.env
11+
*.log
12+
13+
# IDE files
14+
.vscode/
15+
.idea/
16+
17+
# OS files
18+
.DS_Store
19+
Thumbs.db

python/packages/devui/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE

python/packages/devui/README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# DevUI - Agent Framework Debug Interface
2+
3+
A lightweight, standalone sample app interface for running entities (agents/workflows) in the Microsoft Agent Framework supporting both **directory-based discovery** and **in-memory entity registration**.
4+
5+
> [!IMPORTANT]
6+
> DevUI is a **sample app** to help you get started with the Agent Framework. It is **not** intended for production use. For production, or for features beyond what is provided in this sample app, it is recommended that you build your own custom interface and API server using the Agent Framework SDK.
7+
8+
![DevUI Screenshot](./docs/devuiscreen.png)
9+
10+
## Quick Start
11+
12+
```bash
13+
# Install
14+
pip install agent-framework-devui
15+
16+
# Launch web UI + API server
17+
devui ./agents --port 8080
18+
# → Web UI: http://localhost:8080
19+
# → API: http://localhost:8080/v1/*
20+
```
21+
22+
You can also launch it programmatically
23+
24+
```python
25+
from agent_framework import ChatAgent
26+
from agent_framework.openai import OpenAIChatClient
27+
from agent_framework.devui import serve
28+
29+
def get_weather(location: str) -> str:
30+
"""Get weather for a location."""
31+
return f"Weather in {location}: 72°F and sunny"
32+
33+
# Create your agent
34+
agent = ChatAgent(
35+
name="WeatherAgent",
36+
chat_client=OpenAIChatClient(),
37+
tools=[get_weather]
38+
)
39+
40+
# Launch debug UI - that's it!
41+
serve(entities=[agent], auto_open=True)
42+
# → Opens browser to http://localhost:8080
43+
```
44+
45+
## Directory Structure
46+
47+
For your agents to be discovered by the DevUI, they must be organized in a directory structure like below. Each agent/workflow must have an `__init__.py` that exports the required variable (`agent` or `workflow`).
48+
49+
**Note**: `.env` files are optional but will be automatically loaded if present in the agent/workflow directory or parent entities directory. Use them to store API keys, configuration variables, and other environment-specific settings.
50+
51+
```
52+
agents/
53+
├── weather_agent/
54+
│ ├── __init__.py # Must export: agent = ChatAgent(...)
55+
│ ├── agent.py
56+
│ └── .env # Optional: API keys, config vars
57+
├── my_workflow/
58+
│ ├── __init__.py # Must export: workflow = WorkflowBuilder()...
59+
│ ├── workflow.py
60+
│ └── .env # Optional: environment variables
61+
└── .env # Optional: shared environment variables
62+
```
63+
64+
## OpenAI-Compatible API
65+
66+
For convenience, you can interact with the agents/workflows using the standard OpenAI API format. Just specify the `entity_id` in the `extra_body` field. This can be an `agent_id` or `workflow_id`.
67+
68+
```bash
69+
# Standard OpenAI format
70+
curl -X POST http://localhost:8080/v1/responses \
71+
-H "Content-Type: application/json" \
72+
-d @- << 'EOF'
73+
{
74+
"model": "agent-framework",
75+
"input": "Hello world",
76+
"extra_body": {"entity_id": "weather_agent"}
77+
}
78+
EOF
79+
```
80+
81+
Messages and events from agents/workflows are mapped to OpenAI response types in `agent_framework_devui/_mapper.py`. See the mapping table below:
82+
83+
| Agent Framework Content | OpenAI Event | Type |
84+
| --------------------------------- | ----------------------------------------- | -------- |
85+
| `TextContent` | `ResponseTextDeltaEvent` | Official |
86+
| `TextReasoningContent` | `ResponseReasoningTextDeltaEvent` | Official |
87+
| `FunctionCallContent` | `ResponseFunctionCallArgumentsDeltaEvent` | Official |
88+
| `FunctionResultContent` | `ResponseFunctionResultComplete` | Custom |
89+
| `ErrorContent` | `ResponseErrorEvent` | Official |
90+
| `UsageContent` | `ResponseUsageEventComplete` | Custom |
91+
| `DataContent` | `ResponseTraceEventComplete` | Custom |
92+
| `UriContent` | `ResponseTraceEventComplete` | Custom |
93+
| `HostedFileContent` | `ResponseTraceEventComplete` | Custom |
94+
| `HostedVectorStoreContent` | `ResponseTraceEventComplete` | Custom |
95+
| `FunctionApprovalRequestContent` | Custom event | Custom |
96+
| `FunctionApprovalResponseContent` | Custom event | Custom |
97+
| `WorkflowEvent` | `ResponseWorkflowEventComplete` | Custom |
98+
99+
## CLI Options
100+
101+
```bash
102+
devui [directory] [options]
103+
104+
Options:
105+
--port, -p Port (default: 8080)
106+
--host Host (default: 127.0.0.1)
107+
--headless API only, no UI
108+
--config YAML config file
109+
--tracing none|framework|workflow|all
110+
--reload Enable auto-reload
111+
```
112+
113+
## Key Endpoints
114+
115+
- `GET /v1/entities` - List discovered agents/workflows
116+
- `GET /v1/entities/{entity_id}/info` - Get detailed entity information
117+
- `POST /v1/responses` - Execute agent/workflow (streaming or sync)
118+
- `GET /health` - Health check
119+
- `POST /v1/threads` - Create thread for agent (optional)
120+
- `GET /v1/threads?agent_id={id}` - List threads for agent
121+
- `GET /v1/threads/{thread_id}` - Get thread info
122+
- `DELETE /v1/threads/{thread_id}` - Delete thread
123+
- `GET /v1/threads/{thread_id}/messages` - Get thread messages
124+
125+
## Implementation
126+
127+
- **Discovery**: `agent_framework_devui/_discovery.py`
128+
- **Execution**: `agent_framework_devui/_executor.py`
129+
- **Message Mapping**: `agent_framework_devui/_mapper.py`
130+
- **Session Management**: `agent_framework_devui/_session.py`
131+
- **API Server**: `agent_framework_devui/_server.py`
132+
- **CLI**: `agent_framework_devui/_cli.py`
133+
134+
## Examples
135+
136+
See `samples/` for working agent and workflow implementations.
137+
138+
## License
139+
140+
MIT
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""Agent Framework DevUI - Debug interface with OpenAI compatible API server."""
4+
5+
import importlib.metadata
6+
import logging
7+
import webbrowser
8+
from typing import Any
9+
10+
from ._server import DevServer
11+
from .models import AgentFrameworkRequest, OpenAIError, OpenAIResponse, ResponseStreamEvent
12+
from .models._discovery_models import DiscoveryResponse, EntityInfo
13+
14+
logger = logging.getLogger(__name__)
15+
16+
try:
17+
__version__ = importlib.metadata.version(__name__)
18+
except importlib.metadata.PackageNotFoundError:
19+
__version__ = "0.0.0" # Fallback for development mode
20+
21+
22+
def serve(
23+
entities: list[Any] | None = None,
24+
entities_dir: str | None = None,
25+
port: int = 8080,
26+
host: str = "127.0.0.1",
27+
auto_open: bool = False,
28+
cors_origins: list[str] | None = None,
29+
ui_enabled: bool = True,
30+
) -> None:
31+
"""Launch Agent Framework DevUI with simple API.
32+
33+
Args:
34+
entities: List of entities for in-memory registration (IDs auto-generated)
35+
entities_dir: Directory to scan for entities
36+
port: Port to run server on
37+
host: Host to bind server to
38+
auto_open: Whether to automatically open browser
39+
cors_origins: List of allowed CORS origins
40+
ui_enabled: Whether to enable the UI
41+
"""
42+
import re
43+
44+
import uvicorn
45+
46+
# Validate host parameter early for security
47+
if not re.match(r"^(localhost|127\.0\.0\.1|0\.0\.0\.0|[a-zA-Z0-9.-]+)$", host):
48+
raise ValueError(f"Invalid host: {host}. Must be localhost, IP address, or valid hostname")
49+
50+
# Validate port parameter
51+
if not isinstance(port, int) or not (1 <= port <= 65535):
52+
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
53+
54+
# Create server with direct parameters
55+
server = DevServer(
56+
entities_dir=entities_dir, port=port, host=host, cors_origins=cors_origins, ui_enabled=ui_enabled
57+
)
58+
59+
# Register in-memory entities if provided
60+
if entities:
61+
logger.info(f"Registering {len(entities)} in-memory entities")
62+
# Store entities for later registration during server startup
63+
server._pending_entities = entities
64+
65+
app = server.get_app()
66+
67+
if auto_open:
68+
69+
def open_browser() -> None:
70+
import http.client
71+
import re
72+
import time
73+
74+
# Validate host and port for security
75+
if not re.match(r"^(localhost|127\.0\.0\.1|0\.0\.0\.0|[a-zA-Z0-9.-]+)$", host):
76+
logger.warning(f"Invalid host for auto-open: {host}")
77+
return
78+
79+
if not isinstance(port, int) or not (1 <= port <= 65535):
80+
logger.warning(f"Invalid port for auto-open: {port}")
81+
return
82+
83+
# Wait for server to be ready by checking health endpoint
84+
browser_url = f"http://{host}:{port}"
85+
86+
for _ in range(30): # 15 second timeout (30 * 0.5s)
87+
try:
88+
# Use http.client for safe connection handling (standard library)
89+
conn = http.client.HTTPConnection(host, port, timeout=1)
90+
try:
91+
conn.request("GET", "/health")
92+
response = conn.getresponse()
93+
if response.status == 200:
94+
webbrowser.open(browser_url)
95+
return
96+
finally:
97+
conn.close()
98+
except (http.client.HTTPException, OSError, TimeoutError):
99+
pass
100+
time.sleep(0.5)
101+
102+
# Fallback: open browser anyway after timeout
103+
webbrowser.open(browser_url)
104+
105+
import threading
106+
107+
threading.Thread(target=open_browser, daemon=True).start()
108+
109+
logger.info(f"Starting Agent Framework DevUI on {host}:{port}")
110+
uvicorn.run(app, host=host, port=port, log_level="info")
111+
112+
113+
def main() -> None:
114+
"""CLI entry point for devui command."""
115+
from ._cli import main as cli_main
116+
117+
cli_main()
118+
119+
120+
# Export main public API
121+
__all__ = [
122+
"AgentFrameworkRequest",
123+
"DevServer",
124+
"DiscoveryResponse",
125+
"EntityInfo",
126+
"OpenAIError",
127+
"OpenAIResponse",
128+
"ResponseStreamEvent",
129+
"main",
130+
"serve",
131+
]

0 commit comments

Comments
 (0)