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
@@ -1,168 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import (
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
Executor,
|
||||
Workflow,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import ConcurrentBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
from typing_extensions import Never
|
||||
|
||||
"""
|
||||
Sample: Concurrent Orchestration with participant factories and Custom Aggregator
|
||||
|
||||
Build a concurrent workflow with ConcurrentBuilder that fans out one prompt to
|
||||
multiple domain agents and fans in their responses.
|
||||
|
||||
Override the default aggregator with a custom Executor class that uses
|
||||
AzureOpenAIChatClient.get_response() to synthesize a concise, consolidated summary
|
||||
from the experts' outputs.
|
||||
|
||||
All participants and the aggregator are created via factory functions that return
|
||||
their respective ChatAgent or Executor instances.
|
||||
|
||||
Using participant factories allows you to set up proper state isolation between workflow
|
||||
instances created by the same builder. This is particularly useful when you need to handle
|
||||
requests or tasks in parallel with stateful participants.
|
||||
|
||||
Demonstrates:
|
||||
- ConcurrentBuilder(participant_factories=[...]).with_aggregator(callback)
|
||||
- Fan-out to agents and fan-in at an aggregator
|
||||
- Aggregation implemented via an LLM call (chat_client.get_response)
|
||||
- Workflow output yielded with the synthesized summary string
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureOpenAIChatClient (az login + required env vars)
|
||||
"""
|
||||
|
||||
|
||||
def create_researcher() -> ChatAgent:
|
||||
"""Factory function to create a researcher agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_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() -> ChatAgent:
|
||||
"""Factory function to create a marketer agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
" aligned to the prompt."
|
||||
),
|
||||
name="marketer",
|
||||
)
|
||||
|
||||
|
||||
def create_legal() -> ChatAgent:
|
||||
"""Factory function to create a legal/compliance agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
" based on the prompt."
|
||||
),
|
||||
name="legal",
|
||||
)
|
||||
|
||||
|
||||
class SummarizationExecutor(Executor):
|
||||
"""Custom aggregator executor that synthesizes expert outputs into a concise summary."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(id="summarization_executor")
|
||||
self.chat_client = AzureOpenAIChatClient(credential=AzureCliCredential())
|
||||
|
||||
@handler
|
||||
async def summarize_results(self, results: list[Any], ctx: WorkflowContext[Never, str]) -> None:
|
||||
expert_sections: list[str] = []
|
||||
for r in results:
|
||||
try:
|
||||
messages = getattr(r.agent_response, "messages", [])
|
||||
final_text = messages[-1].text if messages and hasattr(messages[-1], "text") else "(no content)"
|
||||
expert_sections.append(f"{getattr(r, 'executor_id', 'expert')}:\n{final_text}")
|
||||
except Exception as e:
|
||||
expert_sections.append(f"{getattr(r, 'executor_id', 'expert')}: (error: {type(e).__name__}: {e})")
|
||||
|
||||
# Ask the model to synthesize a concise summary of the experts' outputs
|
||||
system_msg = ChatMessage(
|
||||
"system",
|
||||
text=(
|
||||
"You are a helpful assistant that consolidates multiple domain expert outputs "
|
||||
"into one cohesive, concise summary with clear takeaways. Keep it under 200 words."
|
||||
),
|
||||
)
|
||||
user_msg = ChatMessage("user", text="\n\n".join(expert_sections))
|
||||
|
||||
response = await self.chat_client.get_response([system_msg, user_msg])
|
||||
|
||||
await ctx.yield_output(response.messages[-1].text if response.messages else "")
|
||||
|
||||
|
||||
async def run_workflow(workflow: Workflow, query: str) -> None:
|
||||
events = await workflow.run(query)
|
||||
outputs = events.get_outputs()
|
||||
|
||||
if outputs:
|
||||
print(outputs[0]) # Get the first (and typically only) output
|
||||
else:
|
||||
raise RuntimeError("No outputs received from the workflow.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Create a concurrent builder with participant factories and a custom aggregator
|
||||
# - register_participants([...]) accepts factory functions that return
|
||||
# SupportsAgentRun (agents) or Executor instances.
|
||||
# - register_aggregator(...) takes a factory function that returns an Executor instance.
|
||||
concurrent_builder = (
|
||||
ConcurrentBuilder(participant_factories=[create_researcher, create_marketer, create_legal])
|
||||
.register_aggregator(SummarizationExecutor)
|
||||
)
|
||||
|
||||
# Build workflow_a
|
||||
workflow_a = concurrent_builder.build()
|
||||
|
||||
# Run workflow_a
|
||||
# Context is maintained across runs
|
||||
print("=== First Run on workflow_a ===")
|
||||
await run_workflow(workflow_a, "We are launching a new budget-friendly electric bike for urban commuters.")
|
||||
print("\n=== Second Run on workflow_a ===")
|
||||
await run_workflow(workflow_a, "Refine your response to focus on the California market.")
|
||||
|
||||
# Build workflow_b
|
||||
# This will create new instances of all participants and the aggregator
|
||||
# The agents will also get new threads
|
||||
workflow_b = concurrent_builder.build()
|
||||
# Run workflow_b
|
||||
# Context is not maintained across instances
|
||||
# Should not expect mentions of electric bikes in the results
|
||||
print("\n=== First Run on workflow_b ===")
|
||||
await run_workflow(workflow_b, "Refine your response to focus on the California market.")
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
=== First Run on workflow_a ===
|
||||
The budget-friendly electric bike market is poised for significant growth, driven by urbanization, ...
|
||||
|
||||
=== Second Run on workflow_a ===
|
||||
Launching a budget-friendly electric bike in California presents significant opportunities, driven ...
|
||||
|
||||
=== First Run on workflow_b ===
|
||||
To successfully penetrate the California market, consider these tailored strategies focused on ...
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,271 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Annotated, cast
|
||||
|
||||
from agent_framework import (
|
||||
AgentResponse,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
Workflow,
|
||||
WorkflowEvent,
|
||||
WorkflowRunState,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
"""Sample: Handoff workflow with participant factories for state isolation.
|
||||
|
||||
This sample demonstrates how to use participant factories in HandoffBuilder to create
|
||||
agents dynamically.
|
||||
|
||||
Using participant factories allows you to set up proper state isolation between workflow
|
||||
instances created by the same builder. This is particularly useful when you need to handle
|
||||
requests or tasks in parallel with stateful participants.
|
||||
|
||||
Routing Pattern:
|
||||
User -> Triage Agent -> Specialist (Refund/Order Status/Return) -> User
|
||||
|
||||
Prerequisites:
|
||||
- `az login` (Azure CLI authentication)
|
||||
- Environment variables for AzureOpenAIChatClient (AZURE_OPENAI_ENDPOINT, etc.)
|
||||
|
||||
Key Concepts:
|
||||
- Participant factories: create agents via factory functions for isolation
|
||||
- State isolation: each workflow instance gets its own agent instances
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
|
||||
# See:
|
||||
# samples/getting_started/tools/function_tool_with_approval.py
|
||||
# samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def process_refund(order_number: Annotated[str, "Order number to process refund for"]) -> str:
|
||||
"""Simulated function to process a refund for a given order number."""
|
||||
return f"Refund processed successfully for order {order_number}."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def check_order_status(order_number: Annotated[str, "Order number to check status for"]) -> str:
|
||||
"""Simulated function to check the status of a given order number."""
|
||||
return f"Order {order_number} is currently being processed and will ship in 2 business days."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def process_return(order_number: Annotated[str, "Order number to process return for"]) -> str:
|
||||
"""Simulated function to process a return for a given order number."""
|
||||
return f"Return initiated successfully for order {order_number}. You will receive return instructions via email."
|
||||
|
||||
|
||||
def create_triage_agent() -> ChatAgent:
|
||||
"""Factory function to create a triage agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You are frontline support triage. Route customer issues to the appropriate specialist agents "
|
||||
"based on the problem described."
|
||||
),
|
||||
name="triage_agent",
|
||||
)
|
||||
|
||||
|
||||
def create_refund_agent() -> ChatAgent:
|
||||
"""Factory function to create a refund agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions="You process refund requests.",
|
||||
name="refund_agent",
|
||||
# In a real application, an agent can have multiple tools; here we keep it simple
|
||||
tools=[process_refund],
|
||||
)
|
||||
|
||||
|
||||
def create_order_status_agent() -> ChatAgent:
|
||||
"""Factory function to create an order status agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions="You handle order and shipping inquiries.",
|
||||
name="order_agent",
|
||||
# In a real application, an agent can have multiple tools; here we keep it simple
|
||||
tools=[check_order_status],
|
||||
)
|
||||
|
||||
|
||||
def create_return_agent() -> ChatAgent:
|
||||
"""Factory function to create a return agent instance."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions="You manage product return requests.",
|
||||
name="return_agent",
|
||||
# In a real application, an agent can have multiple tools; here we keep it simple
|
||||
tools=[process_return],
|
||||
)
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> list[WorkflowEvent[HandoffAgentUserRequest]]:
|
||||
"""Process workflow events and extract any pending user input requests.
|
||||
|
||||
This function inspects each event type and:
|
||||
- Prints workflow status changes (IDLE, IDLE_WITH_PENDING_REQUESTS, etc.)
|
||||
- Displays final conversation snapshots when workflow completes
|
||||
- Prints user input request prompts
|
||||
- Collects all request_info events for response handling
|
||||
|
||||
Args:
|
||||
events: List of WorkflowEvent to process
|
||||
|
||||
Returns:
|
||||
List of WorkflowEvent[HandoffAgentUserRequest] representing pending user input requests
|
||||
"""
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
|
||||
for event in events:
|
||||
if event.type == "handoff_sent":
|
||||
# handoff_sent event: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
# Status event: Indicates workflow state changes
|
||||
print(f"\n[Workflow Status] {event.state.name}")
|
||||
elif event.type == "output":
|
||||
# Output event: Contains contents generated by the workflow
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponse):
|
||||
for message in data.messages:
|
||||
if not message.text:
|
||||
# Skip messages without text (e.g., tool calls)
|
||||
continue
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text}")
|
||||
elif event.type == "output":
|
||||
# The output of the handoff workflow is a collection of chat messages from all participants
|
||||
conversation = cast(list[ChatMessage], event.data)
|
||||
if isinstance(conversation, list):
|
||||
print("\n=== Final Conversation Snapshot ===")
|
||||
for message in conversation:
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
elif event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
# Request info event: Workflow is requesting user input
|
||||
_print_handoff_agent_user_request(event.data.agent_response)
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
|
||||
return requests
|
||||
|
||||
|
||||
def _print_handoff_agent_user_request(response: AgentResponse) -> None:
|
||||
"""Display the agent's response messages when requesting user input.
|
||||
|
||||
This will happen when an agent generates a response that doesn't trigger
|
||||
a handoff, i.e., the agent is asking the user for more information.
|
||||
|
||||
Args:
|
||||
response: The AgentResponse from the agent requesting user input
|
||||
"""
|
||||
if not response.messages:
|
||||
raise RuntimeError("Cannot print agent responses: response has no messages.")
|
||||
|
||||
print("\n[Agent is requesting your input...]")
|
||||
|
||||
# Print agent responses
|
||||
for message in response.messages:
|
||||
if not message.text:
|
||||
# Skip messages without text (e.g., tool calls)
|
||||
continue
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text}")
|
||||
|
||||
|
||||
async def _run_workflow(workflow: Workflow, user_inputs: list[str]) -> None:
|
||||
"""Run the workflow with the given user input and display events."""
|
||||
print(f"- User: {user_inputs[0]}")
|
||||
workflow_result = await workflow.run(user_inputs[0])
|
||||
pending_requests = _handle_events(workflow_result)
|
||||
|
||||
# Process the request/response cycle
|
||||
# The workflow will continue requesting input until:
|
||||
# 1. The termination condition is met (4 user messages in this case), OR
|
||||
# 2. We run out of scripted responses
|
||||
while pending_requests:
|
||||
if user_inputs[1:]:
|
||||
# Get the next scripted response
|
||||
user_response = user_inputs.pop(1)
|
||||
print(f"\n- User: {user_response}")
|
||||
|
||||
# Send response(s) to all pending requests
|
||||
# In this demo, there's typically one request per cycle, but the API supports multiple
|
||||
responses = {
|
||||
req.request_id: HandoffAgentUserRequest.create_response(user_response) for req in pending_requests
|
||||
}
|
||||
else:
|
||||
# No more scripted responses; terminate the workflow
|
||||
responses = {req.request_id: HandoffAgentUserRequest.terminate() for req in pending_requests}
|
||||
|
||||
# Send responses and get new events
|
||||
# We use run(responses=...) to get events, allowing us to
|
||||
# display agent responses and handle new requests as they arrive
|
||||
workflow_result = await workflow.run(responses=responses)
|
||||
pending_requests = _handle_events(workflow_result)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Run the autonomous handoff workflow with participant factories."""
|
||||
# Build the handoff workflow using participant factories
|
||||
# termination_condition: Custom termination that checks if the triage agent has provided a closing message.
|
||||
# This looks for the last message being from triage_agent and containing "welcome",
|
||||
# which indicates the conversation has concluded naturally.
|
||||
workflow_builder = (
|
||||
HandoffBuilder(
|
||||
name="Autonomous Handoff with Participant Factories",
|
||||
participant_factories={
|
||||
"triage": create_triage_agent,
|
||||
"refund": create_refund_agent,
|
||||
"order_status": create_order_status_agent,
|
||||
"return": create_return_agent,
|
||||
},
|
||||
termination_condition=lambda conversation: (
|
||||
len(conversation) > 0
|
||||
and conversation[-1].author_name == "triage_agent"
|
||||
and "welcome" in conversation[-1].text.lower()
|
||||
),
|
||||
)
|
||||
.with_start_agent("triage")
|
||||
)
|
||||
|
||||
# Scripted user responses for reproducible demo
|
||||
# In a console application, replace this with:
|
||||
# user_input = input("Your response: ")
|
||||
# or integrate with a UI/chat interface
|
||||
user_inputs = [
|
||||
"Hello, I need assistance with my recent purchase.",
|
||||
"My order 1234 arrived damaged and the packaging was destroyed. I'd like to return it.",
|
||||
"Is my return being processed?",
|
||||
"Thanks for resolving this.",
|
||||
]
|
||||
|
||||
workflow_a = workflow_builder.build()
|
||||
print("=== Running workflow_a ===")
|
||||
await _run_workflow(workflow_a, list(user_inputs))
|
||||
|
||||
workflow_b = workflow_builder.build()
|
||||
print("=== Running workflow_b ===")
|
||||
# Only provide the last two inputs to workflow_b to demonstrate state isolation
|
||||
# The agents in this workflow have no prior context thus should not have knowledge of
|
||||
# order 1234 or previous interactions.
|
||||
await _run_workflow(workflow_b, user_inputs[2:])
|
||||
"""
|
||||
Expected behavior:
|
||||
- workflow_a and workflow_b maintain separate states for their participants.
|
||||
- Each workflow processes its requests independently without interference.
|
||||
- workflow_a will answer the follow-up request based on its own conversation history,
|
||||
while workflow_b will provide a general answer without prior context.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,126 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
Executor,
|
||||
Workflow,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""
|
||||
Sample: Sequential workflow with participant factories
|
||||
|
||||
This sample demonstrates how to create a sequential workflow with participant factories.
|
||||
|
||||
Using participant factories allows you to set up proper state isolation between workflow
|
||||
instances created by the same builder. This is particularly useful when you need to handle
|
||||
requests or tasks in parallel with stateful participants.
|
||||
|
||||
In this example, we create a sequential workflow with two participants: an accumulator
|
||||
and a content producer. The accumulator is stateful and maintains a list of all messages it has
|
||||
received. Context is maintained across runs of the same workflow instance but not across different
|
||||
workflow instances.
|
||||
"""
|
||||
|
||||
|
||||
class Accumulate(Executor):
|
||||
"""Simple accumulator.
|
||||
|
||||
Accumulates all messages from the conversation and prints them out.
|
||||
"""
|
||||
|
||||
def __init__(self, id: str):
|
||||
super().__init__(id)
|
||||
# Some internal state to accumulate messages
|
||||
self._accumulated: list[str] = []
|
||||
|
||||
@handler
|
||||
async def accumulate(self, conversation: list[ChatMessage], ctx: WorkflowContext[list[ChatMessage]]) -> None:
|
||||
self._accumulated.extend([msg.text for msg in conversation])
|
||||
print(f"Number of queries received so far: {len(self._accumulated)}")
|
||||
await ctx.send_message(conversation)
|
||||
|
||||
|
||||
def create_agent() -> ChatAgent:
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions="Produce a concise paragraph answering the user's request.",
|
||||
name="ContentProducer",
|
||||
)
|
||||
|
||||
|
||||
async def run_workflow(workflow: Workflow, query: str) -> None:
|
||||
events = await workflow.run(query)
|
||||
outputs = events.get_outputs()
|
||||
|
||||
if outputs:
|
||||
messages: list[ChatMessage] = outputs[0]
|
||||
for message in messages:
|
||||
name = message.author_name or ("assistant" if message.role == "assistant" else "user")
|
||||
print(f"{name}: {message.text}")
|
||||
else:
|
||||
raise RuntimeError("No outputs received from the workflow.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Create a builder with participant factories
|
||||
builder = SequentialBuilder(participant_factories=[
|
||||
lambda: Accumulate("accumulator"),
|
||||
create_agent,
|
||||
])
|
||||
# 2) Build workflow_a
|
||||
workflow_a = builder.build()
|
||||
|
||||
# 3) Run workflow_a
|
||||
# Context is maintained across runs
|
||||
print("=== First Run on workflow_a ===")
|
||||
await run_workflow(workflow_a, "Why is the sky blue?")
|
||||
print("\n=== Second Run on workflow_a ===")
|
||||
await run_workflow(workflow_a, "Repeat my previous question.")
|
||||
|
||||
# 4) Build workflow_b
|
||||
# This will create a new instance of the accumulator and content producer
|
||||
# using the same workflow builder
|
||||
workflow_b = builder.build()
|
||||
|
||||
# 5) Run workflow_b
|
||||
# Context is not maintained across instances
|
||||
print("\n=== First Run on workflow_b ===")
|
||||
await run_workflow(workflow_b, "Repeat my previous question.")
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
=== First Run on workflow_a ===
|
||||
Number of queries received so far: 1
|
||||
user: Why is the sky blue?
|
||||
ContentProducer: The sky appears blue due to a phenomenon called Rayleigh scattering.
|
||||
When sunlight enters the Earth's atmosphere, it collides with gases
|
||||
and particles, scattering shorter wavelengths of light (blue and violet)
|
||||
more than the longer wavelengths (red and yellow). Although violet light
|
||||
is scattered even more than blue, our eyes are more sensitive to blue
|
||||
light, and some violet light is absorbed by the ozone layer. As a result,
|
||||
we perceive the sky as predominantly blue during the day.
|
||||
|
||||
=== Second Run on workflow_a ===
|
||||
Number of queries received so far: 2
|
||||
user: Repeat my previous question.
|
||||
ContentProducer: Why is the sky blue?
|
||||
|
||||
=== First Run on workflow_b ===
|
||||
Number of queries received so far: 1
|
||||
user: Repeat my previous question.
|
||||
ContentProducer: I'm sorry, but I can't repeat your previous question as I don't have
|
||||
access to your past queries. However, feel free to ask anything again,
|
||||
and I'll be happy to help!
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -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())
|
||||
+6
-8
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
ChatMessageStore,
|
||||
@@ -70,15 +71,12 @@ async def main() -> None:
|
||||
# Set the message store to store messages in memory.
|
||||
shared_thread.message_store = ChatMessageStore()
|
||||
|
||||
writer_executor = AgentExecutor(writer, agent_thread=shared_thread)
|
||||
reviewer_executor = AgentExecutor(reviewer, agent_thread=shared_thread)
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="writer")
|
||||
.register_agent(factory_func=lambda: writer, name="writer", agent_thread=shared_thread)
|
||||
.register_agent(factory_func=lambda: reviewer, name="reviewer", agent_thread=shared_thread)
|
||||
.register_executor(
|
||||
factory_func=lambda: intercept_agent_response,
|
||||
name="intercept_agent_response",
|
||||
)
|
||||
.add_chain(["writer", "intercept_agent_response", "reviewer"])
|
||||
WorkflowBuilder(start_executor=writer_executor)
|
||||
.add_chain([writer_executor, intercept_agent_response, reviewer_executor])
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+14
-15
@@ -6,6 +6,7 @@ from dataclasses import dataclass, field
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
AgentResponse,
|
||||
@@ -239,22 +240,20 @@ async def main() -> None:
|
||||
"""Run the workflow and bridge human feedback between two agents."""
|
||||
|
||||
# Build the workflow.
|
||||
writer_agent = AgentExecutor(create_writer_agent())
|
||||
final_editor_agent = AgentExecutor(create_final_editor_agent())
|
||||
coordinator = Coordinator(
|
||||
id="coordinator",
|
||||
writer_id="writer_agent",
|
||||
final_editor_id="final_editor_agent",
|
||||
)
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="writer_agent")
|
||||
.register_agent(create_writer_agent, name="writer_agent")
|
||||
.register_agent(create_final_editor_agent, name="final_editor_agent")
|
||||
.register_executor(
|
||||
lambda: Coordinator(
|
||||
id="coordinator",
|
||||
writer_id="writer_agent",
|
||||
final_editor_id="final_editor_agent",
|
||||
),
|
||||
name="coordinator",
|
||||
)
|
||||
.add_edge("writer_agent", "coordinator")
|
||||
.add_edge("coordinator", "writer_agent")
|
||||
.add_edge("final_editor_agent", "coordinator")
|
||||
.add_edge("coordinator", "final_editor_agent")
|
||||
WorkflowBuilder(start_executor=writer_agent)
|
||||
.add_edge(writer_agent, coordinator)
|
||||
.add_edge(coordinator, writer_agent)
|
||||
.add_edge(final_editor_agent, coordinator)
|
||||
.add_edge(coordinator, final_editor_agent)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+9
-14
@@ -98,21 +98,16 @@ async def main() -> None:
|
||||
print("Building workflow with Worker-Reviewer cycle...")
|
||||
# Build a workflow with bidirectional communication between Worker and Reviewer,
|
||||
# and escalation paths for human review.
|
||||
worker = Worker(
|
||||
id="worker",
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
)
|
||||
reviewer = ReviewerWithHumanInTheLoop(worker_id="worker")
|
||||
|
||||
agent = (
|
||||
WorkflowBuilder(start_executor="worker")
|
||||
.register_executor(
|
||||
lambda: Worker(
|
||||
id="sub-worker",
|
||||
chat_client=AzureOpenAIChatClient(credential=AzureCliCredential()),
|
||||
),
|
||||
name="worker",
|
||||
)
|
||||
.register_executor(
|
||||
lambda: ReviewerWithHumanInTheLoop(worker_id="sub-worker"),
|
||||
name="reviewer",
|
||||
)
|
||||
.add_edge("worker", "reviewer") # Worker sends requests to Reviewer
|
||||
.add_edge("reviewer", "worker") # Reviewer sends feedback to Worker
|
||||
WorkflowBuilder(start_executor=worker)
|
||||
.add_edge(worker, reviewer) # Worker sends requests to Reviewer
|
||||
.add_edge(reviewer, worker) # Reviewer sends feedback to Worker
|
||||
.build()
|
||||
.as_agent() # Convert workflow into an agent interface
|
||||
)
|
||||
|
||||
+6
-11
@@ -186,18 +186,13 @@ async def main() -> None:
|
||||
print("=" * 50)
|
||||
|
||||
print("Building workflow with Worker ↔ Reviewer cycle...")
|
||||
worker = Worker(id="worker", chat_client=OpenAIChatClient(model_id="gpt-4.1-nano"))
|
||||
reviewer = Reviewer(id="reviewer", chat_client=OpenAIChatClient(model_id="gpt-4.1"))
|
||||
|
||||
agent = (
|
||||
WorkflowBuilder(start_executor="worker")
|
||||
.register_executor(
|
||||
lambda: Worker(id="worker", chat_client=OpenAIChatClient(model_id="gpt-4.1-nano")),
|
||||
name="worker",
|
||||
)
|
||||
.register_executor(
|
||||
lambda: Reviewer(id="reviewer", chat_client=OpenAIChatClient(model_id="gpt-4.1")),
|
||||
name="reviewer",
|
||||
)
|
||||
.add_edge("worker", "reviewer") # Worker sends responses to Reviewer
|
||||
.add_edge("reviewer", "worker") # Reviewer provides feedback to Worker
|
||||
WorkflowBuilder(start_executor=worker)
|
||||
.add_edge(worker, reviewer) # Worker sends responses to Reviewer
|
||||
.add_edge(reviewer, worker) # Reviewer provides feedback to Worker
|
||||
.build()
|
||||
.as_agent() # Wrap workflow as an agent
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageStore
|
||||
from agent_framework import AgentThread, ChatMessageStore
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
|
||||
@@ -39,27 +39,24 @@ async def main() -> None:
|
||||
# Create a chat client
|
||||
chat_client = OpenAIChatClient()
|
||||
|
||||
# Define factory functions for workflow participants
|
||||
def create_assistant() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="assistant",
|
||||
instructions=(
|
||||
"You are a helpful assistant. Answer questions based on the conversation "
|
||||
"history. If the user asks about something mentioned earlier, reference it."
|
||||
),
|
||||
)
|
||||
assistant = chat_client.as_agent(
|
||||
name="assistant",
|
||||
instructions=(
|
||||
"You are a helpful assistant. Answer questions based on the conversation "
|
||||
"history. If the user asks about something mentioned earlier, reference it."
|
||||
),
|
||||
)
|
||||
|
||||
def create_summarizer() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="summarizer",
|
||||
instructions=(
|
||||
"You are a summarizer. After the assistant responds, provide a brief "
|
||||
"one-sentence summary of the key point from the conversation so far."
|
||||
),
|
||||
)
|
||||
summarizer = chat_client.as_agent(
|
||||
name="summarizer",
|
||||
instructions=(
|
||||
"You are a summarizer. After the assistant responds, provide a brief "
|
||||
"one-sentence summary of the key point from the conversation so far."
|
||||
),
|
||||
)
|
||||
|
||||
# Build a sequential workflow: assistant -> summarizer
|
||||
workflow = SequentialBuilder(participant_factories=[create_assistant, create_summarizer]).build()
|
||||
workflow = SequentialBuilder(participants=[assistant, summarizer]).build()
|
||||
|
||||
# Wrap the workflow as an agent
|
||||
agent = workflow.as_agent(name="ConversationalWorkflowAgent")
|
||||
@@ -124,13 +121,12 @@ async def demonstrate_thread_serialization() -> None:
|
||||
"""
|
||||
chat_client = OpenAIChatClient()
|
||||
|
||||
def create_assistant() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="memory_assistant",
|
||||
instructions="You are a helpful assistant with good memory. Remember details from our conversation.",
|
||||
)
|
||||
memory_assistant = chat_client.as_agent(
|
||||
name="memory_assistant",
|
||||
instructions="You are a helpful assistant with good memory. Remember details from our conversation.",
|
||||
)
|
||||
|
||||
workflow = SequentialBuilder(participant_factories=[create_assistant]).build()
|
||||
workflow = SequentialBuilder(participants=[memory_assistant]).build()
|
||||
agent = workflow.as_agent(name="MemoryWorkflowAgent")
|
||||
|
||||
# Create initial thread and have a conversation
|
||||
|
||||
+13
-14
@@ -17,6 +17,7 @@ else:
|
||||
# `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,
|
||||
@@ -178,23 +179,21 @@ def create_workflow(checkpoint_storage: FileCheckpointStorage) -> Workflow:
|
||||
# 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.
|
||||
writer_agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions="Write concise, warm release notes that sound human and helpful.",
|
||||
name="writer",
|
||||
)
|
||||
writer = AgentExecutor(writer_agent)
|
||||
review_gateway = ReviewGateway(id="review_gateway", writer_id="writer")
|
||||
prepare_brief = BriefPreparer(id="prepare_brief", agent_id="writer")
|
||||
|
||||
workflow_builder = (
|
||||
WorkflowBuilder(
|
||||
max_iterations=6, start_executor="prepare_brief", checkpoint_storage=checkpoint_storage
|
||||
max_iterations=6, start_executor=prepare_brief, checkpoint_storage=checkpoint_storage
|
||||
)
|
||||
.register_agent(
|
||||
lambda: AzureOpenAIChatClient(credential=AzureCliCredential()).as_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")
|
||||
.add_edge("prepare_brief", "writer")
|
||||
.add_edge("writer", "review_gateway")
|
||||
.add_edge("review_gateway", "writer") # revisions loop
|
||||
.add_edge(prepare_brief, writer)
|
||||
.add_edge(writer, review_gateway)
|
||||
.add_edge(review_gateway, writer) # revisions loop
|
||||
)
|
||||
|
||||
return workflow_builder.build()
|
||||
|
||||
@@ -105,12 +105,12 @@ class WorkerExecutor(Executor):
|
||||
async def main():
|
||||
# Build workflow with checkpointing enabled
|
||||
checkpoint_storage = InMemoryCheckpointStorage()
|
||||
start = StartExecutor(id="start")
|
||||
worker = WorkerExecutor(id="worker")
|
||||
workflow_builder = (
|
||||
WorkflowBuilder(start_executor="start", checkpoint_storage=checkpoint_storage)
|
||||
.register_executor(lambda: StartExecutor(id="start"), name="start")
|
||||
.register_executor(lambda: WorkerExecutor(id="worker"), name="worker")
|
||||
.add_edge("start", "worker")
|
||||
.add_edge("worker", "worker") # Self-loop for iterative processing
|
||||
WorkflowBuilder(start_executor=start, checkpoint_storage=checkpoint_storage)
|
||||
.add_edge(start, worker)
|
||||
.add_edge(worker, worker) # Self-loop for iterative processing
|
||||
)
|
||||
|
||||
# Run workflow with automatic checkpoint recovery
|
||||
|
||||
@@ -297,14 +297,14 @@ class LaunchCoordinator(Executor):
|
||||
|
||||
def build_sub_workflow() -> WorkflowExecutor:
|
||||
"""Assemble the sub-workflow used by the parent workflow executor."""
|
||||
writer = DraftWriter()
|
||||
router = DraftReviewRouter()
|
||||
finaliser = DraftFinaliser()
|
||||
sub_workflow = (
|
||||
WorkflowBuilder(start_executor="writer")
|
||||
.register_executor(DraftWriter, name="writer")
|
||||
.register_executor(DraftReviewRouter, name="router")
|
||||
.register_executor(DraftFinaliser, name="finaliser")
|
||||
.add_edge("writer", "router")
|
||||
.add_edge("router", "finaliser")
|
||||
.add_edge("finaliser", "writer") # permits revision loops
|
||||
WorkflowBuilder(start_executor=writer)
|
||||
.add_edge(writer, router)
|
||||
.add_edge(router, finaliser)
|
||||
.add_edge(finaliser, writer) # permits revision loops
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -313,12 +313,12 @@ def build_sub_workflow() -> WorkflowExecutor:
|
||||
|
||||
def build_parent_workflow(storage: FileCheckpointStorage) -> Workflow:
|
||||
"""Assemble the parent workflow that embeds the sub-workflow."""
|
||||
coordinator = LaunchCoordinator()
|
||||
sub_executor = build_sub_workflow()
|
||||
return (
|
||||
WorkflowBuilder(start_executor="coordinator", checkpoint_storage=storage)
|
||||
.register_executor(LaunchCoordinator, name="coordinator")
|
||||
.register_executor(build_sub_workflow, name="sub_executor")
|
||||
.add_edge("coordinator", "sub_executor")
|
||||
.add_edge("sub_executor", "coordinator")
|
||||
WorkflowBuilder(start_executor=coordinator, checkpoint_storage=storage)
|
||||
.add_edge(coordinator, sub_executor)
|
||||
.add_edge(sub_executor, coordinator)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+19
-25
@@ -27,7 +27,6 @@ import asyncio
|
||||
|
||||
from agent_framework import (
|
||||
AgentThread,
|
||||
ChatAgent,
|
||||
ChatMessageStore,
|
||||
InMemoryCheckpointStorage,
|
||||
)
|
||||
@@ -43,20 +42,17 @@ async def basic_checkpointing() -> None:
|
||||
|
||||
chat_client = OpenAIChatClient()
|
||||
|
||||
def create_assistant() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="assistant",
|
||||
instructions="You are a helpful assistant. Keep responses brief.",
|
||||
)
|
||||
assistant = chat_client.as_agent(
|
||||
name="assistant",
|
||||
instructions="You are a helpful assistant. Keep responses brief.",
|
||||
)
|
||||
|
||||
def create_reviewer() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="reviewer",
|
||||
instructions="You are a reviewer. Provide a one-sentence summary of the assistant's response.",
|
||||
)
|
||||
reviewer = chat_client.as_agent(
|
||||
name="reviewer",
|
||||
instructions="You are a reviewer. Provide a one-sentence summary of the assistant's response.",
|
||||
)
|
||||
|
||||
# Build sequential workflow with participant factories
|
||||
workflow = SequentialBuilder(participant_factories=[create_assistant, create_reviewer]).build()
|
||||
workflow = SequentialBuilder(participants=[assistant, reviewer]).build()
|
||||
agent = workflow.as_agent(name="CheckpointedAgent")
|
||||
|
||||
# Create checkpoint storage
|
||||
@@ -87,13 +83,12 @@ async def checkpointing_with_thread() -> None:
|
||||
|
||||
chat_client = OpenAIChatClient()
|
||||
|
||||
def create_assistant() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="memory_assistant",
|
||||
instructions="You are a helpful assistant with good memory. Reference previous conversation when relevant.",
|
||||
)
|
||||
assistant = chat_client.as_agent(
|
||||
name="memory_assistant",
|
||||
instructions="You are a helpful assistant with good memory. Reference previous conversation when relevant.",
|
||||
)
|
||||
|
||||
workflow = SequentialBuilder(participant_factories=[create_assistant]).build()
|
||||
workflow = SequentialBuilder(participants=[assistant]).build()
|
||||
agent = workflow.as_agent(name="MemoryAgent")
|
||||
|
||||
# Create both thread (for conversation) and checkpoint storage (for workflow state)
|
||||
@@ -131,13 +126,12 @@ async def streaming_with_checkpoints() -> None:
|
||||
|
||||
chat_client = OpenAIChatClient()
|
||||
|
||||
def create_assistant() -> ChatAgent:
|
||||
return chat_client.as_agent(
|
||||
name="streaming_assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
assistant = chat_client.as_agent(
|
||||
name="streaming_assistant",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
workflow = SequentialBuilder(participant_factories=[create_assistant]).build()
|
||||
workflow = SequentialBuilder(participants=[assistant]).build()
|
||||
agent = workflow.as_agent(name="StreamingCheckpointAgent")
|
||||
|
||||
checkpoint_storage = InMemoryCheckpointStorage()
|
||||
|
||||
@@ -140,9 +140,9 @@ def create_sub_workflow() -> WorkflowExecutor:
|
||||
"""Create the text processing sub-workflow."""
|
||||
print("🚀 Setting up sub-workflow...")
|
||||
|
||||
text_processor = TextProcessor()
|
||||
processing_workflow = (
|
||||
WorkflowBuilder(start_executor="text_processor")
|
||||
.register_executor(TextProcessor, name="text_processor")
|
||||
WorkflowBuilder(start_executor=text_processor)
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -153,12 +153,12 @@ async def main():
|
||||
"""Main function to run the basic sub-workflow example."""
|
||||
print("🔧 Setting up parent workflow...")
|
||||
# Step 1: Create the parent workflow
|
||||
orchestrator = TextProcessingOrchestrator()
|
||||
sub_workflow_executor = create_sub_workflow()
|
||||
main_workflow = (
|
||||
WorkflowBuilder(start_executor="text_orchestrator")
|
||||
.register_executor(TextProcessingOrchestrator, name="text_orchestrator")
|
||||
.register_executor(create_sub_workflow, name="text_processor_workflow")
|
||||
.add_edge("text_orchestrator", "text_processor_workflow")
|
||||
.add_edge("text_processor_workflow", "text_orchestrator")
|
||||
WorkflowBuilder(start_executor=orchestrator)
|
||||
.add_edge(orchestrator, sub_workflow_executor)
|
||||
.add_edge(sub_workflow_executor, orchestrator)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+26
-28
@@ -169,17 +169,18 @@ def build_resource_request_distribution_workflow() -> Workflow:
|
||||
elif len(self._responses) > self._request_count:
|
||||
raise ValueError("Received more responses than expected")
|
||||
|
||||
orchestrator = RequestDistribution("orchestrator")
|
||||
resource_requester = ResourceRequester("resource_requester")
|
||||
policy_checker = PolicyChecker("policy_checker")
|
||||
result_collector = ResultCollector("result_collector")
|
||||
|
||||
return (
|
||||
WorkflowBuilder(start_executor="orchestrator")
|
||||
.register_executor(lambda: RequestDistribution("orchestrator"), name="orchestrator")
|
||||
.register_executor(lambda: ResourceRequester("resource_requester"), name="resource_requester")
|
||||
.register_executor(lambda: PolicyChecker("policy_checker"), name="policy_checker")
|
||||
.register_executor(lambda: ResultCollector("result_collector"), name="result_collector")
|
||||
.add_edge("orchestrator", "resource_requester")
|
||||
.add_edge("orchestrator", "policy_checker")
|
||||
.add_edge("resource_requester", "result_collector")
|
||||
.add_edge("policy_checker", "result_collector")
|
||||
.add_edge("orchestrator", "result_collector") # For request count
|
||||
WorkflowBuilder(start_executor=orchestrator)
|
||||
.add_edge(orchestrator, resource_requester)
|
||||
.add_edge(orchestrator, policy_checker)
|
||||
.add_edge(resource_requester, result_collector)
|
||||
.add_edge(policy_checker, result_collector)
|
||||
.add_edge(orchestrator, result_collector) # For request count
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -287,25 +288,22 @@ class PolicyEngine(Executor):
|
||||
|
||||
async def main() -> None:
|
||||
# Build the main workflow
|
||||
resource_allocator = ResourceAllocator("resource_allocator")
|
||||
policy_engine = PolicyEngine("policy_engine")
|
||||
sub_workflow_executor = WorkflowExecutor(
|
||||
build_resource_request_distribution_workflow(),
|
||||
"sub_workflow_executor",
|
||||
# Setting allow_direct_output=True to let the sub-workflow output directly.
|
||||
# This is because the sub-workflow is the both the entry point and the exit
|
||||
# point of the main workflow.
|
||||
allow_direct_output=True,
|
||||
)
|
||||
main_workflow = (
|
||||
WorkflowBuilder(start_executor="sub_workflow_executor")
|
||||
.register_executor(lambda: ResourceAllocator("resource_allocator"), name="resource_allocator")
|
||||
.register_executor(lambda: PolicyEngine("policy_engine"), name="policy_engine")
|
||||
.register_executor(
|
||||
lambda: WorkflowExecutor(
|
||||
build_resource_request_distribution_workflow(),
|
||||
"sub_workflow_executor",
|
||||
# Setting allow_direct_output=True to let the sub-workflow output directly.
|
||||
# This is because the sub-workflow is the both the entry point and the exit
|
||||
# point of the main workflow.
|
||||
allow_direct_output=True,
|
||||
),
|
||||
name="sub_workflow_executor",
|
||||
)
|
||||
.add_edge("sub_workflow_executor", "resource_allocator")
|
||||
.add_edge("resource_allocator", "sub_workflow_executor")
|
||||
.add_edge("sub_workflow_executor", "policy_engine")
|
||||
.add_edge("policy_engine", "sub_workflow_executor")
|
||||
WorkflowBuilder(start_executor=sub_workflow_executor)
|
||||
.add_edge(sub_workflow_executor, resource_allocator)
|
||||
.add_edge(resource_allocator, sub_workflow_executor)
|
||||
.add_edge(sub_workflow_executor, policy_engine)
|
||||
.add_edge(policy_engine, sub_workflow_executor)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+15
-19
@@ -153,13 +153,14 @@ def build_email_address_validation_workflow() -> Workflow:
|
||||
)
|
||||
|
||||
# Build the workflow
|
||||
email_sanitizer = EmailSanitizer(id="email_sanitizer")
|
||||
email_format_validator = EmailFormatValidator(id="email_format_validator")
|
||||
domain_validator = DomainValidator(id="domain_validator")
|
||||
|
||||
return (
|
||||
WorkflowBuilder(start_executor="email_sanitizer")
|
||||
.register_executor(lambda: EmailSanitizer(id="email_sanitizer"), name="email_sanitizer")
|
||||
.register_executor(lambda: EmailFormatValidator(id="email_format_validator"), name="email_format_validator")
|
||||
.register_executor(lambda: DomainValidator(id="domain_validator"), name="domain_validator")
|
||||
.add_edge("email_sanitizer", "email_format_validator")
|
||||
.add_edge("email_format_validator", "domain_validator")
|
||||
WorkflowBuilder(start_executor=email_sanitizer)
|
||||
.add_edge(email_sanitizer, email_format_validator)
|
||||
.add_edge(email_format_validator, domain_validator)
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -268,20 +269,15 @@ async def main() -> None:
|
||||
approved_domains = {"example.com", "company.com"}
|
||||
|
||||
# Build the main workflow
|
||||
smart_email_orchestrator = SmartEmailOrchestrator(id="smart_email_orchestrator", approved_domains=approved_domains)
|
||||
email_delivery = EmailDelivery(id="email_delivery")
|
||||
email_validation_workflow = WorkflowExecutor(build_email_address_validation_workflow(), id="email_validation_workflow")
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="smart_email_orchestrator")
|
||||
.register_executor(
|
||||
lambda: SmartEmailOrchestrator(id="smart_email_orchestrator", approved_domains=approved_domains),
|
||||
name="smart_email_orchestrator",
|
||||
)
|
||||
.register_executor(lambda: EmailDelivery(id="email_delivery"), name="email_delivery")
|
||||
.register_executor(
|
||||
lambda: WorkflowExecutor(build_email_address_validation_workflow(), id="email_validation_workflow"),
|
||||
name="email_validation_workflow",
|
||||
)
|
||||
.add_edge("smart_email_orchestrator", "email_validation_workflow")
|
||||
.add_edge("email_validation_workflow", "smart_email_orchestrator")
|
||||
.add_edge("smart_email_orchestrator", "email_delivery")
|
||||
WorkflowBuilder(start_executor=smart_email_orchestrator)
|
||||
.add_edge(smart_email_orchestrator, email_validation_workflow)
|
||||
.add_edge(email_validation_workflow, smart_email_orchestrator)
|
||||
.add_edge(smart_email_orchestrator, email_delivery)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ( # Core chat primitives used to build requests
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest, # Input message bundle for an AgentExecutor
|
||||
AgentExecutorResponse,
|
||||
ChatAgent, # Output from an AgentExecutor
|
||||
@@ -161,19 +162,17 @@ async def main() -> None:
|
||||
# If not spam, hop to a transformer that creates a new AgentExecutorRequest,
|
||||
# then call the email assistant, then finalize.
|
||||
# If spam, go directly to the spam handler and finalize.
|
||||
spam_detection_agent = AgentExecutor(create_spam_detector_agent())
|
||||
email_assistant_agent = AgentExecutor(create_email_assistant_agent())
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="spam_detection_agent")
|
||||
.register_agent(create_spam_detector_agent, name="spam_detection_agent")
|
||||
.register_agent(create_email_assistant_agent, name="email_assistant_agent")
|
||||
.register_executor(lambda: to_email_assistant_request, name="to_email_assistant_request")
|
||||
.register_executor(lambda: handle_email_response, name="send_email")
|
||||
.register_executor(lambda: handle_spam_classifier_response, name="handle_spam")
|
||||
WorkflowBuilder(start_executor=spam_detection_agent)
|
||||
# Not spam path: transform response -> request for assistant -> assistant -> send email
|
||||
.add_edge("spam_detection_agent", "to_email_assistant_request", condition=get_condition(False))
|
||||
.add_edge("to_email_assistant_request", "email_assistant_agent")
|
||||
.add_edge("email_assistant_agent", "send_email")
|
||||
.add_edge(spam_detection_agent, to_email_assistant_request, condition=get_condition(False))
|
||||
.add_edge(to_email_assistant_request, email_assistant_agent)
|
||||
.add_edge(email_assistant_agent, handle_email_response)
|
||||
# Spam path: send to spam handler
|
||||
.add_edge("spam_detection_agent", "handle_spam", condition=get_condition(True))
|
||||
.add_edge(spam_detection_agent, handle_spam_classifier_response, condition=get_condition(True))
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+16
-27
@@ -9,6 +9,7 @@ from typing import Literal
|
||||
from uuid import uuid4
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
ChatAgent,
|
||||
@@ -212,6 +213,10 @@ def create_email_summary_agent() -> ChatAgent:
|
||||
|
||||
async def main() -> None:
|
||||
# Build the workflow
|
||||
email_analysis_agent = AgentExecutor(create_email_analysis_agent())
|
||||
email_assistant_agent = AgentExecutor(create_email_assistant_agent())
|
||||
email_summary_agent = AgentExecutor(create_email_summary_agent())
|
||||
|
||||
def select_targets(analysis: AnalysisResult, target_ids: list[str]) -> list[str]:
|
||||
# Order: [handle_spam, submit_to_email_assistant, summarize_email, handle_uncertain]
|
||||
handle_spam_id, submit_to_email_assistant_id, summarize_email_id, handle_uncertain_id = target_ids
|
||||
@@ -224,39 +229,23 @@ async def main() -> None:
|
||||
return targets
|
||||
return [handle_uncertain_id]
|
||||
|
||||
workflow_builder = (
|
||||
WorkflowBuilder(start_executor="store_email")
|
||||
.register_agent(create_email_analysis_agent, name="email_analysis_agent")
|
||||
.register_agent(create_email_assistant_agent, name="email_assistant_agent")
|
||||
.register_agent(create_email_summary_agent, name="email_summary_agent")
|
||||
.register_executor(lambda: store_email, name="store_email")
|
||||
.register_executor(lambda: to_analysis_result, name="to_analysis_result")
|
||||
.register_executor(lambda: submit_to_email_assistant, name="submit_to_email_assistant")
|
||||
.register_executor(lambda: finalize_and_send, name="finalize_and_send")
|
||||
.register_executor(lambda: summarize_email, name="summarize_email")
|
||||
.register_executor(lambda: merge_summary, name="merge_summary")
|
||||
.register_executor(lambda: handle_spam, name="handle_spam")
|
||||
.register_executor(lambda: handle_uncertain, name="handle_uncertain")
|
||||
.register_executor(lambda: database_access, name="database_access")
|
||||
)
|
||||
|
||||
workflow = (
|
||||
workflow_builder
|
||||
.add_edge("store_email", "email_analysis_agent")
|
||||
.add_edge("email_analysis_agent", "to_analysis_result")
|
||||
WorkflowBuilder(start_executor=store_email)
|
||||
.add_edge(store_email, email_analysis_agent)
|
||||
.add_edge(email_analysis_agent, to_analysis_result)
|
||||
.add_multi_selection_edge_group(
|
||||
"to_analysis_result",
|
||||
["handle_spam", "submit_to_email_assistant", "summarize_email", "handle_uncertain"],
|
||||
to_analysis_result,
|
||||
[handle_spam, submit_to_email_assistant, summarize_email, handle_uncertain],
|
||||
selection_func=select_targets,
|
||||
)
|
||||
.add_edge("submit_to_email_assistant", "email_assistant_agent")
|
||||
.add_edge("email_assistant_agent", "finalize_and_send")
|
||||
.add_edge("summarize_email", "email_summary_agent")
|
||||
.add_edge("email_summary_agent", "merge_summary")
|
||||
.add_edge(submit_to_email_assistant, email_assistant_agent)
|
||||
.add_edge(email_assistant_agent, finalize_and_send)
|
||||
.add_edge(summarize_email, email_summary_agent)
|
||||
.add_edge(email_summary_agent, merge_summary)
|
||||
# Save to DB if short (no summary path)
|
||||
.add_edge("to_analysis_result", "database_access", condition=lambda r: r.email_length <= LONG_EMAIL_THRESHOLD)
|
||||
.add_edge(to_analysis_result, database_access, condition=lambda r: r.email_length <= LONG_EMAIL_THRESHOLD)
|
||||
# Save to DB with summary when long
|
||||
.add_edge("merge_summary", "database_access")
|
||||
.add_edge(merge_summary, database_access)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -62,11 +62,12 @@ async def main() -> None:
|
||||
"""Build a two step sequential workflow and run it with streaming to observe events."""
|
||||
# Step 1: Build the workflow graph.
|
||||
# Order matters. We connect upper_case_executor -> reverse_text_executor and set the start.
|
||||
upper_case_executor = UpperCaseExecutor(id="upper_case_executor")
|
||||
reverse_text_executor = ReverseTextExecutor(id="reverse_text_executor")
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="upper_case_executor")
|
||||
.register_executor(lambda: UpperCaseExecutor(id="upper_case_executor"), name="upper_case_executor")
|
||||
.register_executor(lambda: ReverseTextExecutor(id="reverse_text_executor"), name="reverse_text_executor")
|
||||
.add_edge("upper_case_executor", "reverse_text_executor")
|
||||
WorkflowBuilder(start_executor=upper_case_executor)
|
||||
.add_edge(upper_case_executor, reverse_text_executor)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -56,10 +56,8 @@ async def main():
|
||||
# Step 1: Build the workflow with the defined edges.
|
||||
# Order matters. upper_case_executor runs first, then reverse_text_executor.
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="upper_case_executor")
|
||||
.register_executor(lambda: to_upper_case, name="upper_case_executor")
|
||||
.register_executor(lambda: reverse_text, name="reverse_text_executor")
|
||||
.add_edge("upper_case_executor", "reverse_text_executor")
|
||||
WorkflowBuilder(start_executor=to_upper_case)
|
||||
.add_edge(to_upper_case, reverse_text)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import asyncio
|
||||
from enum import Enum
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
ChatAgent,
|
||||
@@ -125,16 +126,17 @@ async def main():
|
||||
"""Main function to run the workflow."""
|
||||
# Step 1: Build the workflow with the defined edges.
|
||||
# This time we are creating a loop in the workflow.
|
||||
guess_number = GuessNumberExecutor((1, 100), "guess_number")
|
||||
judge_agent = AgentExecutor(create_judge_agent())
|
||||
submit_judge = SubmitToJudgeAgent(judge_agent_id="judge_agent", target=30)
|
||||
parse_judge = ParseJudgeResponse(id="parse_judge")
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="guess_number")
|
||||
.register_executor(lambda: GuessNumberExecutor((1, 100), "guess_number"), name="guess_number")
|
||||
.register_agent(create_judge_agent, name="judge_agent")
|
||||
.register_executor(lambda: SubmitToJudgeAgent(judge_agent_id="judge_agent", target=30), name="submit_judge")
|
||||
.register_executor(lambda: ParseJudgeResponse(id="parse_judge"), name="parse_judge")
|
||||
.add_edge("guess_number", "submit_judge")
|
||||
.add_edge("submit_judge", "judge_agent")
|
||||
.add_edge("judge_agent", "parse_judge")
|
||||
.add_edge("parse_judge", "guess_number")
|
||||
WorkflowBuilder(start_executor=guess_number)
|
||||
.add_edge(guess_number, submit_judge)
|
||||
.add_edge(submit_judge, judge_agent)
|
||||
.add_edge(judge_agent, parse_judge)
|
||||
.add_edge(parse_judge, guess_number)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Literal
|
||||
from uuid import uuid4
|
||||
|
||||
from agent_framework import ( # Core chat primitives used to form LLM requests
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest, # Message bundle sent to an AgentExecutor
|
||||
AgentExecutorResponse, # Result returned by an AgentExecutor
|
||||
Case,
|
||||
@@ -178,28 +179,23 @@ async def main():
|
||||
"""Main function to run the workflow."""
|
||||
# Build workflow: store -> detection agent -> to_detection_result -> switch (NotSpam or Spam or Default).
|
||||
# The switch-case group evaluates cases in order, then falls back to Default when none match.
|
||||
spam_detection_agent = AgentExecutor(create_spam_detection_agent())
|
||||
email_assistant_agent = AgentExecutor(create_email_assistant_agent())
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="store_email")
|
||||
.register_agent(create_spam_detection_agent, name="spam_detection_agent")
|
||||
.register_agent(create_email_assistant_agent, name="email_assistant_agent")
|
||||
.register_executor(lambda: store_email, name="store_email")
|
||||
.register_executor(lambda: to_detection_result, name="to_detection_result")
|
||||
.register_executor(lambda: submit_to_email_assistant, name="submit_to_email_assistant")
|
||||
.register_executor(lambda: finalize_and_send, name="finalize_and_send")
|
||||
.register_executor(lambda: handle_spam, name="handle_spam")
|
||||
.register_executor(lambda: handle_uncertain, name="handle_uncertain")
|
||||
.add_edge("store_email", "spam_detection_agent")
|
||||
.add_edge("spam_detection_agent", "to_detection_result")
|
||||
WorkflowBuilder(start_executor=store_email)
|
||||
.add_edge(store_email, spam_detection_agent)
|
||||
.add_edge(spam_detection_agent, to_detection_result)
|
||||
.add_switch_case_edge_group(
|
||||
"to_detection_result",
|
||||
to_detection_result,
|
||||
[
|
||||
Case(condition=get_case("NotSpam"), target="submit_to_email_assistant"),
|
||||
Case(condition=get_case("Spam"), target="handle_spam"),
|
||||
Default(target="handle_uncertain"),
|
||||
Case(condition=get_case("NotSpam"), target=submit_to_email_assistant),
|
||||
Case(condition=get_case("Spam"), target=handle_spam),
|
||||
Default(target=handle_uncertain),
|
||||
],
|
||||
)
|
||||
.add_edge("submit_to_email_assistant", "email_assistant_agent")
|
||||
.add_edge("email_assistant_agent", "finalize_and_send")
|
||||
.add_edge(submit_to_email_assistant, email_assistant_agent)
|
||||
.add_edge(email_assistant_agent, finalize_and_send)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -51,12 +51,9 @@ async def step3(text: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
def build_workflow():
|
||||
"""Build a simple 3-step sequential workflow (~6 seconds total)."""
|
||||
return (
|
||||
WorkflowBuilder(start_executor="step1")
|
||||
.register_executor(lambda: step1, name="step1")
|
||||
.register_executor(lambda: step2, name="step2")
|
||||
.register_executor(lambda: step3, name="step3")
|
||||
.add_edge("step1", "step2")
|
||||
.add_edge("step2", "step3")
|
||||
WorkflowBuilder(start_executor=step1)
|
||||
.add_edge(step1, step2)
|
||||
.add_edge(step2, step3)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+8
-7
@@ -72,14 +72,15 @@ class Aggregator(Executor):
|
||||
|
||||
async def main() -> None:
|
||||
# 1) Build a simple fan out and fan in workflow
|
||||
dispatcher = Dispatcher(id="dispatcher")
|
||||
average = Average(id="average")
|
||||
summation = Sum(id="summation")
|
||||
aggregator = Aggregator(id="aggregator")
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="dispatcher")
|
||||
.register_executor(lambda: Dispatcher(id="dispatcher"), name="dispatcher")
|
||||
.register_executor(lambda: Average(id="average"), name="average")
|
||||
.register_executor(lambda: Sum(id="summation"), name="summation")
|
||||
.register_executor(lambda: Aggregator(id="aggregator"), name="aggregator")
|
||||
.add_fan_out_edges("dispatcher", ["average", "summation"])
|
||||
.add_fan_in_edges(["average", "summation"], "aggregator")
|
||||
WorkflowBuilder(start_executor=dispatcher)
|
||||
.add_fan_out_edges(dispatcher, [average, summation])
|
||||
.add_fan_in_edges([average, summation], aggregator)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor, # Wraps a ChatAgent as an Executor for use in workflows
|
||||
AgentExecutorRequest, # The message bundle sent to an AgentExecutor
|
||||
AgentExecutorResponse, # The structured result returned by an AgentExecutor
|
||||
ChatAgent, # Tracing event for agent execution steps
|
||||
ChatMessage, # Chat message structure
|
||||
Executor, # Base class for custom Python executors
|
||||
WorkflowBuilder, # Fluent builder for wiring the workflow graph
|
||||
@@ -87,50 +87,44 @@ class AggregateInsights(Executor):
|
||||
await ctx.yield_output(consolidated)
|
||||
|
||||
|
||||
def create_researcher_agent() -> ChatAgent:
|
||||
"""Creates a research domain expert agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_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()).as_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/compliance domain expert agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_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) Build a simple fan out and fan in workflow
|
||||
# 1) Create executor and agent instances
|
||||
dispatcher = DispatchToExperts(id="dispatcher")
|
||||
aggregator = AggregateInsights(id="aggregator")
|
||||
|
||||
researcher = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
||||
" opportunities, and risks."
|
||||
),
|
||||
name="researcher",
|
||||
)
|
||||
)
|
||||
marketer = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
" aligned to the prompt."
|
||||
),
|
||||
name="marketer",
|
||||
)
|
||||
)
|
||||
legal = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
" based on the prompt."
|
||||
),
|
||||
name="legal",
|
||||
)
|
||||
)
|
||||
|
||||
# 2) Build a simple fan out and fan in workflow
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="dispatcher")
|
||||
.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")
|
||||
.add_fan_out_edges("dispatcher", ["researcher", "marketer", "legal"]) # Parallel branches
|
||||
.add_fan_in_edges(["researcher", "marketer", "legal"], "aggregator") # Join at the aggregator
|
||||
WorkflowBuilder(start_executor=dispatcher)
|
||||
.add_fan_out_edges(dispatcher, [researcher, marketer, legal]) # Parallel branches
|
||||
.add_fan_in_edges([researcher, marketer, legal], aggregator) # Join at the aggregator
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+21
-39
@@ -257,49 +257,31 @@ class CompletionExecutor(Executor):
|
||||
async def main():
|
||||
"""Construct the map reduce workflow, visualize it, then run it over a sample file."""
|
||||
|
||||
# Step 1: Create the workflow builder and register executors.
|
||||
workflow_builder = (
|
||||
WorkflowBuilder(start_executor="split_data_executor")
|
||||
.register_executor(lambda: Map(id="map_executor_0"), name="map_executor_0")
|
||||
.register_executor(lambda: Map(id="map_executor_1"), name="map_executor_1")
|
||||
.register_executor(lambda: Map(id="map_executor_2"), name="map_executor_2")
|
||||
.register_executor(
|
||||
lambda: Split(["map_executor_0", "map_executor_1", "map_executor_2"], id="split_data_executor"),
|
||||
name="split_data_executor",
|
||||
)
|
||||
.register_executor(lambda: Reduce(id="reduce_executor_0"), name="reduce_executor_0")
|
||||
.register_executor(lambda: Reduce(id="reduce_executor_1"), name="reduce_executor_1")
|
||||
.register_executor(lambda: Reduce(id="reduce_executor_2"), name="reduce_executor_2")
|
||||
.register_executor(lambda: Reduce(id="reduce_executor_3"), name="reduce_executor_3")
|
||||
.register_executor(
|
||||
lambda: Shuffle(
|
||||
["reduce_executor_0", "reduce_executor_1", "reduce_executor_2", "reduce_executor_3"],
|
||||
id="shuffle_executor",
|
||||
),
|
||||
name="shuffle_executor",
|
||||
)
|
||||
.register_executor(lambda: CompletionExecutor(id="completion_executor"), name="completion_executor")
|
||||
# Step 1: Create executor instances.
|
||||
map_executor_0 = Map(id="map_executor_0")
|
||||
map_executor_1 = Map(id="map_executor_1")
|
||||
map_executor_2 = Map(id="map_executor_2")
|
||||
split_data_executor = Split(["map_executor_0", "map_executor_1", "map_executor_2"], id="split_data_executor")
|
||||
reduce_executor_0 = Reduce(id="reduce_executor_0")
|
||||
reduce_executor_1 = Reduce(id="reduce_executor_1")
|
||||
reduce_executor_2 = Reduce(id="reduce_executor_2")
|
||||
reduce_executor_3 = Reduce(id="reduce_executor_3")
|
||||
shuffle_executor = Shuffle(
|
||||
["reduce_executor_0", "reduce_executor_1", "reduce_executor_2", "reduce_executor_3"],
|
||||
id="shuffle_executor",
|
||||
)
|
||||
completion_executor = CompletionExecutor(id="completion_executor")
|
||||
|
||||
mappers = [map_executor_0, map_executor_1, map_executor_2]
|
||||
reducers = [reduce_executor_0, reduce_executor_1, reduce_executor_2, reduce_executor_3]
|
||||
|
||||
# Step 2: Build the workflow graph using fan out and fan in edges.
|
||||
workflow = (
|
||||
workflow_builder
|
||||
.add_fan_out_edges(
|
||||
"split_data_executor",
|
||||
["map_executor_0", "map_executor_1", "map_executor_2"],
|
||||
) # Split -> many mappers
|
||||
.add_fan_in_edges(
|
||||
["map_executor_0", "map_executor_1", "map_executor_2"],
|
||||
"shuffle_executor",
|
||||
) # All mappers -> shuffle
|
||||
.add_fan_out_edges(
|
||||
"shuffle_executor",
|
||||
["reduce_executor_0", "reduce_executor_1", "reduce_executor_2", "reduce_executor_3"],
|
||||
) # Shuffle -> many reducers
|
||||
.add_fan_in_edges(
|
||||
["reduce_executor_0", "reduce_executor_1", "reduce_executor_2", "reduce_executor_3"],
|
||||
"completion_executor",
|
||||
) # All reducers -> completion
|
||||
WorkflowBuilder(start_executor=split_data_executor)
|
||||
.add_fan_out_edges(split_data_executor, mappers) # Split -> many mappers
|
||||
.add_fan_in_edges(mappers, shuffle_executor) # All mappers -> shuffle
|
||||
.add_fan_out_edges(shuffle_executor, reducers) # Shuffle -> many reducers
|
||||
.add_fan_in_edges(reducers, completion_executor) # All reducers -> completion
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
@@ -188,21 +188,17 @@ async def main() -> None:
|
||||
# store_email -> spam_detection_agent -> to_detection_result -> branch:
|
||||
# False -> submit_to_email_assistant -> email_assistant_agent -> finalize_and_send
|
||||
# True -> handle_spam
|
||||
spam_detection_agent = create_spam_detection_agent()
|
||||
email_assistant_agent = create_email_assistant_agent()
|
||||
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="store_email")
|
||||
.register_agent(create_spam_detection_agent, name="spam_detection_agent")
|
||||
.register_agent(create_email_assistant_agent, name="email_assistant_agent")
|
||||
.register_executor(lambda: store_email, name="store_email")
|
||||
.register_executor(lambda: to_detection_result, name="to_detection_result")
|
||||
.register_executor(lambda: submit_to_email_assistant, name="submit_to_email_assistant")
|
||||
.register_executor(lambda: finalize_and_send, name="finalize_and_send")
|
||||
.register_executor(lambda: handle_spam, name="handle_spam")
|
||||
.add_edge("store_email", "spam_detection_agent")
|
||||
.add_edge("spam_detection_agent", "to_detection_result")
|
||||
.add_edge("to_detection_result", "submit_to_email_assistant", condition=get_condition(False))
|
||||
.add_edge("to_detection_result", "handle_spam", condition=get_condition(True))
|
||||
.add_edge("submit_to_email_assistant", "email_assistant_agent")
|
||||
.add_edge("email_assistant_agent", "finalize_and_send")
|
||||
WorkflowBuilder(start_executor=store_email)
|
||||
.add_edge(store_email, spam_detection_agent)
|
||||
.add_edge(spam_detection_agent, to_detection_result)
|
||||
.add_edge(to_detection_result, submit_to_email_assistant, condition=get_condition(False))
|
||||
.add_edge(to_detection_result, handle_spam, condition=get_condition(True))
|
||||
.add_edge(submit_to_email_assistant, email_assistant_agent)
|
||||
.add_edge(email_assistant_agent, finalize_and_send)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
+39
-42
@@ -4,9 +4,9 @@ import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agent_framework import (
|
||||
AgentExecutor,
|
||||
AgentExecutorRequest,
|
||||
AgentExecutorResponse,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
Executor,
|
||||
WorkflowBuilder,
|
||||
@@ -85,52 +85,49 @@ class AggregateInsights(Executor):
|
||||
await ctx.yield_output(consolidated)
|
||||
|
||||
|
||||
def create_researcher_agent() -> ChatAgent:
|
||||
"""Creates a research domain expert agent."""
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_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()).as_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()).as_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:
|
||||
"""Build and run the concurrent workflow with visualization."""
|
||||
|
||||
# Create agent instances
|
||||
researcher = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're an expert market and product researcher. Given a prompt, provide concise, factual insights,"
|
||||
" opportunities, and risks."
|
||||
),
|
||||
name="researcher",
|
||||
)
|
||||
)
|
||||
|
||||
marketer = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a creative marketing strategist. Craft compelling value propositions and target messaging"
|
||||
" aligned to the prompt."
|
||||
),
|
||||
name="marketer",
|
||||
)
|
||||
)
|
||||
|
||||
legal = AgentExecutor(
|
||||
AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
instructions=(
|
||||
"You're a cautious legal/compliance reviewer. Highlight constraints, disclaimers, and policy concerns"
|
||||
" based on the prompt."
|
||||
),
|
||||
name="legal",
|
||||
)
|
||||
)
|
||||
|
||||
# Create executor instances
|
||||
dispatcher = DispatchToExperts(id="dispatcher")
|
||||
aggregator = AggregateInsights(id="aggregator")
|
||||
|
||||
# Build a simple fan-out/fan-in workflow
|
||||
workflow = (
|
||||
WorkflowBuilder(start_executor="dispatcher")
|
||||
.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")
|
||||
.add_fan_out_edges("dispatcher", ["researcher", "marketer", "legal"])
|
||||
.add_fan_in_edges(["researcher", "marketer", "legal"], "aggregator")
|
||||
WorkflowBuilder(start_executor=dispatcher)
|
||||
.add_fan_out_edges(dispatcher, [researcher, marketer, legal])
|
||||
.add_fan_in_edges([researcher, marketer, legal], aggregator)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user