Content generation, primarily for sales and marketing, is one of the key breakout use cases for large language models (LLMs) and generative AI.
There are many vendors out there that help you use LLMs to generate content. Some are more aimed at workflow, some are more aimed at incorporating SEO practices. Many of our customers use Datograde to help improve their AI content generation.
That being said, you can still spot generated SEO blog content from a mile away. The generic tone, the repetition of the same point, the frequent sign posting.
So, let us embark on an adventure to see how we can solve these issues.
A one line prompt
To make sure I can assess the quality of this post, I am going to turn to another area of passion of mine: cooking. Throughout this guide, I will try to generate the best guide to the five mother sauces there is in french cooking. If you want to follow along, this is a good article about this topic.
Let’s write a simple Python script to generate a blog post with gpt-4o
. We will log the output to Datograde, which means that I don’t need to squint at my terminal to read markdown syntax in my head, or otherwise write more code to format the markdown correctly.
Prompt
Write a blog post about the five mother sauces of French cooking.
import os
from datograde import Datograde
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
datograde = Datograde(api_key=os.getenv("DATOGRADE_API_KEY"))
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{
"role": "user",
"content": "Write a blog post about the five mother sauces of French cooking.",
},
],
)
blog_post = completion.choices[0].message.content
datograde.attempt(
track_id="[Your trackID here, or the challenge name]",
files=[(blog_post, "md")],
)
First attempt at generating a blog post about the five mother sauces
That’s actually pretty good. But let’s make it more … bloggy.
Adding an intro blurb and story
If you look at some (presumably hand written) blogs, they commonly have an introduction blurb. Here’s one from Food52:
They also have some sort of interesting ‘cold open’ story. The Food52 article talks about the history of how the 5 sauces came to be.
In this seriouseats article, the author talks about their personal experience with the food item. Many of seriouseats’ articles start like that. Let’s try to put this into our prompt:
Prompt 1, version 2
Write a blog post about the five mother sauces of French cooking, starting the blog post with a personal anecdote. After that, give me a one sentence blurb to hook the reader into reading the blog post.
PROMPT = "Write a blog post about the five mother sauces of French cooking, starting the blog post with a personal anecdote. After that, give me a one sentence blurb to hook the reader into reading the blog post."
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "user", "content": PROMPT},
],
)
blog_post = completion.choices[0].message.content
datograde.attempt(
track_id="[Your trackID here, or the challenge name]",
new_input_files=[(PROMPT, "md")], # log the prompt as an input
files=[(blog_post, "md")],
)
The results are interesting. Cute intro story, but now the article is much shorter, and and headings are gone. Such is the life of a prompt engineer — the LLM will make stuff up if you don’t specify everything.
Scored this a 3/10 as the main article became much shorter
With our v2 code snippet, we have logged our latest prompt on the left, and continued to get a nicely formatted markdown panel on the right for our resulting blog article.
Fixing the blog post structure
Since the first prompt delivered on the structure, I wonder if we can do two prompts to make this work better.
Fortunately, the datograde SDK enables us to log many prompts at the same time, and also log intermediate files as we get to our result. Let’s see how this work in code.
Prompt 1
Write a blog post about the five mother sauces of French cooking.
Prompt 2
Take this blog post and generate a one sentence blurb to hook the reader into reading the blog post. After the blurb, add a personal anecdote to introduce the content. No need to reproduce the blog post, just write the blurb and anecdote.
PROMPT1 = "Write a blog post about the five mother sauces of French cooking."
PROMPT2 = "Take this blog post and generate a one sentence blurb to hook the reader into reading the blog post. After the blurb, add a personal anecdote to introduce the content. No need to reproduce the blog post, just write the blurb and anecdote."
generate_blog_post = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "user", "content": PROMPT1},
],
)
blog_post = generate_blog_post.choices[0].message.content
generate_blurb = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{
"role": "user",
"content": blog_post + "---" + PROMPT2,
},
],
)
blurb = generate_blurb.choices[0].message.content
datograde.attempt(
track_id="[Your trackID here, or the challenge name]",
new_input_files=[(PROMPT1, "md"), (PROMPT2, "md")], # log the prompt as an input
files=[
("\n\n".join([blurb, blog_post]), "md"),
(blog_post, "md"),
(blurb, "md"),
],
)
The results are promising. You can definitely see that the blurb and the anecdote can work and connect into the blog post. We will probably need to get the 2nd prompt to print a JSON for us to put the blurb/anecdotes in the right place.
Now we logged multiple prompts as inputs, then in reverse order we logged all intermediate steps of our AI content generation pipeline
Why waste time say lot word when few word do trick?
While this quintessential quote from The Office’s Kevin might be a bit extreme, LLMs have a tendency to repeat itself just to get words out. Just look at the first sentence.
In the vast universe of French cooking, mastery of the five "mother sauces" is considered a fundamental stepping stone for any burgeoning chef or cooking enthusiast.
- Why use “fundamental stepping stone”? Are there fundamental things that are not stepping stones?
- The word “burgeoning” is a needlessly fancy word for a guide like this. We are not taking the SATs. “New chef” seems to be so much simpler. Readers want to know about the sauces, not enjoy a literally masterpiece.
- I get that “burgeoning chef” and “cooking enthusiast” are technically two different things. But if every sentence tries to cover every offshoot of an idea, the writing is very hard to read.
What I wanted to see was an edit into something like this
In the vast universe of French cooking, mastery of the five "mother sauces" is fundamental for any new chef.
Let’s make our new prompt, using a one-shot prompting technique with the example we spent time ‘debugging’.
Prompt 1, version 3, with guidelines
Write a blog post about the five mother sauces of French cooking.
Guidelines:
- You MUST use simple language and be concise.
- You MUST avoid overly fancy adjectives or redundant phrases.
- You MUST keep sentences short and focused, and ensure the content flows logically for easy understanding.
- You MUST remove unnecessary adjectives and redundant phrases.
- You MUST avoid repetitive or overly flowery language. Do not use unnecessarily fancy adjectives or duplicate ideas.
For example, instead of saying, 'In the vast universe of French cooking, mastery of the five 'mother sauces' is considered a fundamental stepping stone for any burgeoning chef or cooking enthusiast,' say, 'In French cooking, mastering the five 'mother sauces' is essential for any new chef.'
This is what we got:
Nice result. Not simplifying the anecdote was a happy accident. I focused the output section to just the final blog post, but you can see that the icons let you see different steps of the pipeline
Now that’s much better. Hilariously, because gpt-4o
can’t be verbose, now the article is much shorter.
I am going to keep extending this article, but this is all the time I have today to write this. Check back soon 🙂
Here’s the full code:
import os
from datograde import Datograde
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
datograde = Datograde(access_token=os.getenv("DATOGRADE_API_KEY"))
PROMPT1 = """Write a blog post about the five mother sauces of French cooking.
Guidelines:
- You MUST use simple language and be concise.
- You MUST avoid overly fancy adjectives or redundant phrases.
- You MUST keep sentences short and focused, and ensure the content flows logically for easy understanding.
- You MUST remove unnecessary adjectives and redundant phrases.
- You MUST avoid repetitive or overly flowery language. Do not use unnecessarily fancy adjectives or duplicate ideas.
For example, instead of saying, 'In the vast universe of French cooking, mastery of the five 'mother sauces' is considered a fundamental stepping stone for any burgeoning chef or cooking enthusiast,' say, 'In French cooking, mastering the five 'mother sauces' is essential for any new chef.'
"""
PROMPT2 = "Take this blog post and generate a one sentence blurb to hook the reader into reading the blog post. After the blurb, add a personal anecdote to introduce the content. No need to reproduce the blog post, just write the blurb and anecdote. Don't annotate the blurb with 'Blurb:' or 'Anecdote:' or anything like that. Just write the blurb and anecdote."
generate_blog_post = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "user", "content": PROMPT1},
],
)
blog_post = generate_blog_post.choices[0].message.content
generate_blurb = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{
"role": "user",
"content": blog_post + "---" + PROMPT2,
},
],
)
blurb = generate_blurb.choices[0].message.content
datograde.attempt(
track_id="[Your trackID here, or the challenge name]",
new_input_files=[(PROMPT1, "md"), (PROMPT2, "md")], # log the prompt as an input
files=[
("\n\n".join([blurb, blog_post]), "md"),
(blog_post, "md"),
(blurb, "md"),
],
)
Future directions
- Break every section down by heading, then do a web search to get more facts to add insight.
- Create an agent to both come up with search keywords and relevant links to click on to add factual content to the article.
- Create an agent to find articles to link to internally and externally.