mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
[BREAKING] Python: Remove workflow register factory methods. Update tests and samples (#3781)
* Remove workflow register factory methods. Update tests and samples * Address Copilot feedback
This commit is contained in:
committed by
GitHub
Unverified
parent
f407f726a7
commit
a4c9e43afb
@@ -4,6 +4,7 @@ import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
Executor,
|
||||
Workflow,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
executor,
|
||||
@@ -48,6 +49,11 @@ What this example shows
|
||||
- Fluent WorkflowBuilder API:
|
||||
add_edge(A, B) to connect nodes, set_start_executor(A), then build() -> Workflow.
|
||||
|
||||
- State isolation via helper functions:
|
||||
Wrapping executor instantiation and workflow building inside a function
|
||||
(e.g., create_workflow()) ensures each call produces fresh, independent
|
||||
instances. This is the recommended pattern for reuse.
|
||||
|
||||
- Running and results:
|
||||
workflow.run(initial_input) executes the graph. Terminal nodes yield
|
||||
outputs using ctx.yield_output(). The workflow runs until idle.
|
||||
@@ -152,18 +158,28 @@ class ExclamationAdder(Executor):
|
||||
await ctx.send_message(result) # type: ignore
|
||||
|
||||
|
||||
def create_workflow() -> Workflow:
|
||||
"""Create a fresh workflow with isolated state.
|
||||
|
||||
Wrapping workflow construction in a helper function ensures each call
|
||||
produces independent executor instances. This is the recommended pattern
|
||||
for reuse — call create_workflow() each time you need a new workflow so
|
||||
that no state leaks between runs.
|
||||
"""
|
||||
upper_case = UpperCase(id="upper_case_executor")
|
||||
|
||||
return WorkflowBuilder(start_executor=upper_case).add_edge(upper_case, reverse_text).build()
|
||||
|
||||
|
||||
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) start_executor=... in constructor declares the entry point
|
||||
# 2) add_edge(from_node, to_node) defines a directed edge upper_case -> reverse_text
|
||||
# 3) build() finalizes and returns an immutable Workflow object
|
||||
workflow1 = WorkflowBuilder(start_executor=upper_case).add_edge(upper_case, reverse_text).build()
|
||||
# Workflow 1: Using the helper function pattern for state isolation
|
||||
# ------------------------------------------------------------------
|
||||
# Each call to create_workflow() returns a workflow with fresh executor
|
||||
# instances. This is the recommended pattern when you need to run the
|
||||
# same workflow topology multiple times with clean state.
|
||||
workflow1 = create_workflow()
|
||||
|
||||
# Run the workflow by sending the initial message to the start node.
|
||||
# The run(...) call returns an event collection; its get_outputs() method
|
||||
@@ -175,6 +191,7 @@ async def main():
|
||||
|
||||
# Workflow 2: Using explicit type parameters on @handler
|
||||
# -------------------------------------------------------
|
||||
upper_case = UpperCase(id="upper_case_executor")
|
||||
exclamation_adder = ExclamationAdder(id="exclamation_adder")
|
||||
|
||||
# This workflow demonstrates the explicit input/output feature:
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
Executor,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
executor,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Step 4: Using Factories to Define Executors and Agents
|
||||
|
||||
What this example shows
|
||||
- Defining custom executors using both class-based and function-based approaches.
|
||||
- Registering executor and agent factories with WorkflowBuilder for lazy instantiation.
|
||||
- Building a simple workflow that transforms input text through multiple steps.
|
||||
|
||||
Benefits of using factories
|
||||
- Decouples executor and agent creation from workflow definition.
|
||||
- Isolated instances are created for workflow builder build, allowing for cleaner state management
|
||||
and handling parallel workflow runs.
|
||||
|
||||
It is recommended to use factories when defining executors and agents for production workflows.
|
||||
|
||||
Prerequisites
|
||||
- No external services required.
|
||||
"""
|
||||
|
||||
|
||||
class UpperCase(Executor):
|
||||
def __init__(self, id: str):
|
||||
super().__init__(id=id)
|
||||
|
||||
@handler
|
||||
async def to_upper_case(self, text: str, ctx: WorkflowContext[str]) -> None:
|
||||
"""Convert the input to uppercase and forward it to the next node."""
|
||||
result = text.upper()
|
||||
|
||||
# Send the result to the next executor in the workflow.
|
||||
await ctx.send_message(result)
|
||||
|
||||
|
||||
@executor(id="reverse_text_executor")
|
||||
async def reverse_text(text: str, ctx: WorkflowContext[str]) -> None:
|
||||
"""Reverse the input string and send it downstream."""
|
||||
result = text[::-1]
|
||||
|
||||
# Send the result to the next executor in the workflow.
|
||||
await ctx.send_message(result)
|
||||
|
||||
|
||||
def create_agent() -> ChatAgent:
|
||||
"""Factory function to create a Writer agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=("You decode messages. Try to reconstruct the original message."),
|
||||
name="decoder",
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Build and run a simple 2-step workflow using the fluent builder API."""
|
||||
# Build the workflow using a fluent pattern:
|
||||
# 1) register_executor(factory, name) registers an executor factory
|
||||
# 2) register_agent(factory, name) registers an agent factory
|
||||
# 3) add_chain([node_names]) adds a sequence of nodes to the workflow
|
||||
# 4) set_start_executor(node) declares the entry point
|
||||
# 5) build() finalizes and returns an immutable Workflow object
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="UpperCase")
|
||||
.register_executor(lambda: UpperCase(id="upper_case_executor"), name="UpperCase")
|
||||
.register_executor(lambda: reverse_text, name="ReverseText")
|
||||
.register_agent(create_agent, name="DecoderAgent")
|
||||
.add_chain(["UpperCase", "ReverseText", "DecoderAgent"])
|
||||
.build()
|
||||
)
|
||||
|
||||
first_update = True
|
||||
async for event in workflow.run("hello world", stream=True):
|
||||
# The outputs of the workflow are whatever the agents produce. So the events are expected to
|
||||
# contain `AgentResponseUpdate` from the agents in the workflow.
|
||||
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
|
||||
update = event.data
|
||||
if first_update:
|
||||
print(f"{update.author_name}: {update.text}", end="", flush=True)
|
||||
first_update = False
|
||||
else:
|
||||
print(update.text, end="", flush=True)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
decoder: HELLO WORLD
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user