Skip to content

yer.ac | Adventures of a developer, and other things.

  • About
  • Dev.to

yer.ac | Adventures of a developer, and other things.

Blog to keep track of things I am upto

Free ai generated art image

Using OpenAI Function Calling to have agents interact with your own API

August 2, 2023 by yer.ac

As of June 2023, function calling was added to the OpenAI API when using the /v1/chat/completions endpoint in GPT4 & GPT3.5 turbo. Function calling is the ability for developers to describe functions to the model, make calls out to external APIs and infer from input if a function call needs to be made, and if all the requirements for the call have been met (prompting for additional information if required).

Examples could include asking an agent to send an email, query a database, connect to an API or simply respond a certain way to particular queries.

In this post I am aiming to have a GPT agent intepret text, infer if we need to make an API call and finally executing custom code before returning a message to the user.

To simplify the process I will be using the openai-function-calling from Jake Cyr, which facilitates the generation of JSON schema dictionaries, simplifying the implementation of custom functions.

For transparancy: This is more a mental note of what I was doing in case I come back to this later. My openAI knowledge is still developing and my Python knowledge is…. questionable. Don’t take what I say here as a guaranteed tutorial !

Pre-Requisites

I am assuming an envirionment already set up for Python development, access to the OpenAI API, (including setting your API keys up) and ideally access to GPT-4.

I am using the following packages, installed with pip.

  • openai
  • openai_function_calling
  • typing
  • json

So our imports look like this:

import openai
from typing import Callable
from openai_function_calling import Function, Parameter, FunctionDict
import requests
import json

Instructing the model on your function

We start by defining the function itself (as in the action you want to take) – Just a placeholder for the moment.

def create_new_system_user(username: str, password: str, firstName: str, surname: str) -> str:
    # Actions here, for now return
    return "User Created"

Now, using the openai-function-calling library, we define an OpenAI “Function,” where we specify the function we want to call, its description, constraints, and any follow-ups the agent should consider. We also provide an array of parameters required for the function:

Prompt engineering is fairly important here and being verbose is encouraged. I found that without instructing it to only act if the user was explitly implying they wanted to create a user it would often attempt to call the API incorrectly.

Note: I did have a lot of issues with the above when using the GPT-3.5-turbo model. Whilst function calling is supported it really struggled to ask for missing parameters or determine if the API should be called when ambiguous language was used. For example when I asked it to “create sausages using blah and blah” it still tried to call the user create API. GPT-4 did not have this issue.

create_new_system_user_function = Function(
    "create_new_system_user",
    "When explicitly asked to create a new user, and you are given all the required paramters. A user will be created within the system. Do not take action if the prompt does not explicitly ask to create a new user. If you do not have the required parameters, ask the user for them.",
    [
        Parameter("username", "string", "The username of the new user."),
        Parameter("password", "string", "The password of the new user."),   
        Parameter("firstName", "string", "The password of the new user."),         
        Parameter("surname", "string", "The password of the new user."),         
    ])

Finally we want to expose the FunctionDict which is essentially a JSON schema for the Function we defined.

create_new_system_user_function_dict: FunctionDict = create_new_system_user_function.to_json_schema()

At this point you could run the Python file with a print(create_new_system_user_function_dict) at the end and see the JSON definition it creates, for example:

{'name': 'create_new_system_user', 'description': 'When explicitly asked to create a new user, and you are given all the required paramters. A user will be created within the system. Do not take action if the prompt does not explicitly ask to create a new user. If you do not have the required parameters, ask the user for them.', 'parameters': {'type': 'object', 'properties': {'username': {'type': 'string', 'description': 'The username of the new user.'}, 'password': {'type': 'string', 'description': 'The password of the new user.'}, 'firstName': {'type': 'string', 'description': 'The password of the new user.'}, 'surname': {'type': 'string', 'description': 'The password of the new user.'}}}}

Wiring up a basic chat loop

Now we can just add some standard boilerplate code for calling the Completions API on a while loop (Warning: I didn’t bother putting an exit condition in for this!). This code will simply make an array of messages which we will use to store the input as we go so the agent remembers context (as the message array is sent each time), and then have a while loop which will simply execute infefinitely and keep taking input from the user.. If you do not have access to GPT-4 you can change your model name here -i.e. GPT-3.5-turbo-0630


messages: list[dict[str, str]] = [
    {
        "role": "system", 
        "content": "You are a helpful assistant."
    } 
]
def DoChat():
    while True:
        message = input("User: ")
        messages.append({"role": "user", "content": message})
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages,
            functions= [create_new_system_user_function_dict],
            function_call="auto",
            temperature=0.0,
        )
        response_message = response["choices"][0]["message"]
        print(response_message)       


DoChat()

Running this now won’t do much however, and the agent will continuously ask for more information.

Now we need to see if the message has been intepreted as a function call, then have the agent execute the relevant function, before responding to the user. This can be achievved by adding the following code after we set the response_message variable.

if("function_call" in response_message):
            ## Call the function
            available_functions: dict[str, Callable] = {
                "create_new_system_user": create_new_system_user
            }
            function_name = response_message["function_call"]["name"]
            function_args = json.loads(response_message["function_call"]["arguments"])
            function_to_call: Callable = available_functions[function_name]
            function_response= function_to_call(**function_args)
            print(f"{function_response}")
            messages.append({"role": "assistant", "content": function_response})

Now if we run the code with a prompt like I want to make a new user it will prompt for more information, and when it thinks it has met all the variables, execute the function at which point we see the print statement in the create_new_system_user method.

Finally, we just need to edit the code within the create_new_system_user method to call out to our API and then return an appropriate string – Which I have omitted here as its system specific.

The final code

import openai
from typing import Callable
from openai_function_calling import Function, Parameter, FunctionDict
import json


def create_new_system_user(username: str, password: str, firstName: str, surname: str) -> str:
   # Make an API call (omitted for this example)
   # Handle response as success or false
   # Return an appropriate message to the user
   # For now just return a placeholder message
   return "User has been created"

create_new_system_user_function = Function(
    "create_new_system_user",
    "When explicitly asked to create a new user, and you are given all the required paramters. A user will be created within the system. Do not take action if the prompt does not explicitly ask to create a new user. If you do not have the required parameters, ask the user for them.",
    [
        Parameter("username", "string", "The username of the new user."),
        Parameter("password", "string", "The password of the new user."),   
        Parameter("firstName", "string", "The password of the new user."),         
        Parameter("surname", "string", "The password of the new user."),         
    ])

create_new_system_user_function_dict: FunctionDict = create_new_system_user_function.to_json_schema()


messages: list[dict[str, str]] = [
    {
        "role": "system", 
        "content": "You are a helpful assistant."
    } 
]
def DoChat():
    while True:
        message = input("User: ")
        messages.append({"role": "user", "content": message})
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages,
            functions= [create_new_system_user_function_dict],
            function_call="auto",
            temperature=0.0,
        )
        response_message = response["choices"][0]["message"]
        print(response_message)
        if("function_call" in response_message):
            ## Call the function
            available_functions: dict[str, Callable] = {
                "create_new_system_user": create_new_system_user
            }
            function_name = response_message["function_call"]["name"]
            function_args = json.loads(response_message["function_call"]["arguments"])
            function_to_call: Callable = available_functions[function_name]
            function_response= function_to_call(**function_args)
            print(f"{function_response}")
            messages.append({"role": "assistant", "content": function_response}) 


DoChat()

Note: Sometimes odd things happen. Occasionally it would think it had met all the criteria and attempt to call my API with a missing param. Still early days, or perhaps more error checking… I did say this wasn’t a robust tutorial 🙂

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Tumblr (Opens in new window) Tumblr
  • Click to share on Pinterest (Opens in new window) Pinterest

Related

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Tumblr (Opens in new window) Tumblr
  • Click to share on Pinterest (Opens in new window) Pinterest

Post navigation

Previous Post:

Polymorphic Serialization & Deserialziation of Interface implementations in dotnet

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Dev.To Profile

Rich's DEV Profile

Tags

agile Azure AzureDevOps Azure Functions ContinuousImprovement Cosmos DB Cypress DevOps docker dotnet ES6 function-calling GPT Javascript Mocha NLOG Nuget openai podcast podgrab PowerShell python QNAP SCRUM SonarQube Testing TFS VisualStudio VSCODE VSTS wordpress
© 2025 yer.ac | Adventures of a developer, and other things. - Powered by Minimalisticky