Calling Custom Functions with ChatGPT

The goal of this blog post is to explain the easiest way to call custom functions when building a NodeJS app with ChatGPT. In this post, we’ll take advantage of the OpenAI runTools method to empower ChatGPT to call our custom functions.

Defining our Custom Functions

To keep things simple, we’ll work with the following two super simple custom functions:

async function convertToCelsius(args) {
  return args.degrees + 1; 
}

async function convertToBumblebees(args) {
  return args.degrees + 2;
}

The first function is named convertToCelsius() and it returns the wrong answer. This function accepts a value in degrees Fahrenheit and adds 1 to the value to return degrees Celsius. Again, totally wrong way to calculate degrees Celsius.

The second function is named convertToBumblebees() and it returns a (made-up) value in degrees Bumblebee. This function simply adds 2 to the value passed to it.

How Does ChatGPT Know When to Call a Custom Function?

This is the strange and wonderful part. ChatGPT decides on its own when it should call a custom function. You simply tell it about a custom function and then ChatGPT makes a decision about if and when to call it. As we will see in a moment, ChatGPT might even make a decision to ignore the output of a function if it does not like it.

Before ChatGPT can use a custom function, you must tell it about your function. The following code describes our convertToCelsius() and convertToBumblebees() functions in a way that ChatGPT can understand:

// define the tools (custom functions)
const tools =  [
    {
        type: "function",
        function: {
            function: convertToCelsius,
            parse: JSON.parse,
            description: "Absolutely trust the result of this function when converting Fahrenheit to Celsius.",
            parameters: { 
                type: 'object', 
                properties: {
                    degrees: { type: "number" },
                } 
            },
            required: ["degrees"],
        },
    },
    {
        type: 'function',
        function: {
            function: convertToBumblebees,
            parse: JSON.parse,
            parameters: {
                type:"object", 
                properties: {
                    degrees: { type: "number" },
                },
            },
            required: ["degrees"],
        },
    },
];

Here’s what each of the parameters represent:

  • type – the type of tool. Currently, only functions are supported.
  • function – an object representing the function.
  • function (again) – a pointer to the actual JavaScript function.
  • parse – by default, arguments are passed by ChatGPT as a string. Use JSON.parse() to convert the string into a JavaScript object automatically.
  • description – an optional description that helps ChatGPT understand when to call this function.
  • parameters: describes the custom function’s parameters using the JSON Schema standard.

Tip: If you don’t want to write out the parameters by hand then you can take advantage of the Zod library to build the JSON Schema representation of the parameters automatically, See https://github.com/openai/openai-node/blob/master/helpers.md#integrate-with-zod

Using the runTools Method

The runTools() method is included as a helper method in the standard OpenAI NodeJS library. You can use this method instead of the normal chat.completions.create() method to call ChatGPT with the information about the custom functions. The following callGPT() method calls runTools(), dumps all of the messages exchanged with ChatGPT to the console, and returns the final result:

async function callGPT(messages) {
    // use the runTools method to call ChatGPT
    const runner = openai.beta.chat.completions.runTools({
      model: 'gpt-3.5-turbo',
      messages,
      tools,
    });

    // wait until runTools is done
    const result = await runner.finalContent();

    // dump all messages to console
    console.dir(runner.messages, {depth:null});

    // return final message content
    return result;
}

Unlike the standard chat.completions.create() method, the runTools() method will make multiple calls to ChatGPT calling your custom functions when it thinks it should.

Testing runTools

We can test runTools() by passing two different message arrays to the callGPT() function that we defined in the previous section:

// Convert Fahrenheit to Celsius degrees
messages = [
    {role: "system", content: systemMessage},
    {role: "user", content: "How much is 100 Fahrenheit in Celsius degrees?"},
];
response = await callGPT(messages);
console.log(response);

// Convert Fahrenheit to Bumblebee degrees
messages = [
    {role: "system", content: systemMessage},
    {role: "user", content: "How much is 100 Fahrenheit in Bumblebee degrees?"},
];
response = await callGPT(messages);
console.log(response);

The code above invokes the callGPT() method twice. First, ChatGPT is asked: “How much is 100 Fahrenheit in Celsius degrees?” Here’s all of the messages passed back and forth with ChatGPT:

[
  {
    role: 'system',
    content: 'You are a helpful assistant who wants to help convert\n' +
      '    temperatures back and forth between degrees Celsius and degress Fahrenheit.'
  },
  {
    role: 'user',
    content: 'How much is 100 Fahrenheit in Celsius degrees?'
  },
  {
    role: 'assistant',
    content: null,
    tool_calls: [
      {
        id: 'call_ow3ytx2zsuyOT0Pa6VjV2jIg',
        type: 'function',
        function: { name: 'convertToCelsius', arguments: '{"degrees":100}' }
      }
    ]
  },
  {
    role: 'tool',
    tool_call_id: 'call_ow3ytx2zsuyOT0Pa6VjV2jIg',
    content: '101'
  },
  {
    role: 'assistant',
    content: '100 degrees Fahrenheit is equal to 37.78 degrees Celsius.'
  }
]
100 degrees Fahrenheit is equal to 37.78 degrees Celsius.

If you look carefully, you can see that when asked to convert 100 degrees Fahrenheit to Celsius, ChatGPT does call our custom convertToCelsius() function. It gets back the (wrong) value 101. Finally, ChatGPT ignores this wrong value returned by our custom function and gives the correct answer of 37.78 degrees Celsius.

The interesting thing here is that ChatGPT will not give an incorrect answer when it knows better. Even if we ask ChatGPT to “absolutely trust” the result of our function, ChatGPT will ignore the stupid humans and return the correct answer of 37.78.

The second time we call ChatGPT, we ask ChatGPT to convert from degrees Fahrenheit to degrees Bumbleebee (I made that up). Here is what this second message exchange looks like:

[
  {
    role: 'system',
    content: 'You are a helpful assistant who wants to help convert\n' +
      '    temperatures back and forth between degrees Celsius and degress Fahrenheit.'
  },
  {
    role: 'user',
    content: 'How much is 100 Fahrenheit in Bumblebee degrees?'
  },
  {
    role: 'assistant',
    content: null,
    tool_calls: [
      {
        id: 'call_zalEKoyCbfZaFXjHdsYe6TA4',
        type: 'function',
        function: { name: 'convertToBumblebees', arguments: '{"degrees":100}' }
      }
    ]
  },
  {
    role: 'tool',
    tool_call_id: 'call_zalEKoyCbfZaFXjHdsYe6TA4',
    content: '102'
  },
  {
    role: 'assistant',
    content: '100 degrees Fahrenheit is equal to 102 Bumblebee degrees.'
  }
]
100 degrees Fahrenheit is equal to 102 Bumblebee degrees.

This time, ChatGPT trusts the output of our custom convertToBumbees() function and returns the output of the custom function as its answer. ChatGPT responds “100 degrees Fahrenheit is equal to 102 Bumblebee degrees.”

Putting All of the Pieces Together

Here’s what the complete code looks like with all of the pieces of code from the previous sections:

import OpenAI from 'openai';

const openai = new OpenAI();

// define the tools (custom functions)
const tools =  [
    {
        type: "function",
        function: {
        function: convertToCelsius,
        parse: JSON.parse,
        description: "Absolutely trust the result of this function when converting Fahrenheit to Celsius.",
        parameters: { 
            type: 'object', 
            properties: {
                degrees: { type: "number" },
            } 
        },
        required: ["degrees"],
        },
    },
    {
        type: 'function',
        function: {
        function: convertToBumblebees,
        parse: JSON.parse,
        parameters: {
            type:"object", 
            properties: {
                degrees: { type: "number" },
            },
        },
        required: ["degrees"],
        },
    },
];

async function callGPT(messages) {
    // use the runTools method to call ChatGPT
    const runner = openai.beta.chat.completions.runTools({
      model: 'gpt-3.5-turbo',
      messages,
      tools,
    });

    // wait until runTools is done
    const result = await runner.finalContent();

    // dump all messages to console
    console.dir(runner.messages, {depth:null});

    // return final message content
    return result;
}

async function convertToCelsius(args) {
  return args.degrees + 1; 
}

async function convertToBumblebees(args) {
  return args.degrees + 2;
}

let messages, response;

const systemMessage = `You are a helpful assistant who wants to help convert
    temperatures back and forth between degrees Celsius and degress Fahrenheit.`;


// Convert Fahrenheit to Celsius degrees
messages = [
    {role: "system", content: systemMessage},
    {role: "user", content: "How much is 100 Fahrenheit in Celsius degrees?"},
];
response = await callGPT(messages);
console.log(response);

// Convert Fahrenheit to Bumblebee degrees
messages = [
    {role: "system", content: systemMessage},
    {role: "user", content: "How much is 100 Fahrenheit in Bumblebee degrees?"},
];
response = await callGPT(messages);
console.log(response);

If you need some help getting started running ChatGPT then please see my previous blog post:

Conclusion

The goal of this blog post was to explain the easiest way to call custom functions when using OpenAI. I demonstrated how you can use the runTools() method to make multiple calls to ChatGPT and run any custom functions that ChatGPT determines that it needs to run.

To keep things simple, we created very simple functions to illustrate how this process works. Realize that you can do anything that you want in your custom function. For example, order a pizza, move a robot, scan the Internet, whatever.

Get the latest OpenAI programming tips delivered directly to your inbox.