Designing AI Workflows with AutoGen

As artificial intelligence (AI) continues developed, Agentic AI emerges to solve complex workflow for different scenarios. AutoGen is one of the framework have the capability to achieve the decision-making and problem-solving of a complex workflow. To know more about AutoGen framework, please kindly refer to Building Your First Agentic AI with AutoGen Framework.

In this post, we are going to learn how to design a workflow in different ways with the support of AutoGen. We will start with a simple workflow, performing arithmetic operation.

Understand Agentic AI Workflows

Agentic AI workflow refer to a type of workflow or process that incorporates the use of AI systems to perform tasks traditionally carried out by human such as customer service. With the help of agentic AI, which has the capability to understand complex task and achieve goals autonomously, with little to no human interference, human expertise can free up their time from working on repetitive mundane tasks. While it could also enhance the working efficiency with its strong computation power. Please visit this blog, Introduction to Agentic AI for further explanation on agentic AI.

Arithmetic Operation with AutoGen Agents

In this blog, we will first start with a simpler workflow, arithmetic operation as an example in this post. We can perform arithmetic operations in 3 ways in general, using sequential flow, with Group Chat class, or using tools. Please refer to this blog for the basic set up to use AutoGen framework but this time we are using GPT-4 instead of 3.5 turbo to ensure a better output.

1. Sequential Flow with Multi-Agent

The example is adapted from AutoGen's Conversation Patterns:

We start with setting up the 5 agents. One agent (number_agent) as the control of the whole flow, the other four agents performs the 4 arithmetic operations.

number_agent = ConversableAgent(
	name="Number_Agent",
  system_message="You return me the numbers I give you, one number each line.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
adder_agent = ConversableAgent(
	name="Adder_Agent",
  system_message="You add 2 to each number I give you and return me the new numbers, one number each line.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
multiplier_agent = ConversableAgent(
	name="Multiplier_Agent",
  system_message="You multiply each number I give you by 2 and return me the new numbers, one number each line.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
subtracter_agent = ConversableAgent(
	name="Subtracter_Agent",
  system_message="You subtract 2 from each number I give you and return me the new numbers, one number each line.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
divider_agent = ConversableAgent(
	name="Divider_Agent",
	system_message="You divide each number I give you by 2 and return me the new numbers, one number each line.",
	llm_config=llm_config,
  human_input_mode="NEVER",
)

By default, the carryover will accumulates and carry all context from precious chat result like what the example does in Autogen's tutorial. Therefore, we create our own callable message function in order to only carryover one number only to the next chat.

def my_message(sender: ConversableAgent, recipient: ConversableAgent, context: dict):
    carryover = context.get("carryover", "")
    if isinstance(carryover, list):
        carryover = carryover[-1]
    final_msg = "These are my numbers" + "\nContext: \n" + carryover
    return final_msg

Then, we will perform call initiate_chats to initiate multiple chat with predetermined order (adder, multiplier, subtracter, divisor). We will use the my_message function starting from the second chat to allow the number_agent only give the final number to the next agent instead of all result from pass chats.

chat_results = number_agent.initiate_chats([
    {
    	"recipient": adder_agent,
        "message": "14",
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
    },
    {
    	"recipient": multiplier_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
    },
    {
    	"recipient": subtracter_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
    },
    {
    	"recipient": divider_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
    }
])

Output:

********************************************************************************
Starting a new chat....
********************************************************************************
Number_Agent (to Adder_Agent):

14

--------------------------------------------------------------------------------
Adder_Agent (to Number_Agent):

16

-------------------------------------------------------------------------------
********************************************************************************
Starting a new chat....
********************************************************************************
Number_Agent (to Multiplier_Agent):

These are my numbers
Context:
16

--------------------------------------------------------------------------------
Multiplier_Agent (to Number_Agent):

32

--------------------------------------------------------------------------------
********************************************************************************
Starting a new chat....
********************************************************************************
Number_Agent (to Subtracter_Agent):

These are my numbers
Context:
32

--------------------------------------------------------------------------------
Subtracter_Agent (to Number_Agent):

30

--------------------------------------------------------------------------------
********************************************************************************
Starting a new chat....
********************************************************************************
Number_Agent (to Divider_Agent):

These are my numbers
Context:
30

--------------------------------------------------------------------------------
Divider_Agent (to Number_Agent):

15

--------------------------------------------------------------------------------

For your ease, you could use the following code to test on different numbers with the above predefined operation. The set up and the agents are same as above. In the following code a new parameter "silent" is added to hide the chat and only print the result of the operation:

def predefined_operation(input):
    chat_results = number_agent.initiate_chats(
    [
      {
        "recipient": adder_agent,
        "message": input,
        "max_turns": 1,
        "summary_method": "last_msg",
        "silent":True,
        "clear_history": True,
      },
      {
      	"recipient": multiplier_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
        "silent":True,
      },
      {
      	"recipient": subtracter_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
        "silent":True,
      },
      {
      	"recipient": divider_agent,
        "message": my_message,
        "max_turns": 1,
        "summary_method": "last_msg",
        "clear_history": True,
        "silent":True,
      },
    ])
    return chat_results

def arithmetic_sequential_chat():
    print("Welcome to the arithmetic chatbot!")
    while True:
      user_input = input("Enter a number to perform predefined arithmetic operation or 'exit' to quit: ")
      if user_input.lower() == 'exit':
    	break
      try:
    	num = eval(user_input)
        if(isinstance(num,int) or isinstance(num,float)):
          chat_results = predefined_operation(user_input)
          print(f"Result: {chat_results[-1].summary}")
        else: print("Please input an integer or a floating point number or exit.")
      except Exception as e: print(f"Error: {e}")
      print()

if __name__ == "__main__":
    arithmetic_sequential_chat()

However, with sequential chat, the AI can only perform arithmetic operations in the define order with an input number. It cannot solve an equation like 1+4/2. Therefore we have to use another method to achieve equation solving.

2. Perform calculation with Group Chat

Group Chat is a class for creating a more general conversation pattern, which involves more than two agents. It allows all agents contribute to a single conversation thread and share the same context by another agent named group chat manager. The overall flow of using group chat will be illustrate in the following figure.

credit, AutoGen

We will set up the agent similar to the above section (Number_Agent, Adder_Agent, Multiplier_Agent, Subtracter_Agent, Divider_Agent), but with a extra description. Description is for the other agents like group chat manager to know the job of each agent and may be used as a reference to select the next speaker. The default input is the system_message of the agent.

number_agent = ConversableAgent(
  name="Number_Agent",
  system_message= "You initiate the chat with the conversion number conversion and return 'TERMINATE' when the conversion is completed",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
adder_agent = ConversableAgent(
  name="Adder_Agent",
  system_message="You add 1 to last number I give you and return me the new number, one number each line."
  "Perform one operation only.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
multiplier_agent = ConversableAgent(
  name="Multiplier_Agent",
  system_message="You multiply last number I give you by 2 and return me the new number, one number each line."
  "Perform one operation only.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
# The Subtracter Agent subtracts 1 from each number it receives.
subtracter_agent = ConversableAgent(
  name="Subtracter_Agent",
  system_message="You subtract 1 from last number I give you and return me the new number, one number each line."
  "Perform one operation only.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)
divider_agent = ConversableAgent(
  name="Divider_Agent",
  system_message="You divide last number I give you by 2 and return me the new number, one number each line."
  "Perform one operation only.",
  llm_config=llm_config,
  human_input_mode="NEVER",
)

adder_agent.description = "Add 1 to last input number."
multiplier_agent.description = "Multiply last input number by 2."
subtracter_agent.description = "Subtract 1 from last input number."
divider_agent.description = "Divide last input number by 2.  Do not perform division if the last input number is not even."
number_agent.description = "Terminate the chat after the conversion is completed."

Then we will set up the group chat object. This object contain 3 parameters, which includes agents, messages, and speaker_selection_method.

from autogen import GroupChat

group_chat = GroupChat(
  agents=[adder_agent, multiplier_agent, subtracter_agent, divider_agent, number_agent],
  messages=[],
  speaker_selection_method=custom_speaker_selection_func
)

As the name speaker_selection_method suggested, it refers to how the group chat select the next speaker to generate a reply. By default, it will be "auto",  which means the speaker will be selected by the group chat manager's large language model (LLM). In the above example, we choose to use a custom method as follow to allow a termination by receiving a message with the word "TERMINATE" from the number agent.

def custom_speaker_selection_func(last_speaker, groupchat):
	messages = groupchat.messages
	if last_speaker == number_agent:
		if "TERMINATE" in messages[-1]["content"]:
			return None
	return "auto"

To use the group chat object, we will need a special agent, Group Chat Manager. This manager agent will not participate in the chat but to manage the the flow of the chat. For further information about group chat and group chat manager, you may refer to the AutoGen page here.

from autogen import GroupChatManager

group_chat_manager = GroupChatManager(
    groupchat=group_chat,
    llm_config=llm_config,
)

Last but not least, we can generate the group chat result by initiating chat with the manager.

chat_result = number_agent.initiate_chat(
    group_chat_manager,
    message="My number is 4, I want to turn it into 9.",
    summary_method="reflection_with_llm",
)

Output:

Number_Agent (to chat_manager):

My number is 4, I want to turn it into 9.

--------------------------------------------------------------------------------
Next speaker: Multiplier_Agent

Multiplier_Agent (to chat_manager):

8

--------------------------------------------------------------------------------

Next speaker: Adder_Agent

Adder_Agent (to chat_manager):

9

--------------------------------------------------------------------------------

Next speaker: Number_Agent

Number_Agent (to chat_manager):

TERMINATE

--------------------------------------------------------------------------------

You may also use the following code with the same agents as above to run the operation with different inputs.

With the above group chat we can select the next speaker with the manager agents but not using a predetermined order. This give a variation such that the agents can perform operation to convert one number to another number. However, this still cannot perform a arithmetic operation with an equation. Therefore, we will introduce the third method, using tool, to achieve this goal.

3. Calculation using Tool

Other than group chat, we can simply make use of a calculation tool to perform calculation for arithmetic operations. Tools are pre-defined functions that agents can call after registration. These tools may includes web searching, performing calculations, reading files, etc. This time we are using it to perform calculation. The code is adapted from AutoGen's Tool Use tutorial

We will first define a calculator tool:

from typing import Annotated, Literal, Union

Operator = Literal["+", "-", "*", "/"]

def calculator(a: Union[int,float], b: Union[int,float], operator: Annotated[Operator, "operator"]) -> int:
    if operator == "+":
        return a + b
    elif operator == "-":
        return a - b
    elif operator == "*":
        return a * b
    elif operator == "/":
        return a / b
    else:
        raise ValueError("Invalid operator")

Then we will set up the two Conversable Agent. One as the assistant to decide the order of the operation, another will be the user.

assistant = ConversableAgent(
    name="Assistant",
    system_message="You are a helpful AI assistant. "
    "You can help with simple calculations. "
    "Return 'TERMINATE' when the task is done.",
    llm_config=llm_config,
)

user_proxy = ConversableAgent(
    name="User",
    llm_config=False,
    is_termination_msg=lambda msg: msg.get("content") is not None and "TERMINATE" in msg["content"],
    human_input_mode="NEVER",
)

After that, we have to register the calculator to the two agents. In the example, the assistant will decide on the inputs into the function and user_proxy will use the tool. This is why user_proxy do not need an LLM since it will only use as an executor of the simple calculator.

register_function(
    calculator,
    caller=assistant,
    executor=user_proxy,
    name="calculator",
    description="A simple calculator, with 4 operator '+' for addition, '-' for subtraction,'*' for multiplication and, '/' for division, enter 2 number as input",
)

Last but not least, you can perform operation with initiating the chat between the two agents.

chat_result = user_proxy.initiate_chat(assistant, message="What is (2 + 3 / (4 - 5)) * 6?")

Output:

User (to Assistant):

What is (2 + 3 / (4 - 5)) * 6?

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_sOqa4IrsVF6m4Y8mYS1h2FDO): calculator *****
Arguments: {
    "a": 4,
    "b": 5,
    "operator": "-"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_sOqa4IrsVF6m4Y8mYS1h2FDO) *****
-1
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_oVboqCgA0NyNXPjCaC776AKC): calculator *****
Arguments:{
    "a": 3,
    "b": -1,
    "operator": "/"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_oVboqCgA0NyNXPjCaC776AKC) *****
-3.0
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_Gj98oGkSAwPLd1ggkVVyqG55): calculator *****
Arguments: {
    "a": 2,
    "b": -3,
    "operator": "+"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_Gj98oGkSAwPLd1ggkVVyqG55) *****
-1
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

***** Suggested tool call (call_bNTkqZd6kZFpBfvYy7TPrQdf): calculator *****
Arguments:{
    "a": -1,
    "b": 6,
    "operator": "*"
}
***************************************************************************

--------------------------------------------------------------------------------

>>>>>>>> EXECUTING FUNCTION calculator...
User (to Assistant):

User (to Assistant):

***** Response from calling tool (call_bNTkqZd6kZFpBfvYy7TPrQdf) *****
-6
**********************************************************************

--------------------------------------------------------------------------------

>>>>>>>> USING AUTO REPLY...
Assistant (to User):

The result of the calculation (2 + 3 / (4 - 5)) * 6 is -6.

TERMINATE

--------------------------------------------------------------------------------

You may also use the following code to test on different inputs:

def arithmetic_tool():
    print("Welcome to the arithmetic chatbot!")
    while True:
        user_input1 = input("Enter a arithmetic operation like (2 + 3 / (4 - 5)) * 6 or 'exit' to quit: ")
        if user_input1.lower() == 'exit': break
        try:
            expression = eval(user_input1)
            if(isinstance(expression,int) or isinstance(expression,float)):
                chat_result = user_proxy.initiate_chat(assistant, message=f"What is {user_input1}?")
        except Exception as e: print(f"Error: {e}")
        print()

if __name__ == "__main__":
    arithmetic_tool()

Conclusion

In this post, we learn to use different agents or functions provided by AutoGen to performing arithmetic operations as the start of your journey of using AutoGen agents. Hopefully you can understand how to set up a simple workflow from the tutorial and you may further investigate on AutoGen to create a more complex ones such as writing and executing code.

Extra Resources

The following are some extra resources from AutoGen website for your reference or interests if you want to learn more about what AutoGen can do.

Subscribe to newsletter

Join our e-newsletter to stay up to date on the latest AI trends!