Package: called
OpenAI Functionspip install funcmodelsfromfuncmodelsimportopenai_function@openai_functionThe most intuitive, robust and "pure" way to implement functions for OpenAI function calling.Designed as a more straightforward and ergonomic alternative tojxnl/instructor. Rather than defining your function as aBaseModel, you define your function as afunction, and theBaseModelis created for you.With@openai_function, the ...Pydanticmodel for data validation,definition of yourOpenAI FunctionJSON schema,parsing and validation of raw json string arguments,logic for handling the arguments and producing a result... are allexplicitly defined and encapsulated in one place: your decorated function.@openai_functionwill turn your function into a Pydantic BaseModel class, using your function parameters as attributes.
It parses your docstring for a description and parameter descriptions, and combines those with the BaseModel's JSON
schema to produce a complete OpenAI Function definition. You'll receive back an extended version of this BaseModel class,
equipped with a class attribute,.schemawith the function definition, a.from_json()class method for creation and validation
directly from raw function call arguments from OpenAI, and anexecute()method that passes the instance's validated attributes to the
handler defined by your decorated function.fromtypingimportLiteral@openai_functiondefget_stock_price(ticker:str,currency:Literal["USD","EUR"]="USD"):"""Get the stock price of a company, by ticker symbolParameters----------tickerThe ticker symbol of the companycurrencyThe currency to use"""returnf"182.41{currency}, -0.48 (0.26%) today"get_stock_priceOpenaiFunction({
    "name": "get_stock_price",
    "description": "Get the stock price of a company, by ticker symbol",
    "parameters": {
        "properties": {
            "ticker": {
                "type": "string",
                "description": "The ticker symbol of the company"
            },
            "currency": {
                "default": "USD",
                "enum": [
                    "USD",
                    "EUR"
                ],
                "type": "string",
                "description": "The currency to use"
            }
        },
        "required": [
            "ticker"
        ],
        "type": "object"
    }
})Get our OpenAI function definition dictionaryget_stock_price.schema{'name': 'get_stock_price', 'description': 'Get the stock price of a company, by ticker symbol', 'parameters': {'properties': {'ticker': {'type': 'string', 'description': 'The ticker symbol of the company'}, 'currency': {'default': 'USD', 'enum': ['USD', 'EUR'], 'type': 'string', 'description': 'The currency to use'}}, 'required': ['ticker'], 'type': 'object'}}Instantiate our pydantic model, validating argumentsvalidated_function_call=get_stock_price(ticker="AAPL")Or, go directly from raw json arguments from OpenAIraw_arguments_from_openai='{"ticker": "AAPL"}'validated_function_call=get_stock_price.from_json(raw_arguments_from_openai)validated_function_call.currency'USD'Call our function, with already-validated argumentsvalidated_function_call.execute()'182.41 USD, -0.48 (0.26%) today'If you prefer Pydantic syntax, we can achieve thesamething usingFieldsfrompydanticimportField@openai_functiondefget_stock_price(ticker:str=Field(description="The ticker symbol of the company"),currency:Literal["USD","EUR"]=Field("USD",description="The currency to use."),):"Get the stock price of a company, by ticker symbol"returnf"182.41{currency}, -0.48 (0.26%) today"Here, the field descriptions are defined in the parameters themselves, rather than the docstring.The result is the exact same function definition as before:get_stock_priceOpenaiFunction({
    "name": "get_stock_price",
    "description": "Get the stock price of a company, by ticker symbol",
    "parameters": {
        "properties": {
            "ticker": {
                "type": "string",
                "description": "The ticker symbol of the company"
            },
            "currency": {
                "default": "USD",
                "enum": [
                    "USD",
                    "EUR"
                ],
                "type": "string",
                "description": "The currency to use"
            }
        },
        "required": [
            "ticker"
        ],
        "type": "object"
    }
})Function Groupsdefget_stock_price(ticker:str):return'182.41 USD, -0.48 (0.26%) today'defget_weather(city:str):return"Sunny, 72 degrees, 0% chance of rain"group=openai_function_group(functions=[get_stock_price,get_weather])groupOpenaiFunctionGroup([
    {
        "name": "get_stock_price",
        "parameters": {
            "properties": {
                "ticker": {
                    "type": "string"
                }
            },
            "required": [
                "ticker"
            ],
            "type": "object"
        }
    },
    {
        "name": "get_weather",
        "parameters": {
            "properties": {
                "city": {
                    "type": "string"
                }
            },
            "required": [
                "city"
            ],
            "type": "object"
        }
    }
])Note: Usegroup.function_definitionsto get a list of the raw function schemas to use in thefunctionsargument to OpenAINow, when we get a function call from OpenAI, we can let the group handle it.function_call_from_openai={"name":"get_weather","arguments":'{"city": "Denver"}',}validated_function_call=group.evaluate_function_call(function_call_from_openai)validated_function_callget_weather(city='Denver')result=validated_function_call.execute()result'Sunny, 72 degrees, 0% chance of rain'Create your own ChatGPTBecause each@openai_functionencapsulates all the information needed to facilitate OpenAI function calling, it's
very easy for us to build our own ChatGPT, withautomated function call handling.Step 1. Define conversation handlerimportjsonfromdataclassesimportdataclassfromopenaiimportOpenAIfromopenai.types.chat.chat_completionimportChoicefromfuncmodelsimportOpenaiFunctionGroup,openai_function_group@dataclassclassChatGPTConversation:model:strclient:OpenAIfunctions:OpenaiFunctionGroupmessages:list[dict]defget_openai_response(self)->Choice:response=self.client.chat.completions.create(messages=self.messages,model=self.model,functions=self.functions.function_definitions,function_call="auto",)returnresponse.choices[0]defhandle_function_call(function_call)->dict:# match the function call to the right function, and validate argumentsvalidated_call=self.functions.evaluate_function_call(function_call)# Call the function with the validated argumentsresult=validated_call.execute()returndict(role="function",name=function_call.name,content=str(result))defchat(self):result=self.get_openai_response()self.add_message(result.message.model_dump(exclude_unset=True))if(function_call:=result.message.function_call)isnotNone:self.add_message(self.handle_function_call(function_call))ifresult.finish_reason=='function_call':self.chat()defadd_message(self,message:dict):print(json.dumps(message,indent=4))self.messages.append(message)defsend_message(self,prompt:str):self.add_message({"role":"user","content":prompt})self.chat()Here,chat()is a recursive function that continues sending API requests
for as long as the response'sfinish_reason='function_call'.Step 2. Define openai functionsdefget_stock_price(ticker:str):"Get the stock price of a company, by ticker symbol."return"182.41 USD, −0.48 (0.26%) today"defget_weather(city:str):"Get the current weather in a city."return"Sunny and 75 degrees"defget_current_datetime(city:str):"Get the current date and time in a city."return"Friday, Nov. 10, 2023, 10:00 AM"group=openai_function_group(functions=[get_stock_price,get_weather,get_current_datetime])groupOpenaiFunctionGroup([
    {
        "name": "get_stock_price",
        "description": "Get the stock price of a company, by ticker symbol.",
        "parameters": {
            "properties": {
                "ticker": {
                    "type": "string"
                }
            },
            "required": [
                "ticker"
            ],
            "type": "object"
        }
    },
    {
        "name": "get_weather",
        "description": "Get the current weather in a city.",
        "parameters": {
            "properties": {
                "city": {
                    "type": "string"
                }
            },
            "required": [
                "city"
            ],
            "type": "object"
        }
    },
    {
        "name": "get_current_datetime",
        "description": "Get the current date and time in a city.",
        "parameters": {
            "properties": {
                "city": {
                    "type": "string"
                }
            },
            "required": [
                "city"
            ],
            "type": "object"
        }
    }
])Step 3. Create a new conversationchatgpt=ChatGPTConversation(model="gpt-4-1106-preview",client=OpenAI(api_key=os.environ["OPENAI_API_KEY"]),functions=group,messages=[dict(role="system",content="You are a helpful AI assistant."),])Step 4. Exchange messagesYou'll need to use Jupyter notebooks (or interactive terminal) for thischatgpt.send_message("Hello, how are you?"){
    "role": "user",
    "content": "Hello, how are you?"
}
{
    "content": "Hello! I'm just a machine, so I don't have feelings, but I'm functioning optimally. How can I assist you today?",
    "role": "assistant"
}chatgpt.send_message("I'm enjoying my breakfast here in Denver. Can you list 3 fun things to do here, ""then give me a quick morning update with the information available?"){
    "role": "user",
    "content": "I'm enjoying my breakfast here in Denver. Can you list 3 fun things to do here, then give me a quick morning update with the information available?"
}
{
    "content": "Of course! Here are three fun activities to do in Denver:\n\n1. **Visit the Denver Botanic Gardens**: Enjoy a peaceful morning walking through the various plant collections and exhibits. It's a good way to appreciate nature and the diverse plant life.\n\n2. **Explore the Denver Art Museum**: With its wide range of art from across the world, including contemporary, indigenous, and classic pieces, this museum offers a rich cultural experience.\n\n3. **Take a Stroll Around Larimer Square**: This historic district is perfect for a leisurely walk, as you can explore boutique shops, enjoy local eateries, and take in the unique architecture and vibrant atmosphere.\n\nNow, let's get you that morning update for Denver:\n\nI can provide you with the current weather, the current date and time, and check on the stock price of a company if you're following any in particular. Let's start with the weather and the date and time:\n\n- **Weather**: I'll check the current weather conditions for you.\n- **Current Date and Time**: I\u2019ll provide you with the current date and time in Denver.\n\nLet me fetch the latest information for you, just a moment.",
    "role": "assistant",
    "function_call": {
        "arguments": "{\"city\":\"Denver\"}",
        "name": "get_weather"
    }
}
{
    "role": "function",
    "name": "get_weather",
    "content": "Sunny and 75 degrees"
}
{
    "content": null,
    "role": "assistant",
    "function_call": {
        "arguments": "{\"city\":\"Denver\"}",
        "name": "get_current_datetime"
    }
}
{
    "role": "function",
    "name": "get_current_datetime",
    "content": "Friday, Nov. 10, 2023, 10:00 AM"
}
{
    "content": "Here's your morning update for Denver:\n\n- **Weather:** It's currently sunny and 75 degrees in Denver. It seems like a beautiful day to enjoy any of the outdoor activities I mentioned!\n- **Current Date and Time:** It's Friday, November 10, 2023, and the time is 10:00 AM.\n\nIf there's a particular stock you'd like to check on, just let me know the ticker symbol, and I'll get the latest price for you. Have a fantastic day enjoying Denver!",
    "role": "assistant"
}If we stitch together thecontentof eachassistantmessage (2nd and last messages), we get a continuous block of response text:Denver is a vibrant city with plenty of activities to enjoy, ranging from cultural experiences to outdoor adventures. Here are three fun things you might consider doing in Denver:Visit Denver Botanic Gardens: These gardens offer a peaceful and beautiful urban retreat. You can stroll through various plant displays, including a Japanese garden and a conservatory with exotic tropical and subtropical species.Explore the Denver Art Museum: Known for its collection of American Indian Art, the Denver Art Museum also boasts a wide range of other collections, from pre-Columbian artifacts to contemporary art. The museum's architecture is also a work of art in itself.Discover Red Rocks Park and Amphitheatre: Just outside Denver, this world-famous outdoor venue is renowned for its amazing acoustics and stunning red sandstone formations. During the day, you can hike or bike the trails, and in the evening, possibly catch a concert under the stars.Now, let's get you a morning update on Denver:Weather: I'll check the current weather conditions for you.Local Time: I'll provide the current date and time.Stock Market: If you're interested in a particular stock, I can provide the latest price.Let me gather the weather and local time information for you. One moment, please.
Here's your morning update for Denver:Weather: It's a beautiful sunny day with a pleasant temperature of 75 degrees Fahrenheit. A great day to plan an outdoor activity!Local Time: The current date and time in Denver is Friday, November 10th, 2023, at 10:00 AM.If you have any stock in mind or need more information, feel free to let me know! Enjoy your breakfast and have a fantastic day in Denver.This single response was made up of multiple API calls/responses:Sent:User promptReceived:Content response (PART 1)Function call to:get_weatherSent:Function response from:get_weatherReceived:Function call to:get_current_datetimeSent:Function response from:get_current_datetimeReceived:Content response (PART 2)The response text above combines the content from API responses2and6.