Package: toolcall
OpenAI Functionspip install toolcallThe most natural way to implement functions for OpenAI tool calling.@openai_function:Argument validation of complex typesusing PydanticBaseModelunder the hood.Automatic JSON Schema creationusing a mix of docstring parsing, Pydantic'smodel_json_schema(), and custom enhancements.Utility methods for raw tool-call processingfromtoolcallimportopenai_functionfromtypingimportLiteralimportjson@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({"type":"function","function":{"name":"get_stock_price","description":"Get the stock price of a company, by ticker symbol","parameters":{"type":"object","properties":{"ticker":{"type":"string","description":"The ticker symbol of the company"},"currency":{"type":"string","description":"The currency to use","enum":["USD","EUR"],"default":"USD"}},"required":["ticker"]}}})Everything you need for implementing function calling is encapsulated in a single object.How does it work?@openai_function:Turns your function into a subclass ofpydantic.BaseModelwith your function's parameters as attributes. So, in the example above, runningget_stock_price(ticker="AAPL")would create an instance of this model, validating the arguments.Creates the JSON schema shown above, and stores it as a class attributeImplements a.execute()instance method that passes the instance's attributes to the function you defined.Implements.run_tool_call()and.run_function_call()class methods that process a raw tool/function call from OpenAI end-to-end, producing a tool message as the result, to send back to OpenAIGetting StartedGet OpenAI function definition schemaget_stock_price.schema{'type': 'function', 'function': {'name': 'get_stock_price', 'description': 'Get the stock price of a company, by ticker symbol', 'parameters': {'type': 'object', 'properties': {'ticker': {'type': 'string', 'description': 'The ticker symbol of the company'}, 'currency': {'type': 'string', 'description': 'The currency to use', 'enum': ['USD', 'EUR'], 'default': 'USD'}}, 'required': ['ticker']}}}Instantiate our pydantic model, validating argumentsvalidated_function_call=get_stock_price(ticker="AAPL")Execute the function, with already-validated argumentsvalidated_function_call.execute()'182.41 USD, -0.48 (0.26%) today'End-to-End Tool Call Processing, with Error HandlingWhen an OpenAI model chooses to call theget_stock_pricefunction we defined, it sends us a message like this.message_from_openai={"role":"assistant","content":None,"tool_calls":[{"type":"function","id":"call_LD0WokrRan5j8B5UehILAdMq","function":{"name":"get_stock_price","arguments":"{\"ticker\":\"AAPL\"}"},}]}tool_call=message_from_openai["tool_calls"][0]Ourget_stock_pricehas a utility classmethod,run_tool_call, to handle this elegantly.tool_response_message=get_stock_price.run_tool_call(tool_call)tool_response_message{
    'role': 'tool',
    'tool_call_id': 'call_LD0WokrRan5j8B5UehILAdMq',
    'content': '182.41 USD, -0.48 (0.26%) today'
}This method handles all the boilerplate for you:Keeping track of the tool callidParsing JSONPassing arguments to your function model for Pydantic validationExecuting your function with the validated argumentsWrapping the result in a response messageIt also makes error handling easyTherun_tool_call()method accepts anerror_handlerargument: a callback function that takes an exception and returns a string to send back to OpenAI, documenting the error.Passerror_handler=Trueto use the default handler.Consider this example, where we receive incorrect types:bad_tool_call={"type":"function","id":"call_LD0WokrRan5j8B5UehILAdMq","function":{"name":"get_stock_price","arguments":"{\"ticker\": 5,\"currency\":\"FOOBAR\"}"},}tool_response_message=get_stock_price.run_tool_call(bad_tool_call,error_handler=True)tool_response_message{
   'role': 'tool',
   'tool_call_id': 'call_LD0WokrRan5j8B5UehILAdMq',
   'content': 
      'Validation failed for the following parameters

      ticker:
        Input: 5
        Error: Input should be a valid string

      currency:
        Input: 'FOOBAR'
        Error: Input should be 'USD' or 'EUR'
      ',
}Tool Groupsdefget_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_tool_group([get_stock_price,get_weather,get_current_datetime])groupOpenaiToolGroup([
    {
        "type": "function",
        "function": {
            "name": "get_stock_price",
            "description": "Get the stock price of a company, by ticker symbol.",
            "parameters": {
                "type": "object",
                "properties": {
                    "ticker": {
                        "type": "string"
                    }
                },
                "required": [
                    "ticker"
                ]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather in a city.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string"
                    }
                },
                "required": [
                    "city"
                ]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_datetime",
            "description": "Get the current date and time in a city.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string"
                    }
                },
                "required": [
                    "city"
                ]
            }
        }
    }
])Note: Usegroup.toolsto get a list of the raw tool schemas to use in thetoolsargument to OpenAI. Orgroup.functionsto get only the functions, for the deprecated function calling API.Now, when we get a function tool call from OpenAI, the group can handle it.tool_call={"type":"function","id":"call_LD0WokrRan5j8B5UehILAdMq","function":{"name":"get_weather","arguments":"{\"city\":\"Denver\"}",},}tool_response_message=group.run_tool_call(tool_call,error_handler=True)tool_response_message{
    'role': 'tool',
    'tool_call_id': 'call_LD0WokrRan5j8B5UehILAdMq',
    'content': 'Sunny and 75 degrees'
}Create your own ChatGPT, with automated tool call handlingStep 1. Conversation handlerimportosimportjsonfromtoolcallimportopenai_tool_group,openai_function,OpenaiToolGroupfromdataclassesimportdataclassfromopenaiimportOpenAIfromopenai.types.chat.chat_completionimportChoicefromtypingimportOptional@dataclassclassChatGPTConversation:model:strclient:OpenAItool_group:OpenaiToolGroupmessages:list[dict]defchat(self):result=self.get_openai_response()self.add_message(result.message.model_dump(exclude_unset=True))ifresult.message.tool_calls:forcallinresult.message.tool_calls:result_msg=self.tool_group.run_tool_call(call,error_handler=True)self.add_message(result_msg)ifresult.finish_reason=='tool_calls':self.chat()defget_openai_response(self)->Choice:response=self.client.chat.completions.create(messages=self.messages,model=self.model,tools=self.tool_group.tools,)returnresponse.choices[0]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 method that continues sending API requests
for as long as the response'sfinish_reason='function_call'.Step 2. Create a new conversationchatgpt=ChatGPTConversation(model="gpt-4-1106-preview",client=OpenAI(api_key=os.environ["OPENAI_API_KEY"]),tool_group=group,# using the group defined earliermessages=[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?"
}
{
    "role": "assistant",
    "content": "Hello! I'm just a computer program, so I don't have feelings, but I'm ready and functioning properly. How can I assist you today?"
}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?"){
    "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?"
}
{
    "role": "assistant",
    "content": "Denver is a vibrant city with plenty to offer! Here are three fun things you might enjoy:\n\n1. Explore the Denver Art Museum: The museum is one of the largest in the West and is famous for its collection of American Indian art, as well as its other diverse art collections.\n\n2. Visit the Denver Botanic Gardens: This urban oasis is a great place to enjoy the beauty of nature with a variety of themed gardens, a conservatory, and an amphitheater for seasonal events.\n\n3. Take a stroll through the historic Larimer Square: This historic district is Denver's oldest and most historic block, featuring unique shops, independent boutiques, an energetic nightlife, and some of the city's best restaurants.\n\nNow, let's get you the morning update with the current weather in Denver and the current date and time there. Please hold on a moment.",
    "tool_calls": [
        {
            "id": "call_IvszwiKTgEqxp82BSxt8vyOV",
            "function": {
                "arguments": "{\"city\": \"Denver\"}",
                "name": "get_weather"
            },
            "type": "function"
        },
        {
            "id": "call_oG82zPuqS8V99an5y0hqwdKj",
            "function": {
                "arguments": "{\"city\": \"Denver\"}",
                "name": "get_current_datetime"
            },
            "type": "function"
        }
    ]
}
{
    "role": "tool",
    "tool_call_id": "call_IvszwiKTgEqxp82BSxt8vyOV",
    "content": "Sunny and 75 degrees"
}
{
    "role": "tool",
    "tool_call_id": "call_oG82zPuqS8V99an5y0hqwdKj",
    "content": "Friday, Nov. 10, 2023, 10:00 AM"
}
{
    "role": "assistant",
    "content": "Your morning update for Denver is as follows:\n\n**Weather:** It's currently sunny and 75 degrees, a pleasant morning to enjoy your day!\n\n**Date and Time:** It's Friday, November 10, 2023, and the time is 10:00 AM.\n\nMake the most of your breakfast and have a fantastic time exploring all that Denver has to offer! If you need any more information or assistance, feel free to ask."
}If we stitch together thecontentof eachassistantmessage (2nd and last messages), we get a continuous block of response text:Sure, Denver offers many activities for a fun day out. Here are three fun things to do in Denver:Visit the Denver Art Museum: Recognized for its collection of American Indian Art and its impressive array of modern and contemporary pieces, the Denver Art Museum is a great place to get a dose of culture. The building itself is an architectural work of art.Explore the Denver Botanic Gardens: With a wide variety of plants from all corners of the world, as well as a conservatory and sunken amphitheater that hosts various concerts and events, the gardens provide a beautiful and tranquil escape from the city buzz.Take a stroll in the LoDo Historic District: Lower Downtown, or LoDo as it's affectionately known, is Denver's bustling district filled with late 19th and early 20th-century buildings. You can enjoy boutique shopping, a multitude of restaurants, and a vibrant nightlife.For your morning update, let me provide you with the current weather in Denver and the status of the stock market. Just a moment while I gather this information for you.Here's your morning update for Denver:Weather: It's a sunny day with a current temperature of 75 degrees Fahrenheit.Stock Market (SPY): The SPDR S&P 500 ETF (SPY), a good indicator of the stock market's overall performance, is currently trading at $182.41 USD, with a slight decrease of 0.26% today.Enjoy your breakfast and have a fantastic day exploring Denver! If you need any more assistance or information, feel free to ask.This single response was made up of multiple API calls/responses:Sent:User promptReceived:Content response (PART 1)Tool call to function:get_weatherTool call to function:get_stock_priceSent:Function result from:get_weatherFunction result from:get_stock_priceReceived:Content response (PART 2)The response text above combines the content from API responses2and4.Function Calling API(Deprecated by OpenAI)openai_functionandOpenaiToolGroupalso support the deprecated function-calling API.The function schema is accessible byschema["function"], which can be passed inside a list to thefunctionsargument in your OpenAI request. In anOpenaiToolGroup, this list is quickly accessible via thefunctionsproperty.Use.run_function_call()instead of.run_tool_call(), to process results.Example:message_from_openai={"role":"assistant","content":None,"function_call":{"name":"get_stock_price","arguments":"{\"ticker\":\"AAPL\"}",},}function_call=message_from_openai["function_call"]Userun_function_call()function_response_message=get_stock_price.run_function_call(function_call)function_response_message{
    'role': 'function',
    'name': 'get_stock_price',
    'content': '182.41 USD, -0.48 (0.26%) today'
}Create your own ChatGPT (function calling version)Conversation handler# subclassing our previous versionclassChatGPTFunctionConversation(ChatGPTConversation):defget_openai_response(self)->Choice:response=self.client.chat.completions.create(messages=self.messages,model=self.model,functions=self.tool_group.functions,# Pass `functions` instead of `tools`)returnresponse.choices[0]defchat(self):result=self.get_openai_response()self.add_message(result.message.model_dump(exclude_unset=True))# Handling just one function call, rather than a list of tool calls.func_call=result.message.function_calliffunc_call:result_msg=self.tool_group.run_function_call(func_call,error_handler=True)self.add_message(result_msg)ifresult.finish_reason=='function_call':self.chat()Using the same configuration as before, and sending the same messages.chatgpt=ChatGPTConversation(model="gpt-4-1106-preview",client=OpenAI(api_key=os.environ["OPENAI_API_KEY"]),tool_group=group,messages=[dict(role="system",content="You are a helpful AI assistant."),])chatgpt.send_message("Hello, how are you?"){
    "role": "user",
    "content": "Hello, how are you?"
}
{
    "content": "Hello! I'm just a computer program, so I don't have feelings, but I'm functioning optimally and ready to assist you. How can I help 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?"){
    "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?"
}
{
    "role": "assistant",
    "content": "Denver offers a wide variety of activities to enjoy. Here are three fun things you could consider doing:\n\n1. **Visit the Denver Art Museum**: The museum is one of the largest art museums between the West Coast and Chicago and offers a diverse collection of artworks from around the world.\n\n2. **Explore the Denver Botanic Gardens**: With a variety of themed gardens, a conservatory, and an amphitheater for summer concerts, the Denver Botanic Gardens provides a beautiful and relaxing urban oasis.\n\n3. **Walk or Bike the Cherry Creek Trail**: This scenic path runs through the heart of Denver and is great for biking, walking, or even just taking a leisurely stroll. It will give you great views of the city and nature alike.\n\nFor your morning update, let's check the current weather in Denver and see what the day looks like for you. I'll also provide you with the current date and time to start your day off informed. Please give me a moment to gather that information for you.",
    "function_call": {
        "name": "get_weather",
        "arguments": "{\"city\":\"Denver\"}"
    }
}
{
    "role": "function",
    "name": "get_weather",
    "content": "Sunny and 75 degrees"
}
{
    "role": "assistant",
    "content": null,
    "function_call": {
        "arguments": "{\"city\":\"Denver\"}",
        "name": "get_current_datetime"
    }
}
{
    "role": "function",
    "name": "get_current_datetime",
    "content": "Friday, Nov. 10, 2023, 10:00 AM"
}
{
    "role": "assistant",
    "content": "Here's your morning update for Denver:\n\n- **Weather**: It's a beautiful sunny day with temperatures around 75 degrees Fahrenheit. Perfect weather for any outdoor activities!\n- **Date and Time**: It's currently Friday, November 10, 2023, at 10:00 AM.\n\nWhether you decide to spend the day indoors or outdoors, it looks like it's shaping up to be a lovely day in Denver. Enjoy your breakfast and have a great day!"
}