Python: DevUI improvements. (#1091)

* enable deeplinking in ui, add agent details to entity info, add usage data, add middleware example in samples and foundry agent.

* update ui build

* Update python/packages/devui/frontend/src/components/workflow/workflow-input-form.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update python/packages/devui/pyproject.toml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update python/packages/devui/pyproject.toml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* imporove mapping for agent nodes and serialiation for agent run events

* lint fixes

* update pyproj toml and ui updates

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Victor Dibia
2025-10-03 15:22:03 -07:00
committed by GitHub
Unverified
parent 61ac6d43b2
commit 01f438d710
33 changed files with 2733 additions and 1164 deletions
+2 -235
View File
@@ -158,217 +158,6 @@ async def test_unknown_content_fallback(mapper: MessageMapper, test_request: Age
assert "WeirdUnknownContent" in event.delta
def test_serialize_payload_primitives(mapper: MessageMapper) -> None:
"""Test serialization of primitive types."""
assert mapper._serialize_payload(None) is None
assert mapper._serialize_payload("test") == "test"
assert mapper._serialize_payload(42) == 42
assert mapper._serialize_payload(3.14) == 3.14
assert mapper._serialize_payload(True) is True
assert mapper._serialize_payload(False) is False
def test_serialize_payload_sequences(mapper: MessageMapper) -> None:
"""Test serialization of lists, tuples, and sets."""
# List
result = mapper._serialize_payload([1, 2, "three"])
assert result == [1, 2, "three"]
assert isinstance(result, list)
# Tuple - should convert to list
result = mapper._serialize_payload((1, 2, "three"))
assert result == [1, 2, "three"]
assert isinstance(result, list)
# Set - should convert to list (order may vary)
result = mapper._serialize_payload({1, 2, 3})
assert isinstance(result, list)
assert set(result) == {1, 2, 3}
# Nested sequences
result = mapper._serialize_payload([1, [2, 3], (4, 5)])
assert result == [1, [2, 3], [4, 5]]
def test_serialize_payload_dicts(mapper: MessageMapper) -> None:
"""Test serialization of dictionaries."""
# Simple dict
result = mapper._serialize_payload({"a": 1, "b": 2})
assert result == {"a": 1, "b": 2}
# Dict with non-string keys (should convert to string)
result = mapper._serialize_payload({1: "one", 2: "two"})
assert result == {"1": "one", "2": "two"}
# Nested dicts
result = mapper._serialize_payload({"outer": {"inner": {"deep": 42}}})
assert result == {"outer": {"inner": {"deep": 42}}}
# Dict with mixed value types
result = mapper._serialize_payload({"str": "text", "num": 123, "list": [1, 2], "dict": {"nested": True}})
assert result == {"str": "text", "num": 123, "list": [1, 2], "dict": {"nested": True}}
def test_serialize_payload_dataclass(mapper: MessageMapper) -> None:
"""Test serialization of dataclasses."""
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
active: bool = True
person = Person(name="Alice", age=30)
result = mapper._serialize_payload(person)
assert result == {"name": "Alice", "age": 30, "active": True}
assert isinstance(result, dict)
def test_serialize_payload_pydantic_model(mapper: MessageMapper) -> None:
"""Test serialization of Pydantic models."""
from pydantic import BaseModel
class User(BaseModel):
username: str
email: str
is_active: bool = True
user = User(username="testuser", email="test@example.com")
result = mapper._serialize_payload(user)
assert result == {"username": "testuser", "email": "test@example.com", "is_active": True}
assert isinstance(result, dict)
def test_serialize_payload_nested_pydantic(mapper: MessageMapper) -> None:
"""Test serialization of nested Pydantic models."""
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
class Person(BaseModel):
name: str
address: Address
person = Person(name="Bob", address=Address(street="123 Main St", city="Springfield"))
result = mapper._serialize_payload(person)
assert result == {"name": "Bob", "address": {"street": "123 Main St", "city": "Springfield"}}
def test_serialize_payload_object_with_dict_method(mapper: MessageMapper) -> None:
"""Test serialization of objects with dict() method."""
class CustomObject:
def __init__(self):
self.value = 42
def dict(self):
return {"value": self.value, "type": "custom"}
obj = CustomObject()
result = mapper._serialize_payload(obj)
assert result == {"value": 42, "type": "custom"}
def test_serialize_payload_object_with_to_dict_method(mapper: MessageMapper) -> None:
"""Test serialization of objects with to_dict() method."""
class CustomObject:
def __init__(self):
self.value = 42
def to_dict(self):
return {"value": self.value, "type": "custom_to_dict"}
obj = CustomObject()
result = mapper._serialize_payload(obj)
assert result == {"value": 42, "type": "custom_to_dict"}
def test_serialize_payload_object_with_model_dump_json(mapper: MessageMapper) -> None:
"""Test serialization of objects with model_dump_json() method."""
import json
class CustomObject:
def __init__(self):
self.value = 42
def model_dump_json(self):
return json.dumps({"value": self.value, "type": "json_dump"})
obj = CustomObject()
result = mapper._serialize_payload(obj)
assert result == {"value": 42, "type": "json_dump"}
def test_serialize_payload_object_with_dict_attr(mapper: MessageMapper) -> None:
"""Test serialization of objects with __dict__ attribute."""
class SimpleObject:
def __init__(self):
self.public_value = 42
self._private_value = 100 # Should be excluded
obj = SimpleObject()
result = mapper._serialize_payload(obj)
assert "public_value" in result
assert result["public_value"] == 42
assert "_private_value" not in result
def test_serialize_payload_fallback_to_string(mapper: MessageMapper) -> None:
"""Test that unserializable objects fall back to string representation."""
class WeirdObject:
__slots__ = () # Prevent __dict__ attribute
def __str__(self):
return "weird_object_string"
obj = WeirdObject()
result = mapper._serialize_payload(obj)
assert result == "weird_object_string"
def test_serialize_payload_complex_nested(mapper: MessageMapper) -> None:
"""Test serialization of complex nested structures."""
from dataclasses import dataclass
from pydantic import BaseModel
@dataclass
class DataItem:
value: int
class ConfigModel(BaseModel):
enabled: bool
count: int
complex_data = {
"items": [DataItem(value=1), DataItem(value=2)],
"config": ConfigModel(enabled=True, count=5),
"nested": {"list": [1, 2, 3], "tuple": (4, 5, 6)},
"primitive": 42,
}
result = mapper._serialize_payload(complex_data)
assert result["items"] == [{"value": 1}, {"value": 2}]
assert result["config"] == {"enabled": True, "count": 5}
assert result["nested"] == {"list": [1, 2, 3], "tuple": [4, 5, 6]}
assert result["primitive"] == 42
if __name__ == "__main__":
# Simple test runner
async def run_all_tests() -> None:
@@ -377,7 +166,7 @@ if __name__ == "__main__":
model="agent-framework", input="Test", stream=True, extra_body=AgentFrameworkExtraBody(entity_id="test")
)
async_tests = [
tests = [
("Critical isinstance bug detection", test_critical_isinstance_bug_detection),
("Text content mapping", test_text_content_mapping),
("Function call mapping", test_function_call_mapping),
@@ -386,34 +175,12 @@ if __name__ == "__main__":
("Unknown content fallback", test_unknown_content_fallback),
]
sync_tests = [
("Serialize primitives", test_serialize_payload_primitives),
("Serialize sequences", test_serialize_payload_sequences),
("Serialize dicts", test_serialize_payload_dicts),
("Serialize dataclass", test_serialize_payload_dataclass),
("Serialize pydantic model", test_serialize_payload_pydantic_model),
("Serialize nested pydantic", test_serialize_payload_nested_pydantic),
("Serialize dict method", test_serialize_payload_object_with_dict_method),
("Serialize to_dict method", test_serialize_payload_object_with_to_dict_method),
("Serialize model_dump_json", test_serialize_payload_object_with_model_dump_json),
("Serialize __dict__ attr", test_serialize_payload_object_with_dict_attr),
("Serialize fallback to string", test_serialize_payload_fallback_to_string),
("Serialize complex nested", test_serialize_payload_complex_nested),
]
passed = 0
for _test_name, test_func in async_tests:
for _test_name, test_func in tests:
try:
await test_func(mapper, test_request)
passed += 1
except Exception:
pass
for _test_name, test_func in sync_tests:
try:
test_func(mapper)
passed += 1
except Exception:
pass
asyncio.run(run_all_tests())
@@ -0,0 +1,137 @@
# Copyright (c) Microsoft. All rights reserved.
"""Test schema generation for different input types."""
import sys
from dataclasses import dataclass
from pathlib import Path
import pytest
# Add parent package to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from agent_framework_devui._utils import generate_input_schema
@dataclass
class InputData:
text: str
source: str
@dataclass
class Address:
street: str
city: str
zipcode: str
@dataclass
class PersonData:
name: str
age: int
address: Address
def test_builtin_types_schema_generation():
"""Test schema generation for built-in types."""
# Test str schema
str_schema = generate_input_schema(str)
assert str_schema is not None
assert isinstance(str_schema, dict)
# Test dict schema
dict_schema = generate_input_schema(dict)
assert dict_schema is not None
assert isinstance(dict_schema, dict)
# Test int schema
int_schema = generate_input_schema(int)
assert int_schema is not None
assert isinstance(int_schema, dict)
def test_dataclass_schema_generation():
"""Test schema generation for dataclass."""
schema = generate_input_schema(InputData)
assert schema is not None
assert isinstance(schema, dict)
# Basic schema structure checks
if "properties" in schema:
properties = schema["properties"]
assert "text" in properties
assert "source" in properties
def test_chat_message_schema_generation():
"""Test schema generation for ChatMessage (SerializationMixin)."""
try:
from agent_framework import ChatMessage
schema = generate_input_schema(ChatMessage)
assert schema is not None
assert isinstance(schema, dict)
except ImportError:
pytest.skip("ChatMessage not available - agent_framework not installed")
def test_pydantic_model_schema_generation():
"""Test schema generation for Pydantic models."""
try:
from pydantic import BaseModel, Field
class UserInput(BaseModel):
name: str = Field(description="User's name")
age: int = Field(description="User's age")
email: str | None = Field(default=None, description="Optional email")
schema = generate_input_schema(UserInput)
assert schema is not None
assert isinstance(schema, dict)
# Check if properties exist
if "properties" in schema:
properties = schema["properties"]
assert "name" in properties
assert "age" in properties
assert "email" in properties
except ImportError:
pytest.skip("Pydantic not available")
def test_nested_dataclass_schema_generation():
"""Test schema generation for nested dataclass."""
schema = generate_input_schema(PersonData)
assert schema is not None
assert isinstance(schema, dict)
# Basic schema structure checks
if "properties" in schema:
properties = schema["properties"]
assert "name" in properties
assert "age" in properties
assert "address" in properties
def test_schema_generation_error_handling():
"""Test schema generation with invalid inputs."""
# Test with a non-type object - should handle gracefully
try:
# Use a non-type object that might cause issues
schema = generate_input_schema("not_a_type") # type: ignore
# If it doesn't raise an exception, the result should be valid
if schema is not None:
assert isinstance(schema, dict)
except (TypeError, ValueError, AttributeError):
# It's acceptable for this to raise an error
pass
if __name__ == "__main__":
# Simple test runner for manual execution
pytest.main([__file__, "-v"])