Creating an AI-Powered Cover Letter Generator

Build a simple yet powerful app with Python, OpenAI, and Streamlit

Randy Pettus
Better Programming

--

Photo by JESHOOTS.COM on Unsplash

A Google search for “Is a cover letter necessary?” suggests that many people are trying to cop out of having to write this document in 2023. However, this same search will come up with hit after hit on why you still need to submit one. While I will leave that to you as the reader, the situation is summarized nicely by MrZJones on reddit . “Cover letters are completely unimportant except when they’re the most important part of the application, and you won’t know which it is until after you’ve been rejected (and possibly not even then).”

Example Google Search for “Is a cover letter necessary?”

So cover letters are important. But for job seekers, these documents can be extremely time consuming. I remember getting various tips on creating cover letters in business school. A major emphasis was highlighting the accomplishments and skills most relevant to the job without “parroting” the resume. Thus, job seekers must spend time crafting tailored letters for the specific job that have a personal touch that may be lacking in the resume itself. This can be draining, however.

So the question is, how can we leverage AI to assist in this important yet draining task? I decided to build a simple application to leverage Python, Streamlit, and OpenAI. This post will walk through how I created the application posted here: https://ai-coverletter-generator.streamlit.app

Cover Letter Generator Streamlit App

This Streamlit prototype only requires a user to:

1. Upload their resume or copy any resume/experiences/skills
2. Paste a relevant job description
3. Input some other relevant data specific to the job/user

OpenAI (GPT-3.5 family) is then used to produce a cover letter from this information tailored to the job description. The user can then download the produced output.

Walking Through the App

The application is simple overall, with just a few lines necessary to make the basic AI magic happen. However, you’ll note I spent some time crafting more prompts to get to the final result.

The first part imports the necessary Python modules, grabs the OpenAI key from the Streamlit secrets file, and generates the application text.

import streamlit as st
import openai as ai
from PyPDF2 import PdfReader

# OpenAI API key
ai.api_key = st.secrets["openai_key"]

st.markdown("""
# 📝 AI-Powered Cover Letter Generator

Generate a cover letter. All you need to do is:
1. Upload your resume or copy your resume/experiences
2. Paste a relevant job description
3. Input some other relevant user/job data
"""
)

I wanted the user to be able to upload a resume either or copy/paste in relevant resume experiences or skills. So the next section provides this option. If the user selects the “upload” button, then the text will be extracted using PyPDF2. Then an input form captures additional details that will help craft a specific cover letter, including:

  • Pasted job description (responsibilities)
  • User’s name (Anna Smith)
  • Company (Google)
  • Hiring manager (Bob Jones)
  • Position Title/Role (data scientist)
  • Referral (e.g., LinkedIn)
  • OpenAI Temperature (How creative the generated response will be)
# radio for upload or copy paste option         
res_format = st.radio(
"Do you want to upload or paste your resume/key experience",
('Upload', 'Paste'))

if res_format == 'Upload':
# upload_resume
res_file = st.file_uploader('📁 Upload your resume in pdf format')
if res_file:
pdf_reader = PdfReader(res_file)

# Collect text from pdf
res_text = ""
for page in pdf_reader.pages:
res_text += page.extract_text()
else:
# use the pasted contents instead
res_text = st.text_input('Pasted resume elements')

with st.form('input_form'):
# other inputs
job_desc = st.text_input('Pasted job description')
user_name = st.text_input('Your name')
company = st.text_input('Company name')
manager = st.text_input('Hiring manager')
role = st.text_input('Job title/role')
referral = st.text_input('How did you find out about this opportunity?')
ai_temp = st.number_input('AI Temperature (0.0-1.0) Input how creative the API can be',value=.99)

# submit button
submitted = st.form_submit_button("Generate Cover Letter")

One thing that I noticed when trying to build this using the OpenAI’s completion functionality with GPT-3.5 was that I would easily run out of tokens when trying to feed in all resume and job description text.

According to OpenAI GPT documentation, “Our most capable and cost-effective model in the GPT-3.5 family is gpt-3.5-turbo which has been optimized for chat but works well for traditional completions tasks as well.”

gpt-3.5-turbo only worked with ChatCompletion, so I used this for the application and found the results were quite good for this traditional “completion” task, just as the documentation states. Meanwhile, I didn’t run into the same token issues with this approach.

The final piece only runs if the Streamlit form is submitted and uses OpenAI ChatCompletion. I used various “chats” as prompting to include the various inputs and provide specific direction for cover letter generation. The response is then completed, which is printed using st.write(). The user can then download a .txt file with the response.

# if the form is submitted run the openai completion   
if submitted:

# note that the ChatCompletion is used as it was found to be more effective to produce good results
# using just Completion often resulted in exceeding token limits
# according to https://platform.openai.com/docs/models/gpt-3-5
# Our most capable and cost effective model in the GPT-3.5 family is gpt-3.5-turbo which has been optimized for chat
# but works well for traditional completions tasks as well.

completion = ai.ChatCompletion.create(
#model="gpt-3.5-turbo-16k",
model = "gpt-3.5-turbo",
temperature=ai_temp,
messages = [
{"role": "user", "content" : f"You will need to generate a cover letter based on specific resume and a job description"},
{"role": "user", "content" : f"My resume text: {res_text}"},
{"role": "user", "content" : f"The job description is: {job_desc}"},
{"role": "user", "content" : f"The candidate's name to include on the cover letter: {user_name}"},
{"role": "user", "content" : f"The job title/role : {role}"},
{"role": "user", "content" : f"The hiring manager is: {manager}"},
{"role": "user", "content" : f"How you heard about the opportunity: {referral}"},
{"role": "user", "content" : f"The company to which you are generating the cover letter for: {company}"},
{"role": "user", "content" : f"The cover letter should have three content paragraphs"},
{"role": "user", "content" : f"""
In the first paragraph focus on the following: you will convey who you are, what position you are interested in, and where you heard
about it, and summarize what you have to offer based on the above resume
"""},
{"role": "user", "content" : f"""
In the second paragraph focus on why the candidate is a great fit drawing parallels between the experience included in the resume
and the qualifications on the job description.
"""},
{"role": "user", "content" : f"""
In the 3RD PARAGRAPH: Conclusion
Restate your interest in the organization and/or job and summarize what you have to offer and thank the reader for their time and consideration.
"""},
{"role": "user", "content" : f"""
note that contact information may be found in the included resume text and use and/or summarize specific resume context for the letter
"""},
{"role": "user", "content" : f"Use {user_name} as the candidate"},

{"role": "user", "content" : f"Generate a specific cover letter based on the above. Generate the response and include appropriate spacing between the paragraph text"}
]
)

response_out = completion['choices'][0]['message']['content']
st.write(response_out)

# include an option to download a txt file
st.download_button('Download the cover_letter', response_out)

Working Example

For this demo, I used a sample resume for IM A. SAMPLE that I found on MSMLabs and a sample financial manager job description from Workable.

The screenshot below demonstrates the filled form with information from the above sources and some other fillers for now.

Here’s the generated cover letter, which can be downloaded:

Here’s the full code I used for this application:

import streamlit as st
import os
import openai as ai
from PyPDF2 import PdfReader

ai.api_key = st.secrets["openai_key"]

st.markdown("""
# 📝 AI-Powered Cover Letter Generator

Generate a cover letter. All you need to do is:
1. Upload your resume or copy your resume/experiences
2. Paste a relevant job description
3. Input some other relevant user/job data
"""
)

# radio for upload or copy paste option
res_format = st.radio(
"Do you want to upload or paste your resume/key experience",
('Upload', 'Paste'))

if res_format == 'Upload':
# upload_resume
res_file = st.file_uploader('📁 Upload your resume in pdf format')
if res_file:
pdf_reader = PdfReader(res_file)

# Collect text from pdf
res_text = ""
for page in pdf_reader.pages:
res_text += page.extract_text()
else:
# use the pasted contents instead
res_text = st.text_input('Pasted resume elements')

with st.form('input_form'):
# other inputs
job_desc = st.text_input('Pasted job description')
user_name = st.text_input('Your name')
company = st.text_input('Company name')
manager = st.text_input('Hiring manager')
role = st.text_input('Job title/role')
referral = st.text_input('How did you find out about this opportunity?')
ai_temp = st.number_input('AI Temperature (0.0-1.0) Input how creative the API can be',value=.99)

# submit button
submitted = st.form_submit_button("Generate Cover Letter")

# if the form is submitted run the openai completion
if submitted:

# note that the ChatCompletion is used as it was found to be more effective to produce good results
# using just Completion often resulted in exceeding token limits
# according to https://platform.openai.com/docs/models/gpt-3-5
# Our most capable and cost effective model in the GPT-3.5 family is gpt-3.5-turbo which has been optimized for chat
# but works well for traditional completions tasks as well.

completion = ai.ChatCompletion.create(
#model="gpt-3.5-turbo-16k",
model = "gpt-3.5-turbo",
temperature=ai_temp,
messages = [
{"role": "user", "content" : f"You will need to generate a cover letter based on specific resume and a job description"},
{"role": "user", "content" : f"My resume text: {res_text}"},
{"role": "user", "content" : f"The job description is: {job_desc}"},
{"role": "user", "content" : f"The candidate's name to include on the cover letter: {user_name}"},
{"role": "user", "content" : f"The job title/role : {role}"},
{"role": "user", "content" : f"The hiring manager is: {manager}"},
{"role": "user", "content" : f"How you heard about the opportunity: {referral}"},
{"role": "user", "content" : f"The company to which you are generating the cover letter for: {company}"},
{"role": "user", "content" : f"The cover letter should have three content paragraphs"},
{"role": "user", "content" : f"""
In the first paragraph focus on the following: you will convey who you are, what position you are interested in, and where you heard
about it, and summarize what you have to offer based on the above resume
"""},
{"role": "user", "content" : f"""
In the second paragraph focus on why the candidate is a great fit drawing parallels between the experience included in the resume
and the qualifications on the job description.
"""},
{"role": "user", "content" : f"""
In the 3RD PARAGRAPH: Conclusion
Restate your interest in the organization and/or job and summarize what you have to offer and thank the reader for their time and consideration.
"""},
{"role": "user", "content" : f"""
note that contact information may be found in the included resume text and use and/or summarize specific resume context for the letter
"""},
{"role": "user", "content" : f"Use {user_name} as the candidate"},

{"role": "user", "content" : f"Generate a specific cover letter based on the above. Generate the response and include appropriate spacing between the paragraph text"}
]
)

response_out = completion['choices'][0]['message']['content']
st.write(response_out)

# include an option to download a txt file
st.download_button('Download the cover_letter', response_out)

Conclusion

Depending on your experience and minimal code, you can build a full application that simplifies a job seeker’s hunt in just a couple of hours or less. There are likely many ways to improve and build upon this, but I have found that the results are very useful even with this basic start.

--

--

Randy Pettus
Better Programming

Business-grounded AI. Principal Data Scientist focused on applying artificial intelligence to solve real business problems