Manifest
Part 0 - Introduction
Part 1 - Failing Fast (Building with prompts) *You are Here 💁♀️
Part 2 - MVP and Deployments
Part 3 - MVPolish
Part 4 - So what.
be sure to read Part 0 for the “why”
Getting Started
(For those who prefer not to read the text in the images, rest assured, I'll continue to provide a summary of the progress after each visual update.)
My initial goal is to make a lambda function that will use a timezone database (which I did not specify any details about), compare the current time to the database and return the location, longitude and latitude of the result. I first try to ask GPT-4 to do this like I was writing comments in a python file.
First Fail Fast
Ah, our initial query is also a pivotal realization in our AI-powered odyssey. Indeed, approaching tasks with LLMs like GPT-4 requires a different mindset. Asking too much in one go is a common pitfall, and it's one that I've just encountered firsthand.
Helpfully, ChatGPT has used nominatim
which is a nice library - however, its purpose is to find places by their name or address and turn them into GPS Coordinates like “New York” → 40°42′46″N 74°00′22″W. It can also take a longitude and latitude and turn them into the nearest address like 36°49′25.6″S 139°51′47.5″E → 17 Princes Hwy, Kingston SE SA 5275 Australia.
I want to turn the time into a place and I don’t know where that place is or what its address is.
To attempt this - ChatGPT has taken the current time, iterated over all the timezones provided by the pytz
library (converting to UTC), finds the (doesn’t handle the case were there are multiple “named” timezones where its 5pm) singular timezone where its 5pm and then finds the singular longitude and latitude of the timezone. Can you even do that?
I’ve obviously not explained myself well.
So, the key is to break tasks into bite-sized, ordered steps. This approach mitigates reduces the chance of GPT-4 'hallucinating' - an AI's tendency to generate outputs that are coherent but detached from reality or the provided data. Let's recalibrate and proceed with this refined approach.
Fixing the first error
Remember - I can’t test this code, yet, I know it's wrong. I just told you it's wrong too - but I must push on since I need to give ChatGPT the error the code generates and allow it to make changes.
I quickly get an error - after those initial questions we have the following code for the inaugural lambda function:
import datetime
import pytz
from geopy.geocoders import Nominatim
import json
def get_location_with_time_between_5_and_6():
geolocator = Nominatim(user_agent="myGeocoder")
current_utc_time = datetime.datetime.utcnow()
for tz in pytz.all_timezones:
try:
local_time = current_utc_time.astimezone(pytz.timezone(tz))
if local_time.hour == 17: # Check if the local time is between 5 PM and 5:59 PM
location = geolocator.timezone(tz)
city_info = geolocator.reverse((location.latitude, location.longitude), language='en')
city = city_info.raw.get('address', {}).get('city', '')
return {
"latitude": location.latitude,
"longitude": location.longitude,
"timezone": tz,
"city": city
}
except Exception as e:
pass
return None
def lambda_handler(event, context):
location_data = get_location_with_time_between_5_and_6()
if location_data:
response_body = {
"latitude": location_data["latitude"],
"longitude": location_data["longitude"],
"timezone": location_data["timezone"],
"city": location_data["city"],
}
status_code = 200
else:
response_body = {"error": "No location found where the time is between 5 PM and 5:59 PM."}
status_code = 404
return {
"statusCode": status_code,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(response_body),
}
Finally something I can pass back to Chat GPT to iterate on - I passed the error into the prompt and the soon to be classic response “My apologies for the confusion…”
If you’re familiar with Lambda, you know that the lambda_handler
function looks like this in python:
def lambda_handler(event, context):
It has been shown the door.
In its place, we're charting a less-traveled path - defining a custom lambda handler function name.
This decision, while it certainly dials up the complexity quotient, might seem like an exercise in masochism. Especially when we're also keeping a function named 'lambda_handler' in the code and not using it as the lambda handler, setting up a veritable obstacle course for future readability.
But sometimes, in the pursuit of innovation, conventional wisdom is tossed to the wind, and we are made to navigate the labyrinthine I hope you’re studying for the SAT intricacies of code. This is one of those times?
I update the lambda function to use the find_location.lambda_handler
, as poorly prescribed, and move on.
Now, with two 'lambda_handlers' we push ahead. Changing gears, I contemplate simplifying our function. I've planned on using a DynamoDB table to store the list of cities and timezones, but the data source is still unclear. Instead of the DynamoDB table, could a Python library be the answer? I just need latitude, longitude, name, and timezone. Let's delve into Python's rich ecosystem and see if it already has what we need.
My lucky day. A pre-built library that does exactly what I need. I’m overjoyed with enthusiasm as I have GPT-4 update my lambda to use this newly discovered library. Why hadn’t it been suggested to me previously?
In this example, the get_cities_in_timezone
function takes a target timezone as an argument and returns a list of cities in that timezone. The cities
package provides a dictionary of cities and their related information, including the timezone.
I have it update the script to randomly select a city from the list and also return the longitude and latitude.
Another much longer function emerges. I test for the second time.
My test can’t find the cities
module. ChatGPT claims that I’m facing compatibility issues between a Python package and Lambda?
That's unusual.
I've also spared you from reading quite a few interactions in which GPT-4 assisted me in packaging a zip file for Lambda deployment. It guided me through creating a virtual environment and installing a few packages - pytz
, cities
, and geopy
.
Even with the best tools at our disposal, We still encounter stumbling blocks. It's all part of the journey, reminding us that problem-solving is an integral part of any coding expedition - being human or AI for that matter.
I’m pretty sure the problem is that there is no such thing as cities
or citytimezones
packages - but I’m not allowed to say that.
I break my first rule and strike gold
I had to break the rule.
I was stuck for at least 30 min and could not discover a way forward here without being suggestive. I hated doing this, but I needed wanted progress.
Instead ChatGPT suggests that we use the GeoNames Database! They curate lists of cities and timezones!
I check and the GeoNames Database is real! The provided URL works and this dataset has exactly what I need.
What’s more is that the output of the script parse_geoname.py
is actually pretty good - Spoiler alert: by the end of the project I’m still using some of this code.
GPT-4 then suggests that I update my code for the lambda to use the resulting json file and query it. After 3 errors it starts returning something; although its slow (due to locally processing a rather large json file, but I’m not supposed to know this) typically 8-10 seconds to get an answer. I ask it how I can understand which parts are slow. It suggests I print the time it takes after each section to discover this from the logs:
print(f"Time taken to load cities: {time.time() - start_time:.2f} seconds")
I’m going to speed this part up a bit as there are a lot of prompts, back and forth’s and otherwise boring yet helpful interactions, over the course of this project I asked 443 questions in this chat alone! There’s not enough time and let’s be honest, you’re not interested enough to read all of those messages (if you are a paid subscriber you can read the whole chat log in the paid content section at the end of this post.)
I ask if an AWS service can do this instead of time
Suggests X-Ray
I ask to implement X-Ray
Implements X-Ray in the lambda
I give it the logs from x-ray and CodeGuru Profile (which it implemented too)
It determines which areas in the functions take the longest (but outputs fictitious times)
I ask if I can use DynamoDB to store the json instead - There I go again breaking the rules
Yes you can
Write a script that can do that
Outputs Script !! using a Lambda Function!
Script uses Provisioned Capacity (more expensive than I’m willing to tolerate for a test so I ask it to use
PAY_PER_REQUEST
, I know, I know, I’m bad)The Lambda function takes 10 minutes to run at the largest Lambda Size 🥵
8 different questions about why the table isn’t populating
7 different wrong answers
“stop using floats” - ChatGPT - this works.
2 questions about failed hydration
track duplicate cities (there are none 😉 but I decide not to tell it this)
22 questions to update the Lambda to use the DynamoDB table instead of the json file
working now!
I remind it another 10 times I don’t want provisioned capacity and we have to fix the float issue again and again until it works (it kept either reverting the code or not implementing strings in the right places). And it’s still slow, maybe 10seconds? (Its slow because we’re doing table scans instead of using a GSI in our DynamoDB table)
Here is the code for the lambda at this stage:
def lambda_handler(event, context):
all_timezones = pytz.all_timezones
five_pm_timezones = [
timezone for timezone in all_timezones
if 17 <= datetime.now(pytz.timezone(timezone)).hour < 18
]
random_timezone = random.choice(five_pm_timezones)
try:
response = table.scan(
FilterExpression=Key('timezone').eq(random_timezone)
)
except ClientError as e:
print(e.response['Error']['Message'])
return {
'statusCode': 500,
'body': 'An error occurred while fetching data from DynamoDB.'
}
if not response['Items']:
return {
'statusCode': 404,
'body': f'No cities found in timezone: {random_timezone}'
}
random_city = random.choice(response['Items'])
local_time = datetime.now(pytz.timezone(random_city['timezone'])).strftime('%Y-%m-%d %H:%M:%S')
return {
'statusCode': 200,
'body': {
'city': random_city['city_name'],
'local_time': local_time,
'latitude': random_city['latitude'],
'longitude': random_city['longitude'],
'timezone': random_city['timezone']
}
}
Here's a step-by-step explanation of what it does:
Initialize
all_timezones
: It takes all the time zones available through thepytz
library and stores them in theall_timezones
list.all_timezones = pytz.all_timezones
Find 5 PM Timezones: It filters
all_timezones
to create a new list,five_pm_timezones
, containing only the timezones where the current local time is between 5 PM and 6 PM.five_pm_timezones = [ timezone for timezone in all_timezones if 17 <= datetime.now(pytz.timezone(timezone)).hour < 18 ]
Random Timezone Selection: It randomly selects one timezone from
five_pm_timezones
.random_timezone = random.choice(five_pm_timezones)
Query DynamoDB: It queries an AWS DynamoDB table using the
table.scan
method, filtering records where the 'timezone' key matches therandom_timezone
.
try:
response = table.scan(
FilterExpression=Key('timezone').eq(random_timezone)
)
except ClientError as e:
# Error handling for DynamoDB client error
Handle Errors: If there is a client error with DynamoDB, it prints the error message and returns a response with status code 500.
Check Response: It checks if the query returned any items (
response['Items']
). If not, it returns a response with status code 404 and a message saying no cities were found in therandom_timezone
.
if not response['Items']:
return {
'statusCode': 404,
'body': f'No cities found in timezone: {random_timezone}'
}
Random City Selection: If there are cities found in the
random_timezone
, it randomly picks one.
random_city = random.choice(response['Items'])
Local Time: It finds the local time in the timezone of the randomly picked city and formats it.
local_time = datetime.now(pytz.timezone(random_city['timezone'])).strftime('%Y-%m-%d %H:%M:%S')
Return Data: Finally, it returns the response with status code 200 and the details of the randomly picked city, including its name, local time, latitude, longitude, and timezone.
return { 'statusCode': 200, 'body': { 'city': random_city['city_name'], 'local_time': local_time, 'latitude': random_city['latitude'], 'longitude': random_city['longitude'], 'timezone': random_city['timezone'] } }
IT’S WORKING!
We now have a Lambda function, and a DynamoDB instance returning a random place in the world where it is between 5:00 PM and 5:59:59 PM- now to build the first version of the front end and make some IaC.
To get to this point I’ve only reviewed 1/7 of my chat logs with you.
In the next part I’ll tackle my MVP and Infrastructure as code.
Manifest
Part 0 - Introduction
Part 1 - Failing Fast (Building with prompts) *You are Here 💁♀️
Part 2 - MVP and Deployments
Part 3 - MVPolish
Part 4 - So what.
I’m sure you want to see each and every prompt; but there are hundreds if not thousands of prompts and I couldn’t possibly paste them here…. oh, ChatGPT has link sharing - as a special offer for paid subscribers, you can access the enthralling conversation below
Keep reading with a 7-day free trial
Subscribe to One Twenty Three Cloud Street to keep reading this post and get 7 days of free access to the full post archives.