Tutorial 1: Agents#

Functionalities:#

  • Task-based Agents which will break down tasks into subtasks and solve them in bite-sized portions

  • Agents with registered functions as skills

Setup Guide#

Step 1: Install TaskGen#

# !pip install taskgen-ai

Step 2: Import required functions and setup relevant API keys for your LLM#

# Set up API key and do the necessary imports
from taskgen import *
import os

# this is only if you use OpenAI as your LLM
os.environ['OPENAI_API_KEY'] = '<YOUR API KEY HERE>'

Step 3: Define your own LLM#

  • Take in a system_prompt, user_prompt, and outputs llm response string

def llm(system_prompt: str, user_prompt: str) -> str:
    ''' Here, we use OpenAI for illustration, you can change it to your own LLM '''
    # ensure your LLM imports are all within this function
    from openai import OpenAI

    # define your own LLM here
    client = OpenAI()
    response = client.chat.completions.create(
        model='gpt-4o-mini',
        temperature = 0,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
    )
    return response.choices[0].message.content

# Verify that llm function is working
llm(system_prompt = 'You are a classifier to classify the sentiment of a sentence',
    user_prompt = 'It is a hot and sunny day')

1. Agent Basics#

  • Create an agent by entering your agent’s name and description

  • Agents are task-based, so they will help generate subtasks to fulfil your main task

  • Agents are made to be non-verbose, so they will just focus only on task instruction (Much more efficient compared to conversational-based agentic frameworks like AutoGen)

  • Agent’s interactions will be stored into subtasks_completed by default, which will serve as a memory buffer for future interactions

Inputs for Agent:
  • agent_name: String. Name of agent, hinting at what the agent does

  • agent_description: String. Short description of what the agent does

  • max_subtasks: Int. Default: 5. The maximum number of subtasks the agent can have

  • verbose: Bool. Default: True. Whether to print out agent’s intermediate thoughts

  • llm: Function. The LLM to be used by the Agent

Agent Internal Parameters:
  • Task: String. The task the agent has been assigned to - Defaults to “No task assigned”

  • Subtasks Completed: Dict. The keys are the subtask names and the values are the result of the respective subtask

  • Is Task Completed: Bool. Whether the current Task is completed

Task Running
  • reset(): Resets the Agent Internal Parameters and Subtasks Completed. You should do this at the start of every new task assigned to the Agent to minimise potential confusion of what has been done for this task versus previous tasks

  • run(task: str, num_subtasks: int = max_subtasks): Performs the task. Do note that agent’s state will not be reset, so if you want to reset it, call reset() prior to running this. Runs the task for num_subtasks steps. If not specified, we will take the max_subtasks.

Give User Output
  • reply_user(query: str = ‘’, stateful: bool = True): Using all information from subtasks, give a reply about the query to the user. If query is not given, then it replies based on the current task the agent is doing. If stateful is True, saves this query and reply into subtasks_completed

Check status of Agent:
  • status(): Lists out Agent Name, Agent Description, Available Functions (default function is to use the LLM), Task, Subtasks Completed and Is Task Completed

Example Agent Creation#

my_agent = Agent('Helpful assistant', 'You are a generalist agent')

Example Agent Task Running - Split the assigned task into subtasks and execute each of them#

output = my_agent.run('Give me 5 words rhyming with cool, and make a 4-sentence poem using them')

Check Agent’s Status#

my_agent.status()

Example Agent Reply to User - Reference the subtasks’ output to answer the user’s query#

output = my_agent.reply_user()

Create Agent#

# Create your agent by specifying name and description
my_agent = Agent('Helpful assistant', 'You are a generalist agent', llm = llm)

# Show the agent status - By default agent comes equipped with default function `use_llm` which queries the llm
# end_task is to end the current task if it is completed
my_agent.status()

Automatic Running of Task#

# Do the task by subtasks. This does generation to fulfil task
my_agent.reset()
output = my_agent.run('Give me 5 words rhyming with cool, and then make a 4-sentence poem using them')

# visualise the subtask outputs of the task
print(output)

# see the updated agent status
my_agent.status()

# Generates a meaningful reply to the user about the task according to its current state. Functions like a QA bot
# The reply will go into subtasks_completed to store the conversation with the user for future context
output = my_agent.reply_user()

# You can also ask your questions to the agent, and the agent will reply according to its current state. Functions like a QA bot
# Here, we set stateful is false because we do not want this to go into subtasks_completed
output = my_agent.reply_user('Where is the pool?', stateful = False)

# see the updated agent status
my_agent.status()

my_agent.print_functions()

my_agent.reset()

my_agent.status()

# when you change the task, we will set the task completed to False so you can do it again.
# Do note that the earlier subtasks_history might affect your new task and should be reset if it is not relevant
my_agent.reset()
output = my_agent.run('Give me 5 words rhyming with task, and then make a 4-sentence poem using them')

# see the updated agent staus
my_agent.status()

Running of Task Step by Step#

# Create your agent by specifying name and description
my_agent = Agent('Number Expert', 'You are great with numbers', llm = llm)

# Runs a task for 1 step by editing num_subtasks variable to 1
output = my_agent.run('List me three random numbers from 1 to 50, then give me their sum', num_subtasks = 1)

# visualise the outputs of the task at the subtask level
print(output)

# Visualise the first subtask
my_agent.status()

# Runs the task for another step
output = my_agent.run('List me three random numbers from 1 to 50, then give me their sum', num_subtasks = 1)

# visualise the outputs of the task at the subtask level
print(output)

# Visualise the first and second subtasks
my_agent.status()

# if task already completed, then no need to do further
output = my_agent.run('List me three random numbers from 1 to 50, then give me their sum', num_subtasks = 1)

# Generates a meaningful reply to the user
output = my_agent.reply_user()

2. Power Up your Agents - Bring in Functions (aka Tools)#

  • First define the functions, either using class Function (see Tutorial 0), or just any Python function with input and output types defined in the signature and with a docstring

  • After creating your agent, use assign_functions to assign a list of functions of class Function, or general Python functions (which will be converted to AsyncFunction)

  • Function names will be automatically inferred if not specified

  • Proceed to run tasks by using run()

# This is an example of an LLM-based function (see Tutorial 0)
sentence_style = Function(fn_description = 'Output a sentence with words <var1> and <var2> in the style of <var3>',
                         output_format = {'output': 'sentence'},
                         fn_name = 'sentence_with_objects_entities_emotion',
                         llm = llm)

# This is an example of an external user-defined function (see Tutorial 0)
def binary_to_decimal(binary_number: str) -> int:
    '''Converts binary_number to integer of base 10'''
    return int(str(binary_number), 2)

# Initialise your Agent
my_agent = Agent('Helpful assistant', 'You are a generalist agent')

# Assign the functions
my_agent.assign_functions([sentence_style, binary_to_decimal])

# Run the Agent
output = my_agent.run('First convert binary string 1001 to a number, then generate me a happy sentence with that number and a ball')
  • Approach 1: Automatically Run your agent using run()

  • Approach 2: Manually select and use functions for your task
    • select_function(task: str): Based on the task, output the next function name and input parameters

    • use_function(function_name: str, function_params: dict, subtask: str = ‘’, stateful: bool = True): Uses the function named function_name with function_params. stateful controls whether the output of this function will be saved to subtasks_completed under the key of subtask

Assign/Remove Functions:
  • assign_functions(function_list: list): Assigns a list of functions to the agent

  • remove_function(function_name: str): Removes function named function_name from the list of assigned functions

Show Functions:
  • list_functions(): Returns the list of functions of the agent

  • print_functions(): Prints the list of functions of the agent

Example Internal Function#

sentence_style = Function(fn_description = 'Output a sentence with <obj> and <entity> in the style of <emotion>',
                     output_format = {'output': 'sentence'}, fn_name = 'sentence_with_objects_entities_emotion',
                         llm = llm)

print(sentence_style)

Example External Function#

def binary_to_decimal(binary_string: str) -> int:
    '''Converts binary_string to integer of base 10'''
    return int(str(binary_string), 2)

# Initialise your agent
my_agent = Agent('Helpful assistant', 'You are a generalist agent', llm = llm)

# Assign functions
my_agent.assign_functions(function_list = [sentence_style, binary_to_decimal])

# Show the functions the agent has
my_agent.print_functions()

Approach 1: Automatic Running of Task#

my_agent.reset()
output = my_agent.run('First convert binary string 1001 to a number, then generate me a happy sentence with that number and a ball')

my_agent.status()

# give a response to user
output = my_agent.reply_user()

# query according to what you need
output = my_agent.reply_user('Output only the sentence')

my_agent.status()

my_agent.subtasks_completed

my_agent.thoughts

Approach 2: Manual Selection and Running of Functions#

  • If you want to just see what the agent would choose for a hypothetical task, use select_function. This will not update the internal state, and will output function name and function params

  • If you want specificity in the process, you can just execute the agent’s functions yourself using use_function

  • use_function: Uses an agent’s function using name and params. Note that by default there will be updating of subtasks_completed when performing the function. In order not to update subtasks_completed, set stateful = False

# this should call generate_sentence_with_emotion
my_agent.reset()
function_name, function_params = my_agent.select_function(
    task = 'Output a sentence with bell, dog and happy')
print(f'Selected function name: {function_name}\nSelected function params: {function_params}')

my_agent.use_function(function_name, function_params, stateful = False)

# this should call binary_to_decimal
my_agent.reset()
function_name, function_params = my_agent.select_function(
    task = 'What is the decimal representation of binary number 101?')
print(f'Selected function name: {function_name}\nSelected function params: {function_params}')

my_agent.use_function(function_name, function_params, stateful = False)

# this should call use_llm
my_agent.reset()
function_name, function_params = my_agent.select_function(
    task = 'Research on the benefits of exercise based on LLM')
print(f'Selected function name: {function_name}\nSelected function params: {function_params}')

my_agent.use_function(function_name, function_params, stateful = False)

3. AsyncAgent#

  • AsyncAgent works the same way as Agent, only much faster due to parallelisation of tasks

  • It can only be assigned functions of class AsyncFunction, or general Python functions (which will be converted to AsyncFunction)

  • If you define your own AsyncFunction, you should define the fn_name as well if it is not an External Function

  • As a rule of thumb, just add the await keyword to any function that you run with the AsyncAgent

Example LLM in Async Mode#

async def llm_async(system_prompt: str, user_prompt: str):
    ''' Here, we use OpenAI for illustration, you can change it to your own LLM '''
    # ensure your LLM imports are all within this function
    from openai import AsyncOpenAI

    # define your own LLM here
    client = AsyncOpenAI()
    response = await client.chat.completions.create(
        model='gpt-3.5-turbo',
        temperature = 0,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
    )
    return response.choices[0].message.content

Example Agentic Workflow#

# This is an example of an LLM-based function (see Tutorial 0)
sentence_style = AsyncFunction(fn_description = 'Output a sentence with words <var1> and <var2> in the style of <var3>',
                         output_format = {'output': 'sentence'},
                         fn_name = 'sentence_with_objects_entities_emotion', # you must define fn_name for LLM-based functions
                         llm = llm_async) # use an async LLM function

# This is an example of an external user-defined function (see Tutorial 0)
def binary_to_decimal(binary_number: str) -> int:
    '''Converts binary_number to integer of base 10'''
    return int(str(binary_number), 2)

# Initialise your Agent
my_agent = AsyncAgent('Helpful assistant', 'You are a generalist agent', llm = llm_async)

# Assign the functions
my_agent.assign_functions([sentence_style, binary_to_decimal])

# Run the Agent
output = await my_agent.run('Generate me a happy sentence with a number and a ball. The number is b1001 converted to decimal')
# Define an Async LLM function
async def llm_async(system_prompt: str, user_prompt: str):
    ''' Here, we use OpenAI for illustration, you can change it to your own LLM '''
    # ensure your LLM imports are all within this function
    from openai import AsyncOpenAI

    # define your own LLM here
    client = AsyncOpenAI()
    response = await client.chat.completions.create(
        model='gpt-4o-mini',
        temperature = 0,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ]
    )
    return response.choices[0].message.content

# This is an example of an LLM-based function (see Tutorial 0)
sentence_style = AsyncFunction(fn_description = 'Output a sentence with words <var1> and <var2> in the style of <var3>',
                         output_format = {'output': 'sentence'},
                         fn_name = 'sentence_with_objects_entities_emotion', # you must define fn_name for LLM-based functions
                         llm = llm_async) # you must use an Async LLM function

# This is an example of an external user-defined function (see Tutorial 0)
def binary_to_decimal(binary_number: str) -> int:
    '''Converts binary_number to integer of base 10'''
    return int(str(binary_number), 2)

# Initialise your Agent
my_agent = AsyncAgent('Helpful assistant', 'You are a generalist agent', llm = llm_async)

# Assign the functions
my_agent.assign_functions([sentence_style, binary_to_decimal])

# Run the Agent
output = await my_agent.run('First convert binary string 1001 to a number, then generate me a happy sentence with that number and a ball')

output = await my_agent.reply_user()

my_agent.status()

# this should call generate_sentence_with_emotion
my_agent.reset()
function_name, function_params = await my_agent.select_function(
    task = 'Output a sentence with bell, dog and happy')
print(f'Selected function name: {function_name}\nSelected function params: {function_params}')

await my_agent.use_function(function_name, function_params, stateful = False)

Saving and Loading Agents#

Sometimes you want to configure your agents and save them and load them elsewhere, while maintaining the current agent state

  • When you use the save_agent function, we store the entire agent’s internal state, include name, description, list of functions, subtasks history and all other internal variables into a pickle file

  • When you use the load_agent function, and we will load the entire agent saved in the pickle file into the existing agent

Key functions: - save_agent(pickle_file_name: str): Saves the agent’s internal parameters to a pickle file named pickle_file_name (include .pkl), returns the pickle file - load_agent(pickle_file_name: str): Loads the agent’s internal parameters from a pickle file named pickle_file_name (include .pkl), returns loaded agent

Example 1: Saving Agent#

my_agent.save_agent('myagent.pkl')

Example Output#

Agent saved to myagent.pkl

Example 2: Loading Agent#

new_agent = Agent().load_agent('myagent.pkl')

Example Output#

Agent loaded from myagent.pkl

# run one task to add something to subtasks_completed
my_agent.reset()
output = my_agent.run('What is the decimal representation of binary number 101?')

# see status of agent before saving
my_agent.status()

# save agent
my_agent.save_agent('myagent.pkl')

# load agent using load_agent code
new_agent = Agent().load_agent('myagent.pkl')

# see status of loaded agent (which also includes what is saved in subtasks_completed, and shared_variables)
new_agent.status()

Contributing agent to community#

Once you are done developing your agent, we encourage you to contribute your agent to taskgen community. Agent contribution works by creating a Pull Request to taskgen repo, hence you need to have a github profile. We have taken efforts to make the process simple

Follow below steps to contribute your agent:

  • Create your profile on github

  • Set environment variable GITHUB_USERNAME as your github user name

  • Set environment variable GITHUB_TOKEN as your github token. Steps to create github token below:
    • Sign-in to your github account on your browser

    • Navigate to token settings on github - settings/tokens

    • Generate new token (classic)
      • Provide token name, expiration

      • Select scope - repo:public_repo

  • Execute contribute_agent function on your agent, and provide comments on what your agent should be used for

def add(x: int, y: int) -> int:
    '''Takes in x and y and returns the sum'''
    return x+y

my_agent = Agent('Math Expert', 'Does Math very well. Calculate only what is necessary.', llm = llm).assign_functions(add)

my_agent.run('Calculate 3 + 2 + 5')

my_agent.reply_user()

os.environ['GITHUB_USERNAME'] = '<your username here>'
os.environ['GITHUB_TOKEN'] = '<your token here>'

# my_agent.contribute_agent(author_comments = "This agent should be used for any addition-based calculation")