Originally posted at https://blog.hadenes.io/post/generating-blog-post-summaries-with-chatgpt/
As a blogger, you know how important it is to create compelling content that engages your readers. But coming up with catchy titles, insightful summaries, and relevant tags can be time-consuming and challenging. I know I struggle a lot with this, no matter how well I know the topic I’m writing about.
Enter OpenAI’s ChatGPT API.
They recently (March 1, 2023) released the ChatGPT model that powers the web interface, gpt-3.5-turbo
, and it’s now available for developers to use in their own applications. You can read more about it in their blog post, there are other exciting news there too.
In this blog post, I will walk you through a script that uses this new model to automate the process of generating summaries, tags, and titles for your blog posts.
And yes, a great deal of this post was generated by the ChatGPT model. Sue me.
Prerequisites
Before we begin, you will need to have the following:
- A OpenAI API key
- Python 3.x installed on your system
If you haven’t already, you can sign up for OpenAI API access on their website.
Installing the Dependencies
pip install openai backoff python-frontmatter
The Script
import glob
import json
import sys
import termios
import tty
import backoff
import frontmatter
import openai
openai.api_key = "OPENAI_API_KEY"
def _getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
@backoff.on_exception(backoff.expo, openai.error.RateLimitError)
def completions_with_backoff(**kwargs):
return openai.ChatCompletion.create(**kwargs)
system_prompt = [
"You are ChatGPT, a helpful assistant.",
"You generate insightful short summaries and relevant tags for user supplied blog posts.",
"Assume that the blog posts are in Markdown format.",
"You don't generate summaries that are cryptic or unhelpful.",
"You don't generate clickbait titles or summaries.",
"The summary you generate is SEO friendly and no longer than 80 characters.",
"Tags you generate are SEO friendly, lowercase and a single word each.",
"You can reply with a title field that is different from the original if it's more SEO friendly.",
"If the language is pt_BR, you must generate a summary and a title in Brazilian portuguese.",
"The title field should be a string.",
"The summary field should be a string.",
"The tags field should be a list of strings.",
"Don't include any kind of notes."
"Your response should be in JSON format.",
"Don't reply with anything other than the JSON object."
]
for fname in glob.glob("../content/english/post/*.md"):
if fname.split('/')[-1] == "_index.md":
continue
with open(fname, "r") as f:
post = frontmatter.loads(f.read())
title = post['title']
categories = post['categories']
content = post.content
summary = post.get('summary', '')
tags = post.get('tags', [])
generated = post.get('generated', False)
language = post.get('language', 'en')
print(f'{fname} ({language})')
if generated:
print(" skipping already generated post")
print()
continue
user_prompt = [
"Here's a blog post I'd like to summarize:",
f"title: {title}",
f"categories: {categories}",
f"tags: {tags}",
f"language: {language}",
"content:", content]
context_length = len('\n'.join(system_prompt + user_prompt)) * 0.75
context_length_without_content = len('\n'.join(system_prompt + user_prompt[:-1])) * 0.75
if context_length > 4096:
print(" ! reducing context length...")
diff = int(4096 - context_length_without_content)
user_prompt[-1] = user_prompt[-1][:diff]
while True:
completion = completions_with_backoff(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": '\n'.join(system_prompt)},
{"role": "user", "content": '\n'.join(user_prompt)},
],
)
result = {}
try:
result = json.loads(completion["choices"][0]["message"]["content"].strip())
except json.decoder.JSONDecodeError:
try:
fields = completion["choices"][0]["message"]["content"].strip().split('\n')
for field in fields:
key = field.split(':')[0].strip()
value = ':'.join(field.split(':')[1:])
result[key] = value
except Exception as e:
print(" [-] Failed to parse response")
print(completion)
print()
continue
print(completion)
new_title = result.get('title', title).strip()
new_summary = result.get('summary', summary).strip()
new_tags = result.get('tags', tags)
print()
print(f" oldTitle: {title}")
print(f" newTitle: {new_title}")
print()
print(f" oldTags: {tags}")
print(f" newTags: {new_tags}")
print()
print(f" oldSummary: {summary}")
print(f" newSummary: {new_summary}")
print()
print(" accept? [y/n/s/q] ", end=' ')
sys.stdout.flush()
ch = _getch()
print()
if ch == 'y':
post['title'] = new_title
post['tags'] = new_tags
post['summary'] = new_summary
post['generated'] = True
with open(fname, "w") as f:
f.write(frontmatter.dumps(post))
print(' saved...')
print()
break
elif ch == 'n':
print(' retrying...')
print()
continue
elif ch == 'q':
print(' exiting...')
print()
exit(0)
else:
print(' skipping...')
print()
break
Example output
python3 generate_summaries.py
../content/english/post/prometheus-alerting-rule-for-redis-clients.md (en)
skipping already generated post
../content/english/post/corredores-da-loucura-parte-2.md (pt_BR)
skipping already generated post
../content/english/post/making-the-most-out-of-your-yubikey-4.md (en)
skipping already generated post
../content/english/post/why-management-ladders-should-start-higher.md (en)
skipping already generated post
../content/english/post/on-salary-ceilings.md (en)
skipping already generated post
../content/english/post/the-role-of-the-leader-when-experts-talk.md (en)
skipping already generated post
../content/english/post/on-task-assignment.md (en)
skipping already generated post
../content/english/post/a-natureza-e-o-homem.md (en)
skipping already generated post
../content/english/post/systemd-timers-vs-code-loops.md (en)
! reducing context length...
oldTitle: SystemD Timers vs Code Loops
newTitle: SystemD Timers vs Code Loops
oldTags: ['timers', 'code', 'systemd']
newTags: ['timers', 'code', 'systemd', 'linux', 'timerfd', 'resource accounting']
oldSummary: Comparing the effectiveness of infinite loop strategies on Linux systems
newSummary: Comparing SystemD Timers vs code loops effectiveness in computation
and energy consumption on Linux, including the use of timerfd and
resource accounting
accept? [y/n/s/q]
saved...
Explanation
This script performs a task of generating summaries and relevant tags for user-supplied blog posts using OpenAI’s GPT-3 language model. The script reads the markdown files of blog posts from the ../content/english/post/ directory, extracts the necessary information like title, content, categories, tags, summary, and language from the file’s frontmatter, and generates a user prompt to get the summary and tags using GPT-3.
The script uses the openai Python package to access the GPT-3 API, which requires an API key to be set using openai.api_key. It also uses the frontmatter package to extract information from the markdown files' frontmatter and the glob package to read all markdown files in the specified directory.
The completions_with_backoff function uses the backoff package to retry the API call if it encounters a RateLimitError exception. It calls the openai.ChatCompletion.create() method to generate the completion using the GPT-3 language model specified by the model parameter and the user prompt specified by the messages parameter.
The script generates a system_prompt list containing the prompts for the language model. The user_prompt list is generated for each blog post and contains the information extracted from the post’s frontmatter and the language model prompts.
The script then calculates the context_length and context_length_without_content of the user prompt and checks if it exceeds the maximum length of 4096 characters allowed by the GPT-3 API. If the length exceeds the limit, it truncates the content field of the user prompt.
The script then enters a loop to generate a completion for the user prompt and prints the current state of the post, including the old and new title, tags, and summary generated by GPT-3. The user can then accept, reject, or skip the generated summary and tags by entering ‘y’, ‘n’, or ’s' respectively. Entering ‘q’ will exit the script.
If the user accepts the generated summary and tags, the script updates the post’s frontmatter with the new information and sets the generated field to True. Finally, it writes the updated frontmatter back to the file and moves on to the next post.
Prompting
In the context of natural language processing and artificial intelligence, prompting refers to the process of providing input to a language model or AI system to generate a response or output. A prompt can be a question, a statement, or any other form of input that the language model or AI system can process to generate a relevant response. The quality of the prompt can have a significant impact on the quality and relevance of the output generated by the language model or AI system. Therefore, designing effective prompts is an essential part of developing natural language processing and AI systems that can effectively understand and respond to human language.
This prompt exists as a set of guidelines for OpenAI’s ChatGPT language model to ensure that it generates insightful summaries and relevant tags for user-supplied blog posts in a consistent and user-friendly manner. The prompt outlines the requirements for the length and format of the summary and tags and ensures that they are SEO-friendly and easy to understand. Additionally, the prompt specifies that the language model should not generate clickbait titles or summaries and that it should avoid generating cryptic or unhelpful content. Finally, the prompt provides guidelines for generating responses in different languages and ensuring that the output is formatted correctly.
I also try my best to have the output be as consistent as possible. Language models are not deterministic, so the output can vary slightly each time you run the script. Getting a valid JSON most of the time is already a win.
Conclusion
In conclusion, the potential of language models like GPT-3 is vast and far-reaching, and helping bloggers is just one of the many possibilities they offer.
From healthcare to finance, legal services to customer service, and beyond, these models can transform industries by automating tasks, improving efficiency, and enhancing the way we live and work.
Their ability to understand and generate human-like language has opened up a world of new possibilities for developing innovative solutions that can make our lives easier and more convenient.
As research in this area continues to evolve, we can expect to see even more exciting applications of language models that can shape the future of technology and how we interact with it.
As these language models continue to advance and become more powerful, it raises important questions about the potential impact on information security.
With the ability to generate convincing and sophisticated language, there is a risk (a concrete one, already seem in the wild) of these models being used for malicious purposes, such as generating convincing phishing emails, fake news, or even deepfakes.
As a society, we need to be mindful of these risks and take proactive steps to address them.
This might include developing new strategies for detecting and preventing the misuse of language models, investing in more advanced security measures, and educating users on how to identify and protect themselves against potential threats.
Ultimately, it is up to all of us to ensure that these powerful tools are used responsibly and ethically to advance our collective interests and improve our lives.
Top comments (0)