Python: resolve postponed @response_handler annotations - #8328
Python: resolve postponed @response_handler annotations#8328CoralGarden52 wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Add fallback-path coverage and verify workflow_output_types registration for two-argument contexts.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes postponed-annotation handling for Python @response_handler methods.
Changes:
- Resolve annotations with
typing.get_type_hints, with raw-annotation fallback. - Add regression coverage for future annotations.
File summaries
| File | Summary |
|---|---|
python/packages/core/tests/workflow/test_executor_future.py |
Tests response-handler registration with postponed annotations. |
python/packages/core/agent_framework/_workflows/_request_info_mixin.py |
Resolves response-handler parameter annotations. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Ricky-7-Yan
left a comment
There was a problem hiding this comment.
Reviewed current head cf3e412. Resolving annotations with typing.get_type_hints() and using the same (NameError, AttributeError, RecursionError) raw-annotation fallback as the existing @handler and function-executor validation paths keeps the decorator behaviors aligned. The regression exercises the public class-decoration/discovery path, verifies request/response plus both WorkflowContext type arguments, and the added unresolved-reference case covers the fallback diagnostic. I found no blocking issue in the scoped diff.
The full upstream Python workflows have not run on this external branch; this approval relies on the current code/tests and the author's reported full core suite, Ruff, and Pyright results rather than treating the governance checks as full CI.
| request_type = type_hints.get(original_request_param.name, original_request_param.annotation) | ||
| if request_type == inspect.Parameter.empty: | ||
| request_type = None | ||
| response_type = type_hints.get(response_param.name, response_param.annotation) | ||
| if response_type == inspect.Parameter.empty: |
There was a problem hiding this comment.
Should we reject unresolved TypeVars in both resolved request and response annotations before registering this handler? With postponed annotations, get_type_hints() turns T into ~T, so a generic executor now instantiates successfully but _find_response_handler() passes ~T to isinstance() and crashes on the first response dispatch. The existing @handler and function-executor validators use contains_typevar() to fail during decoration with a concrete-type diagnostic; applying the same check here would keep this path safe.
There was a problem hiding this comment.
Thank you for catching this. Implemented in commit acc2bed: response-handler validation now applies contains_typevar() to both resolved request and response annotations before registration, preventing unresolved TypeVar objects from entering the handler registry and failing later during response dispatch. Regression tests now cover postponed TypeVars in both annotations.
| # Resolve string annotations from `from __future__ import annotations`. | ||
| # Fall back to raw annotations if resolution fails (e.g. unresolvable forward refs, | ||
| # AttributeError, or RecursionError), so registration failures are easier to diagnose. | ||
| try: | ||
| type_hints = typing.get_type_hints(func) | ||
| except (NameError, AttributeError, RecursionError): | ||
| type_hints = {p.name: p.annotation for p in params} |
There was a problem hiding this comment.
Would it make sense to centralize the get_type_hints() fallback policy before adding this third copy? _validate_handler_signature(), _validate_function_signature(), and _validate_response_handler_signature() now each maintain the same exception list and raw-annotation mapping, which is how response handlers missed postponed annotations in the first place. A helper such as _resolve_function_annotations(func, params) in _typing_utils.py could return the mapping while each validator retains its own parameter checks.
There was a problem hiding this comment.
Thank you for pointing this out. I centralized the get_type_hints() resolution and raw-annotation fallback policy in _resolve_function_annotations(func, params) within _typing_utils.py, and updated the handler, function-executor, and @response_handler validators to use it while keeping their parameter-specific validation logic. The fallback behavior is covered by a regression test.
Motivation & Context
The introspection path of
@response_handlerreads raw values frominspect.signature(func). Withfrom __future__ import annotations, valid request, response, andWorkflowContext[...]annotations are strings, so a valid executor class currently fails during decoration withValueErrorand cannot be imported or instantiated. The existing@handlerpath already resolves these annotations; issue #8327 tracks the missing response-handler counterpart.The bug was reproduced on clean upstream commit
3c670707766a8455da6491a9049cc9d575e019f0with a four-parameter response handler usingoriginal_request: str,response: int, andctx: WorkflowContext[str].Description & Review Guide
typing.get_type_hints(func)during signature validation, matching the existing@handlerbehavior.@response_handlermethods normally.ValueError: Response handler parameter 'ctx' must be annotated ... got WorkflowContext[str]; after the fix it registers(str, int)with workflow output typestr.5044 passed, 131 skipped, 2 xfailed; relevant request-info, executor, and future-annotation tests passed; Ruff format/lint passed; Pyright reported0 errors, 0 warningsfor both changed files._validate_handler_signature.Related Issue
Fixes #8327
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.