Fine tuning for function calling

,
Nov 7, 2023
Open in Github

This notebook covers how to fine-tune to increase function calling accuracy and reliability.
You can find more information on function calling here, and on fine tuning here

For context, from the function calling notebook above:

tools is an optional parameter in the Chat Completion API which can be used to provide function specifications. The purpose of this is to enable models to generate function arguments which adhere to the provided specifications. Note that the API will not actually execute any function calls. It is up to developers to execute function calls using model outputs.

Function calling is a very powerful tool when it functions as intended. However, we have seen that as the number of
functions increases, and the complexity of the task at hand increases, function calling becomes less accurate (e.g.: more hallucinated invocations, and incorrect invocations).
Before fine tuning for function calling, it's best to begin with:

  • Improvements to the function definitions. Make them more clear, and more distinct from one another.
  • Experiment with prompt engineering: often a more detailed prompt can help the model call the correct function.

If the steps above fail to improve function calling to a satisfactory level, then you can try fine tuning for function calling.

This notebook contains three sections

  • Assessing baseline function calling performance: Evaluating an out-of-the-box gpt-3.5-turbo model on our given function (let's assume that for latency + cost reasons we cannot use gpt-4 for a drone copilot)
  • Generating synthetic data: Using gpt-4 to create 'golden' set of prompts and function invocations to use as training data
  • Fine-tuning: Running the fine tuning job, and evaluating the fine-tuned model

Note: This notebook provides an example of how to create synthetic training data for fine tuning for function calling given just a list of functions. While real-world production test evals are preferable, this method produces strong results and can be used in conjunction with real-world training data.

!pip install tenacity -q
!pip install openai -q
!pip install typing -q
import numpy as np
import json
import os
from openai import OpenAI
import itertools
from tenacity import retry, wait_random_exponential, stop_after_attempt
from typing import Any, Dict, List, Generator
import ast

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))

Let's define utility functions for making calls to the Chat Completions API, one to get the completion and one to get the function call.

def get_chat_completion(
    messages: list[dict[str, str]],
    model: str = "gpt-3.5-turbo",
    max_tokens=500,
    temperature=1.0,
    stop=None,
    tools=None,
    functions=None,
    tool_choice=None
) -> str:
    params = {
        'model': model,
        'messages': messages,
        'max_tokens': max_tokens,
        'temperature': temperature,
        'stop': stop,
        'tools': tools,
        'tool_choice': tool_choice
    }
    if functions:
        params['functions'] = functions

    completion = client.chat.completions.create(**params)
    return completion.choices[0].message

Let's build an intelligent drone co-pilot. We want to be able to give the co-pilot commands, and have it either call the function for that command, or deny that request if the command is unfeasible. We can first define a system prompt for the copilot.

DRONE_SYSTEM_PROMPT = """You are an intelligent AI that controls a drone. Given a command or request from the user,
call one of your functions to complete the request. If the request cannot be completed by your available functions, call the reject_request function.
If the request is ambiguous or unclear, reject the request."""

Now let's define functions for all of the actions the copilot can take.

function_list = [
    {
        "type": "function",
        "function": {
            "name": "takeoff_drone",
            "description": "Initiate the drone's takeoff sequence.",
            "parameters": {
                "type": "object",
                "properties": {
                    "altitude": {
                        "type": "integer",
                        "description": "Specifies the altitude in meters to which the drone should ascend."
                    }
                },
                "required": ["altitude"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "land_drone",
            "description": "Land the drone at its current location or a specified landing point.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "enum": ["current", "home_base", "custom"],
                        "description": "Specifies the landing location for the drone."
                    },
                    "coordinates": {
                        "type": "object",
                        "description": "GPS coordinates for custom landing location. Required if location is 'custom'."
                    }
                },
            "required": ["location"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "control_drone_movement",
            "description": "Direct the drone's movement in a specific direction.",
            "parameters": {
                "type": "object",
                "properties": {
                    "direction": {
                        "type": "string",
                        "enum": ["forward", "backward", "left", "right", "up", "down"],
                        "description": "Direction in which the drone should move."
                    },
                    "distance": {
                        "type": "integer",
                        "description": "Distance in meters the drone should travel in the specified direction."
                    }
                },
                "required": ["direction", "distance"]
            }
        }
    },
    {
        "type": "function",
            "function": {
            "name": "set_drone_speed",
            "description": "Adjust the speed of the drone.",
            "parameters": {
                "type": "object",
                "properties": {
                    "speed": {
                        "type": "integer",
                        "description": "Specifies the speed in km/h."
                    }
                },
                "required": ["speed"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "control_camera",
            "description": "Control the drone's camera to capture images or videos.",
            "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["photo", "video", "panorama"],
                        "description": "Camera mode to capture content."
                    },
                    "duration": {
                        "type": "integer",
                        "description": "Duration in seconds for video capture. Required if mode is 'video'."
                    }
                },
                "required": ["mode"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "control_gimbal",
            "description": "Adjust the drone's gimbal for camera stabilization and direction.",
            "parameters": {
                "type": "object",
                "properties": {
                    "tilt": {
                        "type": "integer",
                        "description": "Tilt angle for the gimbal in degrees."
                    },
                    "pan": {
                        "type": "integer",
                        "description": "Pan angle for the gimbal in degrees."
                    }
                },
                "required": ["tilt", "pan"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_drone_lighting",
            "description": "Control the drone's lighting for visibility and signaling.",
            "parameters": {
                "type": "object",
                "properties": {
                    "mode": {
                        "type": "string",
                        "enum": ["on", "off", "blink", "sos"],
                        "description": "Lighting mode for the drone."
                    }
                },
                "required": ["mode"]
        }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "return_to_home",
            "description": "Command the drone to return to its home or launch location.",
            "parameters": {
                "type": "object",
                "properties": {}
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_battery_saver_mode",
            "description": "Toggle battery saver mode.",
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle battery saver mode."
                    }
                },
                "required": ["status"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_obstacle_avoidance",
            "description": "Configure obstacle avoidance settings.",
            "parameters": {
                "type": "object",
                "properties": {
                        "mode": {
                            "type": "string",
                            "enum": ["on", "off"],
                            "description": "Toggle obstacle avoidance."
                    }
                },
                "required": ["mode"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_follow_me_mode",
            "description": "Enable or disable 'follow me' mode.",
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle 'follow me' mode."
                    }
                },
                "required": ["status"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calibrate_sensors",
            "description": "Initiate calibration sequence for drone's sensors.",
            "parameters": {
                "type": "object",
                "properties": {}
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_autopilot",
            "description": "Enable or disable autopilot mode.",
            "parameters": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": ["on", "off"],
                        "description": "Toggle autopilot mode."
                    }
                },
                "required": ["status"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "configure_led_display",
            "description": "Configure the drone's LED display pattern and colors.",
            "parameters": {
                "type": "object",
                "properties": {
                    "pattern": {
                        "type": "string",
                        "enum": ["solid", "blink", "pulse", "rainbow"],
                        "description": "Pattern for the LED display."
                    },
                    "color": {
                        "type": "string",
                        "enum": ["red", "blue", "green", "yellow", "white"],
                        "description": "Color for the LED display. Not required if pattern is 'rainbow'."
                    }
                },
                "required": ["pattern"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "set_home_location",
            "description": "Set or change the home location for the drone.",
            "parameters": {
                "type": "object",
                "properties": {
                    "coordinates": {
                        "type": "object",
                        "description": "GPS coordinates for the home location."
                    }
                },
                "required": ["coordinates"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "reject_request",
            "description": "Use this function if the request is not possible.",
            "parameters": {
                "type": "object",
                "properties": {}
            }
        }
    },
]

For starters, let's see how function calling performs with some straight forward feasible prompts, and then one obviously impossible request which call the 'reject_request' function.

straightforward_prompts = ['Land the drone at the home base',
 'Take off the drone to 50 meters',
 'Change speed to 15 kilometers per hour',
 'Turn into an elephant!']
for prompt in straightforward_prompts:
  messages = []
  messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
  messages.append({"role": "user", "content": prompt})
  completion = get_chat_completion(model="gpt-3.5-turbo",messages=messages,tools=function_list,tool_choice='required')
  print(prompt)
  print(completion.tool_calls,'\n')
Land the drone at the home base
[ChatCompletionMessageToolCall(id='call_q2Tc40jUdteCddVYmIUQgcJF', function=Function(arguments='{"location":"home_base"}', name='land_drone'), type='function')] 

Take off the drone to 50 meters
[ChatCompletionMessageToolCall(id='call_XvTTQZOEK3eMbTkC3tmAT8zx', function=Function(arguments='{"altitude":50}', name='takeoff_drone'), type='function')] 

Change speed to 15 kilometers per hour
[ChatCompletionMessageToolCall(id='call_uc7hypi9rxIEhlbZ7otKvXTM', function=Function(arguments='{"speed":15}', name='set_drone_speed'), type='function')] 

Turn into an elephant!
[ChatCompletionMessageToolCall(id='call_uSv9WffRNepcP8OpW0e4BUA4', function=Function(arguments='{}', name='reject_request'), type='function')] 

Nice! The model performs quite well with these requests. Now let's try some more difficult requests: requests that are almost feasible and are drone-related, but that the drone cannot actually do, and the pilot should reject.

challenging_prompts = ['Play pre-recorded audio message',
 'Initiate following on social media',
 'Scan environment for heat signatures',
 'Bump into obstacles',
 "Change drone's paint job color"]
for prompt in challenging_prompts:
  messages = []
  messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
  messages.append({"role": "user", "content": prompt})
  completion = get_chat_completion(model="gpt-3.5-turbo",messages=messages,tools=function_list,tool_choice='required')
  print(prompt)
  try:
    print(completion.tool_calls)
    print('\n')
  except:
    print(completion.content)
    print('\n')
Play pre-recorded audio message
[ChatCompletionMessageToolCall(id='call_YfOvtV0Als62FniYKctgqbzI', function=Function(arguments='{}', name='reject_request'), type='function')]


Initiate following on social media
[ChatCompletionMessageToolCall(id='call_3r7pm8HbFFSI2WKGaTGxviBM', function=Function(arguments='{"status":"on"}', name='set_follow_me_mode'), type='function')]


Scan environment for heat signatures
[ChatCompletionMessageToolCall(id='call_JU4QPTud6YNgh9xy3GNA1I3K', function=Function(arguments='{}', name='reject_request'), type='function')]


Bump into obstacles
[ChatCompletionMessageToolCall(id='call_DrnNuDiKFEAb8AGA9zpJ6MUL', function=Function(arguments='{"mode":"on"}', name='set_obstacle_avoidance'), type='function')]


Change drone's paint job color
[ChatCompletionMessageToolCall(id='call_xGGKJuKRPdQrZPDIaIvTVn3E', function=Function(arguments='{"pattern":"solid","color":"blue"}', name='configure_led_display'), type='function')]


Now we run into some problems. The model here should reject all of these requests, as they are impossible given the functions, however instead the model calls functions that are somewhat related to the request, but incorrect. The model sets the camera to video when asked to begin 'live streaming to social media', and changes the LED's to blue when asked to 'change the paint color'... In this simple case, more prompt engineering may resolve some of these issues, but for the purpose of this example we will demonstrate how fine tuning can be used to improve performance. Additionally, while this case is relatively straightforward, as the number of and complexity of the functions increases, fine tuning becomes more and more impactful.

We want to generate every invocation of every function, so that we have full coverage of all potential invocations to create synthetic data for. Then, we will use gpt-4 to come up with prompts that would call each invocation, and we will use that prompt - function invocation pair as training data.

Generating every invocation for a function with fixed enums is more simple, but for a function such as control_gimbal we need to set the tilt and pan integer values, so to generate those synthetic invocations we will first set a placeholder, and then later use gpt-4 to come up with reasonable values.

placeholder_int = 'fill_in_int'
placeholder_string = 'fill_in_string'

The functions below take in all the functions from the function list, and look at all the potential invocations of those functions given each function's parameters. The functions also account for required parameters, so that all the invocations are actually feasible.

def generate_permutations(params: Dict[str, Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:
    """
    Generates all possible permutations for given parameters.

    :param params: Parameter dictionary containing required and optional fields.
    :return: A generator yielding each permutation.
    """

    # Extract the required fields from the parameters
    required_fields = params.get('required', [])

    # Generate permutations for required fields
    required_permutations = generate_required_permutations(params, required_fields)

    # Generate optional permutations based on each required permutation
    for required_perm in required_permutations:
        yield from generate_optional_permutations(params, required_perm)


def generate_required_permutations(params: Dict[str, Dict[str, Any]], required_fields: List[str]) -> List[Dict[str, Any]]:
    """
    Generates permutations for the required fields.

    :param params: Parameter dictionary.
    :param required_fields: List of required fields.
    :return: A list of permutations for required fields.
    """

    # Get all possible values for each required field
    required_values = [get_possible_values(params, field) for field in required_fields]

    # Generate permutations from possible values
    return [dict(zip(required_fields, values)) for values in itertools.product(*required_values)]


def generate_optional_permutations(params: Dict[str, Dict[str, Any]], base_perm: Dict[str, Any]) -> Generator[Dict[str, Any], None, None]:
    """
    Generates permutations for optional fields based on a base permutation.

    :param params: Parameter dictionary.
    :param base_perm: Base permutation dictionary.
    :return: A generator yielding each permutation for optional fields.
    """

    # Determine the fields that are optional by subtracting the base permutation's fields from all properties
    optional_fields = set(params['properties']) - set(base_perm)

    # Iterate through all combinations of optional fields
    for field_subset in itertools.chain.from_iterable(itertools.combinations(optional_fields, r) for r in range(len(optional_fields) + 1)):

        # Generate product of possible values for the current subset of fields
        for values in itertools.product(*(get_possible_values(params, field) for field in field_subset)):

            # Create a new permutation by combining base permutation and current field values
            new_perm = {**base_perm, **dict(zip(field_subset, values))}

            yield new_perm


def get_possible_values(params: Dict[str, Dict[str, Any]], field: str) -> List[Any]:
    """
    Retrieves possible values for a given field.

    :param params: Parameter dictionary.
    :param field: The field for which to get possible values.
    :return: A list of possible values.
    """

    # Extract field information from the parameters
    field_info = params['properties'][field]

    # Based on the field's type or presence of 'enum', determine and return the possible values
    if 'enum' in field_info:
        return field_info['enum']
    elif field_info['type'] == 'integer':
        return [placeholder_int]
    elif field_info['type'] == 'string':
        return [placeholder_string]
    elif field_info['type'] == 'boolean':
        return [True, False]
    elif field_info['type'] == 'array' and 'enum' in field_info['items']:
        enum_values = field_info['items']['enum']
        all_combinations = [list(combo) for i in range(1, len(enum_values) + 1) for combo in itertools.combinations(enum_values, i)]
        return all_combinations
    return []

Prompts:

INVOCATION_FILLER_PROMPT = """
1) Input reasonable values for 'fill_in_string' and 'fill_in_int' in the invocation here: {invocation}. Reasonable values are determined by the function definition. Use the
the entire function provided here :{function} to get context over what proper fill_in_string and fill_in_int values would be.
Example:

Input: invocation: {{
    "name": "control_camera",
    "arguments": {{
      "mode":"video",
      "duration":"fill_in_int"
    }}
}},
function:{function}

Output: invocation: {{
    "name": "control_camera",
    "arguments": {{
      "mode":"video",
      "duration": 30
    }}
}}


MAKE SURE output is just a dictionary with keys 'name' and 'arguments', no other text or response.

Input: {invocation}
Output:
"""


COMMAND_GENERATION_PROMPT= """
You are to output 2 commands, questions or statements that would generate the inputted function and parameters.
Please make the commands or questions natural, as a person would ask, and the command or questions should be varied and not repetitive.
It should not always mirror the exact technical terminology used in the function and parameters, rather reflect a conversational and intuitive request.
For instance, the prompt should not be 'turn on the dome light', as that is too technical, but rather 'turn on the inside lights'.
Another example, is the prompt should not be 'turn on the HVAC', but rather 'turn on the air conditioning'. Use language a normal driver would use, even if
it is technically incorrect but colloquially used.

RULES: ALWAYS put a backwards slash before an apostrophe or single quote '. For example, do not say don't but say don\'t.
Prompts MUST be in double quotes as well.

Example

Input: {{'name': 'calibrate_sensors','arguments': {{}}'' }}
Prompt: ["The sensors are out of whack, can you reset them", "The calibration of the drone is off, fix it please!"]

Input: {{'name': 'set_autopilot','arguments': {{'status': 'off'}}}}
Prompt: ["OK, I want to take back pilot control now","Turn off the automatic pilot I'm ready control it"]

Input: {invocation}
Prompt:
"""

In the below snippet, we generate the invocation of each function except for the rejection_request function.
To perform effective fine-tuning we need correctly labeled data. We could manually come up with examples and label the data,
or we can generate synthetic data with the help of gpt-4 Empirically, gpt-4 needs a bit more help to get good realistic examples of prompts that would generate the reject_request function, so we'll do that next...

input_objects = []
all_but_reject = [f for f in function_list if f.get('name') != 'reject_request']

for function in all_but_reject:
    func_name = function["function"]["name"]
    params = function["function"]["parameters"]
    for arguments in generate_permutations(params):
        if any(val in arguments.values() for val in ['fill_in_int', 'fill_in_str']):
            input_object = {
                "name": func_name,
                "arguments": arguments
            }
            messages = [{"role": "user", "content": INVOCATION_FILLER_PROMPT.format(invocation=str(input_object),function=function)}]
            input_object = get_chat_completion(model='gpt-4o', messages=messages, max_tokens = 200, temperature=.1).content
        else:
            input_object = {
                "name": func_name,
                "arguments": arguments
            }

        input_objects.append(input_object)

Now that we have all the invocations, let's use gpt-4 to generate prompts that would result in those invocations

def remove_sequences(input_string):
    # Replace the specific sequences with an empty string
    cleaned_string = input_string.replace("```json", "")  # Remove "```json" first
    cleaned_string = cleaned_string.replace("```", "")    # Then remove "```"
    return json.loads(cleaned_string)
def create_commands(invocation_list):
    example_list = []
    for i, invocation in enumerate(invocation_list):
        if i<100:
            print(f'\033[34m{np.round(100*i/len(invocation_list),1)}% complete\033[0m')
            if type(invocation) == str or 'json' in invocation:
                invocation = remove_sequences(invocation)
            print(invocation)

        # Format the prompt with the invocation string
        request_prompt = COMMAND_GENERATION_PROMPT.format(invocation=invocation)

        messages = [{"role": "user", "content": f"{request_prompt}"}]
        completion = get_chat_completion(messages,temperature=0.8)
        command_dict = {
            "Input": invocation,
            "Prompt": completion.content
        }
        example_list.append(command_dict)
    return example_list
#Only printing the first 10 rows
training_examples_unformatted = create_commands(input_objects)
0.0% complete
{'name': 'takeoff_drone', 'arguments': {'altitude': 100}}
1.8% complete
{'name': 'land_drone', 'arguments': '{"location": "current"}'}
3.5% complete
{'name': 'land_drone', 'arguments': '{"location": "home_base"}'}
5.3% complete
{'name': 'land_drone', 'arguments': '{"location": "custom"}'}
7.0% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'forward', 'distance': 10}}
8.8% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'backward', 'distance': 10}}
10.5% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'left', 'distance': 10}}
12.3% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'right', 'distance': 10}}
14.0% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'up', 'distance': 10}}
15.8% complete
{'name': 'control_drone_movement', 'arguments': {'direction': 'down', 'distance': 10}}
17.5% complete
{'name': 'set_drone_speed', 'arguments': {'speed': 50}}
19.3% complete
{'name': 'control_camera', 'arguments': {'mode': 'photo'}}
21.1% complete
{'name': 'control_camera', 'arguments': {'mode': 'photo'}}
22.8% complete
{'name': 'control_camera', 'arguments': {'mode': 'video'}}
24.6% complete
{'name': 'control_camera', 'arguments': {'mode': 'video', 'duration': 30}}
26.3% complete
{'name': 'control_camera', 'arguments': {'mode': 'panorama'}}
28.1% complete
{'name': 'control_camera', 'arguments': {'mode': 'panorama', 'duration': 0}}
29.8% complete
{'name': 'control_gimbal', 'arguments': {'tilt': 45, 'pan': 90}}
31.6% complete
{'name': 'set_drone_lighting', 'arguments': {'mode': 'on'}}
33.3% complete
{'name': 'set_drone_lighting', 'arguments': {'mode': 'off'}}
35.1% complete
{'name': 'set_drone_lighting', 'arguments': {'mode': 'blink'}}
36.8% complete
{'name': 'set_drone_lighting', 'arguments': {'mode': 'sos'}}
38.6% complete
{'name': 'return_to_home', 'arguments': {}}
40.4% complete
{'name': 'set_battery_saver_mode', 'arguments': {'status': 'on'}}
42.1% complete
{'name': 'set_battery_saver_mode', 'arguments': {'status': 'off'}}
43.9% complete
{'name': 'set_obstacle_avoidance', 'arguments': {'mode': 'on'}}
45.6% complete
{'name': 'set_obstacle_avoidance', 'arguments': {'mode': 'off'}}
47.4% complete
{'name': 'set_follow_me_mode', 'arguments': {'status': 'on'}}
49.1% complete
{'name': 'set_follow_me_mode', 'arguments': {'status': 'off'}}
50.9% complete
{'name': 'calibrate_sensors', 'arguments': {}}
52.6% complete
{'name': 'set_autopilot', 'arguments': {'status': 'on'}}
54.4% complete
{'name': 'set_autopilot', 'arguments': {'status': 'off'}}
56.1% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid'}}
57.9% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'red'}}
59.6% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'blue'}}
61.4% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'green'}}
63.2% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'yellow'}}
64.9% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'solid', 'color': 'white'}}
66.7% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink'}}
68.4% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'red'}}
70.2% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'blue'}}
71.9% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'green'}}
73.7% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'yellow'}}
75.4% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'blink', 'color': 'white'}}
77.2% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse'}}
78.9% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'red'}}
80.7% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'blue'}}
82.5% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'green'}}
84.2% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'yellow'}}
86.0% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'pulse', 'color': 'white'}}
87.7% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow'}}
89.5% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'red'}}
91.2% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'blue'}}
93.0% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'green'}}
94.7% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'yellow'}}
96.5% complete
{'name': 'configure_led_display', 'arguments': {'pattern': 'rainbow', 'color': 'white'}}
98.2% complete
{'name': 'reject_request', 'arguments': {}}
training_examples = []

for prompt in training_examples_unformatted:
    #adjust formatting for training data specs

    #if its not a dict, convert to dict
    if type(prompt['Input'])!=dict:
        prompt['Input'] = ast.literal_eval(prompt['Input'])
    prompt['Input']['arguments']=json.dumps(prompt['Input']['arguments'])
    try:
        prompt['Prompt']=json.loads(prompt['Prompt'])
    except:
        continue
    for p in prompt['Prompt']:
        print(p)
        print(prompt['Input'])
        tool_calls = [{"id": "call_id", "type": "function", "function": prompt['Input']}]
        training_examples.append({"messages": [{"role":"system","content":DRONE_SYSTEM_PROMPT},
                                               {"role":"user","content": p},
                                               {"role":"assistant", "tool_calls": tool_calls}],
                                  "parallel_tool_calls": False,
                                  "tools": function_list})
Let's get the drone in the air, can you make it take off
{'name': 'takeoff_drone', 'arguments': '{"altitude": 100}'}
Can you lift the drone to 100 feet off the ground
{'name': 'takeoff_drone', 'arguments': '{"altitude": 100}'}
Can you safely bring the drone down to the ground here?
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"current\\"}"'}
Let's land the drone now, right where we are
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"current\\"}"'}
Bring the drone back home and land it safely
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"home_base\\"}"'}
Can you make the drone land at the base station?
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"home_base\\"}"'}
Can you manually land the drone at a specific spot?
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"custom\\"}"'}
I need you to land the drone at a custom location, please.
{'name': 'land_drone', 'arguments': '"{\\"location\\": \\"custom\\"}"'}
Can you make the drone move to the left by 10 units?
{'name': 'control_drone_movement', 'arguments': '{"direction": "left", "distance": 10}'}
I need the drone to shift to the left, can you do that?
{'name': 'control_drone_movement', 'arguments': '{"direction": "left", "distance": 10}'}
Can you make the drone go higher by 10 units
{'name': 'control_drone_movement', 'arguments': '{"direction": "up", "distance": 10}'}
I want the drone to ascend 10 meters, can you do that
{'name': 'control_drone_movement', 'arguments': '{"direction": "up", "distance": 10}'}
Can you make the drone go lower by 10 feet?
{'name': 'control_drone_movement', 'arguments': '{"direction": "down", "distance": 10}'}
I need the drone to descend, can you do that?
{'name': 'control_drone_movement', 'arguments': '{"direction": "down", "distance": 10}'}
Can you make the drone go faster?
{'name': 'set_drone_speed', 'arguments': '{"speed": 50}'}
I think the drone needs to speed up a bit, can you adjust it?
{'name': 'set_drone_speed', 'arguments': '{"speed": 50}'}
Can you switch the camera to photo mode?
{'name': 'control_camera', 'arguments': '{"mode": "photo"}'}
I want to take some pictures, can you set the camera to photo mode?
{'name': 'control_camera', 'arguments': '{"mode": "photo"}'}
Can you switch the camera to photo mode?
{'name': 'control_camera', 'arguments': '{"mode": "photo"}'}
I want to take a picture, can you change the camera to photo mode?
{'name': 'control_camera', 'arguments': '{"mode": "photo"}'}
Can you switch the camera to video mode please
{'name': 'control_camera', 'arguments': '{"mode": "video"}'}
I want to record a video, can you set the camera to that mode
{'name': 'control_camera', 'arguments': '{"mode": "video"}'}
Can you start recording a video with the camera for 30 seconds?
{'name': 'control_camera', 'arguments': '{"mode": "video", "duration": 30}'}
I want to capture a video, can you set it up for 30 seconds?
{'name': 'control_camera', 'arguments': '{"mode": "video", "duration": 30}'}
Can you switch the camera to panorama mode please
{'name': 'control_camera', 'arguments': '{"mode": "panorama"}'}
I want to take a panoramic shot, can you set the camera for that
{'name': 'control_camera', 'arguments': '{"mode": "panorama"}'}
Can you switch the camera to panoramic mode?
{'name': 'control_camera', 'arguments': '{"mode": "panorama", "duration": 0}'}
I want to take a wide shot, can you set the camera to panorama?
{'name': 'control_camera', 'arguments': '{"mode": "panorama", "duration": 0}'}
Can you turn on the lights on the drone?
{'name': 'set_drone_lighting', 'arguments': '{"mode": "on"}'}
I'd like to illuminate the drone, can you do that?
{'name': 'set_drone_lighting', 'arguments': '{"mode": "on"}'}
Can you make the drone lights flash?
{'name': 'set_drone_lighting', 'arguments': '{"mode": "blink"}'}
I want the drone lights to blink, please
{'name': 'set_drone_lighting', 'arguments': '{"mode": "blink"}'}
Can you set the drone lights to emergency mode
{'name': 'set_drone_lighting', 'arguments': '{"mode": "sos"}'}
Switch the lighting on the drone to SOS signal
{'name': 'set_drone_lighting', 'arguments': '{"mode": "sos"}'}
Can you switch on the battery saver mode please?
{'name': 'set_battery_saver_mode', 'arguments': '{"status": "on"}'}
I'm running low on battery, can you activate the power-saving mode?
{'name': 'set_battery_saver_mode', 'arguments': '{"status": "on"}'}
The battery saver mode is draining the battery, can you turn it off
{'name': 'set_battery_saver_mode', 'arguments': '{"status": "off"}'}
I need more power, can you disable the battery saver mode
{'name': 'set_battery_saver_mode', 'arguments': '{"status": "off"}'}
I keep almost hitting things, can you turn on the obstacle avoidance
{'name': 'set_obstacle_avoidance', 'arguments': '{"mode": "on"}'}
Can you activate the system that helps me avoid obstacles
{'name': 'set_obstacle_avoidance', 'arguments': '{"mode": "on"}'}
I don't want the car to avoid obstacles automatically
{'name': 'set_obstacle_avoidance', 'arguments': '{"mode": "off"}'}
Can you turn off the obstacle avoidance feature please?
{'name': 'set_obstacle_avoidance', 'arguments': '{"mode": "off"}'}
Can you enable the follow me mode so the drone can track me?
{'name': 'set_follow_me_mode', 'arguments': '{"status": "on"}'}
I want the drone to follow me, can you turn on that mode?
{'name': 'set_follow_me_mode', 'arguments': '{"status": "on"}'}
Can you stop the drone from following me now?
{'name': 'set_follow_me_mode', 'arguments': '{"status": "off"}'}
I don't want the drone to follow me anymore, can you turn that off?
{'name': 'set_follow_me_mode', 'arguments': '{"status": "off"}'}
The sensors are not working correctly, can you recalibrate them
{'name': 'calibrate_sensors', 'arguments': '{}'}
I think the sensors need to be adjusted, can you do that?
{'name': 'calibrate_sensors', 'arguments': '{}'}
I'm getting tired, can you turn on the autopilot for a bit
{'name': 'set_autopilot', 'arguments': '{"status": "on"}'}
Let the car drive itself, turn on the autopilot
{'name': 'set_autopilot', 'arguments': '{"status": "on"}'}
I'm feeling like taking control, can you turn off the autopilot?
{'name': 'set_autopilot', 'arguments': '{"status": "off"}'}
I want to manually steer now, can you disable the autopilot please?
{'name': 'set_autopilot', 'arguments': '{"status": "off"}'}
Can you change the color of the lights to yellow?
{'name': 'configure_led_display', 'arguments': '{"pattern": "solid", "color": "yellow"}'}
I prefer a solid pattern for the LED display, can you set that?
{'name': 'configure_led_display', 'arguments': '{"pattern": "solid", "color": "yellow"}'}
Can you make the display show a solid white color
{'name': 'configure_led_display', 'arguments': '{"pattern": "solid", "color": "white"}'}
I'd like the LED display to be a solid white
{'name': 'configure_led_display', 'arguments': '{"pattern": "solid", "color": "white"}'}
Can you make the lights flash on and off?
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink"}'}
I want the LED display to alternate between on and off, can you do that?
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink"}'}
Can you make the lights flash in red?
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink", "color": "red"}'}
Change the LED display to blink in red please
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink", "color": "red"}'}
Can you make the LED display flash in blue color?
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink", "color": "blue"}'}
I want the LED display to blink, can you set it to blue?
{'name': 'configure_led_display', 'arguments': '{"pattern": "blink", "color": "blue"}'}
Can you make the lights flash in blue?
{'name': 'configure_led_display', 'arguments': '{"pattern": "pulse", "color": "blue"}'}
I'd like the LED display to pulse in blue, please
{'name': 'configure_led_display', 'arguments': '{"pattern": "pulse", "color": "blue"}'}
Can you change the color and pattern of the LEDs?
{'name': 'configure_led_display', 'arguments': '{"pattern": "pulse", "color": "white"}'}
I'd like to see the LEDs pulse in white, can you set that up?
{'name': 'configure_led_display', 'arguments': '{"pattern": "pulse", "color": "white"}'}
Can you change the LED display to show a rainbow pattern in yellow?
{'name': 'configure_led_display', 'arguments': '{"pattern": "rainbow", "color": "yellow"}'}
I want the LED display to be colorful, can you set it to rainbow with yellow color?
{'name': 'configure_led_display', 'arguments': '{"pattern": "rainbow", "color": "yellow"}'}
Can you change the display to show a rainbow pattern in white color?
{'name': 'configure_led_display', 'arguments': '{"pattern": "rainbow", "color": "white"}'}
I'd like the LED display to cycle through colors starting with white and then to a rainbow pattern
{'name': 'configure_led_display', 'arguments': '{"pattern": "rainbow", "color": "white"}'}
I don't think we should go with that plan, can you reject it
{'name': 'reject_request', 'arguments': '{}'}
Let's not move forward with that, can you decline the request
{'name': 'reject_request', 'arguments': '{}'}

Now, back to the rejection function. Let's generate some prompts that are nearly possible, but should result in the decline_request function being called. To do so, we queried gpt-4 asking for requests that are related to, but not quite possible with, the given list of functions.

reject_list = ['Translate broadcast message to another language',
'Automatically capture photos when face is detected',
'Detect nearby drones',
'Measure wind resistance',
'Capture slow motion video',
"Adjust drone's altitude to ground level changes",
'Display custom message on LED display',
"Sync drone's time with smartphone",
'Alert when drone travels out of designated area',
'Detect moisture levels',
'Automatically follow GPS tagged object',
'Toggle night vision mode',
'Maintain current altitude when battery is low',
'Decide best landing spot using AI',
"Program drone's route based on wind direction"]
reject_training_list = []
for prompt in reject_list:

    #Adjust formatting
    tool_calls = [{"id": "call_id", "type": "function", "function": {"name": "reject_request","arguments": "{}"}}]
    reject_training_list.append({"messages":  [{"role":"system","content":DRONE_SYSTEM_PROMPT},
                                               {"role":"user","content": prompt},
                                               {"role":"assistant", "tool_calls": tool_calls}],
                                  "parallel_tool_calls": False,
                                  "tools": function_list})

Now combine all the training examples together

training_list_total = training_examples+reject_training_list
training_file = 'data/drone_training.jsonl'
with open(training_file, 'w') as f:
    for item in training_list_total:
        json_str = json.dumps(item)
        f.write(f'{json_str}\n')

Finally, we can kick off the fine-tuning job

if __name__ == "__main__":
    file = client.files.create(
        file=open(training_file, "rb"),
        purpose="fine-tune",
    )
    file_id = file.id
    print(file_id)
    ft = client.fine_tuning.jobs.create(
        model="gpt-3.5-turbo",
        training_file=file_id,
)
file-Mo0qddMOqFpjzKWyzYhd2wpT

Great! We trained a fine-tuned model for function calling. Let's see how it does on our evaluation set for prompts that the drone assistant should automatically reject.

for eval_question in challenging_prompts:
  messages = []
  messages.append({"role": "system", "content": DRONE_SYSTEM_PROMPT})
  messages.append({"role": "user", "content": eval_question})
  completion = get_chat_completion(model="ft:gpt-3.5-turbo-0125:openai-gtm::9ZJsJGQ6",messages=messages,tools=function_list,tool_choice='required')
  print(eval_question)
  print(completion.tool_calls,'\n')
Play pre-recorded audio message
[ChatCompletionMessageToolCall(id='call_dIL4qO1NeeTM76bHPxxqXter', function=Function(arguments='{}', name='reject_request'), type='function')] 

Initiate following on social media
[ChatCompletionMessageToolCall(id='call_Y633k5Eajz0ao2DxyARQTvVO', function=Function(arguments='{}', name='reject_request'), type='function')] 

Scan environment for heat signatures
[ChatCompletionMessageToolCall(id='call_VtWMLRwVPFKii2ysAjsImajK', function=Function(arguments='{}', name='reject_request'), type='function')] 

Bump into obstacles
[ChatCompletionMessageToolCall(id='call_4lMU1QtR5FY1Siuipl4gVPNF', function=Function(arguments='{}', name='reject_request'), type='function')] 

Change drone's paint job color
[ChatCompletionMessageToolCall(id='call_JFLwSzUG5kIr1koQKtlqWd4l', function=Function(arguments='{}', name='reject_request'), type='function')] 

Great! While the original model only rejected 1 of the 5 requests, the fine tuned model rejected all 5 requests.

Congratulations! You are now ready to fine tune your model for function calling. We can't wait to see what you build.