OpenAI Function Calling Explained: Chat Completions & Assistants API

OpenAI Function Calling Explained: Chat Completions & Assistants API

Introduction

In the rapidly evolving landscape of AI and chatbot technology, mastering the art of function calling within OpenAI's APIs has become a cornerstone for developers looking to push the boundaries of conversational AI. This blog delves into the intricate world of OpenAI's Chat Completions and Assistant APIs, unlocking the potential of custom function integration to revolutionize chatbot interactions.

Our journey through this blog will take you from the foundational concepts of function calling in the Chat Completions API to the more advanced, stateful interactions enabled by the Assistant API. We'll explore these concepts through the lens of a practical application: an e-commerce chatbot adept at handling complex user queries about order status. By the end of this guide, you'll not only grasp the theoretical aspects but also acquire hands-on skills for implementing these advanced functionalities in your own AI projects.

For those who prefer a more visual and interactive learning experience, don't miss our accompanying YouTube tutorial, which brings these concepts to life. Together, the blog and the video provide a comprehensive toolkit for any developer eager to explore the frontiers of AI-driven chatbot development.

Setting Up the Environment

Before we dive into the specifics of function calling with OpenAI's APIs, it's essential to ensure your development environment is ready. Here's a quick setup guide:

Installing the OpenAI Python Package: To interact with OpenAI's APIs, start by installing the OpenAI Python package. Run the following command in your Python environment:

pip install openai -q

Initializing the OpenAI Client: Next, initialize the OpenAI client in your Python script. This client will facilitate your interactions with OpenAI's services. Here's a sample initialization:

from openai import OpenAI
client = OpenAI(
    api_key="your_api_key",  # Replace with your actual OpenAI API key
)

Remember to replace "your_api_key" with your actual API key.

With these two steps, your environment is set up and ready for exploring OpenAI's API functionalities.

Understanding Function Calling in Chat Completions API:

The Chat Completions API from OpenAI offers a unique capability to integrate custom functions into chatbot conversations. In this section, we'll set up a function that our chatbot can call to retrieve order details, and then integrate it with the Chat Completions API.

Defining the get_order_details Function:

The core of our chatbot's functionality lies in its ability to fetch specific order details. We define a function, get_order_details, which makes an API request to retrieve information about a given order:

import requests

def get_order_details(order_id):
    url = "http://your_api_endpoint/order_info"  # Replace with your actual endpoint
    params = {'order_id': order_id}
    response = requests.post(url, params=params)

    if response.status_code == 200:
        return response.json()['Result'][0]
    else:
        return f"Error: Unable to fetch order details. Status code: {response.status_code}"
order_details = get_order_details(order_id=4)
print(order_details)

{
    'order_id': 4,
    'total_amount': 299.99,
    'delivery_status': 'Shipped',
    'current_location': 'Warehouse B',
    'expected_delivery_date': '2023-01-18',
    'customer_name': 'Alice Brown',
    'product_id': 4,
    'product_name': 'Tablet'
}

Describe functions

To integrate this function with the Chat Completions API, we need to define it in a format that the API can understand. We create two dictionaries: available_functions and functions. The former maps function names to their corresponding Python functions, and the latter describes the functions for the API.

available_functions = {
    "get_order_details": get_order_details,
}

functions = [
    {
        "name": "get_order_details",
        "description": "Retrieves the details of an order given its order ID.",
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {"type": "integer", "description": "The order ID."}
            },
            "required": ["order_id"],
        },
    }
]

Implementing the get_gpt_response Function:

Now, we need a function to send user messages to the Chat Completions API and receive AI-generated responses. This function will also specify that our get_order_details function can be called by the model if needed.

def get_gpt_response(messages):
    return client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=messages,
        functions=functions,
        function_call="auto",
    )

With these elements in place, our chatbot is now equipped to understand when to call the get_order_details function based on user queries and to integrate the response into the ongoing conversation.

When the AI model decides that a function call is needed, it responds with a message object containing the function_call attribute. For instance, if the bot decides to call get_order_details with order_id 4, the response would look like this:

ChatCompletionMessage(content=None, role='assistant', function_call=FunctionCall(arguments='{"order_id":4}', name='get_order_details'), tool_calls=None)

Here, function_call is not None, indicating that the model has identified a need to call a function. The arguments attribute contains the parameters for the function call, in this case, order_id: 4

Perfect, let's proceed to the next section which will focus on executing the function and submitting the function response back to the Chat Completions API. This step is crucial as it demonstrates how the chatbot processes and responds to user queries in real time by leveraging custom functions.

Having established how our chatbot decides when to call a function, let's now look at how it executes the function and integrates the response into the chat.

Executing the Function Call:

def execute_function_call(function_name,arguments):
    function = available_functions.get(function_name,None)
    if function:
        arguments = json.loads(arguments)
        results = function(**arguments)
    else:
        results = f"Error: function {function_name} does not exist"
    return results
  1. Extract Function Details: The chatbot first extracts the function name and arguments from the function_call attribute in the API's response.

  2. Execute Function: Using the extracted details, the chatbot calls the appropriate function. In our case, it's the get_order_details function with the provided order_id.

  3. Function Response: The execute_function_call function executes the specified function with the given arguments and returns the result.

function_name = response.choices[0].message.function_call.name
arguments = response.choices[0].message.function_call.arguments

function_response = execute_function_call(function_name, json.loads(arguments))

Submitting the Function Response:

With the function response obtained, the next step is to submit this response back to the Chat Completions API. This allows the API to continue the conversation, incorporating the function response into its next message.

  1. Append Function Response: Let append the existing assistant reply and the function response to the conversation history. This step is crucial as it updates the chat context with the new information.
# extend conversation with assistant's reply
messages.append(response.choices[0].message)  
messages.append(
    {
        "role": "function",
        "name": function_name,
        "content": str(function_response),
    }
)
messages

Here's what the messages array looks like at this stage:

[
    {'role': 'user', 'content': 'what is my order status of order id 4'},
    ChatCompletionMessage(content=None, role='assistant', function_call=FunctionCall(arguments='{"order_id":4}', name='get_order_details'), tool_calls=None),
    {'role': 'function',
     'name': 'get_order_details',
     'content': "{'order_id': 4, 'total_amount': 299.99, 'delivery_status': 'Shipped', 'current_location': 'Warehouse B', 'expected_delivery_date': '2023-01-18', 'customer_name': 'Alice Brown', 'product_id': 4, 'product_name': 'Tablet'}"}
]

Generating the Final GPT Response:

With the updated messages array, the chatbot then calls the get_gpt_response function again. This time, the API uses the entire conversation context, including the function response, to generate its next message. The final GPT output provides a synthesized and user-friendly summary of the order details:

resposne = get_gpt_response(messages)
print(resposne.choices[0].message.content)

"The order with ID 4 is for a Tablet with a total amount of $299.99. The order has been shipped and is currently at Warehouse B. The expected delivery date is 2023-01-18. The customer's name is Alice Brown."

This response demonstrates the chatbot's ability to interpret the function output and convey the information clearly and concisely, tailored to the user's original query.

By effectively managing the conversation flow and utilizing the function output, the chatbot can provide detailed and context-specific responses, enhancing the user experience significantly.


Integrating Function Calling with the Assistants API

After exploring the Chat Completions API, we now turn our attention to the Assistant API, which offers a more advanced and stateful way to create assistant-like experiences. This part of the blog will cover how to integrate function calling into the Assistants API, using the given code as a basis.

Creating Functions for the Assistant API:

Similar to the Chat Completions API, you need to define the functions your assistant will use. In our example, we've used the get_order_details function.

Defining Tools for the Assistant:

In the Assistant API, functions are referred to as tools. You need to define these tools in a similar manner to how we defined functions for the Chat Completions API. The tool definition includes the function name, description, and parameters.

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_order_details",
            "description": "Retrieves the details of an order given its order ID.",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "integer",
                        "description": "The unique identifier of the order."
                    }
                },
                "required": ["order_id"]
            }
        }
    }
]

Creating an Assistant:

You create an assistant by providing its name, instructions, model, and the tools it has access to. The instructions are crucial as they guide the assistant on how to use the provided tools.

assistant = client.beta.assistants.create(
  name="Ecommerce bot",
  instructions="You are an ecommerce bot. Use the provided functions to answer questions. Synthesise answer based on provided function output and be consise",
  model="gpt-4-1106-preview",
  tools = tools
)

Create Message and Run:

The Assistant API manages conversations through threads. You create a message within a thread and then run the assistant. The assistant processes the user input and determines if a tool (function) needs to be called.

Create a message in the conversation thread and initiate a run of the assistant.

def create_message_and_run(assistant,query,thread=None):
  if not thread:
    thread = client.beta.threads.create()

  message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content=query
  )
  run = client.beta.threads.runs.create(
  thread_id=thread.id,
  assistant_id=assistant.id
  )
  return run,thread

query = "I want to know my order status" 
run,thread = create_message_and_run(assistant=assistant,query=query)

Process Function Call Requirements:

def get_function_details(run):

  print("\nrun.required_action\n",run.required_action)

  function_name = run.required_action.submit_tool_outputs.tool_calls[0].function.name
  arguments = run.required_action.submit_tool_outputs.tool_calls[0].function.arguments
  function_id = run.required_action.submit_tool_outputs.tool_calls[0].id

  print(f"function_name: {function_name} and arguments: {arguments}")

  return function_name, arguments, function_id

When a user's message triggers a function, the run enters a requires_action state, indicating a function needs to be called:

  1. Check Run Status:

    • Regularly check the run's status. When it's requires_action, it means the Assistant needs a function to be executed.
  2. Retrieve Required Action:

    • Fetch the required action details to identify which function to call and its arguments.

Submit Function Response:

def submit_tool_outputs(run,thread,function_id,function_response):
    run = client.beta.threads.runs.submit_tool_outputs(
    thread_id=thread.id,
    run_id=run.id,
    tool_outputs=[
      {
        "tool_call_id": function_id,
        "output": str(function_response),
      }
    ]
    ) 
    return run

After executing the function, submit its output back to the assistant. The assistant will then continue the conversation, incorporating the function response.

Looping for Continuous Interaction:

For an ongoing conversation, you can loop through these steps, allowing the assistant to handle multiple queries and function calls as the conversation progresses.

while True:
    run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)
    print("run status", run.status)

    if run.status=="requires_action":

        function_name, arguments, function_id  = get_function_details(run)

        function_response = execute_function_call(function_name,arguments)

        run = submit_tool_outputs(run,thread,function_id,function_response)

        continue
    if run.status=="completed":

        messages = client.beta.threads.messages.list(thread_id=thread.id)
        latest_message = messages.data[0]
        text = latest_message.content[0].text.value
        print(text)

        user_input = input()
        if user_input == "STOP":
          break

        run,thread = create_message_and_run(assistant=assistant,query=user_input,thread=thread)

        continue;
    time.sleep(1)
run status completed
To help you with your order status, I will need the order ID. Please provide the order ID associated with the purchase you are inquiring about.
order id is 1
run status queued
run status requires_action

run.required_action
 RequiredAction(submit_tool_outputs=RequiredActionSubmitToolOutputs(tool_calls=[RequiredActionFunctionToolCall(id='call_SOy0g26dzHmNG6HS4qobEUvk', function=Function(arguments='{"order_id":1}', name='get_order_details'), type='function')]), type='submit_tool_outputs')
function_name: get_order_details and arguments: {"order_id":1}
run status queued
run status in_progress
run status in_progress
run status completed
The order with ID 1 has the following status:

- Delivery Status: Shipped
- Current Location: Warehouse A
- Expected Delivery Date: 2023-01-10

The order consists of a laptop, and the total amount for this order is $999.99. If you have any more questions or need further assistance, feel free to ask!
when can i expect delivery
run status queued
run status in_progress
run status in_progress
run status completed
You can expect the delivery of your order with ID 1 by the expected delivery date, which is January 10, 2023. Please note that delivery times may vary due to shipping and handling procedures, so it's always a good idea to track your order for any updates closer to the delivery date.
STOP

The Assistant API's approach to function calling is more interactive and continuous compared to the Chat Completions API. It's particularly well-suited for scenarios where a stateful conversation is necessary, like in our e-commerce chatbot example.


Final Conclusion:

As we conclude our journey through the intricacies of OpenAI's function calling capabilities, it's clear that the landscape of AI-driven chatbot development is brimming with possibilities. The Chat Completions and Assistant APIs offer not just tools, but gateways to creating more intuitive, responsive, and intelligent chatbot experiences. By mastering these functionalities, developers can transcend traditional boundaries, crafting solutions that are not only technically proficient but also contextually aware and user-centric.

In this blog, we've traversed the path from the basic setup to executing complex function calls, all within the realm of a practical e-commerce chatbot scenario. This hands-on approach, coupled with the visual aid of our YouTube tutorial, aims to equip you with both the knowledge and the confidence to explore these technologies in your own projects.

If you're curious about the latest in AI technology, I invite you to visit my project, AI Demos, at aidemos.com. It's a rich resource offering a wide array of video demos showcasing the most advanced AI tools. My goal with AI Demos is to educate and illuminate the diverse possibilities of AI.

For even more in-depth exploration, be sure to visit my YouTube channel at youtube.com/@aidemos.futuresmart. Here, you'll find a wealth of content that delves into the exciting future of AI and its various applications.

Code : https://github.com/PradipNichite/Youtube-Tutorials/blob/main/OpenAI_Function_Calling_Tutorial_Assistents_API_ipynb.ipynb