Python: WorkflowBuilder registry (#2486)

* Add workflow builder factory pattern

* Add internal edge groups to registered executors; next samples

* Update samples: Part 1

* register -> register_executor

* update hil samples

* Update other samples

* Update agent  samples

* Update doc string

* Add new sample

* Fix mypy

* Address comments

* Fix mypy
This commit is contained in:
Tao Chen
2025-12-04 21:26:10 -08:00
committed by GitHub
Unverified
parent 6809510413
commit f2ed5b55f6
33 changed files with 1609 additions and 696 deletions
@@ -10,7 +10,6 @@ from typing import Any, override
# `agent_framework.builtin` chat client or mock the writer executor. We keep the
# concrete import here so readers can see an end-to-end configuration.
from agent_framework import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
ChatMessage,
@@ -173,25 +172,25 @@ class ReviewGateway(Executor):
def create_workflow(checkpoint_storage: FileCheckpointStorage) -> Workflow:
"""Assemble the workflow graph used by both the initial run and resume."""
# The Azure client is created once so our agent executor can issue calls to the hosted
# model. The agent id is stable across runs which keeps checkpoints deterministic.
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
agent = chat_client.create_agent(instructions="Write concise, warm release notes that sound human and helpful.")
writer = AgentExecutor(agent, id="writer")
gateway = ReviewGateway(id="review_gateway", writer_id=writer.id)
prepare = BriefPreparer(id="prepare_brief", agent_id=writer.id)
# Wire the workflow DAG. Edges mirror the numbered steps described in the
# module docstring. Because `WorkflowBuilder` is declarative, reading these
# edges is often the quickest way to understand execution order.
workflow_builder = (
WorkflowBuilder(max_iterations=6)
.set_start_executor(prepare)
.add_edge(prepare, writer)
.add_edge(writer, gateway)
.add_edge(gateway, writer) # revisions loop
.register_agent(
lambda: AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
instructions="Write concise, warm release notes that sound human and helpful.",
# The agent name is stable across runs which keeps checkpoints deterministic.
name="writer",
),
name="writer",
)
.register_executor(lambda: ReviewGateway(id="review_gateway", writer_id="writer"), name="review_gateway")
.register_executor(lambda: BriefPreparer(id="prepare_brief", agent_id="writer"), name="prepare_brief")
.set_start_executor("prepare_brief")
.add_edge("prepare_brief", "writer")
.add_edge("writer", "review_gateway")
.add_edge("review_gateway", "writer") # revisions loop
.with_checkpointing(checkpoint_storage=checkpoint_storage)
)
@@ -99,16 +99,14 @@ class WorkerExecutor(Executor):
async def main():
# Create workflow executors
start_executor = StartExecutor(id="start")
worker_executor = WorkerExecutor(id="worker")
# Build workflow with checkpointing enabled
workflow_builder = (
WorkflowBuilder()
.set_start_executor(start_executor)
.add_edge(start_executor, worker_executor)
.add_edge(worker_executor, worker_executor) # Self-loop for iterative processing
.register_executor(lambda: StartExecutor(id="start"), name="start")
.register_executor(lambda: WorkerExecutor(id="worker"), name="worker")
.set_start_executor("start")
.add_edge("start", "worker")
.add_edge("worker", "worker") # Self-loop for iterative processing
)
checkpoint_storage = InMemoryCheckpointStorage()
workflow_builder = workflow_builder.with_checkpointing(checkpoint_storage=checkpoint_storage)
@@ -292,16 +292,16 @@ class LaunchCoordinator(Executor):
def build_sub_workflow() -> WorkflowExecutor:
writer = DraftWriter()
router = DraftReviewRouter()
finaliser = DraftFinaliser()
"""Assemble the sub-workflow used by the parent workflow executor."""
sub_workflow = (
WorkflowBuilder()
.set_start_executor(writer)
.add_edge(writer, router)
.add_edge(router, finaliser)
.add_edge(finaliser, writer) # permits revision loops
.register_executor(DraftWriter, name="writer")
.register_executor(DraftReviewRouter, name="router")
.register_executor(DraftFinaliser, name="finaliser")
.set_start_executor("writer")
.add_edge("writer", "router")
.add_edge("router", "finaliser")
.add_edge("finaliser", "writer") # permits revision loops
.build()
)
@@ -309,14 +309,14 @@ def build_sub_workflow() -> WorkflowExecutor:
def build_parent_workflow(storage: FileCheckpointStorage) -> Workflow:
coordinator = LaunchCoordinator()
sub_executor = build_sub_workflow()
"""Assemble the parent workflow that embeds the sub-workflow."""
return (
WorkflowBuilder()
.set_start_executor(coordinator)
.add_edge(coordinator, sub_executor)
.add_edge(sub_executor, coordinator)
.register_executor(LaunchCoordinator, name="coordinator")
.register_executor(build_sub_workflow, name="sub_executor")
.set_start_executor("coordinator")
.add_edge("coordinator", "sub_executor")
.add_edge("sub_executor", "coordinator")
.with_checkpointing(storage)
.build()
)