Errors in last step of appending messages in function calling

from common import *

class GoogleCalendarClient:
    def __init__(self):
        self.SCOPES = ['https://www.googleapis.com/auth/calendar']
        self.creds = self.get_credentials()
        self.service = self.build_calendar_service()

    def get_credentials(self):
        creds = None
        if os.path.exists('../token.pickle'):
            with open('../token.pickle', 'rb') as token:
                creds = pickle.load(token)

        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    '../credentials.json', self.SCOPES)
                creds = flow.run_local_server(port=0)

            with open('../calendar_token.pickle', 'wb') as token:
                pickle.dump(creds, token)

        return creds

    def build_calendar_service(self):
        return build('calendar', 'v3', credentials=self.creds)


    def create_event(self, summary, description, email, start_datetime, end_datetime):

        event_data = {
            'summary': summary,
            'description': description,
              'attendees': [
                {'email': email}
            ],
            'start': {
                'dateTime': start_datetime,
                'timeZone': 'Europe/London',
            },
            'end': {
                'dateTime': end_datetime,
                'timeZone': 'Europe/London',
            },
        }

        event = self.service.events().insert(calendarId='primary', body=event_data).execute()
        return event



    def update_event(self, event_id, updated_event_data):
        event = self.service.events().update(calendarId='primary', eventId=event_id, body=updated_event_data).execute()
        return event

    def delete_event(self, event_id):
        self.service.events().delete(calendarId='primary', eventId=event_id).execute()


google_calendar_client = GoogleCalendarClient()

# You can add more methods as needed to interact with the Google Calendar API



available_tools = {
    "create_event": google_calendar_client.create_event,
    "update_event": google_calendar_client.update_event,
    "delete_event": google_calendar_client.delete_event,

}


tools = [
    {   "type": "function",
        "function": {
        "name": "create_event",
        "description": "Creates an event in Google Calendar",
        "parameters": {
            "type": "object",
            "properties": {
                "summary": {"type": "string", "description": "The title of the event"},
                "description": {"type": "string", "description": "The body of the event. If not specified, always enter a relevant description"},
                "email": {"type": "string", "description": "The recipient of the event"},
                "start_datetime": {"type": "string", "description": "The start time of the event. Always assume year to be 2024."},
                "end_datetime": {"type": "string", "description": "The end time of the event. Always assume year to be 2024. If not specified, assume the event is 30 mins long"},
            },
            "required": ["email", "start_datetime", "end_datetime"]
        }
                     }},
    
        {"type": "function",
        "function": {
        "name": "update_event",
        "description": "Updates an event in Google Calendar",
        "parameters": {
            "type": "object",
            "properties": {
                "event_id": {"type": "string", "description": "The ID of the event"},
            },
            "required": ["event_id"]
        }
    }},

    {  "type": "function",
        "function": {
        "name": "delete_event",
        "description": "Deletes an event in Google Calendar",
        "parameters": {
            "type": "object",
            "properties": {
                "event_id": {"type": "string", "description": "The ID of the event"},
            },
            "required": ["event_id"]
        }
    }}
          ]





user_input = "Create an intro meeting for 31 January at 12pm with adam@google.com. Add a description too."

messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": user_input},
    ]

def get_gpt_response(input):
    return client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=input,
        tools=tools,
        tool_choice='auto'
    )




response = get_gpt_response(messages)
response


tool_name = response.choices[0].message.tool_calls[0].function.name
#print(tool_name)
arguments = response.choices[0].message.tool_calls[0].function.arguments
arguments = json.loads(arguments)
#print(arguments)
#tool_call_id = response.choices[0].message.tool_calls[0].id

def execute_tool(tool_name, args):
    # Check if the tool name is in the available tools
    if tool_name in available_tools:
        # Retrieve the corresponding function from available_tools
        tool_func = available_tools[tool_name]

        # Call the function with the provided arguments
        return tool_func(**args)
    else:
        raise ValueError("Tool not found")


tool_response = execute_tool(tool_name, arguments)
tool_response

So the above code works in making the API call to create a Google Calendar event. The next step (below) is getting the response from the API call and appending it to the messages list. Then we need to call the API again with the new messages list. However, I am stuck here. This is what I have:

# Get the assistant's reply from the API response
assistant_reply = response.choices[0].message
assistant_reply

# Convert the tool response to a JSON string
tool_response_str = json.dumps(tool_response)
tool_response_str

# Extend the conversation by appending the assistant's reply to the messages list
messages.append(assistant_reply)

# Append the tool response to the messages list
messages.append({"role": "assistant", "content": tool_response_str})

# Call the API again with the updated messages list

response = get_gpt_response(messages)
response 

I keep getting errors along the lines of:

openai.BadRequestError: Error code: 400 - {'error': {'message': "An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_HhdMH9JARdZpiXOodXcP6HF1", 'type': 'invalid_request_error', 'param': 'messages.[3].role', 'code': None}}

I have tried different variations of code in an attempt to solve this but have failed.

Can anyone see what I am doing wrong?

You need to add on to the chat history after the user input with both a “simulation” of what the AI emitted:

params['messages'].extend(
[
  {
    "role": "assistant",
    "content": "Let me look up the weather in those cities for you...",
    "tool_calls": [
         {
          "id": "call_rygjilssMBx8JQGUgEo7QqeY",
          "type": "function",
          "function": {
            "name": "get_weather_forecast",
            "arguments": "{\"location\": \"Seattle\", \"format\": \"fahrenheit\", \"time_period\": 1}"
          }
        },
        {
          "id": "call_pI6vxWtSMU5puVBHNm5nJhw3",
          "type": "function",
          "function": {
            "name": "get_weather_forecast",
            "arguments": "{\"location\": \"Miami\", \"format\": \"fahrenheit\", \"time_period\": 1}"
        },
    }
    ]
  }
]
)

And then also the response created by your tool’s code or call:

params['messages'].extend(
[
  {
    "role": "tool", "tool_call_id": "call_rygjilssMBx8JQGUgEo7QqeY", "content":
        "Seattle 2022-12-15 forecast: high 62, low 42, partly cloudy\n"  
  },
  {
    "role": "tool", "tool_call_id": "call_pI6vxWtSMU5puVBHNm5nJhw3", "content":
        "Miami 2022-12-15 forecast: high 77, low 66, sunny\n"  
  }
]
)

Both of these must be supplied as a message pairing, with matching tool id for each of emitted tool call and tool answer.