How to Build a Manager-Workers of Agents#

python-icon Download Python Script

Python script/notebook for this guide.

ManagerWorkers how-to script

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;

Example of a multi-agent system

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",
    )

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)