Hi and welcome to this mini-course on AI engineering! In this mini-course you will learn the basic principles of AI engineering by using ChatGPT to automatically write blog articles on any news topic.
I will show you some cool tricks along the way, but rest assured, you don’t need to be an expert in coding or anything else to follow along. This is a beginner-friendly tutorial and we will have ample explanation for everything we do along the way. So, let’s get started!
What we’ll need
To follow along with this tutorial, you’ll need a code editor. I recommend using Visual Studio Code, but you can use any code editor you like. If you don’t have a code editor installed, you can download Visual Studio Code from here.
You’ll also need to have Python installed on your computer. If you don’t have Python installed, you can download it from here. If you’re asked whether you want to add Python to your PATH, make sure to check yes.
The last thing we’ll need is a ChatGPT account. If this is your first time using it, don’t worry, you will get a bunch of free credits after signing up for your first account so you should be able to follow along without having to pay any money.
Go to https://platform.openai.com/ and log in. If you already use OpenAI and already have an account set up with a free or paid API key, you can use that. If you don’t have an account just log in with your Google account. It will ask you something simple, like to fill in your birthday, and ta-da, you have an account!
When you log in on a brand new account you will see something like this (navigate to the dashboard if you land on a different page):
Find API keys in the left sidebar and click on it. If this is a new account it will ask you to verify your phone in order to create a new API key:
The reason they do this is to prevent bots from creating loads of free accounts and abusing their system. Just give them a phone number and they will send you a verification code to enter. You will also get a bunch of free credits from them to follow along with this tutorial, so it’s a win-win!
Find the green button to + Create new secret key
in the top right corner and click on it:
In the next window, you can leave everything as is. You don’t need to give it a name or select a project. You can do these things if you want to, but I’ll just create a nameless general key for now by accepting everything as is and clicking the green Create secret key
button:
You will now see your new API key:
So make sure you press the Copy
button and save it somewhere safe, maybe a password manager. You won’t be able to see this key again, though you can always generate a new one if you lose it. Make sure not to share your key as anyone with your key can use your credits!
Now that we have our API key, we can start on our automatic blog writing journey!
Project setup
So let’s take a brief test-run of how ChatGPT works when using the API with Python. After this, it will be a lot easier to create our automated blog writing script. First of all, create a folder for your project. I’ll name mine Intro_to_AI_engineering
:
π Intro_to_AI_engineering
Now open the folder in VSCode. You can do this by right-clicking the folder and selecting Open with Code
. If you don’t see this option, you can open VSCode and then open the folder from there.
The first thing we’ll need is to save our API key in this project. We don’t want to hardcode it into our scripts, as that would be a security risk. Instead, we’ll save it in a separate file and read it from there. This way, we can also easily share our code without sharing our API key.
Create a new file in the folder by right-clicking the explorer/browser sidebar and selecting New File
. Name it .env
:
π Intro_to_AI_engineering π .env
The file has no name but only the extension .env and will store our API key. Open the file and paste your API key in there:
OPENAI_API_KEY=sk-loadsoflettersandnumbers
Make sure to replace sk-loadsoflettersandnumbers
with your actual API key. Save and close this file. If you ever share your code, make sure to exclude this file from the shared code.
The next thing we’ll need to do is install two libraries for Python. Open a terminal in VSCode by clicking Terminal
in the top menu and selecting New Terminal
, or using the shortcut [Ctrl + `]
. In the terminal, type the following command and press Enter:
pip install openai --upgrade
This will either install or update (if you already have it) the OpenAI Python library. This library will allow us to easily interact with the ChatGPT API.
The next library we need is called python-dotenv
. This library will allow us to read the API key from the .env
file we created. Install it by typing the following command in the terminal and pressing Enter:
pip install python-dotenv
Making our first ChatGPT request
Now that we have everything set up and ready to go, let’s create a new Python file in the folder by right-clicking the folder in the sidebar and selecting New File
. Name the file first_request.py
:
π Intro_to_AI_engineering π .env π first_request.py
Open up this file and let’s make a request. The first thing we’ll put in this file is our imports:
import os from openai import OpenAI from dotenv import load_dotenv
The os
(operating-system) library will allow us to read the API key from the .env
file, used in combination with the load_dotenv
import. The OpenAI
import is the library we installed earlier that will allow us to interact with the ChatGPT API.
Now we’ll load the API key and set up the OpenAI object. Add this below the imports:
load_dotenv() CLIENT = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
Calling the load_dotenv()
function will read the .env
file with our API key and make it so the os
library now has access to this key. We then create a CLIENT
object using the OpenAI
class we imported and pass in an argument api_key
with the value of our API key.
The os.getenv('OPENAI_API_KEY')
function will read the environment variable OPENAI_API_KEY
from the .env
file. This is why it’s important to name the key in the .env
file the same as the environment variable we’re trying to read. The OpenAI
object which we named CLIENT
now has access to our api_key
.
Don’t worry too much about this if it seems confusing, this is actually one of the ‘hardest’ parts of the tutorial and is just sort of boilerplate setup code.
So now that we have a CLIENT
to interact with ChatGPT, let’s write some instructions that we want to give to ChatGPT. Add the following code below the previous code:
duck_instructions = """ You are a duck. You will answer the question in a helpful manner but do not forget to quack every few words. My question is: What is the fastest animal on Earth? """
We create a variable duck_instructions
and assign it a multi-line string. A multi-line string just means that it can span multiple lines and is enclosed by triple quotes """
. This string contains instructions for ChatGPT to follow.
We’re asking ChatGPT to pretend it’s a duck and answer the question “What is the fastest animal on Earth?” in a helpful manner, but also to quack every few words.
Now let’s continue below our previous code and make a call to ChatGPT using our CLIENT
object:
print("Asking DuckGPT...") duck_response = CLIENT.chat.completions.create( model="gpt-4o", messages=[ {"role": "user", "content": duck_instructions} ] )
First of all, we print a message to the terminal window saying we’re asking DuckGPT. We can do this by calling the print
function and passing in a string value, which is enclosed by quotes "
.
Then we call the CLIENT.chat.completions.create
function. This function will send a message to ChatGPT and get a response back.
We pass in two arguments to this function. The first argument is model="gpt-4o"
. This tells ChatGPT which model to use. We’re using the gpt-4o
model, which is the latest version of ChatGPT at the time of writing this tutorial.
The second argument is messages=[{"role": "user", "content": duck_instructions}]
. This is a list [list]
of messages we’re sending to ChatGPT. We’re sending one message, which is a dictionary {dict}
with two keys: role
and content
.
The role
key is set to "user"
, which means we’re pretending to be the user asking the question. The content
key is set to duck_instructions
, which is the variable containing our instructions for ChatGPT.
This call will return a response that will be assigned to the variable duck_response
. This response will contain the answer from ChatGPT.
The response that ChatGPT sent us, which is saved in the variable duck_response
, is an object with a lot of information and not just the answer to our question. We can access the answer by using the following code:
quack_talk = duck_response.choices[0].message.content
Now this is quite long and I’m not sure why the object is so complex, but it is what it is. Please don’t worry too much about the exact structure here but the answer is located in the response object’s choices
list’s first item [0]
and then the message
key and then the content
key. This means that the quack_talk
variable now contains the actual text answer from ChatGPT.
Finally, let’s print the answer to the terminal window:
print(f"The duck says: {quack_talk}")
We use a string value inside the print
function again, but this time we’re using an f-string
. An f-string
is a string that is prefixed with an f
before the opening quote "
and allows us to insert variables into the string by using curly braces {}
. The variable quack_talk
will be inserted into the string where the curly braces are.
Your entire code will now look like this:
import os from openai import OpenAI from dotenv import load_dotenv # pip install python-dotenv load_dotenv() CLIENT = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) duck_instructions = """ You are a duck. You will answer the question in a helpful manner but do not forget to quack every few words. My question is: What is the fastest animal on Earth? """ print("Asking DuckGPT...") duck_response = CLIENT.chat.completions.create( model="gpt-4o", messages=[ {"role": "user", "content": duck_instructions} ] ) quack_talk = duck_response.choices[0].message.content print(f"The duck says: {quack_talk}")
Now if you save this file and look in the top right corner above your code, you should see a play button:
Go ahead and press this button and it will run your code. You should see an output in the terminal window below your code that says something like:
Asking DuckGPT... The duck says: Quack! The fastest quack animal on Earth quack is the peregrine falcon. When quack in a dive, quack this bird quack can reach speeds of over quack 240 miles per hour (386 kilometers quack per hour). Quack!
And that’s it! You’ve just made your first request to ChatGPT using Python. That is one really fast bird by the way! You’ve just completed the hardest part of this tutorial, so give yourself a pat on the back!
Writing a blog article
Now that you have a basic understanding of how to call ChatGPT, all we need is to take this knowledge and apply it to writing blog articles. We’ll take this to the next level but the basic idea will be exactly the same.
So close this file for now and let’s get started on our article generator. The first thing we’ll need is some content to write an article from. For this example, I will be using this quarterly report for NVIDIA. Click the link and you should see a long page containing the quarterly report:
We’ll keep this simple for now and keep automated web-scraping out of the loop. Now press CTRL + A
on the page to select all the text and then CTRL + C
to copy it:
Then create a new empty text file in your project root called content.txt
:
π Intro_to_AI_engineering π .env π content.txt π first_request.py
Paste the text you just copied into this empty content.txt
text file using CTRL + V
. There will be a very large amount of text in this file now. Go ahead and save and close this file. You can of course use different source material if you like but I’ll use this quarterly report.
Next up, create a new file in your project folder named article_generator.py
:
π Intro_to_AI_engineering π .env π article_generator.py π content.txt π first_request.py
Here is where we’ll make the magic happen! Inside article_generator.py
let’s start with our imports just like last time:
import os import uuid import requests from openai import OpenAI from dotenv import load_dotenv
You’ll notice I have added two new imports. The first is uuid
which is a library that will allow us to generate a unique identifier which we’ll explain in a moment. The second one is the requests
library which will allow us to make HTTP requests to the internet. We will explain this later on when we use it to download an image.
Now we’ll load up our API key from the .env
file like last time, and set up our CLIENT
object:
load_dotenv() CLIENT = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) UNIQUE_ID = uuid.uuid4()
This is exactly the same as we did last time, the first method loads the keys from the file into the environment variables, after which the os.getenv
call can read it from memory and pass it into our CLIENT
object.
I added a new line here where we call Python’s uuid.uuid4()
function which generates a random unique code. The output of this call will look something like 123e4567-e89b-12d3-a456-426614174000
. We’ll save this unique identifier in the UNIQUE_ID
variable for now so we can use it later when we want to save our article and make sure it has a unique name.
The next step is to read the content from our content.txt
file into a variable so we can use it:
print("Reading content from content.txt...") with open("content.txt", "r", encoding="utf-8") as file: text_content = file.read() print(f"Content read successfully. Length: {len(text_content)} characters.")
The first thing I do is print a message to the terminal window saying we’re reading the content from the file. This is just to keep ourselves updated in the terminal on what is happening so we can keep up with progress.
Then we open the file content.txt
in read mode "r"
and specify the encoding as "utf-8"
. This is the most used encoding and ensures that special characters get read correctly.
You will notice the with open
part. Without going into the specific details about this, it basically means that the file is open during all of the indented code that follows and then automatically closes the file when the code block is done (the indentation ends). This way we don’t have to remember to manually close the file.
The as file
part simply means that we can refer to the file object using the name file
in the indented code block that follows, as the file remains open during all the indented code.
You can probably guess what the next line does right? We call file.read()
since we can refer to the file by the name file
and this reads the entire content of the file into the variable text_content
.
Finally, we print a message to the terminal window (using one of those f-strings again) saying that the content was read successfully and also print the length of the content (text file) in characters.
The variable we inserted in the f-string is len(text_content)
. The len
function will return the length of the string that is passed into it. In this case, the string is the content of the file we just read.
Okay, now that we have our basics set up and we have read our source material into a variable, it’s time to move on to the next step which is to write a blog article. We’ll start with the instructions for ChatGPT, just like we did for our duck example last time:
write_blog_article_instructions = f""" Please write a blog article on the topic provided in the text below. The blog post should be around 2000 characters long and cover all the most important points from the material provided. You are a knowledgeable expert in the field, so please write in a professional tone, but make sure to keep it engaging, fun, and easy to read. Use markdown formatting to structure the article with headings, bullet points, numbered lists, and other markdown features where appropriate. Make sure you only return the article in valid markdown format and do not include introduction statements that are not part of the article like "sure I can help you, here is the article:". Please insert only a single ![image](image.png) tag in the article, at an appropriate location, where the image will be inserted later. You can just use 'image' and 'image.png' as placeholders for now. Make sure you insert only a single image tag and do not include any other images in the article. Text: {text_content} Blog Article: """
Note that we have a multi-line string variable here again, indicated by the """
at the start and end, but it is also preceded by an f
which makes it a multi-line-f-string. This allows us to insert variables into the string using curly braces {}
.
We basically just give instructions to write a blog article. As ChatGPT doesn’t really count I found the number 2000 to work quite well and give me articles that are around 3500 characters on average.
We also ask for a professional yet engaging tone and we ask for it to not provide any introduction statements in its answer to us, like “Sure here is your answer” which we would have to manually remove from our blog article.
We also ask for formatting in markdown. In case you’re not familiar with markdown, or need a short refresher, here is markdown in 60 seconds or less:
Markdown is a super simple way to add formatting to your text without needing to know any fancy coding. Imagine you’re writing a plain text document, but you want to add some headings, lists, or even images. With Markdown, you can do all that with just a few special characters.
For example, if you want to make a heading, you just put one or more #
symbols in front of your text, where a single #
is a large heading, ##
is a slightly smaller heading, ###
is even smaller, and so on. So you could create a medium heading like this:
## What is markdown?
Which then shows up like this:
What is markdown?
Want to make a list? Just use *
or -
:
* An easy way to format text * Great for writing blog posts * Super simple to learn
Which shows up like this:
- An easy way to format text
- Great for writing blog posts
- Super simple to learn
And if you want to add a link, you can do it like this:
[Click here](https://example.com)
Which shows up like this:
Markdown is super easy to learn and makes your text look great with minimal effort! In fact, the written version of this tutorial you’re reading right now is formatted in markdown!
By asking ChatGPT to use markdown, it can easily provide nice formatting in the blog article it writes for us, and the markdown format is used in many places so also easy to convert to website usage.
In the final part of the instructions, we ask ChatGPT to insert a placeholder for an image in the article using the markdown syntax ![image](image.png)
. What this will do is if there is a file with the name image.png
in the same folder as the article, it will insert that image into the article at that location.
Later on, we will generate an image to fit with the article, and save it under the name image.png
in the same folder, so asking for ChatGPT to insert this into our article right now will then automatically display the image we provide later.
After that we give it the Text:
, inserting the entire contents of our text file into our multi-line-f-string, which is very large but we’ve already read it into the text_content
variable.
We end by just saying Blog Article:
. Why do we do this? Remember that ChatGPT is like a chat completion kind of thing so we have sent it a very clear hint now that the next thing that is supposed to come in this conversation is the blog article without any introduction stuff in between. This is another trick to avoid getting answers like:
Sure, here is your blog article, I hope you like it bladiebla: Blog article here....
Ok, so those are our instructions for ChatGPT, saved in the variable write_blog_article_instructions
. Now let’s call ChatGPT with these instructions and get our blog article:
print("Generating blog article...") article_response = CLIENT.chat.completions.create( model="gpt-4o", messages=[ {"role": "user", "content": write_blog_article_instructions} ] )
You’ll notice this is the same syntax we used for our DuckGPT example before, we merely switched out the duck instructions with more elaborate blog article instructions. I also added a print statement again for clarity in the console. The response will be saved in the variable article_response
.
Remember that the response object we get from ChatGPT is quite complex and contains a lot of information. We want to extract the actual textual response like we did with our Duck example before:
blog_article = article_response.choices[0].message.content if not blog_article: print("Something went wrong, please try again.") exit() print(f"Blog article generated. Length: {len(blog_article)} characters.")
We access the response from ChatGPT in the same way as before, by using the choices
list and then the message
key and then the content
key. We save this in the variable blog_article
.
We then check if the blog_article
variable is empty. If it is empty, which it shouldn’t be, there was probably some kind of error with the API response. We print a message to the terminal window saying something went wrong and then exit the script without running any further code.
If it’s not empty, the code will keep running and we print a message saying the blog article was generated successfully and also print the length of the article in characters using the len
function as we did before.
Saving the output to a file
Now that we have a blog article generated, I’d like to save it to a file. I want to save it in a folder named output
inside of which I want to have a folder with a unique name for the project, inside of which we can store our markdown blog article. Something like output/Article_name_here-123e4567-uNiQuE-cOdE
, which would look like this (do not actually manually create these folders):
π Intro_to_AI_engineering π output π Article_name_here-123e4567-uNiQuE-cOdE π article.md π .env π article_generator.py π content.txt π first_request.py
Note that .md
is the file extension for markdown files, which are just text files with markdown formatting. Back to the code, I’m going to first get the Article_name_here
part:
project_name = blog_article[:25]
This line uses the slice operator [:25]
to get the first 25 characters of the blog_article
variable. This will be our project name. Now we need to create the full path to the output folder:
output_folder = f"output/{project_name}-{UNIQUE_ID}"
We simply use an f-string to create a path starting on the output
folder followed by a /
, then the project_name
we just created, then a dash -
, then the UNIQUE_ID
we generated earlier. This will be the full path to our output folder inside which we want to save our markdown article file.
The problem we have is that if these directories do not exist, our script will crash. Luckily the os
library we imported as one of the first lines of code in this file has a good solution for us:
if not os.path.exists(output_folder): os.makedirs(output_folder)
This is extremely readable and does exactly what it says on the box. The os.path.exists
function checks if a path exists. If it doesn’t, we’re going to create the folders needed using the os.makedirs
(make directories) function.
Feel free to combine the previous 4 lines of code into a single block like this as it kind of compromises one logical unit/step in our code:
project_name = blog_article[:25] output_folder = f"output/{project_name}-{UNIQUE_ID}" if not os.path.exists(output_folder): os.makedirs(output_folder)
Ok, now that we know exactly where we want to save our article, and we know that the folders exist so that we can save it, let’s save the article to a file. This is remarkably similar to how we read the other file earlier except this time we’re writing instead of reading:
blog_article_save_location = f"{output_folder}/article.md" with open(blog_article_save_location, "w", encoding="utf-8") as file: file.write(blog_article) print(f"Article saved as {blog_article_save_location}")
First of all, we need to add /article.md
to the output_folder
path to actually add the filename we want to save it as. We’ll save this as the variable name blog_article_save_location
.
Then we use the with open
syntax again to open the file at the location we specified (which will create the file if it doesn’t exist) in write mode "w"
and with the encoding "utf-8"
. Unless you have a good reason to do otherwise, generally stick with "utf-8"
for reading or writing any text files where you don’t know the encoding.
This time we simply call file.write()
instead of read
and we pass in the blog_article
as the thing to be written to the file. This will write the entire blog article to the file. Finally, we print a message to the terminal window saying the article was saved and also print the location.
A first test run
The hardest part is behind us! At this point you can give it a test run, and it will totally work. You will use about 0.2 dollars or 20 cents for this to run if you’re using the same massive input text as I am. This may seem expensive but for a 130.000+ character input writing a professional blog article of 3500 characters in mere seconds, it’s a steal!
If you want to wait to test it until we finish the script that’s also fine, you can look along on my screen. I’ve run it and the appropriate folders have been created and the article has been saved in the correct location:
The file name is a bit long but that’s fine for now. If you open the file you will see a markdown format blog article!
You will also see a small icon with a magnifying glass in the top right corner of the file display:
Click this icon so that you can see the markdown file as it will really show up instead of in the ‘editing’ mode:
That is pretty awesome! Of course it does have that broken image
link in the middle, but we’ll work on that issue next. We know our article writer is working and it did an awesome job for us!
Getting a prompt for our image
So let’s continue on our code. The last thing we need is the image. Now I’m sure that you’ve all tried one of those AI image generators by now. You give it an input prompt of what you want to see, like “An adorable strawberry shark with tiny arms and legs in space” and it will generate an image for you based on the description:
Since we want to have a fitting image for our blog article, we’ll have to go through a two-step process. First of all, we’ll need to have a good description of the image that we would like to generate, instead of a “strawberry shark in space” which is not going to fit with our Nvidia quarterly report very well! Second, when we have a good description in textual format of the image we want, we can use an image generation AI to generate the actual image for us.
So how do we get a description for an image that fits with the blog article that was automatically written for us? We ask ChatGPT of course! Continue on in the article_generator.py
below all the previous code we have written so far, and let’s start with instructions for ChatGPT to generate a description for an image for us:
create_image_description_instructions = f""" I have a blog article that I would like to create an image for. Please read through the article provided below and then come up with a prompt for an image that would be suitable for the content. Make sure your prompt doesn't request for any text to be included in the image, we want to include only visual elements but no text in the image itself. Your prompt is going to be fed into DALL-E-3 to generate an image, so make sure that your prompt is descriptive enough to guide the model in creating a relevant image that goes well with the blog article provided. Blog Article: {blog_article} Image Prompt: """
I’m simply telling it to read through the blog article and then come up with a prompt for an image that would be suitable for the content. I also ask it to not include any text in the image itself as this sometimes ends up being weird warped fantasy letters that don’t make sense.
I also mention that the prompt is going to be fed into DALL-E-3 which is an image generation AI model by OpenAI (more on that in a bit). This way it knows very clearly what the prompt will be used for which will give us a better output for the purpose.
We then simply provide the {blog_article}
variable which still contains our markdown format blog article since the code above will run first and generate the blog article before this code runs. We then simply end with the Image Prompt:
line to try and avoid any introductions in the response as before.
We have our instructions, now we simply need to call ChatGPT. We can do this basically in exactly the same way we did before. (I’m not going to worry about writing functions for repetitive code in this tutorial, let’s just keep this simple as it is an introduction):
print("Getting prompt for image generation...") image_prompt_response = CLIENT.chat.completions.create( model="gpt-4o", messages=[ {"role": "user", "content": create_image_description_instructions} ] ) image_generation_prompt = image_prompt_response.choices[0].message.content if not image_generation_prompt: print("Something went wrong, please try again.") exit()
We print a message and call ChatGPT in exactly the same way but this time with the create_image_description_instructions
variable as the content of the message. We then extract the response in the same way as before and save it in the variable image_generation_prompt
. We then check if the variable is empty just to be sure, although this is unlikely.
You don’t have to test run the file at this point, but it will return an answer something like this which is perfect for our uses:
A futuristic and dynamic illustration featuring elements symbolizing NVIDIA's diverse technological advancements and financial growth. In the background, an expansive cityscape with skyscrapers and futuristic buildings highlight the company's global reach and influence in sectors like AI and autonomous vehicles. Prominently, a large, stylized NVIDIA GPU chip is central to the image, surrounded by ... (the rest of the description)
Generating the image for our article
Now that we have a description of the image that we want to generate saved in the variable image_generation_prompt
, the only remaining step is to actually generate it. For this, we’ll use OpenAI’s DALL-E-3. DALL-E-3 is an image-generation AI model that can generate images based on textual prompts.
The great thing is that we can use our ChatGPT API key we already have saved in our .env
file to use it, as it is made by the same company. What’s more, we can even use the same CLIENT
object we already have been using to interact with ChatGPT to interact with DALL-E-3!
Add the following at the bottom of our code to call DALL-E-3 and generate the image:
print(f"Generating image with prompt: {image_generation_prompt}") dalle_response = CLIENT.images.generate( prompt=image_generation_prompt, model="dall-e-3", response_format="url", size="1024x1024", n=1 )
You can see that this is very similar to using ChatGPT except this time we called CLIENT.images.generate
. We pass in the prompt
we prepared with the image description and the model
we want to use which is dall-e-3
.
The API has different options for the format in which you receive the image. I’ll stick to url
here as it is very simple to work with, we will just get a url
in response from the API which links to our image, hosted on the internet on OpenAI’s servers.
The size
of the image we want is 1024x1024
pixels, and n=1
means we only want one image generated. The dalle_response
variable will now contain an object with many properties we do not need, just like before with the ChatGPT responses. The url
we need which links to our image is located in the data
list’s first item [0]
and then the url
key:
image_url = dalle_response.data[0].url image_save_location = f"{output_folder}/image.png"
We first get the image_url
and save it in a variable by accessing it as described. Again this is a bit of a complex structure with the .data[0].url
but don’t worry too much about it, this is just the structure OpenAI chose to use for their API responses.
We then prepare the location where we want to save our image by reusing the output_folder
variable we created earlier which has the exact path where the article.md
file has been saved. We add /image.png
to the end of this path to make sure the image gets saved in the same folder so it will work with the image placeholder we asked ChatGPT to insert when writing our article!
Now all we need to do is download and save the image:
if image_url: image_response = requests.get(image_url) with open(image_save_location, "wb") as image_file: image_file.write(image_response.content) print(f"Image saved as {image_save_location}") else: print("Image generation failed. Please try again.")
We have an if/else
statement here that checks if there is an image_url
. If there is, the code in the if
block will execute, but if for some reason the image_url
is empty because the API call failed, the code will skip ahead to the else
block and run the print statement to print an error message instead.
If the image_url
is not empty, we use the requests.get
function to make a GET
request to the image_url
and save the response in the variable image_response
. If you’re not familiar with these terms, the requests
library basically allows us to make requests to the internet in our code, kind of like opening a page or web address in your browser.
Making a GET
request is like opening a page in your browser, it just gets the content of the page without changing anything. So we’re just opening the link to the image and then downloading the content into image_response
.
Now we use the with open
trick again on the image_save_location
which is where we want to save our image. We use the "wb"
mode this time which stands for write binary
. This is because images are binary files and not text like we had before. We give our file the name image_file
to refer to it in the indented code block that follows.
We then write the content
of the image_response
to the image_file
using the write
function. (The requests
library has saved the actual image data under .content
which is why we access this key). This will save the image to the file. Finally, we print a message to the terminal window saying the image was saved and also print the location where it was saved.
And that’s it! Go ahead and save this file and it’s now time for the big moment you’ve been waiting for! Use the play button in the top right corner of the file to run the script, just like we did with our first_request.py
file before, and you will see the output starting to appear in the terminal window:
Reading content from content.txt... Content read successfully. Length: 137738 characters. Generating blog article... Blog article generated. Length: 4392 characters. Article saved as output/# An Overview of NVIDIA's-5974955d-b731-41ca-8854-b29ec7219bf9/article.md Getting prompt for image generation... Generating image with prompt: Create a high-tech, visually captivating image representing NVIDIA's technological advancements and financial growth. The scene should include futuristic AI and data center elements like glowing, advanced server racks and holographic graphics displaying growth charts and financial metrics. Incorporate hints of cutting-edge GPU architecture, such as sleek GPU chips floating in the background. Additionally, add dynamic elements like robotic arms or AI robots working on these technologies, symbolizing innovation and automation. The overall color scheme should feature NVIDIAβs iconic green along with shades of metallic and neon tones to emphasize a modern, technological vibe. The background can include a world map highlighting key regions like the US, Taiwan, China, and Singapore, emphasizing NVIDIAβs global market presence. Image saved as output/# An Overview of NVIDIA's-5974955d-b731-41ca-8854-b29ec7219bf9/image.png
Looks like everything worked as intended! It generated a very detailed image description for us and then generated the image and saved it. I can see both an article.md
file and an image.png
file in the output
folder:
So open up the article.md
file and find that magnifying glass icon in the top right again to open the real preview of the markdown file:
And we have a very awesome-looking finished blog article here with our image already automatically inserted for us!
It’s detailed and engaging, based on our source material. I won’t post the whole thing here as it’s quite a long image and you’ll have your own amazing result to look at, but as we scroll down we can see it has very good Markdown formatting exactly as instructed and is very much ready to use as a finished product:
Debugging
Now, whenever you write a script like this and run it a few times, you’ll notice that sometimes something you did not anticipate happens or does not work quite as expected. Not to worry, this is normal and part of the process. Instead of pretending everything always works perfectly, we’re going to look at the debugging together, to give a more realistic picture!
The first bug I noticed when testing this, is that sometimes the article contains text characters that Windows or any other operating system does not allow in file names. This can cause the script to crash when trying to save the article or image and you’ll get a bug like this:
OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect: 'output/```markdown\n# NVIDIA Corp-b7cb1e42-1c5b-4f5e-8a3c-49a9dff920f0'
The project_name
variable contains characters that cannot be used for a file or folder name. To fix this bug we could have a complicated function that replaces all these characters with something else, but let’s just keep it simple for now and simply not use the project_name
variable for the folder name.
We’ll just use the UNIQUE_ID
variable which is guaranteed to be unique and not contain any special characters. So find the part in your code that deals with the output folder and creating it and change it to this:
# project_name = blog_article[:25] output_folder = f"output/{UNIQUE_ID}" if not os.path.exists(output_folder): os.makedirs(output_folder)
Another very common bug and probably the most common one when working with AI models is that sometimes the AI will interpret something in a way you did not expect or be very literal with something. As this is so common I wanted to include it as a last bugfix example here. In our case, I noticed that the AI would sometimes return our markdown article in this format:
So you can see (selected in blue) that it has included an opening (```markdown
) and closing (```
) to indicate to us that it is giving us a response in markdown. This is annoying as we have to manually delete this from our generated article.
This is just a case of going back to your instructions and amending them to be clearer or adding something you never thought would be needed.
In this case, just go to the write_blog_article_instructions
variable and add an extra line in the multi-line-string that says:
Do not use ```markdown code blocks``` in the article or anywhere in your response, I know that the response will be markdown, so you do not have to indicate that.
Voila! Fixed. Just know that this type of debugging is completely normal and everyday stuff. It’s part of the process. AI’s have a fun way to interpret stuff sometimes and nobody writes flawless code the first time around.
Conclusion
Awesome! Pat yourself on the back for making it all the way here and starting your journey into AI engineering! You’ve just automated the process of writing a blog article and generating an image to go with it, all in a matter of seconds and on any topic you want.
If you enjoyed this mini course and want to learn more, we have many courses available in the π€ Finxter AI Engineering Academy at https://academy.finxter.com/ where we go much more in-depth into AI automation, Python, and many other related topics. My hope is that this will not be the end but the beginning of a new and exciting journey!
As always, it was a pleasure and an honor to take this journey together with you. I hope to see you again in the future, and until then, happy coding! ππ