Search code examples
artificial-intelligencechatbotopenai-apichatgpt-apigpt-3

OpenAI Function Calling Error ---- openai.error.InvalidRequestError: <exception str() failed>


I am creating a chatbot which can query all 'Views' in my database based on user query. I tried many other methods but didn't succeed so now I thought I should try OpenAI's function calling.

What I did: I created a function for one of the view. In that, I am calling GPT3 to create a SQL query based on the user question that I provide in the parameter. I have given instructions and schema to the model so it can create correct query. Below is the function.

def get_rent_details(user_query):
    """Get the current weather in a given location"""
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        prompt="""User will ask you the question regarding their properties, assets and finance. 

        Follow below steps to get correct answer:

        1. Understand the user question and prepare a syntactically correct SQL query to retrieve the correct data.
        2. If you don't find the data in the table, just type "No answer found".
        3. Do not make up any answer by your own.
        4. Instead of '=', always use 'LIKE' statement with 'WHERE' statement.
        5. The user will mention either property name or tenant name. So to make sure the query is correct, use both columns 'TenantName' and 'PropertyName' with 'WHERE' statement. For example: SELECT PropertyCode FROM viewRentRoll WHERE PropertyName LIKE 'Younger, 3003' OR TenantName LIKE 'Younger, 3003'.
        6. DO NOT create any DML query like UPDATE, INSERT, DELETE, ADD.
        7. Below is the table schema to run query on:

        CREATE TABLE [dbo].[viewRentRoll] (            
       [PropertyPKId] [bigint]
      ,[PropertyCode] [nvarchar]
      ,[PropertyName] [nvarchar]
      ,[PropertyList] [nvarchar]
      ,[LeaseCode] [nvarchar]
      ,[TenantName] [nvarchar]
      ,[SnP Rating] [nvarchar]
      ,[Unit Number] [nvarchar]
      ,[Lease Status] [nvarchar]
      ,[Lease Start Date] [datetime]
      ,[Lease Expiration Date] [datetime]
      ,[Unit Square Feet] [bigint]
      ,[Remaining Lease Term] [bigint]
      ,[Currently Monthly Base Rent] [bigint]
      ,[Rent PSF] [bigint]
      ,[ABR] [bigint]
      ,[local tenant] [nvarchar]
      ,[Current Annualized Base Rent PSF] [bigint]
      ,[CreatedLeaseExpirationDate] [datetime]
      ,[TenantCategory] [nvarchar]
  )
    """ + user_query,
        max_tokens=200,
        temperature=0,
    )
    return (response['choices'][0]['text'])

I am thinking to create such functions for each view. After this I got the code from OpenAI Function Calling documentation and modified it as per my need. Below is the 'function calling' function:

def run_conversation(user_query):
    # Step 1: send the conversation and available functions to GPT

    print("Running run_conversion............\n\n")

    messages = [{"role": "user", "content": user_query}]
    functions = [
        {
            "name": "get_rent_details",
            "description": "Get the details of rent of tenants or properties",
            "parameters": {
                "type": "object",
                "user_query" : {
                    "type" : "string",
                    "description" : "User's question regarding the rent of Tenant or properties"
                }
            }
        }
    ]
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=messages,
        functions=functions,
        function_call="auto",  # auto is default, but we'll be explicit
    )
    response_message = response["choices"][0]["message"]

    # Step 2: check if GPT wanted to call a function
    if response_message.get("function_call"):
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors
        available_functions = {
            "get_rent_details": get_rent_details,
        }  # only one function in this example, but you can have multiple
        function_name = response_message["function_call"]["name"]
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message["function_call"]["arguments"])
        function_response = fuction_to_call(
            user_query=function_args.get("user_query"),
        )

        # Step 4: send the info on the function call and function response to GPT
        messages.append(response_message)  # extend conversation with assistant's reply
        messages.append(
            {
                "role": "function",
                "name": function_name,
                "content": function_response,
            }
        )  # extend conversation with function response
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=messages,
        )  # get a new response from GPT where it can see the function response
        return second_response

This is the first time I am trying Function Calling so I am not hundred percent sure if this will work. When I run this code, I am getting this error: openai.error.InvalidRequestError: <exception str() failed> for response = openai.ChatCompletion.create() in run_conversation(user_query) function.

Can anyone please guide me where I am making mistakes?

I am providing whole code below:

import openai
import json
import os

user_query = "What is the monthly rent of Good Neighbor Homes, Inc."

openai.api_key=os.environ['OPENAI_API_KEY']

def run_conversation(user_query):
    # Step 1: send the conversation and available functions to GPT

    print("Running run_conversion............\n\n")

    messages = [{"role": "user", "content": user_query}]
    functions = [
        {
            "name": "get_rent_details",
            "description": "Get the details of rent of tenants or properties",
            "parameters": {
                "type": "object",
                "user_query" : {
                    "type" : "string",
                    "description" : "User's question regarding the rent of Tenant or properties"
                }
            }
        }
    ]
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=messages,
        functions=functions,
        function_call="auto",  # auto is default, but we'll be explicit
    )
    response_message = response["choices"][0]["message"]

    # Step 2: check if GPT wanted to call a function
    if response_message.get("function_call"):
        # Step 3: call the function
        # Note: the JSON response may not always be valid; be sure to handle errors
        available_functions = {
            "get_rent_details": get_rent_details,
        }
        function_name = response_message["function_call"]["name"]
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message["function_call"]["arguments"])
        function_response = fuction_to_call(
            user_query=function_args.get("user_query"),
        )

        # Step 4: send the info on the function call and function response to GPT
        messages.append(response_message)  # extend conversation with assistant's reply
        messages.append(
            {
                "role": "function",
                "name": function_name,
                "content": function_response,
            }
        )  # extend conversation with function response
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=messages,
        )  # get a new response from GPT where it can see the function response
        return second_response


def get_rent_details(user_query):
    """Get the current weather in a given location"""
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        prompt="""User will ask you the question regarding their properties, assets and finance. 

        Follow below steps to get correct answer:

        1. Understand the user question and prepare a syntactically correct SQL query to retrieve the correct data.
        2. If you don't find the data in the table, just type "No answer found".
        3. Do not make up any answer by your own.
        4. Instead of '=', always use 'LIKE' statement with 'WHERE' statement.
        5. The user will mention either property name or tenant name. So to make sure the query is correct, use both columns 'TenantName' and 'PropertyName' with 'WHERE' statement. For example: SELECT PropertyCode FROM viewRentRoll WHERE PropertyName LIKE 'Younger, 3003' OR TenantName LIKE 'Younger, 3003'.
        6. DO NOT create any DML query like UPDATE, INSERT, DELETE, ADD.
        7. Below is the table schema to run query on:

        CREATE TABLE [dbo].[viewRentRoll] (            
       [PropertyPKId] [bigint]
      ,[PropertyCode] [nvarchar]
      ,[PropertyName] [nvarchar]
      ,[PropertyList] [nvarchar]
      ,[LeaseCode] [nvarchar]
      ,[TenantName] [nvarchar]
      ,[SnP Rating] [nvarchar]
      ,[Unit Number] [nvarchar]
      ,[Lease Status] [nvarchar]
      ,[Lease Start Date] [datetime]
      ,[Lease Expiration Date] [datetime]
      ,[Unit Square Feet] [bigint]
      ,[Remaining Lease Term] [bigint]
      ,[Currently Monthly Base Rent] [bigint]
      ,[Rent PSF] [bigint]
      ,[ABR] [bigint]
      ,[local tenant] [nvarchar]
      ,[Current Annualized Base Rent PSF] [bigint]
      ,[CreatedLeaseExpirationDate] [datetime]
      ,[TenantCategory] [nvarchar]
  )

"""+user_query+"?",
        max_tokens=200,
        temperature=0,
    )
    print(response['choices'][0]['text'])
    return (response['choices'][0]['text'])

run_conversation(user_query)

Solution

  • Try modifying the function to something like this:

    {
        "name": "get_rent_details",
        "description": "Get the details of rent of tenants or properties",
        "parameters": {
            "type": "object",
            "properties": {
                "user_query": {
                    "type": "string",
                    "description": "User's question regarding the rent of Tenant or properties"
                }
            },
            "required": ["user_query"]
        }
    }
    

    I.e. add properties and required.

    I was facing a similar problem and what worked for me was to start with an official Open AI function that I verified to work, and then I modified it line by line to verify that no change breaks it. I had missed one of the required attributes.

    I built myself also a validation function to check that I pass only valid functions to Open AI in the future. It's not perfect but has already helped me catch a few bugs.

    def validate_function(function):
        # example func
        """
        function = {
            "name": "get_current_weather",
            "description": "Get the current weather in a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    },
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["location"],
            },
        }
        """
    
        # Check the presence of required keys and their types
        assert "name" in function and isinstance(
            function["name"], str
        ), "'name' must be a string."
        assert "description" in function and isinstance(
            function["description"], str
        ), "'description' must be a string."
        assert "parameters" in function and isinstance(
            function["parameters"], dict
        ), "'parameters' must be a dictionary."
    
        # Check the structure of 'parameters' key
        params = function["parameters"]
    
        assert (
            "type" in params and params["type"] == "object"
        ), "'type' must be 'object' in parameters."
        assert "properties" in params and isinstance(
            params["properties"], dict
        ), "'properties' must be a dictionary."
        assert "required" in params and isinstance(
            params["required"], list
        ), "'required' must be a list."
    
        # Check the structure of 'properties' in 'parameters'
        for key, prop in params["properties"].items():
            assert "type" in prop and isinstance(
                prop["type"], str
            ), f"'type' must be a string in properties of {key}."
    
            if prop["type"] == "array":
                assert (
                    "items" in prop
                ), f"'items' must be present in properties of {key} when type is 'array'."
    
            # Enum check only if it exists
            if "enum" in prop:
                assert isinstance(
                    prop["enum"], list
                ), f"'enum' must be a list in properties of {key}."
    
        # Check 'required' properties are in 'properties'
        for key in params["required"]:
            assert (
                key in params["properties"]
            ), f"'{key}' mentioned in 'required' must exist in 'properties'."