mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Add explicit input, output, and workflow_output parameters to @handler, @executor and request_info (#3472)
* Support specifying types via handler and executor decorators * Add handling for string types * Fix typing * Address PR feedback * All or nothing for handler typing approach * Fix mypy issues * type support for request info * Fix naming issue * Fix mypy
This commit is contained in:
committed by
GitHub
Unverified
parent
8d939f8ffa
commit
f56218fa1e
@@ -34,6 +34,18 @@ What this example shows
|
||||
Simple steps can use this form; a terminal step can yield output
|
||||
using ctx.yield_output() to provide workflow results.
|
||||
|
||||
- Explicit type parameters with @handler:
|
||||
Instead of relying on type introspection from function signatures, you can explicitly
|
||||
specify `input`, `output`, and/or `workflow_output` on the @handler decorator.
|
||||
This is "all or nothing": when ANY explicit parameter is provided, ALL types come
|
||||
from explicit parameters (introspection is disabled). The `input` parameter is
|
||||
required; `output` and `workflow_output` are optional.
|
||||
|
||||
Examples:
|
||||
@handler(input=str | int) # Accepts str or int, no outputs
|
||||
@handler(input=str, output=int) # Accepts str, outputs int
|
||||
@handler(input=str, output=int, workflow_output=bool) # All three specified
|
||||
|
||||
- Fluent WorkflowBuilder API:
|
||||
add_edge(A, B) to connect nodes, set_start_executor(A), then build() -> Workflow.
|
||||
|
||||
@@ -46,8 +58,8 @@ Prerequisites
|
||||
"""
|
||||
|
||||
|
||||
# Example 1: A custom Executor subclass
|
||||
# ------------------------------------
|
||||
# Example 1: A custom Executor subclass using introspection (traditional approach)
|
||||
# ---------------------------------------------------------------------------------
|
||||
#
|
||||
# Subclassing Executor lets you define a named node with lifecycle hooks if needed.
|
||||
# The work itself is implemented in an async method decorated with @handler.
|
||||
@@ -71,14 +83,15 @@ class UpperCase(Executor):
|
||||
Note: The WorkflowContext is parameterized with the type this handler will
|
||||
emit. Here WorkflowContext[str] means downstream nodes should expect str.
|
||||
"""
|
||||
|
||||
result = text.upper()
|
||||
|
||||
# Send the result to the next executor in the workflow.
|
||||
await ctx.send_message(result)
|
||||
|
||||
|
||||
# Example 2: A standalone function-based executor
|
||||
# -----------------------------------------------
|
||||
# Example 2: A standalone function-based executor using introspection
|
||||
# --------------------------------------------------------------------
|
||||
#
|
||||
# For simple steps you can skip subclassing and define an async function with the
|
||||
# same signature pattern (typed input + WorkflowContext[T_Out, T_W_Out]) and decorate it with
|
||||
@@ -102,30 +115,95 @@ async def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
await ctx.yield_output(result)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Build and run a simple 2-step workflow using the fluent builder API."""
|
||||
# Example 3: Using explicit type parameters on @handler
|
||||
# -----------------------------------------------------
|
||||
#
|
||||
# Instead of relying on type introspection, you can explicitly specify input,
|
||||
# output, and/or workflow_output on the @handler decorator. This is "all or nothing":
|
||||
# when ANY explicit parameter is provided, ALL types come from explicit parameters
|
||||
# (introspection is completely disabled). The input parameter is required.
|
||||
#
|
||||
# This is useful when:
|
||||
# - You want to accept multiple types (union types) without complex type annotations
|
||||
# - The function signature uses Any or a base type for flexibility
|
||||
# - You want to decouple the runtime type routing from the static type annotations
|
||||
|
||||
|
||||
class ExclamationAdder(Executor):
|
||||
"""An executor that adds exclamation marks, demonstrating explicit @handler types.
|
||||
|
||||
This example shows how to use explicit input and output parameters
|
||||
on the @handler decorator instead of relying on introspection from the function
|
||||
signature. This approach is especially useful for union types.
|
||||
"""
|
||||
|
||||
def __init__(self, id: str):
|
||||
super().__init__(id=id)
|
||||
|
||||
@handler(input=str, output=str)
|
||||
async def add_exclamation(self, message: str, ctx: WorkflowContext) -> None:
|
||||
"""Add exclamation marks to the input.
|
||||
|
||||
Note: The input=str and output=str are explicitly specified on @handler,
|
||||
so the framework uses those instead of introspecting the function signature.
|
||||
The WorkflowContext here has no type parameters because the explicit types
|
||||
on @handler take precedence.
|
||||
"""
|
||||
result = f"{message}!!!"
|
||||
await ctx.send_message(result)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Build and run workflows using the fluent builder API."""
|
||||
|
||||
# Workflow 1: Using introspection-based type detection
|
||||
# -----------------------------------------------------
|
||||
upper_case = UpperCase(id="upper_case_executor")
|
||||
|
||||
# Build the workflow using a fluent pattern:
|
||||
# 1) add_edge(from_node, to_node) defines a directed edge upper_case -> reverse_text
|
||||
# 2) set_start_executor(node) declares the entry point
|
||||
# 3) build() finalizes and returns an immutable Workflow object
|
||||
workflow = WorkflowBuilder().add_edge(upper_case, reverse_text).set_start_executor(upper_case).build()
|
||||
workflow1 = WorkflowBuilder().add_edge(upper_case, reverse_text).set_start_executor(upper_case).build()
|
||||
|
||||
# Run the workflow by sending the initial message to the start node.
|
||||
# The run(...) call returns an event collection; its get_outputs() method
|
||||
# retrieves the outputs yielded by any terminal nodes.
|
||||
events = await workflow.run("hello world")
|
||||
print(events.get_outputs())
|
||||
# Summarize the final run state (e.g., IDLE)
|
||||
print("Final state:", events.get_final_state())
|
||||
print("Workflow 1 (introspection-based types):")
|
||||
events1 = await workflow1.run("hello world")
|
||||
print(events1.get_outputs())
|
||||
print("Final state:", events1.get_final_state())
|
||||
|
||||
# Workflow 2: Using explicit type parameters on @handler
|
||||
# -------------------------------------------------------
|
||||
exclamation_adder = ExclamationAdder(id="exclamation_adder")
|
||||
|
||||
# This workflow demonstrates the explicit input/output feature:
|
||||
# exclamation_adder uses @handler(input=str, output=str) to
|
||||
# explicitly declare types instead of relying on introspection.
|
||||
workflow2 = (
|
||||
WorkflowBuilder()
|
||||
.add_edge(upper_case, exclamation_adder)
|
||||
.add_edge(exclamation_adder, reverse_text)
|
||||
.set_start_executor(upper_case)
|
||||
.build()
|
||||
)
|
||||
|
||||
print("\nWorkflow 2 (explicit @handler types):")
|
||||
events2 = await workflow2.run("hello world")
|
||||
print(events2.get_outputs())
|
||||
print("Final state:", events2.get_final_state())
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
Workflow 1 (introspection-based types):
|
||||
['DLROW OLLEH']
|
||||
Final state: WorkflowRunState.IDLE
|
||||
|
||||
Workflow 2 (explicit @handler types):
|
||||
['!!!DLROW OLLEH']
|
||||
Final state: WorkflowRunState.IDLE
|
||||
"""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user