Python: Add DevUI to AgentFramework (#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>
This commit is contained in:
Victor Dibia
2025-09-22 16:30:08 -07:00
committed by GitHub
Unverified
parent adb6dcd2af
commit 1ef24d3e91
98 changed files with 18045 additions and 4 deletions
@@ -0,0 +1,284 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Message Capture Script - Debug message flow
- This script is intended to provide a reference for the types of events
that are emitted by the server when agents and workflows are executed
"""
import asyncio
import contextlib
import http.client
import json
import threading
import time
from pathlib import Path
from typing import Any
import uvicorn
from openai import OpenAI
from agent_framework_devui import DevServer
def start_server() -> tuple[str, Any]:
"""Start server with samples directory."""
# Get samples directory
current_dir = Path(__file__).parent
samples_dir = current_dir.parent / "samples"
# Create and start server with simplified parameters
server = DevServer(
entities_dir=str(samples_dir.resolve()),
host="127.0.0.1",
port=8085, # Use different port
ui_enabled=False,
)
app = server.get_app()
server_config = uvicorn.Config(
app=app,
host="127.0.0.1",
port=8085,
log_level="info", # More verbose to see tracing setup
)
server_instance = uvicorn.Server(server_config)
def run_server():
asyncio.run(server_instance.serve())
server_thread = threading.Thread(target=run_server, daemon=True)
server_thread.start()
# Wait for server to start
time.sleep(5) # Increased wait time
# Verify server is running with retries
max_retries = 10
for attempt in range(max_retries):
try:
conn = http.client.HTTPConnection("127.0.0.1", 8085, timeout=5)
try:
conn.request("GET", "/health")
response = conn.getresponse()
if response.status == 200:
break
finally:
conn.close()
except Exception as e:
if attempt < max_retries - 1:
time.sleep(2)
else:
raise RuntimeError(f"Server failed to start after {max_retries} attempts: {e}") from e
return "http://127.0.0.1:8085", server_instance
def capture_agent_stream_with_tracing(client: OpenAI, agent_id: str, scenario: str = "success") -> list[dict[str, Any]]:
"""Capture agent streaming events."""
try:
stream = client.responses.create(
model="agent-framework",
input="Tell me about the weather in Tokyo. I want details.",
stream=True,
extra_body={"entity_id": agent_id},
)
events = []
for event in stream:
# Serialize the entire event object
try:
event_dict = json.loads(event.model_dump_json())
except Exception:
# Fallback to dict conversion if model_dump_json fails
event_dict = event.__dict__ if hasattr(event, "__dict__") else str(event)
events.append(event_dict)
# Just capture everything as-is
if len(events) >= 200: # Increased limit
break
return events
except Exception as e:
# Return error information as events
error_event = {
"type": "error",
"scenario": scenario,
"error_message": str(e),
"error_type": type(e).__name__,
"timestamp": time.time(),
}
return [error_event]
def capture_workflow_stream_with_tracing(
client: OpenAI, workflow_id: str, scenario: str = "success"
) -> list[dict[str, Any]]:
"""Capture workflow streaming events."""
try:
stream = client.responses.create(
model="agent-framework",
input=(
"Process this spam detection workflow with multiple emails: "
"'Buy now!', 'Hello mom', 'URGENT: Click here!'"
),
stream=True,
extra_body={"entity_id": workflow_id},
)
events = []
for event in stream:
# Serialize the entire event object
try:
event_dict = json.loads(event.model_dump_json())
except Exception:
# Fallback to dict conversion if model_dump_json fails
event_dict = event.__dict__ if hasattr(event, "__dict__") else str(event)
events.append(event_dict)
# Just capture everything as-is
if len(events) >= 200: # Increased limit
break
return events
except Exception as e:
# Return error information as events
error_event = {
"type": "error",
"scenario": scenario,
"error_message": str(e),
"error_type": type(e).__name__,
"timestamp": time.time(),
"entity_type": "workflow",
}
return [error_event]
def capture_agent_with_bad_config(base_url: str, agent_id: str) -> list[dict[str, Any]]:
"""Capture agent events with intentionally bad configuration to test error handling."""
# Test with invalid API key
bad_client = OpenAI(base_url=f"{base_url}/v1", api_key="invalid-api-key-123")
try:
return capture_agent_stream_with_tracing(bad_client, agent_id, "bad_api_key")
except Exception as e:
return [
{
"type": "error",
"scenario": "bad_api_key",
"error_message": str(e),
"error_type": type(e).__name__,
"timestamp": time.time(),
}
]
def capture_agent_with_wrong_model(base_url: str, agent_id: str) -> list[dict[str, Any]]:
"""Capture agent events with wrong model name to test error handling."""
client = OpenAI(
base_url=f"{base_url}/v1",
api_key="dummy-key", # Use the same key as success case
)
try:
stream = client.responses.create(
model="gpt-4-nonexistent-model", # Wrong model name
input="Tell me about the weather in Tokyo. I want details.",
stream=True,
extra_body={"entity_id": agent_id},
)
events = []
for event in stream:
# Serialize the entire event object
try:
event_dict = json.loads(event.model_dump_json())
except Exception:
# Fallback to dict conversion if model_dump_json fails
event_dict = event.__dict__ if hasattr(event, "__dict__") else str(event)
events.append(event_dict)
if len(events) >= 200:
break
return events
except Exception as e:
return [
{
"type": "error",
"scenario": "wrong_model",
"error_message": str(e),
"error_type": type(e).__name__,
"timestamp": time.time(),
}
]
def main():
"""Main capture script - testing both success and failure scenarios."""
# Setup
output_dir = Path(__file__).parent / "captured_messages"
output_dir.mkdir(exist_ok=True)
# Start server
base_url, server_instance = start_server()
try:
# Create OpenAI client for success scenario
client = OpenAI(base_url=f"{base_url}/v1", api_key="dummy-key")
# Discover entities
conn = http.client.HTTPConnection("127.0.0.1", 8085, timeout=10)
try:
conn.request("GET", "/v1/entities")
response = conn.getresponse()
response_data = response.read().decode("utf-8")
entities = json.loads(response_data)["entities"]
finally:
conn.close()
all_results = {}
# Test each entity
for entity in entities:
entity_type = entity["type"]
entity_id = entity["id"]
if entity_type == "agent":
events = capture_agent_stream_with_tracing(client, entity_id, "success")
elif entity_type == "workflow":
events = capture_workflow_stream_with_tracing(client, entity_id, "success")
else:
continue
all_results[f"{entity_type}_{entity_id}"] = {"entity_info": entity, "events": events}
# Save results
file_path = output_dir / "entities_stream_events.json"
with open(file_path, "w") as f:
json.dump(
{"timestamp": time.time(), "server_type": "DevServer", "entities_tested": all_results},
f,
indent=2,
default=str,
)
finally:
# Cleanup server
with contextlib.suppress(Exception):
server_instance.should_exit = True
if __name__ == "__main__":
main()
@@ -0,0 +1,101 @@
# Copyright (c) Microsoft. All rights reserved.
"""Focused tests for entity discovery functionality."""
import asyncio
import tempfile
from pathlib import Path
import pytest
from agent_framework_devui._discovery import EntityDiscovery
@pytest.fixture
def test_entities_dir():
"""Use the samples directory which has proper entity structure."""
# Get the samples directory relative to the current test file
current_dir = Path(__file__).parent
samples_dir = current_dir.parent / "samples"
return str(samples_dir.resolve())
@pytest.mark.asyncio
async def test_discover_agents(test_entities_dir):
"""Test that agent discovery works and returns valid agent entities."""
discovery = EntityDiscovery(test_entities_dir)
entities = await discovery.discover_entities()
agents = [e for e in entities if e.type == "agent"]
# Test that we can discover agents (not specific count)
assert len(agents) > 0, "Should discover at least one agent"
# Test agent structure/properties
for agent in agents:
assert agent.id, "Agent should have an ID"
assert agent.name, "Agent should have a name"
assert agent.type == "agent", "Should be identified as agent type"
assert hasattr(agent, "description"), "Agent should have description attribute"
@pytest.mark.asyncio
async def test_discover_workflows(test_entities_dir):
"""Test that workflow discovery works and returns valid workflow entities."""
discovery = EntityDiscovery(test_entities_dir)
entities = await discovery.discover_entities()
workflows = [e for e in entities if e.type == "workflow"]
# Test that we can discover workflows (not specific count)
assert len(workflows) > 0, "Should discover at least one workflow"
# Test workflow structure/properties
for workflow in workflows:
assert workflow.id, "Workflow should have an ID"
assert workflow.name, "Workflow should have a name"
assert workflow.type == "workflow", "Should be identified as workflow type"
assert hasattr(workflow, "description"), "Workflow should have description attribute"
@pytest.mark.asyncio
async def test_empty_directory():
"""Test discovery with empty directory."""
with tempfile.TemporaryDirectory() as temp_dir:
discovery = EntityDiscovery(temp_dir)
entities = await discovery.discover_entities()
assert len(entities) == 0
if __name__ == "__main__":
# Simple test runner
async def run_tests():
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create test files
agent_file = temp_path / "test_agent.py"
agent_file.write_text("""
class WeatherAgent:
name = "Weather Agent"
description = "Gets weather information"
def run_stream(self, input_str):
return f"Weather in {input_str}"
""")
workflow_file = temp_path / "test_workflow.py"
workflow_file.write_text("""
class DataWorkflow:
name = "Data Processing Workflow"
description = "Processes data"
def run(self, data):
return f"Processed {data}"
""")
discovery = EntityDiscovery(str(temp_path))
await discovery.discover_entities()
asyncio.run(run_tests())
@@ -0,0 +1,189 @@
# Copyright (c) Microsoft. All rights reserved.
"""Focused tests for execution flow functionality."""
import asyncio
import os
import tempfile
from pathlib import Path
import pytest
from agent_framework_devui._discovery import EntityDiscovery
from agent_framework_devui._executor import AgentFrameworkExecutor, EntityNotFoundError
from agent_framework_devui._mapper import MessageMapper
from agent_framework_devui.models._openai_custom import AgentFrameworkExtraBody, AgentFrameworkRequest
@pytest.fixture
def test_entities_dir():
"""Use the samples directory which has proper entity structure."""
current_dir = Path(__file__).parent
samples_dir = current_dir.parent / "samples"
return str(samples_dir.resolve())
@pytest.fixture
async def executor(test_entities_dir):
"""Create configured executor."""
discovery = EntityDiscovery(test_entities_dir)
mapper = MessageMapper()
executor = AgentFrameworkExecutor(discovery, mapper)
# Discover entities
await executor.discover_entities()
return executor
@pytest.mark.asyncio
async def test_executor_entity_discovery(executor):
"""Test executor entity discovery."""
entities = await executor.discover_entities()
# Should find entities from samples directory
assert len(entities) > 0, "Should discover at least one entity"
entity_types = [e.type for e in entities]
assert "agent" in entity_types, "Should find at least one agent"
assert "workflow" in entity_types, "Should find at least one workflow"
# Test entity structure
for entity in entities:
assert entity.id, "Entity should have an ID"
assert entity.name, "Entity should have a name"
assert entity.type in ["agent", "workflow"], "Entity should have valid type"
@pytest.mark.asyncio
async def test_executor_get_entity_info(executor):
"""Test getting entity info by ID."""
entities = await executor.discover_entities()
entity_id = entities[0].id
entity_info = executor.get_entity_info(entity_id)
assert entity_info is not None
assert entity_info.id == entity_id
assert entity_info.type in ["agent", "workflow"]
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="requires OpenAI API key")
@pytest.mark.asyncio
async def test_executor_sync_execution(executor):
"""Test synchronous execution."""
entities = await executor.discover_entities()
# Find an agent entity to test with
agents = [e for e in entities if e.type == "agent"]
assert len(agents) > 0, "No agent entities found for testing"
agent_id = agents[0].id
request = AgentFrameworkRequest(
model="agent-framework", input="test data", stream=False, extra_body=AgentFrameworkExtraBody(entity_id=agent_id)
)
response = await executor.execute_sync(request)
assert response.model == "agent-framework"
assert response.object == "response"
assert len(response.output) > 0
assert response.usage.total_tokens > 0
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="requires OpenAI API key")
@pytest.mark.asyncio
async def test_executor_streaming_execution(executor):
"""Test streaming execution."""
entities = await executor.discover_entities()
# Find an agent entity to test with
agents = [e for e in entities if e.type == "agent"]
assert len(agents) > 0, "No agent entities found for testing"
agent_id = agents[0].id
request = AgentFrameworkRequest(
model="agent-framework",
input="streaming test",
stream=True,
extra_body=AgentFrameworkExtraBody(entity_id=agent_id),
)
event_count = 0
text_events = []
async for event in executor.execute_streaming(request):
event_count += 1
if hasattr(event, "type") and event.type == "response.output_text.delta":
text_events.append(event.delta)
if event_count > 10: # Limit for testing
break
assert event_count > 0
assert len(text_events) > 0
@pytest.mark.asyncio
async def test_executor_invalid_entity_id(executor):
"""Test execution with invalid entity ID."""
with pytest.raises(EntityNotFoundError):
executor.get_entity_info("nonexistent_agent")
@pytest.mark.asyncio
async def test_executor_missing_entity_id(executor):
"""Test execution without entity ID."""
request = AgentFrameworkRequest(
model="agent-framework",
input="test",
stream=False,
extra_body=None, # Test case for missing entity_id
)
entity_id = request.get_entity_id()
assert entity_id is None
if __name__ == "__main__":
# Simple test runner
async def run_tests():
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create test agent
agent_file = temp_path / "streaming_agent.py"
agent_file.write_text("""
class StreamingAgent:
name = "Streaming Test Agent"
description = "Test agent for streaming"
async def run_stream(self, input_str):
for i, word in enumerate(f"Processing {input_str}".split()):
yield f"word_{i}: {word} "
""")
discovery = EntityDiscovery(str(temp_path))
mapper = MessageMapper()
executor = AgentFrameworkExecutor(discovery, mapper)
# Test discovery
entities = await executor.discover_entities()
if entities:
# Test sync execution
request = AgentFrameworkRequest(
model="agent-framework",
input="test input",
stream=False,
extra_body=AgentFrameworkExtraBody(entity_id=entities[0].id),
)
await executor.execute_sync(request)
# Test streaming execution
request.stream = True
event_count = 0
async for _event in executor.execute_streaming(request):
event_count += 1
if event_count > 5: # Limit for testing
break
asyncio.run(run_tests())
+192
View File
@@ -0,0 +1,192 @@
# Copyright (c) Microsoft. All rights reserved.
"""Clean focused tests for message mapping functionality."""
import asyncio
import sys
from pathlib import Path
from typing import Any
import pytest
# Add the main agent_framework package for real types
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "main"))
# Import Agent Framework types (assuming they are always available)
from agent_framework._types import AgentRunResponseUpdate, ErrorContent, FunctionCallContent, Role, TextContent
from agent_framework_devui._mapper import MessageMapper
from agent_framework_devui.models._openai_custom import AgentFrameworkExtraBody, AgentFrameworkRequest
def create_test_content(content_type: str, **kwargs: Any) -> Any:
"""Create test content objects."""
if content_type == "text":
return TextContent(text=kwargs.get("text", "Hello, world!"))
if content_type == "function_call":
return FunctionCallContent(
call_id=kwargs.get("call_id", "test_call_id"),
name=kwargs.get("name", "test_func"),
arguments=kwargs.get("arguments", {"param": "value"}),
)
if content_type == "error":
return ErrorContent(message=kwargs.get("message", "Test error"), error_code=kwargs.get("code", "test_error"))
raise ValueError(f"Unknown content type: {content_type}")
def create_test_agent_update(contents: list[Any]) -> Any:
"""Create test AgentRunResponseUpdate - NO fake attributes!"""
return AgentRunResponseUpdate(
contents=contents, role=Role.ASSISTANT, message_id="test_msg", response_id="test_resp"
)
@pytest.fixture
def mapper() -> MessageMapper:
return MessageMapper()
@pytest.fixture
def test_request() -> AgentFrameworkRequest:
return AgentFrameworkRequest(
model="agent-framework",
input="Test input",
stream=True,
extra_body=AgentFrameworkExtraBody(entity_id="test_agent"),
)
@pytest.mark.asyncio
async def test_critical_isinstance_bug_detection(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""CRITICAL: Test that would have caught the isinstance vs hasattr bug."""
content = create_test_content("text", text="Bug detection test")
update = create_test_agent_update([content])
# Key assertions that would have caught the bug
assert hasattr(update, "contents") # Real attribute ✅
assert not hasattr(update, "response") # Fake attribute should not exist ✅
# Test isinstance works with real types
assert isinstance(update, AgentRunResponseUpdate)
# Test mapper conversion - should NOT produce "Unknown event"
events = await mapper.convert_event(update, test_request)
assert len(events) > 0
assert all(hasattr(event, "type") for event in events)
# Should never get unknown events with proper types
assert all(event.type != "unknown" for event in events)
@pytest.mark.asyncio
async def test_text_content_mapping(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test TextContent mapping."""
content = create_test_content("text", text="Hello, clean test!")
update = create_test_agent_update([content])
events = await mapper.convert_event(update, test_request)
assert len(events) == 1
assert events[0].type == "response.output_text.delta"
assert events[0].delta == "Hello, clean test!"
@pytest.mark.asyncio
async def test_function_call_mapping(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test FunctionCallContent mapping."""
content = create_test_content("function_call", name="test_func", arguments={"location": "TestCity"})
update = create_test_agent_update([content])
events = await mapper.convert_event(update, test_request)
assert len(events) >= 1
assert all(event.type == "response.function_call_arguments.delta" for event in events)
# Check JSON is chunked
full_json = "".join(event.delta for event in events)
assert "TestCity" in full_json
@pytest.mark.asyncio
async def test_error_content_mapping(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test ErrorContent mapping."""
content = create_test_content("error", message="Test error", code="test_code")
update = create_test_agent_update([content])
events = await mapper.convert_event(update, test_request)
assert len(events) == 1
assert events[0].type == "error"
assert events[0].message == "Test error"
assert events[0].code == "test_code"
@pytest.mark.asyncio
async def test_mixed_content_types(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test multiple content types together."""
contents = [
create_test_content("text", text="Starting..."),
create_test_content("function_call", name="process", arguments={"data": "test"}),
create_test_content("text", text="Done!"),
]
update = create_test_agent_update(contents)
events = await mapper.convert_event(update, test_request)
assert len(events) >= 3
# Should have both types of events
event_types = {event.type for event in events}
assert "response.output_text.delta" in event_types
assert "response.function_call_arguments.delta" in event_types
@pytest.mark.asyncio
async def test_unknown_content_fallback(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test graceful handling of unknown content types."""
# Test the fallback path directly since we can't create invalid AgentRunResponseUpdate
# due to Pydantic validation. Instead, test the content mapper's unknown content handling.
class MockUnknownContent:
def __init__(self):
self.__class__.__name__ = "WeirdUnknownContent" # Not in content_mappers
# Test the content mapper directly
context = mapper._get_or_create_context(test_request)
unknown_content = MockUnknownContent()
# This should trigger the unknown content fallback in _convert_agent_update
event = await mapper._create_unknown_content_event(unknown_content, context)
assert event.type == "response.output_text.delta"
assert "Unknown content type" in event.delta
assert "WeirdUnknownContent" in event.delta
if __name__ == "__main__":
# Simple test runner
async def run_all_tests() -> None:
mapper = MessageMapper()
test_request = AgentFrameworkRequest(
model="agent-framework", input="Test", stream=True, extra_body=AgentFrameworkExtraBody(entity_id="test")
)
tests = [
("Critical isinstance bug detection", test_critical_isinstance_bug_detection),
("Text content mapping", test_text_content_mapping),
("Function call mapping", test_function_call_mapping),
("Error content mapping", test_error_content_mapping),
("Mixed content types", test_mixed_content_types),
("Unknown content fallback", test_unknown_content_fallback),
]
passed = 0
for _test_name, test_func in tests:
try:
await test_func(mapper, test_request)
passed += 1
except Exception:
pass
asyncio.run(run_all_tests())
+135
View File
@@ -0,0 +1,135 @@
# Copyright (c) Microsoft. All rights reserved.
"""Focused tests for server functionality."""
import asyncio
import tempfile
from pathlib import Path
import pytest
from agent_framework_devui import DevServer
from agent_framework_devui.models._openai_custom import AgentFrameworkExtraBody, AgentFrameworkRequest
@pytest.fixture
def test_entities_dir():
"""Use the samples directory which has proper entity structure."""
current_dir = Path(__file__).parent
samples_dir = current_dir.parent / "samples"
return str(samples_dir.resolve())
@pytest.mark.asyncio
async def test_server_health_endpoint(test_entities_dir):
"""Test /health endpoint."""
server = DevServer(entities_dir=test_entities_dir)
executor = await server._ensure_executor()
# Test entity count
entities = await executor.discover_entities()
assert len(entities) > 0
# Framework name is now hardcoded since we simplified to single framework
@pytest.mark.asyncio
async def test_server_entities_endpoint(test_entities_dir):
"""Test /v1/entities endpoint."""
server = DevServer(entities_dir=test_entities_dir)
executor = await server._ensure_executor()
entities = await executor.discover_entities()
assert len(entities) >= 1
# Should find at least the weather agent
agent_entities = [e for e in entities if e.type == "agent"]
assert len(agent_entities) >= 1
agent_names = [e.name for e in agent_entities]
assert "WeatherAgent" in agent_names
@pytest.mark.asyncio
async def test_server_execution_sync(test_entities_dir):
"""Test sync execution endpoint."""
server = DevServer(entities_dir=test_entities_dir)
executor = await server._ensure_executor()
entities = await executor.discover_entities()
agent_id = entities[0].id
request = AgentFrameworkRequest(
model="agent-framework",
input="San Francisco",
stream=False,
extra_body=AgentFrameworkExtraBody(entity_id=agent_id),
)
response = await executor.execute_sync(request)
assert response.model == "agent-framework"
assert len(response.output) > 0
@pytest.mark.asyncio
async def test_server_execution_streaming(test_entities_dir):
"""Test streaming execution endpoint."""
server = DevServer(entities_dir=test_entities_dir)
executor = await server._ensure_executor()
entities = await executor.discover_entities()
agent_id = entities[0].id
request = AgentFrameworkRequest(
model="agent-framework", input="New York", stream=True, extra_body=AgentFrameworkExtraBody(entity_id=agent_id)
)
event_count = 0
async for _event in executor.execute_streaming(request):
event_count += 1
if event_count > 5: # Limit for testing
break
assert event_count > 0
def test_configuration():
"""Test basic configuration."""
server = DevServer(entities_dir="test", port=9000, host="localhost")
assert server.port == 9000
assert server.host == "localhost"
assert server.entities_dir == "test"
assert server.cors_origins == ["*"]
assert server.ui_enabled
if __name__ == "__main__":
# Simple test runner
async def run_tests():
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
# Create test agent
agent_file = temp_path / "weather_agent.py"
agent_file.write_text("""
class WeatherAgent:
name = "Weather Agent"
description = "Gets weather information"
def run_stream(self, input_str):
return f"Weather in {input_str} is sunny"
""")
server = DevServer(entities_dir=str(temp_path))
executor = await server._ensure_executor()
entities = await executor.discover_entities()
if entities:
request = AgentFrameworkRequest(
model="agent-framework",
input="test location",
stream=False,
extra_body=AgentFrameworkExtraBody(entity_id=entities[0].id),
)
await executor.execute_sync(request)
asyncio.run(run_tests())