Ants GPT: OpenAI LLM and rule based agents

Ants GPT: OpenAI LLM and rule based agents preview image

1 collaborator

Tags

Visible to everyone | Changeable by the author
Model was written in NetLogo 6.4.0 • Viewed 33 times • Downloaded 5 times • Run 0 times
Download the 'Ants GPT: OpenAI LLM and rule based agents' modelDownload this modelEmbed this model

Do you have questions or comments about this model? Ask them here! (You'll first need to log in.)


CREDITS AND REFERENCES

If you mention this model in a publication, we ask that you include these citations for the model itself and for the NetLogo software:

  • Cristian Jimenez-Romero, Alper Yegenoglu, Christian Blum Multi-Agent Systems Powered By Large Language Models: Applications In Swarm Intelligence. In ArXiv Pre-print, March 2025.

  • Wilensky, U. (1999). NetLogo. http://ccl.northwestern.edu/netlogo/. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.

Comments and Questions

About Ants GPT:

This work examines the integration of large language models (LLMs) into multi-agent simulations by replacing the hard-coded programs of agents with LLM-driven prompts. The proposed approach is showcased in the context of two examples of complex systems from the field of swarm intelligence: ant colony foraging and bird flocking. Central to this study is a toolchain that integrates LLMs with the NetLogo simulation platform, leveraging its Python extension to enable communication with GPT-4o via the OpenAI API. This toolchain facilitates prompt-driven behavior generation, allowing agents to respond adaptively to environmental data. For both example applications mentioned above, we employ both structured, rule-based prompts and autonomous, knowledge-driven prompts. Our work demonstrates how this toolchain enables LLMs to study self-organizing processes and induce emergent behaviors within multi-agent environments, paving the way for new approaches to exploring intelligent systems and modeling swarm intelligence inspired by natural phenomena. We provide the code, including simulation files and data at: https://github.com/crjimene/swarm_gpt

Posted 5 days ago

Click to Run Model

;; AntGPT Colony - Hybrid LLM - Rule-based agents
;; Author: Cristian Jimenez Romero - 2025

extensions [ py ]

patches-own [
  chemical             ;; amount of chemical on this patch
  food                 ;; amount of food on this patch (0, 1, or 2)
  nest?                ;; true on nest patches, false elsewhere
  nest-scent           ;; number that is higher closer to the nest
  food-source-number   ;; number (1, 2, or 3) to identify the food sources
]

globals
[
  is-stopped?          ; flag to specify if the model is stopped
  food_collected
  all-food-amounts
  activate_llm
]

breed [ants ant]

ants-own [
  ant-id
  action-move_forward             ;; amount of chemical on this patch
  action-rotate                ;; amount of food on this patch (0, 1, or 2)
  action-pick_up_food                ;; true on nest patches, false elsewhere
  action-drop_pheromone           ;; number that is higher closer to the nest
  action-drop_food   ;; number (1, 2, or 3) to identify the food sources
  action-status-ok
  action-status-code
  sense-pheromone-left
  sense-pheromone-front
  sense-pheromone-right
  sense-on-nest
  sense-nest-left
  sense-nest-front
  sense-nest-right
  sense-food-quantity
  sense-carrying-food

  recovered_ant_data_move
  recovered_ant_data_rotate_r
  recovered_ant_data_rotate_l
  recovered_ant_data_random_l
  recovered_ant_data_random_r
  recovered_ant_data_pick_up_food
  recovered_ant_data_drop_pheromone
  recovered_ant_data_drop_food

  food_pickup_time
  food_drop_time
  food_from_source
  steps_searching
]

to setup-patches
  ask patches
  [ setup-nest
    setup-food
    recolor-patch ]
end 

to setup-nest  ;; patch procedure
  ;; set nest? variable to true inside the nest, false elsewhere
  set nest? (distancexy 0 0) < 5
  ;; spread a nest-scent over the whole world -- stronger near the nest
  set nest-scent 200 - distancexy 0 0
end 

to setup-food-test  ;; patch procedure
  set food_collected 0
  ;; setup food source one on the right
  if (distancexy (0.6 * max-pxcor) 9) < 4
  [ set food-source-number 1 ]
  ;; setup food source two on the lower-left
  if (distancexy (-0.6 * max-pxcor) (-0.6 * max-pycor)) < 4
  [ set food-source-number 2 ]
  ;; setup food source three on the upper-left
  if (distancexy (-0.8 * max-pxcor) (0.8 * max-pycor)) < 4
  [ set food-source-number 3 ]
  ;; set "food" at sources to either 1 or 2, randomly
  if food-source-number > 0
  [ set food one-of [1 2] ]
end 

to setup-food  ;; patch procedure
  ;; setup food source one on the right
  if (distancexy (0.6 * max-pxcor) 0) < 5
  [ set food-source-number 1 ]
  ;; setup food source two on the lower-left
  if (distancexy (-0.6 * max-pxcor) (-0.6 * max-pycor)) < 5
  [ set food-source-number 2 ]
  ;; setup food source three on the upper-left
  if (distancexy (-0.8 * max-pxcor) (0.8 * max-pycor)) < 5
  [ set food-source-number 3 ]
  ;; set "food" at sources to either 1 or 2, randomly
  if food-source-number > 0
  [ set food one-of [1 2] ]
end 

to recolor-patch  ;; patch procedure
  ;; give color to nest and food sources
  ifelse nest?
  [ set pcolor violet ]
  [ ifelse food > 0
    [ if food-source-number = 1 [ set pcolor cyan ]
      if food-source-number = 2 [ set pcolor sky  ]
      if food-source-number = 3 [ set pcolor blue ] ]
    ;; scale color to show chemical concentration
    [ set pcolor scale-color green chemical 0.1 5 ] ]
end 

to setup_ants
  clear-all
  set activate_llm true
  random-seed read-from-string used_seed;  21504; 6890;351973;19562;47822
  set-default-shape turtles "bug"
  create-ants 10
  [ set ant-id who
    set sense-carrying-food "False"
    set size 2
    set color red
    set food_pickup_time 0
    set food_drop_time 0
    set food_from_source 0
  ]
  setup-patches
  set all-food-amounts []
  reset-ticks
end 

to export-food-collected-to-csv [filename]
  ; Open the file for writing. The file will be saved in the current directory.
  file-open filename

  ; Write header row to the CSV file
  file-print "step_number,food_amount"
  ; Iterate over each step in all-distances
  let step-count length all-food-amounts
  let step-number 0
  repeat step-count [
    let step-data item step-number all-food-amounts
    file-print (word step-number "," step-data)
    set step-number step-number + 1
  ]
  ; Close the file after writing is done
  file-close
end 

to setup
  setup_ants
  py:setup py:python
  py:run "import math"
  py:run "import sys"
  py:run "import json"
  py:run "from openai import OpenAI"
  py:run "client = OpenAI(api_key='Insert your API key here')"
  py:run "elements_list = []"
  (py:run
    "def parse_response(response):"
    "    text = response"
    "    text = text.lower()"
    "    text = text.strip()"
    "    text = text.replace(chr(39), chr(34))"
    "    text = text.replace('_', '-')"
    "    parse_ok = 'True'"
    "    error_code = 'None'"
    "    try:"
    "        index = text.find('{')"
    "        text = text[index:]"
    "        index = text.find('}')"
    "        text = text[:index + 1]"
    "        print ('pre-processed-text: *****', text, '*****')"
    "        text = json.loads(text)"
    "        move_forward = text['move-forward']"
    "        move_forward = str(move_forward)"
    "        rotate = text['rotate']"
    "        rotate = str(rotate)"
    "        pick_up_food = text['pick-up-food']"
    "        pick_up_food = str(pick_up_food)"
    "        drop_pheromone = text['drop-pheromone']"
    "        drop_pheromone = str(drop_pheromone)"
    "        drop_food = text['drop-food']"
    "        drop_food = str(drop_food)"
    "        elements_list.append(parse_ok)"
    "        elements_list.append(error_code)"
    "        elements_list.append(move_forward.lower())"
    "        elements_list.append(rotate.lower())"
    "        elements_list.append(pick_up_food.lower())"
    "        elements_list.append(drop_pheromone.lower())"
    "        elements_list.append(drop_food.lower())"
    "        print('Parsed ok: ', elements_list)"
    "    except json.JSONDecodeError as e:"
    "        error_code = str(e)"
    "        parse_ok = 'False'"
    "        elements_list.append(parse_ok)"
    "        elements_list.append(error_code)"
    "        print ('Error: ', error_code)"
    "    except Exception as e:"
    "        error_code = str(e)"
    "        parse_ok = 'False'"
    "        elements_list.append(parse_ok)"
    "        elements_list.append(error_code)"
    "        print ('Error: ', error_code)"
    "def create_prompt(sense_pheromone_left, sense_pheromone_front, sense_pheromone_right, sense_on_nest, sense_nest_left, sense_nest_front, sense_nest_right, sense_food_quantity, sense_carrying_food):"
    "    sense_pheromone_left = float(sense_pheromone_left)"
    "    sense_pheromone_front = float(sense_pheromone_front)"
    "    sense_pheromone_right = float(sense_pheromone_right)"
    "    sense_nest_left = float(sense_nest_left)"
    "    sense_nest_front = float(sense_nest_front)"
    "    sense_nest_right = float(sense_nest_right)"
    "    if sense_on_nest.lower() == 'false':"
    "       on_nest_text = '**False** (You are not currently at the nest)'"
    "    else:"
    "       on_nest_text = '**True** (You are currently at the nest)'"
    "    if sense_carrying_food.lower() == 'false':"
    "       carrying_food_text = '**False** (You are not currently carrying food)'"
    "    else:"
    "       carrying_food_text = '**True** (You are currently carrying food)'"
    "    if sense_pheromone_left > sense_pheromone_front and sense_pheromone_left > sense_pheromone_right:"
    "       pheromone_text = 'Left'"
    "    elif sense_pheromone_right > sense_pheromone_front and sense_pheromone_right > sense_pheromone_left:"
    "       pheromone_text = 'Right'"
    "    elif sense_pheromone_front > 0 and (sense_pheromone_front >= sense_pheromone_right or sense_pheromone_front >= sense_pheromone_left):"
    "       pheromone_text = 'Front'"
    "    else:"
    "       pheromone_text = 'None'"
    "    if sense_nest_left > sense_nest_front and sense_nest_left > sense_nest_right:"
    "       nest_text = 'Left'"
    "    elif sense_nest_right > sense_nest_front and sense_nest_right > sense_nest_left:"
    "       nest_text = 'Right'"
    "    else:"
    "       nest_text = 'Front'"
    "    system_text = 'You are an ant in a 2D simulation. Your task is to pick up food and release it at the nest. Release pheromone on food source and while you are carrying food. Use nest scent to navigate back to the nest only when carrying food, prioritizing nest scent over pheromones. Use highest pheromone scent to navigate to food when not carrying any. Move away from nest and rotate randomly if you are not carrying any food and you are not sensing any pheromone. Format your actions as a Python dictionary with these keys and options: ' + chr(34) + 'move-forward' + chr(34) + ' (options: True, False), ' + chr(34) + 'rotate' + chr(34) + ' (options: ' + chr(34) + 'left'+ chr(34) + ', '+ chr(34) + 'right' + chr(34)+ ', ' + chr(34) + 'none' + chr(34) + ', ' + chr(34) + 'random' + chr(34) + ' ), ' + chr(34) + 'pick-up-food' + chr(34) + ' (options: True, False), ' + chr(34) + 'drop-pheromone' + chr(34) + ' (options: True, False), ' + chr(34) + 'drop-food' + chr(34) + ' (options: True, False). You will be provided with environment information. Keep your response concise, under 45 tokens.'"
    "    prompt_text = 'This is your current environment: -Highest Pheromone Concentration: ' + pheromone_text + ', -Nest Presence: ' + on_nest_text + ', -Stronger Nest Scent: ' + nest_text + ', -Food Concentration at your location: ' + sense_food_quantity + ', -Carrying Food Status ' + carrying_food_text "
    "    return prompt_text, system_text"
    "def process_step(file_name, step):"
    "    # Open the text file"
    "    with open(file_name, 'r') as file:"
    "        lines = file.readlines()"
    "    # Initialize variables"
    "    in_step_section = False"
    "    in_ant_section = False"
    "    actions_list = []"
    "    ant_info = {}"
    "    # Iterate through the lines"
    "    for line in lines:"
    "        # Check for the start of the step section"
    "        if line.strip() == f'step: {step}':"
    "            in_step_section = True"
    "            continue"
    "        # Check for the end of the step section"
    "        if line.strip() == 'end step':"
    "            if in_step_section:"
    "                break"
    "            else:"
    "                continue"
    "        # If we are in the correct step section, look for ant sections"
    "        if in_step_section:"
    "            if line.startswith('Start-AntID:'):"
    "                in_ant_section = True"
    "                ant_id = line.split(':')[1].strip()"
    "                # Initialize variables for the new ant"
    "                ant_info = {"
    "                    'AntID': ant_id,"
    "                    'move': False,"
    "                    'rotate_right': False,"
    "                    'rotate_left': False,"
    "                    'rotate_random_l': False,"
    "                    'rotate_random_r': False,"
    "                    'pick_up_food': False,"
    "                    'drop_pheromone': False,"
    "                    'drop_food': False,"
    "                    'action_ok': False"
    "                }"
    "                continue"
    "            if line.startswith('End-AntID:'):"
    "                end_ant_id = line.split(':')[1].strip()"
    "                if in_ant_section and end_ant_id == ant_info['AntID']:"
    "                    in_ant_section = False"
    "                    # Add the ant_info to actions_list as a list of its values"
    "                    actions_list.append(["
    "                        ant_info['AntID'],"
    "                        ant_info['action_ok'],"
    "                        ant_info['move'],"
    "                        ant_info['rotate_right'],"
    "                        ant_info['rotate_left'],"
    "                        ant_info['rotate_random_l'],"
    "                        ant_info['rotate_random_r'],"
    "                        ant_info['pick_up_food'],"
    "                        ant_info['drop_pheromone'],"
    "                        ant_info['drop_food']"
    "                    ])"
    "                continue"
    "            # If we are in the correct ant section, check for the required texts"
    "            if in_ant_section:"
    "                if 'Parser ok' in line:"
    "                    ant_info['action_ok'] = True"
    "                if '--- action move' in line:"
    "                    ant_info['move'] = True"
    "                if '--- action rotate-right' in line:"
    "                    ant_info['rotate_right'] = True"
    "                if '--- action rotate-left' in line:"
    "                    ant_info['rotate_left'] = True"
    "                if '--- action rotate-random-l' in line:"
    "                    ant_info['rotate_random_l'] = True"
    "                if '--- action rotate-random-r' in line:"
    "                    ant_info['rotate_random_r'] = True"
    "                if '--- action pick-up-food' in line:"
    "                    ant_info['pick_up_food'] = True"
    "                if '--- action drop-pheromone' in line:"
    "                    ant_info['drop_pheromone'] = True"
    "                if '--- action drop-food' in line:"
    "                    ant_info['drop_food'] = True"
    "    # Return the actions list"
    "    return actions_list    "
   )
end 

to-report get_llm_data
   let llm_data py:runresult "elements_list"
   report llm_data
end 

to-report populate_ant_with_llm_data [ llm_data ]
  let parse_ok item 0 llm_data
  let return_ok true
  ifelse parse_ok = "True" [
    print "Parser ok"
    set action-move_forward item 2 llm_data
    set action-rotate item 3 llm_data
    set action-pick_up_food item 4 llm_data
    set action-drop_pheromone item 5 llm_data
    set action-drop_food item 6 llm_data
    set action-status-ok true
    set action-status-code 0

    if action-pick_up_food = "true" [
      print "--- action pick-up-food"
      if food > 0 and sense-carrying-food != "True" [
        set food food - 1
        set sense-carrying-food "True"
        set color green
      ]
    ]

    if action-move_forward = "true" [
      print "--- action move"
      ifelse not can-move? ant_speed [ rt 180 ][fd ant_speed]
    ]

    ifelse action-rotate != "none" [
      ifelse action-rotate = "right" [
        rt rotation-ang
        print "--- action rotate-right"
      ]
      [
        ifelse action-rotate = "left" [
          rt -1 * rotation-ang
          print "--- action rotate-left"
        ]
        [
          ifelse action-rotate = "180deg" [
            rt -180
            print "--- action rotate-180deg"
          ]
          [
             ifelse ( random 10 ) > 4 [ rt rotation-ang / 2 fd 0 print "--- action rotate-random-r" ][ rt -1 * rotation-ang / 2 fd 0 print "--- action rotate-random-l" ]
          ]
        ]
      ]
    ]
    [
      print "--- action rotate-none"
    ]

    if action-drop_pheromone = "true" [
      print "--- action drop-pheromone"
      set chemical chemical + 60
    ]

    if action-drop_food = "true" [
      print "--- action drop-food"
      if sense-carrying-food = "True" and nest? [
        set sense-carrying-food "False"
        set food_collected food_collected + 1
        set color red
      ]
    ]
  ]
  [
    print "Parser error"
    set action-status-ok false
    set action-status-code 1
    set return_ok false
  ]
  print "end parser"
  report return_ok
end 

to run_llm
  print (word "Start-AntID: " ant-id)
  let populate_prompt (word "prepared_prompt, system_prompt = create_prompt('" sense-pheromone-left "', '" sense-pheromone-front "','" sense-pheromone-right "','" sense-on-nest "','" sense-nest-left "','" sense-nest-front "','" sense-nest-right "','" sense-food-quantity "','" sense-carrying-food "')" )
  py:run populate_prompt
  py:run "elements_list = []"
  py:run "print('User prompt: ' + prepared_prompt)"
  py:run "print('Complete prompt: ' + system_prompt + prepared_prompt)"
  py:run "response = client.chat.completions.create(model= 'gpt-4o-2024-08-06', timeout=15, max_tokens=500, messages=[ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': prepared_prompt}], temperature=0.1)"
  py:run "response = response.choices[0].message.content"
  py:run "parse_response(response)"
  print "--------------- llm data: ----------------"
  let llm_data get_llm_data
  let populate_ok populate_ant_with_llm_data llm_data
  print (word "End-AntID: " ant-id)
end 

to-report nest-scent-at-angle-llm [angle]
  let p patch-right-and-ahead angle 2 ;2
  if p = nobody [ report 0 ]
  report [nest-scent] of p
end 

to-report nest-scent-at-angle [angle]
  let p patch-right-and-ahead angle 1
  if p = nobody [ report 0 ]
  report [nest-scent] of p
end 

to-report chemical-scent-at-angle [angle]
  let p patch-right-and-ahead angle 1
  if p = nobody [ report 0 ]
  report [chemical] of p
end 

to wiggle
  rt random 40
  lt random 40
  if not can-move? 1 [ rt 180 ]
end 

to-report chemical-scent-at-angle-llm [angle]
  let p patch-right-and-ahead angle 1 ;1
  if p = nobody [ report 0 ]
  let pchem [chemical] of p
  ifelse pchem > 5.0 [
    set pchem 5.0
  ]
  [
    if pchem < 0.001 [ set pchem 0 ]
  ]
  report pchem
end 

to-report food-in-front
  let p patch-ahead 1
  if p = nobody [ report 0 ]
  report [food] of p
end 

to sense-world

  ifelse nest? [
  set sense-on-nest "True"
  ][ set sense-on-nest "False" ]

  set sense-pheromone-front ( word precision (chemical-scent-at-angle-llm   0) 2 )
  set sense-pheromone-right ( word precision (chemical-scent-at-angle-llm  45) 2 )
  set sense-pheromone-left  ( word precision (chemical-scent-at-angle-llm -45) 2 )

  set sense-nest-front ( word precision (nest-scent-at-angle-llm   0) 2 )
  set sense-nest-right ( word precision (nest-scent-at-angle-llm  45) 2 )
  set sense-nest-left  ( word precision (nest-scent-at-angle-llm -45) 2 )

  set sense-food-quantity (word food) ;food-in-front
end 

to go_ants  ;; forever button
  let step_text ( word "step: " ticks )
  ask ants
  [
    ifelse ant-id < 5 [ ;Ants 0 to 4 are steered by LLM
      ifelse activate_llm [
        sense-world run_llm
      ]
      [
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        if recovered_ant_data_pick_up_food = true [
          if food > 0 and sense-carrying-food != "True" [
            set food food - 1
            set sense-carrying-food "True"
            set color green
            set food_pickup_time ticks
            set food_from_source food-source-number
            let food_text ( word food_from_source ", " steps_searching )
            print food_text
            set steps_searching 0
          ]
        ]

        if sense-carrying-food = "False" [ set steps_searching steps_searching + 1 ]

        if recovered_ant_data_move = true [
          ifelse not can-move? ant_speed [ rt 180 ][fd ant_speed]
        ]
        if recovered_ant_data_rotate_r = true [
          rt rotation-ang
        ]
        if recovered_ant_data_rotate_l = true [
          rt -1 * rotation-ang
        ]
        if recovered_ant_data_random_l = true [
          rt -1 * rotation-ang / 2 ; fd 2
          let x ( random 10 )
        ]
        if recovered_ant_data_random_r = true [
          rt rotation-ang / 2 ; fd 2
          let x ( random 10 )
        ]

        if recovered_ant_data_drop_pheromone = true [
          set chemical chemical + 60
        ]
        if recovered_ant_data_drop_food = true [
          if sense-carrying-food = "True" and nest? [
            set sense-carrying-food "False"
            set food_collected food_collected + 1
            set color red
            set food_drop_time ticks
            let food_return_duration food_drop_time - food_pickup_time
          ]
        ]
      ]
    ]
    [
      ifelse color = red
      [ look-for-food  ]       ;; not carrying food? look for it
      [ return-to-nest ]       ;; carrying food? take it back to nest
      wiggle
      fd 1
    ]
  ]
  diffuse chemical (diffusion-rate / 100)
  ask patches
  [ set chemical chemical * (100 - evaporation-rate) / 100  ;; slowly evaporate chemical
    recolor-patch ]
  set all-food-amounts lput food_collected all-food-amounts
  tick
end 

to return-to-nest  ;; turtle procedure
  ifelse nest?
  [ ;; drop food and head out again
    set food_collected food_collected + 1
    set color red
    rt 180
    set food_drop_time ticks
    let food_return_duration food_drop_time - food_pickup_time
  ]
  [ set chemical chemical + 60  ;; drop some chemical
    uphill-nest-scent ]         ;; head toward the greatest value of nest-scent
end 

to look-for-food  ;; turtle procedure
  if food > 0
  [ set color orange + 1     ;; pick up food
    set food food - 1        ;; and reduce the food source
    rt 180                   ;; and turn around
    set food_pickup_time ticks
    set food_from_source food-source-number
    let food_text ( word food_from_source ", " steps_searching )
    print food_text
    set steps_searching 0

    stop ]
  set steps_searching steps_searching + 1
  ;; go in the direction where the chemical smell is strongest
  if (chemical >= 0.05) and (chemical < 2)
  [ uphill-chemical ]
end 

;; sniff left and right, and go where the strongest smell is

to uphill-nest-scent  ;; turtle procedure
  let scent-ahead nest-scent-at-angle   0
  let scent-right nest-scent-at-angle  45
  let scent-left  nest-scent-at-angle -45
  if (scent-right > scent-ahead) or (scent-left > scent-ahead)
  [ ifelse scent-right > scent-left
    [ rt 45 ]
    [ lt 45 ] ]
end 

;; sniff left and right, and go where the strongest smell is

to uphill-chemical  ;; turtle procedure
  let scent-ahead chemical-scent-at-angle   0
  let scent-right chemical-scent-at-angle  45
  let scent-left  chemical-scent-at-angle -45
  if (scent-right > scent-ahead) or (scent-left > scent-ahead)
  [ ifelse scent-right > scent-left
    [ rt 45 ]
    [ lt 45 ] ]
end 

There is only one version of this model, created 5 days ago by Cristian Jimenez Romero.

Attached files

File Type Description Last updated
Ants GPT: OpenAI LLM and rule based agents.png preview Preview for 'Ants GPT: OpenAI LLM and rule based agents' 5 days ago, by Cristian Jimenez Romero Download

This model does not have any ancestors.

This model does not have any descendants.