Generative AIBuilding with foundation modelsOpenAI platformOpenAI Assistants API

Function calling with the Assistant

12 minutes read

Function calling is a feature that allows us to expand the capabilities of the OpenAI Assistant by using external functions. If you're familiar with using function calling through the chat completion API, you'll find it easy to learn how to do the same with the Assistant API.

Let's explore how you can use function calling to enhance interactions with the Assistant API.

Overview of Assistant’s function calling

Function calling lets you introduce external functions to the Assistants, and it will smartly give you the functions that should be called along with their details. When the Assistants API requires to call functions, it stops running temporarily. You can then provide the results of the function back to resume the run. In this topic, we'll show you how to use function calling by making an Assistant that assists us in solving some math problems.

Setting up functions

To make it clearer, let's create two basic functions. One function will add up the first nn non-negative numbers and the other will multiply the first nn non-negative numbers together, which is also known as finding the factorial of nn.

def sum_of_first_n(n):
    """
    Calculate the sum of the first n integers.

    Parameters:
    n (int): The number of integers to sum.

    Returns:
    int: The sum of the first n integers.
    """
    return n * (n + 1) // 2

def product_of_first_n(n):
    """
    Calculate the product of the first n integers.

    Parameters:
    n (int): The number of integers to calculate the product.

    Returns:
    int: The product of the first n integers.
    """
    if n == 0:
        return 0
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

To teach the Assistant about these functions, we need to prepare a list of structured JSON objects that represent each function. This format is similar to the function list we used when calling functions with chat completion.

functions_list = [
    {
        "type": "function",
        "function": {
            "name": "sum_of_first_n",
            "description": "Get the sum of the first n integers",
            "parameters": {
                "type": "object",
                "properties": {
                    "n": {"type": "number", "description": "The number of integers to sum"}
                },
                "required": ["n"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "product_of_first_n",
            "description": "Get the product of the first n integers or get the factorial of n",
            "parameters": {
                "type": "object",
                "properties": {
                    "n": {"type": "number", "description": "The number of integers to multiply or the number which factorial need to calculate"}
                },
                "required": ["n"],
            },
        },
    }
]

Setting up Assistant and thread

Let’s create our math tutor Assistant that will ask us to call the previous two functions when necessary.

assistant = client.beta.assistants.create(
  name="Math solver",
  description="You are great at solving math problems. You don't solve math problem yourself. You call external functions to find the answer to math problems.",
  model="gpt-3.5-turbo",
  tools=functions_list
)

The procedure is similar to what we have done in chat completion API. We provide the functions_list as tools for the assistant.

Now to start our conversation, we have to create a thread:

thread = client.beta.threads.create()

At this point, we can throw a math problem to our newly created math assistant. Let's add our message to the thread and run it using the Assistant:

# Adding message to the thread
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="I have to find both the sum and the product of the first 10 integers. Can you help me?"
)

# Running the thread
run = client.beta.threads.runs.create(
  thread_id=thread.id,
  assistant_id=assistant.id
)

Immediately, the run will enter the queued state. We can check this using the following command:

print(run.status)

Output:

queued

Almost instantaneously, the run will enter the requires_action state if it requires function calling to complete the response. We can retrieve this updated status from the OpenAI server by running this code:

 run = client.beta.threads.runs.retrieve(
  thread_id=thread.id,
  run_id=run.id
)

Now, let's check the status again:

 print(run.status)

Output:

 requires_action

When the run reaches the requires_action state, you need to call the necessary functions and send the outputs back to the assistant to finish its response. If you take too long (around 10 minutes) to provide the outputs, the run will expire, and you'll need to start the process anew.

Screenshots

Screenshot of code creating a math solver in a programming interface.

Understanding the Assistant’s response

When the run is at the requires_action status, we can check the required_action to find the functions we need to call and the corresponding arguments:

print(run.required_action)

Output:

RequiredAction(
    submit_tool_outputs=RequiredActionSubmitToolOutputs(
        tool_calls=[
            RequiredActionFunctionToolCall(
                id="call_C8NBShK69ivnfZq0uHoUQvdM",
                function=Function(arguments='{"n": 10}', name="sum_of_first_n"),
                type="function",
            ),
            RequiredActionFunctionToolCall(
                id="call_wrnC8AZjLpVv7Dv5R4PxGjkM",
                function=Function(arguments='{"n": 10}', name="product_of_first_n"),
                type="function",
            ),
        ]
    ),
    type="submit_tool_outputs",
)

Similar to function calling using chat completion, the Assistant gives us a list tool_calls that includes the names of functions and their arguments. Also, note that there is an ID for each function. We will use these IDs to return the corresponding function outputs.

Screenshots

Screenshot of computer code output showing a 'RequiredAction' function with parameters.

Returning functions’ outputs

At this point, we have to extract the arguments from the Assistant’s response and run the corresponding functions. We have discussed how to extract the arguments from the tool_calls list while studying the function calls using chat completions. In this section, we will learn how to return the output to the Assistant since this is different from the chat completion API.

After executing the functions with the required arguments, we find that the sum of the first 1010 integers is 5555 and the product of the first 1010 integers is 36288003628800. To return these results we create a list of JSON objects, function_outputs. Each JSON object contains the tool_call_id and the corresponding output.

tool_calls = run.required_action.submit_tool_outputs.tool_calls
function_outputs=[
      {
        "tool_call_id": tool_calls[0].id,
        "output": str(55),
      },
      {
        "tool_call_id": tool_calls[1].id,
        "output": str(3628800),
      },
]

One important thing to note here is that the output value should always be string type.

Let's submit the outputs to the Assistant. We do this in the following way:

run = client.beta.threads.runs.submit_tool_outputs(
  thread_id=thread.id,
  run_id=run.id,
  tool_outputs=function_outputs
)

Immediately after running the codes above, the run status will be queued. We have to retrieve the updated run again following the same procedure we did before:

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

Output:

completed

Finally, we can now see the Assistant's response in the thread:

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

Output:

 [TextContentBlock(
    text=Text(annotations=[], 
    value='The sum of the first 10 integers is 55, and the product of the first 10 integers is 3628800.'), 
    type='text')
]
Screenshots

Screenshot of computer code in a text editor.Screenshot of a code snippet for retrieving a run object in a programming environment.Screenshot of code output displaying a text content block with mathematical content.

Great! We have created a handy math tutor Assistant that can call user-defined functions to respond to the users.

Conclusion

In conclusion, the utilization of function calling within the OpenAI Assistant presents an innovative approach to expanding its capabilities. By seamlessly integrating external functions, users can enhance their interactions with the Assistant, enabling it to provide tailored solutions to a variety of tasks. Through this exploration, we've illustrated the process of setting up functions, engaging with the Assistant, and interpreting its responses, ultimately showcasing the potential for creating personalized and efficient AI assistants for diverse needs.

How did you like the theory?
Report a typo