Python: fix: DevUI list[Message] input for declarative ToolAgent entry (#6533)#6534
Python: fix: DevUI list[Message] input for declarative ToolAgent entry (#6533)#6534anneheartrecord wants to merge 1 commit into
Conversation
) When a declarative ToolAgent is created with default settings the entry JoinExecutor declares `input_types = [dict | str | list[Message] | ActionTrigger | ...]`. DevUI called `select_primary_input_type` which returned bare `Message` instead of `list[Message]`, then passed a single Message to the executor that expects a list — causing a "cannot handle message of type Message" runtime error. Changes: - Add `_is_list_message_type` helper (GenericAlias cannot be used with isinstance; get_origin/get_args required). - Add `_find_chat_message_type` that recursively searches union members and returns `list[Message]` in preference to bare `Message`. - `select_primary_input_type`: first-pass uses `_find_chat_message_type` so the declarative entry type is correctly returned as `list[Message]`. - `generate_input_schema`: returns `{"type":"string"}` for `list[Message]` so DevUI renders a plain text box. - Add `_looks_like_message_dict` heuristic (role present, type=="message", or exactly {"input":...}) to distinguish serialised Message payloads from structured workflow inputs without false positives. - `parse_input_for_type`: handle `list[Message]` target — wrap plain strings/Message objects, convert lists of dicts item-by-item, pass structured workflow inputs through unchanged. - Add 12 regression tests (57 total pass).
|
@anneheartrecord please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR improves DevUI handling of declarative entry executors whose input union includes list[Message], ensuring the UI presents a simple text box while runtime input parsing wraps user text into the expected list[Message] shape.
Changes:
- Add recursive detection of
list[Message]vsMessageinselect_primary_input_type. - Render
list[Message]input schemas as a plain JSON{"type":"string"}and parse raw inputs intolist[Message]. - Add regression tests covering schema generation and
parse_input_for_typebehavior forlist[Message].
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| python/packages/devui/agent_framework_devui/_utils.py | Adds list[Message] detection, schema rendering as string, and input parsing logic to wrap/convert into list[Message]. |
| python/packages/devui/tests/devui/test_server.py | Adds regression tests for select_primary_input_type and parse_input_for_type with list[Message]. |
| python/packages/devui/tests/devui/test_schema_generation.py | Adds regression test ensuring list[Message] generates a simple string schema. |
Comments suppressed due to low confidence (2)
python/packages/devui/agent_framework_devui/_utils.py:476
generate_input_schemaandparse_input_for_typeaccept and are now called withlist[Message], which is atypes.GenericAlias(not atype) in modern Python. Keeping the parameter annotations astypeis misleading for callers and type checkers, andisinstance(input_data, target_type)will raiseTypeErrorfor other generic aliases (e.g.,list[str]) if they ever get passed in. Updating these signatures to acceptAny(ortype[Any] | object) and guardingisinstance(only call it whentarget_typeis a realtype) would make this API safer and more accurate.
def generate_input_schema(input_type: type) -> dict[str, Any]:
python/packages/devui/agent_framework_devui/_utils.py:536
generate_input_schemaandparse_input_for_typeaccept and are now called withlist[Message], which is atypes.GenericAlias(not atype) in modern Python. Keeping the parameter annotations astypeis misleading for callers and type checkers, andisinstance(input_data, target_type)will raiseTypeErrorfor other generic aliases (e.g.,list[str]) if they ever get passed in. Updating these signatures to acceptAny(ortype[Any] | object) and guardingisinstance(only call it whentarget_typeis a realtype) would make this API safer and more accurate.
def parse_input_for_type(input_data: Any, target_type: type) -> Any:
| if isinstance(input_data, target_type): | ||
| return input_data |
| parsed_dict = _string_key_dict(input_data) | ||
| if parsed_dict is not None: | ||
| if parsed_dict and _looks_like_message_dict(parsed_dict): | ||
| return [_build_message_from_legacy_payload(parsed_dict)] |
| return input_data | ||
|
|
||
|
|
||
| def _looks_like_message_dict(d: dict[str, Any]) -> bool: |
| if d.get("type") == "message": | ||
| return True | ||
| if "role" in d: | ||
| return True | ||
| return set(d.keys()) == {"input"} |
When a declarative ToolAgent is created, its entry JoinExecutor declares input_types = [dict | str | list[Message] | ...]. DevUI called select_primary_input_type which returned bare Message instead of list[Message], passing a single Message to an executor that expects a list -- causing 'cannot handle message of type Message'.
Root cause: select_primary_input_type iterated over message_types without searching inside the union for list[Message].
Changes:
12 regression tests added; 57 total pass.
Closes #6533