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
@@ -4,10 +4,10 @@ import asyncio
from dataclasses import dataclass
from agent_framework import (
AgentExecutor,
AgentExecutorRequest,
AgentExecutorResponse,
AgentRunEvent,
ChatAgent,
ChatMessage,
Executor,
Role,
@@ -39,19 +39,11 @@ Prerequisites:
class DispatchToExperts(Executor):
"""Dispatches the incoming prompt to all expert agent executors (fan-out)."""
def __init__(self, expert_ids: list[str], id: str | None = None):
super().__init__(id=id or "dispatch_to_experts")
self._expert_ids = expert_ids
@handler
async def dispatch(self, prompt: str, ctx: WorkflowContext[AgentExecutorRequest]) -> None:
# Wrap the incoming prompt as a user message for each expert and request a response.
initial_message = ChatMessage(Role.USER, text=prompt)
for expert_id in self._expert_ids:
await ctx.send_message(
AgentExecutorRequest(messages=[initial_message], should_respond=True),
target_id=expert_id,
)
await ctx.send_message(AgentExecutorRequest(messages=[initial_message], should_respond=True))
@dataclass
@@ -66,10 +58,6 @@ class AggregatedInsights:
class AggregateInsights(Executor):
"""Aggregates expert agent responses into a single consolidated result (fan-in)."""
def __init__(self, expert_ids: list[str], id: str | None = None):
super().__init__(id=id or "aggregate_insights")
self._expert_ids = expert_ids
@handler
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
# Map responses to text by executor id for a simple, predictable demo.
@@ -100,53 +88,57 @@ class AggregateInsights(Executor):
await ctx.yield_output(consolidated)
def create_researcher_agent() -> ChatAgent:
"""Creates a research domain expert agent."""
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
instructions=(
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
" opportunities, and risks."
),
name="researcher",
)
def create_marketer_agent() -> ChatAgent:
"""Creates a marketing domain expert agent."""
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
instructions=(
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
" aligned to the prompt."
),
name="marketer",
)
def create_legal_agent() -> ChatAgent:
"""Creates a legal domain expert agent."""
return AzureOpenAIChatClient(credential=AzureCliCredential()).create_agent(
instructions=(
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
" based on the prompt."
),
name="legal",
)
async def main() -> None:
# 1) Create agent executors for domain experts
chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
"""Build and run the concurrent workflow with visualization."""
researcher = AgentExecutor(
chat_client.create_agent(
instructions=(
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
" opportunities, and risks."
),
),
id="researcher",
)
marketer = AgentExecutor(
chat_client.create_agent(
instructions=(
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
" aligned to the prompt."
),
),
id="marketer",
)
legal = AgentExecutor(
chat_client.create_agent(
instructions=(
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
" based on the prompt."
),
),
id="legal",
)
expert_ids = [researcher.id, marketer.id, legal.id]
dispatcher = DispatchToExperts(expert_ids=expert_ids, id="dispatcher")
aggregator = AggregateInsights(expert_ids=expert_ids, id="aggregator")
# 2) Build a simple fan-out/fan-in workflow
# 1) Build a simple fan-out/fan-in workflow
workflow = (
WorkflowBuilder()
.set_start_executor(dispatcher)
.add_fan_out_edges(dispatcher, [researcher, marketer, legal])
.add_fan_in_edges([researcher, marketer, legal], aggregator)
.register_agent(create_researcher_agent, name="researcher")
.register_agent(create_marketer_agent, name="marketer")
.register_agent(create_legal_agent, name="legal")
.register_executor(lambda: DispatchToExperts(id="dispatcher"), name="dispatcher")
.register_executor(lambda: AggregateInsights(id="aggregator"), name="aggregator")
.set_start_executor("dispatcher")
.add_fan_out_edges("dispatcher", ["researcher", "marketer", "legal"])
.add_fan_in_edges(["researcher", "marketer", "legal"], "aggregator")
.build()
)
# 2.5) Generate workflow visualization
# 1.5) Generate workflow visualization
print("Generating workflow visualization...")
viz = WorkflowViz(workflow)
# Print out the mermaid string.
@@ -162,7 +154,7 @@ async def main() -> None:
svg_file = viz.export(format="svg")
print(f"SVG file saved to: {svg_file}")
# 3) Run with a single prompt
# 2) Run with a single prompt
async for event in workflow.run_stream("We are launching a new budget-friendly electric bike for urban commuters."):
if isinstance(event, AgentRunEvent):
# Show which agent ran and what step completed.