Python: Bug fixes for devui serialization and agent structured outputs (#1055)

* Bug fixes for devui serialization and agent structured outputs

* Fix typing

* Add mapper serialization unit tests
This commit is contained in:
Evan Mattson
2025-10-01 20:55:54 +09:00
committed by GitHub
Unverified
parent 2575e8cab0
commit 19b9aeec5f
3 changed files with 297 additions and 3 deletions
@@ -6,6 +6,7 @@ import json
import logging
import uuid
from collections.abc import Sequence
from dataclasses import asdict, is_dataclass
from datetime import datetime
from typing import Any, Union
@@ -257,12 +258,13 @@ class MessageMapper:
List of OpenAI response stream events
"""
try:
serialized_payload = self._serialize_payload(getattr(event, "data", None))
# Create structured workflow event
workflow_event = ResponseWorkflowEventComplete(
type="response.workflow_event.complete",
data={
"event_type": event.__class__.__name__,
"data": getattr(event, "data", None),
"data": serialized_payload,
"executor_id": getattr(event, "executor_id", None),
"timestamp": datetime.now().isoformat(),
},
@@ -278,6 +280,59 @@ class MessageMapper:
logger.warning(f"Error converting workflow event: {e}")
return [await self._create_error_event(str(e), context)]
def _serialize_payload(self, value: Any) -> Any:
"""Best-effort JSON serialization for workflow payloads."""
if value is None:
return None
if isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, (list, tuple, set)):
return [self._serialize_payload(item) for item in value]
if isinstance(value, dict):
return {str(k): self._serialize_payload(v) for k, v in value.items()}
if is_dataclass(value) and not isinstance(value, type):
try:
return self._serialize_payload(asdict(value))
except Exception as exc:
logger.debug("Failed to serialize dataclass payload: %s", exc)
model_dump_method = getattr(value, "model_dump", None)
if model_dump_method is not None and callable(model_dump_method):
try:
dumped = model_dump_method()
return self._serialize_payload(dumped)
except Exception as exc:
logger.debug("Failed to serialize payload via model_dump: %s", exc)
dict_method = getattr(value, "dict", None)
if dict_method is not None and callable(dict_method):
try:
dict_result = dict_method()
return self._serialize_payload(dict_result)
except Exception as exc:
logger.debug("Failed to serialize payload via dict(): %s", exc)
to_dict_method = getattr(value, "to_dict", None)
if to_dict_method is not None and callable(to_dict_method):
try:
to_dict_result = to_dict_method()
return self._serialize_payload(to_dict_result)
except Exception as exc:
logger.debug("Failed to serialize payload via to_dict(): %s", exc)
model_dump_json_method = getattr(value, "model_dump_json", None)
if model_dump_json_method is not None and callable(model_dump_json_method):
try:
json_str = model_dump_json_method()
if isinstance(json_str, (str, bytes, bytearray)):
return json.loads(json_str)
except Exception as exc:
logger.debug("Failed to serialize payload via model_dump_json: %s", exc)
if hasattr(value, "__dict__"):
try:
return self._serialize_payload({
key: self._serialize_payload(val) for key, val in value.__dict__.items() if not key.startswith("_")
})
except Exception as exc:
logger.debug("Failed to serialize payload via __dict__: %s", exc)
return str(value)
# Content type mappers - implementing our comprehensive mapping plan
async def _map_text_content(self, content: Any, context: dict[str, Any]) -> ResponseTextDeltaEvent: