Back

GPT Itinerary

2023 / 08 / 23

Intro

In a couple of my previous posts I outlined some of the work that went into SetOut, an iOS app which generates a travel itinerary, based on a set of criteria. While researching similar apps online, I decided to see how much GPT4 knows about places and more specifically, about tourist attractions in different countries.

Outlining the Scope

I didn't really want to spend more than a few hours on this mini-experiment. I was mostly interested in trying out the OpenAI API and interacting with GPT4. As a language model, I didn't expect it to know any accurate location information (such as geo coordinates), so I decided to use the Apple Maps Server API for geo coding and calculating the distance between the points of interested. To keep things simple, I wanted GPT4 to only provide a list of locations, without any descriptions.

The way I imagined it to work was as follows:

1. Authenticate with both OpenAI's and Apple Maps' Server API

2. Prompt the user to input a City and Country

3. Query GPT4 with the information provided by the user

4. Geo code the results and estimate the ETA between each location

5. Output the result

gpt-itinerary

A high-level view of what I expected the process to look like.

Interacting With GPT4

OpenAI's API docs are worth the read, even if you're simply interested in maximising the performance of your prompts to get more accurate responses from the model. Better prompts also allow you to specify the format of the model's response. This is very useful if you want responses to be formatted as a JSON and follow a specific schema. In my use-case I simply wanted to get a list of must see attractions, given a City and Country inputs.

To get started, it's best to provide an example query and expected response as context. The initial context is provided as a sample dialogue between the "user" and the "assistant", with GPT4 being the latter. In my case I used the following:

async def ChatCompletion(CITY: str, COUNTRY: str, MODEL: str = "gpt-4", TEMP: float = 0.5): response = await openai.ChatCompletion.acreate( model = MODEL, messages = [ {"role": "system", "content": "You are a helpful assistant. You provide users with a list of the top destinations to visit in a city. "}, {"role": "system", "name": "example_user", "content": "Generate a one day itinerary to visit the top tourist attractions in London, UK."}, {"role": "system", "name": "example_assistant", "content": "Tower Bridge|The British Museum|Covent Garden|St. Paul's Cathedral"}, {"role": "user", "content": f"Generate a one day itinerary to visit the top tourist attractions in {CITY}, {COUNTRY}."}, ], temperature = TEMP ) return response["choices"][0]["message"]["content"]

In the above example, messages labelled as "system" provide the context, while the "user" message is the one I'd like the model to provide a response to. The context section provides the model with an example request and the expected response format, which in my case was a list of tourist attractions with a delimiter.

Location Geo Coding and ETA

After getting the top attractions to visit in a city from GPT4, I wanted the app to output the distance between them, as well as an ETA. In the few tests I conducted, GPT4 wasn't always accurate about the geo coordinates of the locations it was providing, so I opted to use Apple Maps to get accurate results and to also get an ETA. I split the communication with Apple Maps' Server API in two steps. First, getting the geo coordinates of the tourist attractions provided by GPT4 and second, calculating the ETA between the coordinates. To get the coordinates I used the /v1/goecode endpoint:

async def GeoCodeLocation(access_token: str, address: str, country: str): headers = { "Content-Type": "application/json", "Authorization": "Bearer "+access_token } base_url = "https://maps-api.apple.com/v1/geocode" url = f"{base_url}?q={quote(address)}&limitToCountries={quote(country)}" async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as response: data = await response.json() if response.status != 200: print(f"Error with status code: {response.status}, response: {data}") return "" results = data.get("results", []) if not results or "coordinate" not in results[0]: return "" coordinate = results[0]["coordinate"] return f"{coordinate['latitude']},{coordinate['longitude']}"

To calculate the ETA I used the /v1/etas endpoint, which takes the origin and destination coordinates, along with an optional transport type:

async def GetEta(access_token: str, origin_coordinate: str, destination_coordinate: str): headers = { "Content-Type": "application/json", "Authorization": "Bearer "+access_token } base_url = "https://maps-api.apple.com/v1/etas" url = f"{base_url}?origin={origin_coordinate}&destinations={destination_coordinate}&transportType=Automobile" async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as response: data = await response.json() if response.status != 200: print(f"Error with status code: {response.status}, response: {data}") return "" results = data.get("etas", []) if not results: return "" distance_km = results[0]["distanceMeters"] / 1000 travel_minutes = results[0]["expectedTravelTimeSeconds"] / 60 return distance_km, travel_minutes

Putting it All Together

With the basic components in place, it was time to check how much GPT4 knows about places. Unsurprisingly, the answer was "A LOT". Regardless of how small or obscure the destination was, the model provided a consistent list of relevant results. The most inconsistent part of the app turned out to be the interaction with Apple Maps' API, which sometimes failed to geo code the locations correctly. I suspect that was due to my incorrect use of the API, since separate curl requests always yielded a result. Here's a fun example, from Mumbai, India:

@User itinerary-generator % python3 main.py --start "Mumbai" --country "IN" ['Gateway of India', 'Elephanta Caves', 'Marine Drive', 'Chhatrapati Shivaji Maharaj Vastu Sangrahalaya'] ------------------------------- ['Travel time between Gateway of India and Elephanta Caves is 1690.72 kilometers and 1814 minutes', 'Travel time between Elephanta Caves and Marine Drive is 51.45 kilometers and 388 minutes', 'Travel time between Marine Drive and Chhatrapati Shivaji Maharaj Vastu Sangrahalaya is 2.42 kilometers and 8 minutes']
gpt-itinerary-mumbai

The MacOS app correctly estimated the route, while the API gave an odd result.

At first I thought GPT4 got it wrong and suggested tourist attractions that are too far apart, but after checking Apple Maps, it turned out that the Elephanta Caves are located on a small island off Mumbai and are only accessible via a ferry. For some reason, the Apple Maps Server API calculated a different result from the Apple Maps MacOS app.

Looking Ahead

That was a fun little experiment, which took a couple of hours to put together. The full Python app can be found on my Github page. Overall I was really impressed by GPT4's knowledge about places. Instead of trying to set up my own database of locations, like I did when working on the SetOut app, I simply had to integrate with GPT4 and then use Apple Maps to calculate the ETAs and render the itinerary for the user. The ability to prototype ideas quickly with GPT4 is both inspiring and exciting. In one of my next projects, I'd love to delve deeper into the concept of AI Agents (or Assistants). Giving a model, as powerful as GPT4, tools to solve problems is an exciting prospect!