Multiple API calls and Steps (messages): Is this the correct way?

I am using the GPT-3.5-Turbo API in order to help my clients create new posts for their WordPress websites. What they do, is write a bunch of keywords, send them to AI and then generate an article.

But in order to achieve what I want, I have to make alot of different API calls to the AI and each API call consists of many steps (messages). Here are my prompts:

// AI Instructions
  messages = [
    { "role":"system","content": "You are a helpful assistant specialized in writing Greek SEO-friendly blog posts. Change the writing style to be 'Professional' and the tone to be 'Encouraging'." },
    { "role": "user", "content": article },
    { "role": "user", "content": `Generate a Greek post (around 500 words) for my WordPress website, that incorporates these keywords naturally: "${keywordsString}". Spread them out and don't cluster them.` },
    { "role": "user", "content": "The post should be SEO-friendly, cohesive, reader-friendly and provide genuine value to the reader. Remember to include the keywords I provided as-they-are." },
    { "role": "user", "content": "Your response should not include any comments or affirmations from you. Just the post in Greek." },
  ];
  article = await callAPI(messages);
messages = [
    { "role":"system", "content": "You are a helpful assistant specialized in proofreading and editing Greek text to make it grammatically correct, coherent, and colloquially natural for a Greek-speaking audience." },
    { "role": "user", "content": article },
    { "role": "user", "content": "Please proofread and edit the given Greek text to ensure it is grammatically correct, coherent, and uses phrases and expressions that are colloquially natural for a Greek-speaking audience. Make any necessary adjustments." },
    { "role": "user", "content": `Inside the article you will find these comma-seperated keywords: "${keywordsString}". They must remain the same, so please leave them unchanged.` },
    { "role": "user", "content": "Your response should not include any comments or affirmations from you. Just the post." },
  ];
  article = await callAPI(messages);
// Add HTML tags
  messages = [
    { "role":"system","content": "You are a helpful assistant specialized in adding HTML tags articles." },
    { "role": "user", "content": article },
    { "role": "user", "content": "Bold the important words or phrases in the text by wrapping them in HTML tags like this: `<strong>important word</strong>` ." },
    { "role": "user", "content": "Add meaningful titles above paragraphs, and wrap them in HTML tags like this: `<h2>Here goes a heading</h2>` ." },
    { "role": "user", "content": "Your response should not include any comments or affirmations from you. Just the post." },
  ];
  article = await callAPI(messages);
// FAQ
  messages = [
    { "role":"system","content": "You are a helpful assistant specialized in reading Greek blog posts and then adding helpful and genuine FAQ with questions and answers. Never change the context of the post." },
    { "role": "user", "content": article },
    { "role": "user", "content": "The post contains some HTML tags (<strong> and <h2>). Please do not change them. Based on the post I provided, add a FAQ at the bottom of the post. I need 3 to 5 FAQ questions." },
    { "role": "user", "content": "Your response should not include any comments or affirmations from you. Just the post in Greek." },
    { "role": "user", "content": `The whole post should have around 500 words. Respond with the whole post, along with the FAQ in Greek. Remember to also retain original HTML tags.` },
  ];
  article = await callAPI(messages);

I know the article generation takes a total of 8mins to finish but I don’t mind that. The main issue is that the article usually has grammar mistakes (hence the addition of the proofreading call), it rarely contains all the keywords I provide (the end message contains only 2 or 3 instead of all the user-provided ones).

Am I doing something wrong in the way I try to achieve my goal?

The callAPI function is this, in case you wanted to see it:

const callAPI = async (messages) => {
  const requestOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${API_KEY}`
    },
    body: JSON.stringify({
      model: "gpt-3.5-turbo-16k", // gpt-3.5-turbo | gpt-3.5-turbo-16k
      messages: messages
    })
  };

  try {
    const response = await fetch("https://api.openai.com/v1/chat/completions", requestOptions);
    const jsonResponse = await response.json();
    return jsonResponse.choices[0]?.message?.content.trim();
    console.log(data.usage.total_tokens);
  } catch (error) {
    console.error("API call failed:", error);
    return null;
  }
};

You do not show API parameters, but you should limit the type of tokens that can be creatively generated. This can be done with settings such as temperature = 0.4, top_p = 0.95 instead of the defaults of 1.0. Especially with less-commonly used world languages. And experiment with the results.

PS, stop ruining the web.

I didn’t include parameters because I left the defaults to be used.
Apart from these parameters, you think the whole structure is correct? It isn’t an overkill to use 4 API calls where each call has multiple messages?

It feels towards the end, that the API responses are forgetting things. Sometimes there are no <strong> tags in the final response or <h2> tags. Sometimes I see no titles; only paragraphs. It’s as if the AI forgets what I ask.

That’s why I was wondering if the current way I am interacting with it is the correct one.

As for “ruining the web” I understand your concerns but as a developer I am not the one who takes the company decisions.

Yes, for each place a word should be generated, the AI outputs many choices of possibilities. The defaults of temperature and top_p allow very unlikely tokens to be occasionally output, which can include ones with incorrect grammar.

A tip you can try for other symptoms: gpt-3.5-turbo has become very poor at following multi-step instructions. Instead, use the prior model gpt-3.5-turbo-0301 and see how it performs at tasks like extracting keywords from articles.

And if the instructions are TL;DR for me, the AI might have the same sentiment. So you can make them clear and concise. All that text about how to rewrite and proofread? Just write “improve quality” (which may be no longer necessary with constrained token parameters).

OK so what I did was add different temperatures and top_p values in my different prompts like this:

// 1st prompt
article = await callAPI(messages, 0.7, 0.85);

// 2nd prompt
article = await callAPI(messages, 0.2, 0.7);

// 3rd prompt
article = await callAPI(messages, 0.1, 0.1);

etc.

And the function for callAPI changed to:

const callAPI = async (messages, temperature = 0.65, top_p = 0.9) => {
  const requestOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${API_KEY}`
    },
    body: JSON.stringify({
      model: "gpt-3.5-turbo-16k-0613", // gpt-3.5-turbo | gpt-3.5-turbo-16k | gpt-3.5-turbo-16k-0613
      messages: messages,
      temperature: temperature,
      top_p: top_p
    })
  };

  try {
    const response = await fetch("https://api.openai.com/v1/chat/completions", requestOptions);
    const jsonResponse = await response.json();
    return jsonResponse.choices[0]?.message?.content.trim();
    console.log(data.usage.total_tokens);
  } catch (error) {
    console.error("API call failed:", error);
    return null;
  }
};

The results are better, although I still had to min/max some prompts for more accurate responses.
Thanks for your help, I appreciate it!