Python: Fix AG-UI resume rejecting JSON for typed responses - #7684
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Improves runtime typing/coercion for workflow payloads so JSON-shaped responses can be converted into typed Python objects (dataclasses, containers, unions), and wires that into AG-UI response handling.
Changes:
- Enhanced
try_coerce_to_typeto handleUnion, typed containers, dataclass field coercion/validation,Literal, andfrom_dict/model_validate. - Added extensive unit tests around dataclass coercion,
Literal, union selection, and container coercion. - Updated AG-UI workflow response coercion to leverage the core coercion utilities and normalize AG-UI message wire formats by field annotation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| python/packages/core/tests/workflow/test_typing_utils.py | Adds coverage for new coercion behaviors (dataclasses, containers, literals, unions, bool→float guard, from_dict types). |
| python/packages/core/agent_framework/_workflows/_typing_utils.py | Implements richer coercion logic and dataclass field-level validation/coercion. |
| python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py | Switches response coercion to core coercion utilities and adds message-field normalization based on annotations. |
| python/packages/ag-ui/tests/ag_ui/test_workflow_run.py | Adds AG-UI tests for dataclass/pydantic response coercion and field-annotation-driven message normalization. |
| python/packages/ag-ui/tests/ag_ui/test_endpoint.py | Adds endpoint-level regression test for resuming dataclass responses from JSON payloads. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||||||||||||
|
Please also look at the failing CI/CD checks. |
ac7525e to
927873d
Compare
|
Please have a look at some conflicts, thanks. NekoPunch (@orangeCatDeveloper) |
927873d to
7025872
Compare
rebased |
|
/review |
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (4 commit(s)): 534af1f093e0, 32230375d11b, 5aa5b5a2511c, 7025872aec80
Model: gpt-5.6-sol
Overview
The PR centralizes recursive typed-response coercion in core and keeps AG-UI-specific Message normalization at the protocol boundary, with strong coverage for nested dataclasses, common containers, constructor failures, and invalid resume handling. Two gaps remain: the new dataclass field discovery rejects valid InitVar constructor arguments, and the new frozenset conversion can accept members that violate the declared element type.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: python/packages/core/agent_framework/_workflows/_typing_utils.py
7025872 to
3e3165f
Compare
AG-UI clients send plain JSON, but structured response types were only accepted as already-built instances, and core's coercion stopped at the outer object, letting raw dicts sit inside typed fields. Coercion now walks declared annotations and returns the input untouched whenever it cannot satisfy them.
3e3165f to
7ca5737
Compare
|
Rebased to resolve conflict |
Motivation & Context
Resuming a Magentic plan-review interrupt over AG-UI fails for the payload an AG-UI client naturally sends —
{"review": []}, the canonical "plan approved" answer:Apps have to hand-build
MagenticPlanReviewResponseto work around it. Magentic is only where this surfaced: anyrequest_infowhoseresponse_typeis a dataclass or model hits the same wall.Description & Review Guide
_coerce_response_for_requestfell back toisinstance(candidate, target_type), which a JSON dict can never satisfy, so AG-UI rejected the resume before it reached core'stry_coerce_to_type— the helper the direct-SDK path already uses to build these objects. That helper had a second defect:target_cls(**data)built the outer object and left nested values as raw dicts, and since_send_responses_internalvalidates the outer type only,MagenticPlanReviewResponse(review=[dict])passed.The coercion therefore lives in core, where every entrypoint benefits: it walks the declared annotations recursively, and a field that does not match its annotation fails the whole build rather than yielding a half-typed object. AG-UI keeps only what is protocol-specific — translating the AG-UI message wire shape into
Messagefor fields declared as such, and turning a coercion failure intoRUN_ERROR.Field validation is stricter:
Point(**{"x": 1, "y": "two"})used to construct and pass, and is now rejected. Message shorthand is translated for first-levelMessage/list[Message]fields only; nested messages need the canonicalcontentsform. The interruptresponseSchemafor structured response types is still an opaque description, left for separate work.Same resume request, before and after:
Nested coercion, which the direct-SDK path got wrong silently:
Related Issue
Fixes #7513
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.