Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions python/packages/core/agent_framework/_workflows/_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,20 @@ def decorator(
resolve_type_annotation(workflow_output, func.__globals__) if workflow_output is not None else None
)

# Check for unresolved TypeVars in explicit type parameters
for param_name, param_type in [
("input", resolved_input_type),
("output", resolved_output_type),
("workflow_output", resolved_workflow_output_type),
]:
if param_type is not None and isinstance(param_type, TypeVar):
raise ValueError(
f"Handler '{func.__name__}' has an unresolved TypeVar '{param_type}' "
f"as its {param_name} type. "
f"Use @handler(input=ConcreteType, output=ConcreteType) with concrete types "
f"for parameterized executors."
)

# Validate signature structure (correct number of params, ctx is WorkflowContext)
# but skip type extraction since we're using explicit types
_validate_handler_signature(func, skip_message_annotation=True)
Expand Down Expand Up @@ -652,6 +666,15 @@ def decorator(
"or explicit type parameters (input, output, workflow_output)"
)

# Check for unresolved TypeVar in introspected message type
if isinstance(message_type, TypeVar):
raise ValueError(
f"Handler '{func.__name__}' has an unresolved TypeVar '{message_type}' "
f"as its message type. "
f"Use @handler(input=ConcreteType, output=ConcreteType) with concrete types "
f"for parameterized executors."
)

final_output_types = inferred_output_types
final_workflow_output_types = inferred_workflow_output_types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import types
import typing
from collections.abc import Awaitable, Callable
from typing import Any
from typing import Any, TypeVar

from ._executor import Executor
from ._typing_utils import normalize_type_to_list, resolve_type_annotation
Expand Down Expand Up @@ -94,6 +94,19 @@ def __init__(
_validate_function_signature(func, skip_message_annotation=resolved_input_type is not None)
)

# Check for unresolved TypeVars in explicit type parameters
for param_name, param_type in [
("input", resolved_input_type),
("output", resolved_output_type),
("workflow_output", resolved_workflow_output_type),
]:
if param_type is not None and isinstance(param_type, TypeVar):
raise ValueError(
f"Executor '{func.__name__}' has an unresolved TypeVar '{param_type}' "
f"as its {param_name} type. "
f"Use @executor(input=ConcreteType, output=ConcreteType) with concrete types."
)

# Use explicit types if provided, otherwise fall back to introspection
message_type = resolved_input_type if resolved_input_type is not None else introspected_message_type
output_types: list[type[Any] | types.UnionType] = (
Expand All @@ -114,6 +127,14 @@ def __init__(
"or an explicit input_type parameter"
)

# Check for unresolved TypeVar in introspected message type
if isinstance(message_type, TypeVar):
raise ValueError(
f"Executor '{func.__name__}' has an unresolved TypeVar '{message_type}' "
f"as its message type. "
f"Use @executor(input=ConcreteType, output=ConcreteType) with concrete types."
)

# Store the original function
self._original_func = func
# Determine if function has WorkflowContext parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,27 @@ def _is_type_like(x: Any) -> bool:
if type_arg is Any:
continue

# Check for unresolved TypeVar early with an actionable error message
if isinstance(type_arg, TypeVar):
raise ValueError(
f"{context_description} {parameter_name} {param_description} "
f"has an unresolved TypeVar '{type_arg}'. "
f"Use @handler(input=ConcreteType, output=ConcreteType) with concrete types "
f"for parameterized executors."
)

# Check if it's a union type and validate each member
union_origin = get_origin(type_arg)
if union_origin in (Union, UnionType):
union_members = get_args(type_arg)
typevar_members = [m for m in union_members if isinstance(m, TypeVar)]
if typevar_members:
raise ValueError(
f"{context_description} {parameter_name} {param_description} "
f"contains unresolved TypeVar(s): {typevar_members}. "
f"Use @handler(input=ConcreteType, output=ConcreteType) with concrete types "
f"for parameterized executors."
)
invalid_members = [m for m in union_members if not _is_type_like(m) and m is not Any]
if invalid_members:
raise ValueError(
Expand Down