Introduction
OpenAI’s function calling feature, introduced earlier, empowers developers to create interactive applications by enabling language models to call functions. This feature allows models to return structured data that can trigger APIs, extract data, or perform tasks.
Recently, OpenAI has enhanced this capability with Structured Outputs, simplifying the way developers interact with models. Now, instead of defining complex JSON schemas for each function, the new feature allows developers to define output structures using simpler SDK tools, making function calling more accessible and efficient.
In this blog, we’ll compare our previous blog “OpenAI Function Calling With External API Examples” with the new structured output feature, demonstrating how this new approach significantly reduces complexity while improving functionality. Before starting with this blog, please generate API keys for Finnhub and Alpha Vantage from our previous blog. Once, you have generated the APIs, you can start with the following steps:
Steps:
Step 1:
Install the required libraries using the below code:
# Install required packages
!pip install openai==1.47.1
!pip install finnhub-python==2.4.20
Step 2:
Next, define the necessary variables that hold the values for API keys and the OpenAI model name you want to use. After that, initialize the OpenAI and Finnhub clients for further processing.
# Import required library
import json
import requests
import finnhub
import openai
from openai import OpenAI
from pydantic import BaseModel
# Define variables
GPT_MODEL = "gpt-4o-mini"
finnhub_api_key = "your_finnhub_api_key"
openai_api_key = "your_openai_api_key"
alphavantage_api_key = "your_alphavantage_api_key"
# Init OpenAI and finnhub client
client = OpenAI(api_key=openai_api_key)
finnhub_client = finnhub.Client(api_key = finnhub_api_key)
Step 3:
Then, we will define the functions that help to fetch the current price of the stock using the finnhub API and convert the currency using the alphavantage API.
# Call `finnhub` API and get the current price of the stock
# Call `finnhub` API and get the current price of the stock
def get_current_stock_price(arguments):
try:
arguments = json.loads(arguments)['ticker_symbol']
price_data=finnhub_client.quote(arguments)
stock_price = price_data.get('c', None)
print(stock_price)
if stock_price == 0:
return "This company is not listed within USA, please provide another name."
else:
return stock_price
except:
return "This company is not listed within USA, please provide another name."
# Call `alphavantage` API and exchange the currency rate
def currency_exchange_rate(arguments):
try:
from_country_currency = json.loads(arguments)['from_country_currency']
to_country_currency = json.loads(arguments)['to_country_currency']
url = f'https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency={from_country_currency}&to_currency={to_country_currency}&apikey={alphavantage_api_key}'
r = requests.get(url)
data = r.json()
return data['Realtime Currency Exchange Rate']['5. Exchange Rate']
except:
return "I am unable to parse this, please try something new."
Step 4:
This is the step that simplified our work for defining the function schema. Earlier we had to create detailed definitions for functions like below:
{
"name": "get_current_stock_price",
"description": "It will get the current stock price of the US company.",
"parameters": {
"type": "object",
"properties": {
"ticker_symbol": {
"type": "string",
"description": "This is the symbol of the company.",
}
},
"required": ["ticker_symbol"],
},
}
The previous approach of manually defining function schemas using JSON was time-consuming and error-prone. Even for functions with a single variable, describing the schema in detail took considerable effort. Now, with the structured outputs feature, the complexity is reduced. All we need to do is define a Pydantic class for each function, specifying the required input fields. Then, using the pydantic_function_tool, you can easily convert these classes into the function tool format. You can use the below function, to create a function schema for both stock price extraction and currency exchange rate:
# Define pydantic classes, which specify the required input for the functions
class stockPriceData(BaseModel):
ticker_symbol: str
class currencyExchangeRate(BaseModel):
from_country_currency: str
to_country_currency: str
# Convert pydantic class to function call definition, which we were writing manually
tools = [openai.pydantic_function_tool(stockPriceData), openai.pydantic_function_tool(currencyExchangeRate)]
Step 5:
This is the final step, which helps to combine the whole process and generate the response. In this step, we will take the input from the user and pass prompt instruction, user input and available functions (tools) to the OpenAI model to identify the tool to be called.
Once the model has detected the appropriate tool, we will call the corresponding function with the extracted function argument and generate the response. This process will continue till the user says ‘bye’ or ‘exit’.
# Below code combines the whole flow to generate output
user_input = input("Please enter your question here: (if you want to exit then write 'exit' or 'bye'.) ")
# We will continue QNA till user says `exit` or `bye`
while user_input.strip().lower() != "exit" and user_input.strip().lower() != "bye":
# Prepare messages to send to OpenAI
messages = [
{
"role": "system",
"content": "You are a helpful customer support assistant. Use the supplied tools to assist the user.",
},
{
"role": "user",
"content": user_input,
}
]
# Call OpenAI with prompt instruction, User Input and function definition (tools)
response = client.chat.completions.create(
model=GPT_MODEL, messages=messages, tools=tools
)
# Check if model has detected the tool call
if response.choices[0].finish_reason == "tool_calls":
# Fetch tool name and arguments
tool_name = response.choices[0].message.tool_calls[0].function.name
fn_argument = response.choices[0].message.tool_calls[0].function.arguments
print("Detected tool", tool_name)
print("Extracted function arguments:", fn_argument)
# call the function associated with the particular tool
if tool_name == "stockPriceData":
result = get_current_stock_price(fn_argument)
print(f"Stock price of {json.loads(fn_argument)['ticker_symbol']}:", result)
elif tool_name == "currencyExchangeRate":
result = currency_exchange_rate(fn_argument)
print(f"Currency exchange rate from {json.loads(fn_argument)['from_country_currency']} to {json.loads(fn_argument)['to_country_currency']} is {result}.")
# Check for the normal replies
elif response.choices[0].finish_reason == "stop":
print("response:", response.choices[0].message.content)
# Check if OpeAI is identifying our content as restricted one
elif response.choices[0].finish_reason == "content_filter":
print("Your request or response may include restricted content.")
# Check if we are exceeding maximum context window
elif response.choices[0].finish_reason == "length":
print(f"Your input token exceed the maximum input window of the model `{GPT_MODEL}`.")
# Continue next iteration
user_input = input("Please enter your question here: ")
Sample response
Below are some sample response tests using the above script:
Question: 1 dollar is equal to what rs?
Detected tool: currencyExchangeRate
Extracted function arguments: {"from_country_currency":"USD","to_country_currency":"INR"}
Response: Currency exchange rate from USD to INR is 83.55200000.
Question: What is the current stock price of Amazon?
Detected tool: stockPriceData
Extracted function arguments: {"ticker_symbol":"AMZN"}
193.96
Response: Stock price of AMZN: 193.96
Question: Currency exchange rate between India and Canada?
Detected tool: currencyExchangeRate
Extracted function arguments: {"from_country_currency": "INR", "to_country_currency": "CAD"}
Response: Currency exchange rate from INR to CAD is 0.01600000.
Question: Stock price of Tesla?
Detected tool: stockPriceData
Extracted function arguments: {"ticker_symbol":"TSLA"}
254.27
Response: Stock price of TSLA: 254.27
Conclusion
The introduction of structured outputs with OpenAI’s function calling feature has streamlined the process of defining function schemas. This improvement not only simplifies the development process but also reduces errors, allowing for faster, more efficient application development. By adopting these enhancements, developers can focus more on building functionality rather than getting bogged down by schema definitions.