A clear guide to OpenAI function calling with Python

Naren Yellavula
Dev bits
Published in
5 min readJun 15, 2023

--

Image generated from https://vidura.ai

Hi readers. It had been a very long time since I’ve written an article. In this short article about OpenAI, we are going to see how to use Python OpenAI library to make use of new function calling feature from OpenAI that enforces a structured output from GPT models.

Before you go, are you still using Google Docs to store your favourite ChatGPT prompts ? Then, checkout this cool prompt manager called Vidura for free. You can generate text with GPT-3 and images with Stable Diffusion in one place:

Installing a virtual environment

First create a Python virtual environment and install `openai` pip package using below commands:

python3 -m venv venv
source venv/bin/activate
pip install openai --upgrade

Once the package is installed successfully, navigate to your openAI account and create a access key here: https://platform.openai.com/account/api-keys

Let’s say your API key is: `sk-xyzabc` (never reveal your real key to anyone)

Adding Open AI API key to environment

Export this key as an environment variable using below command:

export OPENAI_API_KEY=sk-xyzabc

It is strongly discouraged to hard-code your API key in code as there is are chances to commit it accidentally to the version control.

Install PyDantic for generating JSON schema

OpenAI API takes a JSON schema for function output. To simplify creating this in Python, we can define a PyDantic class to structure a model and convert it to JSON schema to avoid verbosity and errors.

pip install pydantic --upgrade

With this installation we are ready to try out OpenAI API. Let us define a goal and an example to achieve that goal.

Goal:

Enforce a predictable JSON output from`gpt-3.5-turbo` model for questions like:

“Explain a process of ?” or “How to ?”

Solution:

To do that, we need to come up with a schema first. Let us define a PyDantic model. I am doing this to guide an OpenAI model to “Have a title and series of steps for a requested question, and generate a JSON-parsable output”.

from typing import List
from pydantic import BaseModel

class StepByStepAIResponse(BaseModel):
title: str
steps: List[str]

It is a simple Python class (which inherits a pydantic BaseModel). In order to convert this class to JSON schema, just call the `schema()` function:

schema = StepByStepAIResponse.schema() # returns a dict like JSON schema

# schema content looks like below

"""
{
'title': 'StepByStepAIResponse',
'type': 'object',
'properties': {'title': {'title': 'Title', 'type': 'string'},
'steps': {'title': 'Steps', 'type': 'array', 'items': {'type': 'string'}}},
'required': ['title', 'steps']
}
"""

We will use this schema soon in our examples.

Now, let’s make an actual call to OpenAI and apply an “illusionary” function called “get_answer_for_user_query” by guiding AI to return JSON output. I am calling that function `illusionary` because it’s control execution is automatically inferred by OpenAI model using name, description, and output schema but not defined by the developer.

Making OpenAI API function call

We need to call “ChatCompletion.create()” method with necessary arguments to pass a user query to OpenAI model. We need to use `gpt-3.5-turbo-0613` (mark `0613` at the end, function calls are only available on this model or `gpt-4–0613` as of this writing). The code looks like below.

import openai
import os
import json

openai.api_key = os.getenv("OPENAI_API_KEY")

response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[
{"role": "user", "content": "Explain how to assemble a PC"}
],
functions=[
{
"name": "get_answer_for_user_query",
"description": "Get user answer in series of steps",
"parameters": StepByStepAIResponse.schema()
}
],
function_call={"name": "get_answer_for_user_query"}
)

output = json.loads(response.choices[0]["message"]["function_call"]["arguments"])

# output content
"""
{
'title': 'Steps to assemble a PC',
'steps': [
'1. Gather all necessary components',
'2. Prepare the PC case',
'3. Install the power supply',
'4. Mount the motherboard',
'5. Install the CPU and CPU cooler',
'6. Install RAM modules',
'7. Install storage devices',
'8. Install the graphics card',
'9. Connect all cables',
'10. Test the PC'
]
}
"""

Now, one can programmatically use the output (dict) in Python. The important argument in the code for `ChatCompletion .complete()` is `functions=[{}]` which takes a list of functions. The “name” key specifies a function name and “description” specifies meta information about the function to OpenAI model. The “parameters” key is where we pass the JSON schema. And this code,

function_call={"name": "get_answer_for_user_query"}

specifies whether OpenAI model should use a function/s from the list of functions. Making this value “auto” can make AI model guess, so I always suggest to add this function name instead of auto.

If you want to create a StepByStepAIResponse object from this response dict, you can do this:

sbs = StepByStepAIResponse(**output)

# Now access sbs.title, sbs.steps in your code

Let’s try another user query :“How to make a pancake ?”. I modified “content” value in messages parameter to adjust the query. The code looks like this.

# ... imports and other code

response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[
{"role": "user", "content": "How to make a pancake ?"}
],
functions=[
{
"name": "get_answer_for_user_query",
"description": "Get user answer in series of steps",
"parameters": StepByStepAIResponse.schema()
}
],
function_call={"name": "get_answer_for_user_query"}
)

output = json.loads(response.choices[0]["message"]["function_call"]["arguments"])

# output content

"""
{
'title': 'Pancake Recipe',
'steps':
[
'In a mixing bowl, whisk together flour, sugar, baking powder, and salt.',
'In a separate bowl, whisk together milk, eggs, and melted butter.',
'Add the wet ingredients to the dry ingredients and mix until just combined. Do not overmix.',
'Heat a non-stick skillet or griddle over medium heat. Lightly grease with butter or cooking spray.',
'Pour 1/4 cup of batter onto the skillet for each pancake. Cook until bubbles form on the surface, then flip and cook until golden brown.',
'Serve the pancakes hot with your favorite toppings, such as syrup, fruits, or whipped cream.'
]
}
"""

For the query , we got a list of steps in a nice sequential manner. This is how we can instruct an OpenAI model (in this case `gpt-3-turbo-0613`), to enforce output in a particular structure.

The full code snippet for this example is available here: https://gist.github.com/narenaryan/e8a5aac3825b0e752cf70daa69abb65a

Conclusion

OpenAI API function calls are a powerful way to guide OpenAI models to generate a predictable response, which otherwise needs meticulous prompt instructions. Definitely, function calling applications are exciting to think of. Thanks for reading this article and have a nice day :)

References:

--

--

Naren Yellavula
Dev bits

When I immerse myself in passionate writing, time, hunger, and sleep fade away. Only absolute joy remains! --- Isn't this what some call "Nirvana"?