How to Build a Manager-Workers of Agents#
Prerequisites
This guide assumes familiarity with Agents.
With the advent of increasingly powerful Large Language Models (LLMs), multi-agent systems are becoming more relevant and are expected to be particularly valuable in scenarios requiring high-levels of autonomy and/or processing of diverse sources of information.
There are various types of multi-agent systems, each serving different purposes and applications. Some notable examples include hierarchical structures, agent swarms, and mixtures of agents.
This guide demonstrates an example of a hierarchical multi-agent system (also known as manager-workers pattern) and will show you how to:
Build expert agents equipped with tools and an manager agent;
Test the expert agents individually;
Build a ManagerWorkers using the defined agents;
Execute the ManagerWorkers of agents;
Diagram: Multi-agent system shown in this how-to guide, comprising a manager agent (customer service agent) and two expert agents equipped with tools (refund specialist and satisfaction surveyor).
See also
To access short code snippets demonstrating how to use other agentic patterns in WayFlow, refer to the Reference Sheet.
To follow this guide, you need an LLM. WayFlow supports several LLM API providers. Select an LLM from the options below:
from wayflowcore.models import OCIGenAIModel
if __name__ == "__main__":
llm = OCIGenAIModel(
model_id="provider.model-id",
service_endpoint="https://url-to-service-endpoint.com",
compartment_id="compartment-id",
auth_type="API_KEY",
)
from wayflowcore.models import VllmModel
llm = VllmModel(
model_id="model-id",
host_port="VLLM_HOST_PORT",
)
from wayflowcore.models import OllamaModel
llm = OllamaModel(
model_id="model-id",
)
Building and testing expert Agents#
In this guide you will use the following helper function to print messages:
def print_messages(messages):
from wayflowcore.messagelist import MessageType
for message in messages:
message_type = message.message_type
prefix = (
f"{message_type}"
if message_type == MessageType.USER
else f"{message_type} ({message.sender})"
)
content = (
f"{message.content}"
if message_type != MessageType.TOOL_REQUEST
else f"{message.content.strip()}\n{message.tool_requests}"
)
print(f"{prefix} >>> {content}")
API Reference: MessageType
Refund specialist agent#
The refund specialist agent is equipped with two tools.
from wayflowcore.tools import tool
@tool
def check_refund_eligibility(
order_id: Annotated[str, "The unique identifier for the order."],
customer_id: Annotated[str, "The unique identifier for the customer."],
) -> Dict[str, Union[bool, float, str]]:
"""
Checks if a given order is eligible for a refund based on company policy.
Returns:
A dictionary containing eligibility status and details.
Example: {"eligible": True, "max_refundable_amount": 50.00, "reason": "Within return window"}
{"eligible": False, "reason": "Order past 30-day return window"}
"""
# Simulate checking eligibility (e.g., database lookup, policy check)
# In a real system, this would interact with backend systems.
if "123" in order_id: # Simulate eligible order
return {
"eligible": True,
"max_refundable_amount": 50.00,
"reason": "Within 30-day return window and item is returnable.",
}
elif "999" in order_id: # Simulate ineligible order
return {"eligible": False, "reason": "Order past 30-day return window."}
else: # Simulate item not found or other issue
return {"eligible": False, "reason": "Order ID not found or invalid."}
@tool
def process_refund(
order_id: Annotated[str, "The unique identifier for the order to be refunded."],
amount: Annotated[float, "The amount to be refunded."],
reason: Annotated[str, "The reason for the refund."],
) -> Dict[str, Union[bool, str]]:
"""
Processes a refund for a specific order and amount.
Returns:
A dictionary confirming the refund status.
Example: {"success": True, "refund_id": "REF_789XYZ", "message": "Refund processed successfully."}
{"success": False, "message": "Refund processing failed due to payment gateway error."}
"""
# Simulate refund processing (e.g., calling a payment gateway API)
# In a real system, this would trigger financial transactions.
if float(amount) > 0:
refund_id = f"REF_{order_id[:3]}{int(amount * 100)}" # Generate a pseudo-unique ID
return {
"success": True,
"refund_id": refund_id,
"message": f"Refund of ${amount:.2f} processed successfully.",
}
else:
return {"success": False, "message": "Refund amount must be greater than zero."}
API Reference: tool
The first tool is used to check whether a given order is eligible for a refund, while the second is used to process the specific refund.
System prompt#
REFUND_SPECIALIST_SYSTEM_PROMPT = """
You are a Refund Specialist agent whose objective is to process customer refund requests accurately and efficiently based on company policy.
# Instructions
- Receive the refund request details (e.g., order ID, customer ID, reason) from the 'CustomerServiceManager'.
- Use the `check_refund_eligibility` tool to verify if the request meets the refund policy criteria using the provided order and customer IDs.
- If the check indicates eligibility, determine the correct refund amount (up to the maximum allowed from the eligibility check).
- If eligible, use the `process_refund` tool to execute the refund for the determined amount, providing order ID and reason.
- If ineligible based on the check, clearly note the reason provided by the tool.
- Report the final outcome (e.g., "Refund processed successfully, Refund ID: [ID], Amount: [Amount]", or "Refund denied: [Reason from eligibility check]") back to the 'CustomerServiceManager'.
- Do not engage in general conversation; focus solely on the refund process.
""".strip()
Important
The quality of the system prompt is paramount to ensuring proper behaviour of the multi-agent system, because slight deviations in the behaviour can lead to cascading unintended effects as the number of agents scales up.
Building the Agent#
from wayflowcore.agent import Agent
refund_specialist_agent = Agent(
name="RefundSpecialist",
description="Specializes in processing customer refund requests by verifying eligibility and executing the refund transaction using available tools.",
llm=llm,
custom_instruction=REFUND_SPECIALIST_SYSTEM_PROMPT,
tools=[check_refund_eligibility, process_refund],
agent_id="RefundSpecialist", # for the `print_messages` utility function
)
API Reference: Agent
Testing the Agent#
refund_conversation = refund_specialist_agent.start_conversation()
refund_conversation.append_user_message(
"Please handle a refund request. Details: Order ID='123', Customer ID='CUST456', Reason='Item arrived damaged'."
# "Please handle a refund request. Details: Order ID='999', Customer ID='CUST789', Reason='No longer needed'."
# "Please handle a refund request. Details: Order ID='INVALID_ID', Customer ID='CUST101', Reason='Item defective'."
)
refund_conversation.execute()
print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
print_messages(refund_conversation.get_messages())
# USER >>> Please handle a refund request. Details: Order ID='123', Customer ID='CUST456', Reason='Item arrived damaged'.
# TOOL_REQUEST (RefundSpecialist) >>> To process the refund request, we first need to check if the order is eligible for a refund based on company policy.
# [ToolRequest(name='check_refund_eligibility', args={'order_id': '123', 'customer_id': 'CUST456'}, tool_request_id='42d8f215-e80e-4426-b8d1-9c18d6d8059e')]
# TOOL_RESULT (RefundSpecialist) >>> {'eligible': True, 'max_refundable_amount': 50.0, 'reason': 'Within 30-day return window and item is returnable.'}
# TOOL_REQUEST (RefundSpecialist) >>> The order is eligible for a refund. Now, we need to process the refund for the maximum refundable amount.
# [ToolRequest(name='process_refund', args={'order_id': '123', 'amount': 50.0, 'reason': 'Item arrived damaged'}, tool_request_id='6b8f07eb-2caa-4551-9ea3-dcd2b53916b8')]
# TOOL_RESULT (RefundSpecialist) >>> {'success': True, 'refund_id': 'REF_1235000', 'message': 'Refund of $50.00 processed successfully.'}
# AGENT (RefundSpecialist) >>> Refund processed successfully. Refund ID: REF_1235000, Amount: $50.00
Test the agents individually to ensure they perform as expected.
Statisfaction surveyor agent#
Tools#
The statisfaction surveyor agent is equipped with one tool.
@tool
def record_survey_response(
customer_id: Annotated[str, "The unique identifier for the customer."],
satisfaction_score: Annotated[
Optional[int], "The customer's satisfaction rating (e.g., 1-5), if provided."
] = None,
comments: Annotated[
Optional[str], "Any additional comments provided by the customer, if provided."
] = None,
) -> Dict[str, Union[bool, str]]:
"""
Records the customer's satisfaction survey response.
Returns:
A dictionary confirming the recording status.
Example: {"success": True, "message": "Survey response recorded."}
{"success": False, "message": "Failed to record survey response."}
"""
# Simulate storing the response (e.g., writing to a database or logging system)
# In a real system, this would persist the feedback data.
if customer_id:
return {"success": True, "message": "Survey response recorded successfully."}
else:
return {"success": False, "message": "Customer ID is required to record response."}
The record_survey_response
tool is simulating the recording of
user feedback data.
System prompt#
SURVEYOR_SYSTEM_PROMPT = """
You are a Satisfaction Surveyor agent tasked with collecting customer feedback about their recent service experience in a friendly manner.
# Instructions
- Receive the trigger to conduct a survey from the 'CustomerServiceManager', including context like the customer ID and the nature of the interaction if provided.
- Politely ask the customer if they have a moment to provide feedback on their recent interaction.
- If the customer agrees, ask 1-2 concise questions about their satisfaction (e.g., "On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?", "Is there anything else you'd like to share about your experience?").
- Use the `record_survey_response` tool to log the customer's feedback, including the satisfaction score and any comments provided. Ensure you pass the correct customer ID.
- If the customer declines to participate, thank them for their time anyway. Do not pressure them. Use the `record_survey_response` tool to log the declination if possible (e.g., score=None, comments="Declined survey").
- Thank the customer for their participation if they provided feedback.
- Report back to the 'CustomerServiceManager' confirming that the survey was attempted and whether it was completed or declined.
""".strip()
Building the Agent#
surveyor_agent = Agent(
name="SatisfactionSurveyor",
description="Conducts brief surveys to gather feedback on customer satisfaction following service interactions.",
llm=llm,
custom_instruction=SURVEYOR_SYSTEM_PROMPT,
tools=[record_survey_response],
agent_id="SatisfactionSurveyor", # for the `print_messages` utility function
)
Testing the Agent#
surveyor_conversation = surveyor_agent.start_conversation()
surveyor_conversation.append_user_message(
"Engage customer for feedback. Details: Customer ID='CUST456', Context='Recent successful refund'."
)
surveyor_conversation.execute()
print_messages([surveyor_conversation.get_last_message()])
# AGENT (SatisfactionSurveyor) >>> Hi there, I'm reaching out on behalf of our Customer Service team. We're glad to hear that your recent refund was successful. If you have a moment, we'd greatly appreciate any feedback you can share about your experience. It will help us improve our services. Would you be willing to answer a couple of quick questions?
surveyor_conversation.append_user_message("yes")
surveyor_conversation.execute()
print_messages([surveyor_conversation.get_last_message()])
# AGENT (SatisfactionSurveyor) >>> On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?
surveyor_conversation.append_user_message("5")
surveyor_conversation.execute()
print_messages([surveyor_conversation.get_last_message()])
# AGENT (SatisfactionSurveyor) >>> Is there anything else you'd like to share about your experience?
surveyor_conversation.append_user_message("Very quick!")
surveyor_conversation.execute()
print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
print_messages(surveyor_conversation.get_messages())
# USER >>> Engage customer for feedback. Details: Customer ID='CUST456', Context='Recent successful refund'.
# AGENT (SatisfactionSurveyor) >>> Hi there, I'm reaching out on behalf of our Customer Service team. We're glad to hear that your recent refund was successful. If you have a moment, we'd greatly appreciate any feedback you can share about your experience. It will help us improve our services. Would you be willing to answer a couple of quick questions?
# USER >>> yes
# AGENT (SatisfactionSurveyor) >>> On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?
# USER >>> 5
# AGENT (SatisfactionSurveyor) >>> Is there anything else you'd like to share about your experience?
# USER >>> Very quick!
# TOOL_REQUEST (SatisfactionSurveyor) >>> Now that we have the customer's feedback, we can record it using the record_survey_response tool.
# [ToolRequest(name='record_survey_response', args={'customer_id': 'CUST456', 'satisfaction_score': 5, 'comments': 'Very quick!'}, tool_request_id='3d7ad387-13b0-465c-a425-42b50b7cfdd4')]
# TOOL_RESULT (SatisfactionSurveyor) >>> {'success': True, 'message': 'Survey response recorded successfully.'}
# TOOL_REQUEST (SatisfactionSurveyor) >>> Now that we have the customer's feedback, we can record it using the record_survey_response tool.
# [ToolRequest(name='record_survey_response', args={'customer_id': 'CUST456', 'satisfaction_score': 5, 'comments': 'Very quick!'}, tool_request_id='e1d00a28-ecbe-4c6f-91fc-a75c13e2e467')]
# TOOL_RESULT (SatisfactionSurveyor) >>> {'success': True, 'message': 'Survey response recorded successfully.'}
# AGENT (SatisfactionSurveyor) >>> Thank you so much for taking the time to share your feedback with us! Your input is invaluable in helping us improve our services. Have a great day!
Again, the expert agent behaves as intended.
Manager Agent#
In the our built-in ManagerWorkers component, we allow passing an Agent as the group manager. Therefore, we just need to define an agent as usual.
In this example, our manager agent will be a Customer Service Manager.
System prompt#
MANAGER_SYSTEM_PROMPT = """
You are a Customer Service Manager agent tasked with handling incoming customer interactions and orchestrating the resolution process efficiently.
# Instructions
- Greet the customer politely and acknowledge their message.
- Analyze the customer's message to understand their core need (e.g., refund request, general query, feedback).
- Answer common informational questions (e.g., about shipping times, return policy basics) directly if you have the knowledge, before delegating.
- If the request is clearly about a refund, gather necessary details (like Order ID) if missing, and then assign the task to the 'RefundSpecialist' agent. Provide all relevant context.
- If the interaction seems successfully concluded (e.g., refund processed, query answered) and requesting feedback is appropriate, assign the task to the 'SatisfactionSurveyor' agent. Provide customer context.
- For general queries you cannot handle directly and that don't fit the specialist agents, state your limitations clearly and politely.
- Await responses or status updates from specialist agents you have assigned to.
- Summarize the final outcome or confirmation for the customer based on specialist agent reports.
- Maintain a helpful, empathetic, and professional tone throughout the interaction.
# Additional Context
Customer ID: {{customer_id}}
Company policies: {{company_policy_info}}
""".strip()
Building the manager Agent#
customer_service_manager = Agent(
name="CustomerServiceManager",
description="Acts as the primary contact point for customer inquiries, analyzes the request, routes tasks to specialized agents (Refund Specialist, Satisfaction Surveyor), and ensures resolution.",
llm=llm,
custom_instruction=MANAGER_SYSTEM_PROMPT,
agent_id="CustomerServiceManager", # for the `print_messages` utility function
)
Building and testing ManagerWorkers of Agents#
Building the ManagerWorkers of Agents#
from wayflowcore.managerworkers import ManagerWorkers
group = ManagerWorkers(
group_manager=customer_service_manager,
workers=[refund_specialist_agent, surveyor_agent],
)
API Reference: ManagerWorkers
The ManagerWorkers has two main parameters:
group_manager
This can be either an Agent or an LLM.If an LLM is provided, a manager agent will automatically be created using that LLM along with the default
custom_instruction
for group managers.In this example, we explicitly pass an Agent (the Customer Service Manager Agent) so we can use our own defined
custom_instruction
.
workers
- List of Agents These agents serve as the workers within the group and are coordinated by the manager agent.Worker agents cannot interact with the end user directly.
When invoked, each worker can leverage its equipped tools to complete the assigned task and report the result back to the group manager.
Executing the ManagerWorkers#
The power of mult-agent systems is their high adaptiveness. In the following example, it is demonstrated how the manager can decide not to call the expert agents for simple user queries.
main_conversation = group.start_conversation(
inputs={
"customer_id": "CUST456",
"company_policy_info": "Shipping times: 3-5 business days in the US",
}
)
main_conversation.append_user_message(
"Hi, I was wondering what your standard shipping times are for orders within the US?"
)
main_conversation.execute()
"""
last_message = main_conversation.get_last_message()
print(f"{last_message.message_type} >>> {last_message.content or last_message.tool_requests}")
# AGENT >>> Hello! Thank you for reaching out to us. Our standard shipping times for orders within the US are 3-5 business days. Is there anything else I can help you with?
main_conversation.append_user_message("Okay, thanks! Does that include weekends?")
main_conversation.execute()
last_message = main_conversation.get_last_message()
print(f"{last_message.message_type} >>> {last_message.content or last_message.tool_requests}")
# AGENT >>> Our standard shipping times of 3-5 business days are for weekdays only (Monday through Friday). Weekends and holidays are not included in that timeframe. If you have any other questions or need further assistance, feel free to ask!
print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
print_messages(main_conversation.get_messages())
# USER >>> Hi, I was wondering what your standard shipping times are for orders within the US?
# AGENT >>> Hello! Thank you for reaching out to us. Our standard shipping times for orders within the US are 3-5 business days. Is there anything else I can help you with?
# USER >>> Okay, thanks! Does that include weekends?
# AGENT >>> Our standard shipping times of 3-5 business days are for weekdays only (Monday through Friday). Weekends and holidays are not included in that timeframe. If you have any other questions or need further assistance, feel free to ask!
"""
However, the manager is explicitly prompted to assign to the specialized agents for more complex tasks. This is demonstrated in the following example.
main_conversation = group.start_conversation(inputs={
"customer_id": "CUST456",
"company_policy_info": "Shipping times: 3-5 business days in the US"
})
main_conversation.execute()
"""
print_messages([main_conversation.get_last_message()])
main_conversation.append_user_message("Thank you")
main_conversation.execute()
print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
print_messages(main_conversation.get_messages())
# ------------------------------
# FULL CONVERSATION:
# ------------------------------
# USER >>> Hi, I need to request a refund for order #123. The item wasn't what I expected.
# TOOL_REQUEST (CustomerServiceManager) >>> The user is requesting a refund for order #123 because the item did not meet their expectations. I will forward this request to the Refund Specialist to verify the eligibility and process the refund accordingly.
# [ToolRequest(name='send_message', args={'message': 'A customer is requesting a refund for order #123 due to the item not meeting their expectations. Please verify the eligibility for the refund and proceed with the necessary steps.', 'recipient': 'RefundSpecialist'}, tool_request_id='19c3cad0-683f-4aa5-9047-57f446405932')]
# TOOL_RESULT (None) >>> The refund for order #123 has been processed successfully. The refund amount is $50.00. Refund ID: REF_789XYZ
# AGENT (CustomerServiceManager) >>> Your refund for order #123 has been processed successfully. The refund amount of $50.00 has been issued to you. If there's anything else you need, please feel free to ask!
# USER >>> Thank you
# TOOL_REQUEST (CustomerServiceManager) >>> --- MESSAGE: From: CustomerServiceManager ---
# I will now initiate a brief satisfaction survey with the user regarding their refund experience to ensure complete service quality.
# [ToolRequest(name='send_message', args={'message': 'Please conduct a brief satisfaction survey with the customer regarding their recent refund experience for order #123.', 'recipient': 'SatisfactionSurveyor'}, tool_request_id='b2368dd2-4bab-447d-9bf3-a6483b403beb')]
# TOOL_RESULT (None) >>> Hi there, I hope you’re doing well. My name is John, and I’m a Satisfaction Surveyor. I’m reaching out because you recently had a refund experience with us for order #123. I wanted to take a moment to thank you for being our customer, and I was wondering if you might have a moment to provide some feedback on that experience. Would you be willing to answer a couple of quick questions about your recent interaction?
# AGENT (CustomerServiceManager) >>> Hi there, I hope you’re doing well. My name is John, and I’m a Satisfaction Surveyor. I’m reaching out because you recently had a refund experience with us for order #123. I wanted to take a moment to thank you for being our customer, and I was wondering if you might have a moment to provide some feedback on that experience. Would you be willing to answer a couple of quick questions about your recent interaction?
# ... can continue similarly to the example above with the satisfaction surveyor
"""
Agent Spec Exporting/Loading#
You can export the assistant configuration to its Agent Spec configuration using the AgentSpecExporter
and the ManagerWorkers plugin. Using the plugin ensures the proper serialization of
the custom ManagerWorkers component.
from wayflowcore.agentspec import AgentSpecExporter
serialized_group = AgentSpecExporter().to_yaml(group)
And load it back using the AgentSpecLoader
.
Similar to the serialization, use the Managerworkers deserialization plugin to ensure the proper deserialization of the custom ManagerWorkers component.
from wayflowcore.agentspec import AgentSpecLoader
TOOL_REGISTORY = {
"record_survey_response": record_survey_response,
"check_refund_eligibility": check_refund_eligibility,
"process_refund": process_refund,
}
deserialized_group: ManagerWorkers = AgentSpecLoader(
tool_registry=TOOL_REGISTORY
).load_yaml(serialized_group)
Next steps#
Now that you have learned how to define a ManagerWorkers, you may proceed to Build a Swarm of Agents.
Full code#
Click on the card at the top of this page to download the full code for this guide or copy the code below.
1# Copyright © 2025 Oracle and/or its affiliates.
2#
3# This software is under the Universal Permissive License
4# %%[markdown]
5# Code Example - Build a ManagerWorkers of Agents
6# -----------------------------------------------
7
8# How to use:
9# Create a new Python virtual environment and install the latest WayFlow version.
10# ```bash
11# python -m venv venv-wayflowcore
12# source venv-wayflowcore/bin/activate
13# pip install --upgrade pip
14# pip install "wayflowcore==26.1"
15# ```
16
17# You can now run the script
18# 1. As a Python file:
19# ```bash
20# python howto_managerworkers.py
21# ```
22# 2. As a Notebook (in VSCode):
23# When viewing the file,
24# - press the keys Ctrl + Enter to run the selected cell
25# - or Shift + Enter to run the selected cell and move to the cell below# (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl) or Apache License
26# 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0), at your option.
27
28from typing import Annotated, Dict, Optional, Union
29
30from wayflowcore.agent import Agent
31from wayflowcore.models import VllmModel
32from wayflowcore.tools import tool
33
34llm = VllmModel(
35 model_id="model-id",
36 host_port="VLLM_HOST_PORT",
37)
38
39
40
41# %%[markdown]
42## Helper method for printing conversation messages
43
44# %%
45def print_messages(messages):
46 from wayflowcore.messagelist import MessageType
47
48 for message in messages:
49 message_type = message.message_type
50 prefix = (
51 f"{message_type}"
52 if message_type == MessageType.USER
53 else f"{message_type} ({message.sender})"
54 )
55 content = (
56 f"{message.content}"
57 if message_type != MessageType.TOOL_REQUEST
58 else f"{message.content.strip()}\n{message.tool_requests}"
59 )
60 print(f"{prefix} >>> {content}")
61
62
63
64
65# %%[markdown]
66## Specialist tools
67
68# %%
69from wayflowcore.tools import tool
70
71@tool
72def check_refund_eligibility(
73 order_id: Annotated[str, "The unique identifier for the order."],
74 customer_id: Annotated[str, "The unique identifier for the customer."],
75) -> Dict[str, Union[bool, float, str]]:
76 """
77 Checks if a given order is eligible for a refund based on company policy.
78
79 Returns:
80 A dictionary containing eligibility status and details.
81 Example: {"eligible": True, "max_refundable_amount": 50.00, "reason": "Within return window"}
82 {"eligible": False, "reason": "Order past 30-day return window"}
83 """
84 # Simulate checking eligibility (e.g., database lookup, policy check)
85 # In a real system, this would interact with backend systems.
86 if "123" in order_id: # Simulate eligible order
87 return {
88 "eligible": True,
89 "max_refundable_amount": 50.00,
90 "reason": "Within 30-day return window and item is returnable.",
91 }
92 elif "999" in order_id: # Simulate ineligible order
93 return {"eligible": False, "reason": "Order past 30-day return window."}
94 else: # Simulate item not found or other issue
95 return {"eligible": False, "reason": "Order ID not found or invalid."}
96
97@tool
98def process_refund(
99 order_id: Annotated[str, "The unique identifier for the order to be refunded."],
100 amount: Annotated[float, "The amount to be refunded."],
101 reason: Annotated[str, "The reason for the refund."],
102) -> Dict[str, Union[bool, str]]:
103 """
104 Processes a refund for a specific order and amount.
105
106 Returns:
107 A dictionary confirming the refund status.
108 Example: {"success": True, "refund_id": "REF_789XYZ", "message": "Refund processed successfully."}
109 {"success": False, "message": "Refund processing failed due to payment gateway error."}
110 """
111 # Simulate refund processing (e.g., calling a payment gateway API)
112 # In a real system, this would trigger financial transactions.
113 if float(amount) > 0:
114 refund_id = f"REF_{order_id[:3]}{int(amount * 100)}" # Generate a pseudo-unique ID
115 return {
116 "success": True,
117 "refund_id": refund_id,
118 "message": f"Refund of ${amount:.2f} processed successfully.",
119 }
120 else:
121 return {"success": False, "message": "Refund amount must be greater than zero."}
122
123
124
125# %%[markdown]
126## Specialist prompt
127
128# %%
129REFUND_SPECIALIST_SYSTEM_PROMPT = """
130You are a Refund Specialist agent whose objective is to process customer refund requests accurately and efficiently based on company policy.
131
132# Instructions
133- Receive the refund request details (e.g., order ID, customer ID, reason) from the 'CustomerServiceManager'.
134- Use the `check_refund_eligibility` tool to verify if the request meets the refund policy criteria using the provided order and customer IDs.
135- If the check indicates eligibility, determine the correct refund amount (up to the maximum allowed from the eligibility check).
136- If eligible, use the `process_refund` tool to execute the refund for the determined amount, providing order ID and reason.
137- If ineligible based on the check, clearly note the reason provided by the tool.
138- Report the final outcome (e.g., "Refund processed successfully, Refund ID: [ID], Amount: [Amount]", or "Refund denied: [Reason from eligibility check]") back to the 'CustomerServiceManager'.
139- Do not engage in general conversation; focus solely on the refund process.
140""".strip()
141
142
143# %%[markdown]
144## Specialist agent
145
146# %%
147from wayflowcore.agent import Agent
148
149refund_specialist_agent = Agent(
150 name="RefundSpecialist",
151 description="Specializes in processing customer refund requests by verifying eligibility and executing the refund transaction using available tools.",
152 llm=llm,
153 custom_instruction=REFUND_SPECIALIST_SYSTEM_PROMPT,
154 tools=[check_refund_eligibility, process_refund],
155 agent_id="RefundSpecialist", # for the `print_messages` utility function
156)
157
158
159# %%[markdown]
160## Specialist test
161
162# %%
163refund_conversation = refund_specialist_agent.start_conversation()
164refund_conversation.append_user_message(
165 "Please handle a refund request. Details: Order ID='123', Customer ID='CUST456', Reason='Item arrived damaged'."
166 # "Please handle a refund request. Details: Order ID='999', Customer ID='CUST789', Reason='No longer needed'."
167 # "Please handle a refund request. Details: Order ID='INVALID_ID', Customer ID='CUST101', Reason='Item defective'."
168)
169refund_conversation.execute()
170print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
171print_messages(refund_conversation.get_messages())
172
173# USER >>> Please handle a refund request. Details: Order ID='123', Customer ID='CUST456', Reason='Item arrived damaged'.
174# TOOL_REQUEST (RefundSpecialist) >>> To process the refund request, we first need to check if the order is eligible for a refund based on company policy.
175# [ToolRequest(name='check_refund_eligibility', args={'order_id': '123', 'customer_id': 'CUST456'}, tool_request_id='42d8f215-e80e-4426-b8d1-9c18d6d8059e')]
176# TOOL_RESULT (RefundSpecialist) >>> {'eligible': True, 'max_refundable_amount': 50.0, 'reason': 'Within 30-day return window and item is returnable.'}
177# TOOL_REQUEST (RefundSpecialist) >>> The order is eligible for a refund. Now, we need to process the refund for the maximum refundable amount.
178# [ToolRequest(name='process_refund', args={'order_id': '123', 'amount': 50.0, 'reason': 'Item arrived damaged'}, tool_request_id='6b8f07eb-2caa-4551-9ea3-dcd2b53916b8')]
179# TOOL_RESULT (RefundSpecialist) >>> {'success': True, 'refund_id': 'REF_1235000', 'message': 'Refund of $50.00 processed successfully.'}
180# AGENT (RefundSpecialist) >>> Refund processed successfully. Refund ID: REF_1235000, Amount: $50.00
181
182
183# %%[markdown]
184## Surveyor tools
185
186# %%
187@tool
188def record_survey_response(
189 customer_id: Annotated[str, "The unique identifier for the customer."],
190 satisfaction_score: Annotated[
191 Optional[int], "The customer's satisfaction rating (e.g., 1-5), if provided."
192 ] = None,
193 comments: Annotated[
194 Optional[str], "Any additional comments provided by the customer, if provided."
195 ] = None,
196) -> Dict[str, Union[bool, str]]:
197 """
198 Records the customer's satisfaction survey response.
199
200 Returns:
201 A dictionary confirming the recording status.
202 Example: {"success": True, "message": "Survey response recorded."}
203 {"success": False, "message": "Failed to record survey response."}
204 """
205 # Simulate storing the response (e.g., writing to a database or logging system)
206 # In a real system, this would persist the feedback data.
207 if customer_id:
208 return {"success": True, "message": "Survey response recorded successfully."}
209 else:
210 return {"success": False, "message": "Customer ID is required to record response."}
211
212
213
214
215# %%[markdown]
216## Surveyor prompt
217
218# %%
219SURVEYOR_SYSTEM_PROMPT = """
220You are a Satisfaction Surveyor agent tasked with collecting customer feedback about their recent service experience in a friendly manner.
221
222# Instructions
223- Receive the trigger to conduct a survey from the 'CustomerServiceManager', including context like the customer ID and the nature of the interaction if provided.
224- Politely ask the customer if they have a moment to provide feedback on their recent interaction.
225- If the customer agrees, ask 1-2 concise questions about their satisfaction (e.g., "On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?", "Is there anything else you'd like to share about your experience?").
226- Use the `record_survey_response` tool to log the customer's feedback, including the satisfaction score and any comments provided. Ensure you pass the correct customer ID.
227- If the customer declines to participate, thank them for their time anyway. Do not pressure them. Use the `record_survey_response` tool to log the declination if possible (e.g., score=None, comments="Declined survey").
228- Thank the customer for their participation if they provided feedback.
229- Report back to the 'CustomerServiceManager' confirming that the survey was attempted and whether it was completed or declined.
230""".strip()
231
232
233# %%[markdown]
234## Surveyor agent
235
236# %%
237surveyor_agent = Agent(
238 name="SatisfactionSurveyor",
239 description="Conducts brief surveys to gather feedback on customer satisfaction following service interactions.",
240 llm=llm,
241 custom_instruction=SURVEYOR_SYSTEM_PROMPT,
242 tools=[record_survey_response],
243 agent_id="SatisfactionSurveyor", # for the `print_messages` utility function
244)
245
246
247# %%[markdown]
248## Surveyor test
249
250# %%
251surveyor_conversation = surveyor_agent.start_conversation()
252surveyor_conversation.append_user_message(
253 "Engage customer for feedback. Details: Customer ID='CUST456', Context='Recent successful refund'."
254)
255surveyor_conversation.execute()
256print_messages([surveyor_conversation.get_last_message()])
257# AGENT (SatisfactionSurveyor) >>> Hi there, I'm reaching out on behalf of our Customer Service team. We're glad to hear that your recent refund was successful. If you have a moment, we'd greatly appreciate any feedback you can share about your experience. It will help us improve our services. Would you be willing to answer a couple of quick questions?
258surveyor_conversation.append_user_message("yes")
259surveyor_conversation.execute()
260print_messages([surveyor_conversation.get_last_message()])
261# AGENT (SatisfactionSurveyor) >>> On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?
262surveyor_conversation.append_user_message("5")
263surveyor_conversation.execute()
264print_messages([surveyor_conversation.get_last_message()])
265# AGENT (SatisfactionSurveyor) >>> Is there anything else you'd like to share about your experience?
266surveyor_conversation.append_user_message("Very quick!")
267surveyor_conversation.execute()
268print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
269print_messages(surveyor_conversation.get_messages())
270# USER >>> Engage customer for feedback. Details: Customer ID='CUST456', Context='Recent successful refund'.
271# AGENT (SatisfactionSurveyor) >>> Hi there, I'm reaching out on behalf of our Customer Service team. We're glad to hear that your recent refund was successful. If you have a moment, we'd greatly appreciate any feedback you can share about your experience. It will help us improve our services. Would you be willing to answer a couple of quick questions?
272# USER >>> yes
273# AGENT (SatisfactionSurveyor) >>> On a scale of 1 to 5, where 5 is highly satisfied, how satisfied were you with the resolution provided today?
274# USER >>> 5
275# AGENT (SatisfactionSurveyor) >>> Is there anything else you'd like to share about your experience?
276# USER >>> Very quick!
277# TOOL_REQUEST (SatisfactionSurveyor) >>> Now that we have the customer's feedback, we can record it using the record_survey_response tool.
278# [ToolRequest(name='record_survey_response', args={'customer_id': 'CUST456', 'satisfaction_score': 5, 'comments': 'Very quick!'}, tool_request_id='3d7ad387-13b0-465c-a425-42b50b7cfdd4')]
279# TOOL_RESULT (SatisfactionSurveyor) >>> {'success': True, 'message': 'Survey response recorded successfully.'}
280# TOOL_REQUEST (SatisfactionSurveyor) >>> Now that we have the customer's feedback, we can record it using the record_survey_response tool.
281# [ToolRequest(name='record_survey_response', args={'customer_id': 'CUST456', 'satisfaction_score': 5, 'comments': 'Very quick!'}, tool_request_id='e1d00a28-ecbe-4c6f-91fc-a75c13e2e467')]
282# TOOL_RESULT (SatisfactionSurveyor) >>> {'success': True, 'message': 'Survey response recorded successfully.'}
283# AGENT (SatisfactionSurveyor) >>> Thank you so much for taking the time to share your feedback with us! Your input is invaluable in helping us improve our services. Have a great day!
284
285
286# %%[markdown]
287## Manager prompt
288
289# %%
290MANAGER_SYSTEM_PROMPT = """
291You are a Customer Service Manager agent tasked with handling incoming customer interactions and orchestrating the resolution process efficiently.
292
293# Instructions
294- Greet the customer politely and acknowledge their message.
295- Analyze the customer's message to understand their core need (e.g., refund request, general query, feedback).
296- Answer common informational questions (e.g., about shipping times, return policy basics) directly if you have the knowledge, before delegating.
297- If the request is clearly about a refund, gather necessary details (like Order ID) if missing, and then assign the task to the 'RefundSpecialist' agent. Provide all relevant context.
298- If the interaction seems successfully concluded (e.g., refund processed, query answered) and requesting feedback is appropriate, assign the task to the 'SatisfactionSurveyor' agent. Provide customer context.
299- For general queries you cannot handle directly and that don't fit the specialist agents, state your limitations clearly and politely.
300- Await responses or status updates from specialist agents you have assigned to.
301- Summarize the final outcome or confirmation for the customer based on specialist agent reports.
302- Maintain a helpful, empathetic, and professional tone throughout the interaction.
303
304# Additional Context
305Customer ID: {{customer_id}}
306Company policies: {{company_policy_info}}
307""".strip()
308
309
310# %%[markdown]
311## Manager agent
312
313# %%
314customer_service_manager = Agent(
315 name="CustomerServiceManager",
316 description="Acts as the primary contact point for customer inquiries, analyzes the request, routes tasks to specialized agents (Refund Specialist, Satisfaction Surveyor), and ensures resolution.",
317 llm=llm,
318 custom_instruction=MANAGER_SYSTEM_PROMPT,
319 agent_id="CustomerServiceManager", # for the `print_messages` utility function
320)
321
322
323# %%[markdown]
324## Managerworkers pattern
325
326# %%
327from wayflowcore.managerworkers import ManagerWorkers
328
329group = ManagerWorkers(
330 group_manager=customer_service_manager,
331 workers=[refund_specialist_agent, surveyor_agent],
332)
333
334
335# %%[markdown]
336## Managerworkers answers without expert
337
338# %%
339main_conversation = group.start_conversation(
340 inputs={
341 "customer_id": "CUST456",
342 "company_policy_info": "Shipping times: 3-5 business days in the US",
343 }
344)
345main_conversation.append_user_message(
346 "Hi, I was wondering what your standard shipping times are for orders within the US?"
347)
348main_conversation.execute()
349"""
350last_message = main_conversation.get_last_message()
351print(f"{last_message.message_type} >>> {last_message.content or last_message.tool_requests}")
352# AGENT >>> Hello! Thank you for reaching out to us. Our standard shipping times for orders within the US are 3-5 business days. Is there anything else I can help you with?
353main_conversation.append_user_message("Okay, thanks! Does that include weekends?")
354main_conversation.execute()
355last_message = main_conversation.get_last_message()
356print(f"{last_message.message_type} >>> {last_message.content or last_message.tool_requests}")
357# AGENT >>> Our standard shipping times of 3-5 business days are for weekdays only (Monday through Friday). Weekends and holidays are not included in that timeframe. If you have any other questions or need further assistance, feel free to ask!
358print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
359print_messages(main_conversation.get_messages())
360# USER >>> Hi, I was wondering what your standard shipping times are for orders within the US?
361# AGENT >>> Hello! Thank you for reaching out to us. Our standard shipping times for orders within the US are 3-5 business days. Is there anything else I can help you with?
362# USER >>> Okay, thanks! Does that include weekends?
363# AGENT >>> Our standard shipping times of 3-5 business days are for weekdays only (Monday through Friday). Weekends and holidays are not included in that timeframe. If you have any other questions or need further assistance, feel free to ask!
364"""
365
366
367# %%[markdown]
368## Managerworkers answers with expert
369
370# %%
371main_conversation = group.start_conversation(inputs={
372 "customer_id": "CUST456",
373 "company_policy_info": "Shipping times: 3-5 business days in the US"
374})
375main_conversation.execute()
376"""
377print_messages([main_conversation.get_last_message()])
378
379main_conversation.append_user_message("Thank you")
380main_conversation.execute()
381
382print(f"{'-'*30}\nFULL CONVERSATION:\n{'-'*30}\n")
383print_messages(main_conversation.get_messages())
384# ------------------------------
385# FULL CONVERSATION:
386# ------------------------------
387# USER >>> Hi, I need to request a refund for order #123. The item wasn't what I expected.
388# TOOL_REQUEST (CustomerServiceManager) >>> The user is requesting a refund for order #123 because the item did not meet their expectations. I will forward this request to the Refund Specialist to verify the eligibility and process the refund accordingly.
389# [ToolRequest(name='send_message', args={'message': 'A customer is requesting a refund for order #123 due to the item not meeting their expectations. Please verify the eligibility for the refund and proceed with the necessary steps.', 'recipient': 'RefundSpecialist'}, tool_request_id='19c3cad0-683f-4aa5-9047-57f446405932')]
390# TOOL_RESULT (None) >>> The refund for order #123 has been processed successfully. The refund amount is $50.00. Refund ID: REF_789XYZ
391# AGENT (CustomerServiceManager) >>> Your refund for order #123 has been processed successfully. The refund amount of $50.00 has been issued to you. If there's anything else you need, please feel free to ask!
392# USER >>> Thank you
393# TOOL_REQUEST (CustomerServiceManager) >>> --- MESSAGE: From: CustomerServiceManager ---
394# I will now initiate a brief satisfaction survey with the user regarding their refund experience to ensure complete service quality.
395# [ToolRequest(name='send_message', args={'message': 'Please conduct a brief satisfaction survey with the customer regarding their recent refund experience for order #123.', 'recipient': 'SatisfactionSurveyor'}, tool_request_id='b2368dd2-4bab-447d-9bf3-a6483b403beb')]
396# TOOL_RESULT (None) >>> Hi there, I hope you’re doing well. My name is John, and I’m a Satisfaction Surveyor. I’m reaching out because you recently had a refund experience with us for order #123. I wanted to take a moment to thank you for being our customer, and I was wondering if you might have a moment to provide some feedback on that experience. Would you be willing to answer a couple of quick questions about your recent interaction?
397# AGENT (CustomerServiceManager) >>> Hi there, I hope you’re doing well. My name is John, and I’m a Satisfaction Surveyor. I’m reaching out because you recently had a refund experience with us for order #123. I wanted to take a moment to thank you for being our customer, and I was wondering if you might have a moment to provide some feedback on that experience. Would you be willing to answer a couple of quick questions about your recent interaction?
398
399# ... can continue similarly to the example above with the satisfaction surveyor
400"""
401
402
403# %%[markdown]
404## Export config to Agent Spec
405
406# %%
407from wayflowcore.agentspec import AgentSpecExporter
408
409serialized_group = AgentSpecExporter().to_yaml(group)
410
411
412# %%[markdown]
413## Load Agent Spec config
414
415# %%
416from wayflowcore.agentspec import AgentSpecLoader
417
418TOOL_REGISTORY = {
419 "record_survey_response": record_survey_response,
420 "check_refund_eligibility": check_refund_eligibility,
421 "process_refund": process_refund,
422}
423
424deserialized_group: ManagerWorkers = AgentSpecLoader(
425 tool_registry=TOOL_REGISTORY
426).load_yaml(serialized_group)