[TUTO] Que diriez-vous de « coder » votre propre équipe de rédacteurs ?

Depuis tout petit… enfin mes 21 ou 22 ans, je fais du seo par-ci, du seo par-là!

Au début, c’était tout nouveau pour moi, j’étais alors tombé sur le blog d’un type louche que j’ai longtemps considéré comme mon mentor. Il avait plein d’idées rigolotes… et moi, je commençais tout juste à faire des trucs avec PHP, Curl, etc… j’avais une mine de challenges à relever et c’était kiffant.

Bref… au fils des ans, je me suis rendu compte qu’on courrait un peu tous après le même truc en SEO: le contenu de qualité.

Le souci, c’est que quand on est un peu borderline, on veut le beurre et l’argent du beurre. Passer 3h à pondre un article de 1500 mots, tout le monde n’est pas fan. En fait pas grand monde.

On a alors inventé des tas de trucs: content spinning, traduction automatisé via api, génération de texte à base de Markov, des essais avec les Ngrams, etc…

En fait, le référenceur un peu black hat adore automatiser toute sorte de tâche.

Enfin bref, on cherchait tous le saint graal de la production de contenu…

… jusqu’à ce qu’Openai débarque avec son IA l’année dernière.

Pas question d’avoir un ChatGPT, il fallait faire la queue dans une wait list rien que pour pouvoir tester l’API de GPT3.

Et bizarrement, le jour ou j’ai reçu mon mail d’acceptation, j’avais déjà des dév prévu pour ça. Il ne me manquait qu’une clé API à mettre dedans et tester.

Alors les premiers résultats => BIDONS !

Je n’avais pas encore trop compris comment tout ça fonctionnait mais j’ai pu titiller le truc pas mal de temps ensuite.

Et bizarrement, aujourd’hui, alors que tout le monde se rue sur GPT4, j’ai encore des programmes qui tournent sous GPT3. Ok GPT4 est moins cher… mais j’ai passé trop de temps a peaufiner mes prompts pour GPT3 qu’il faut tout refaire pour GPT4. Et tout ça pour…

… un résultat quasi identique!

Je m’explique:

GPT4 réflechit et intéragit beaucoup mieux, c’est clair. En one shot, il sait vous pondre un texte digne d’être publié sur un blog lambda avec quelques retouches.

Alors qu’avec GPT3, vous étiez déjà limité a 2048 Tokens… à peine 700 mots.

La loose en fait à l’époque (l’année dernière en fait)

L’idée de base pour le script à la fin de ce tuto était donc simple:

  • J’avais accès à une API qui pouvait m’envoyer des trucs pour 2048 Tokens… prompt compris, mais je pouvais faire autant de requêtes que souhaitées
  • Je pouvais donc faire rédiger les différentes parties d’un article, une par requête. L’idée étant de produire des textes de 2000+ mots. 3 parties, une intro et une conclusion: 5 requêtes avec un potentiel de 700 mots en retour, soit 3500 mots.

Ça, c’est l’idée de base !

Mais souci: si je peux produire des centaines d’articles aussi rapidement… j’en fais quoi de ces conneries ?

Ben easy, on va brancher l’API de WordPress sur le script qui enverra direct les articles.

Jusque là, l’idée est sympa… mais il y a un mais…

Envoyer des articles sans les relire, bof bof, surtout que GPT3 n’est clairement pas GPT4. Il y avait des patterns de répétition à traiter et tout un tas de trucs chelous. Mais on y vient.

L’idée a donc germé et voici quelques points de ce qu’on va faire:

  1. On va demander à GPT3 de générer un titre d’article génial d’après une liste de mots-clés (rédigés par nos soins… aujourd’hui ChatGPT)
  2. D’après ce titre, une petite requête GPT3 pour récupérer 3 mots clés principaux.
  3. Ayant maintenant un titre et 3 mots clés, je peux les utiliser pour demander un plan d’article à GPT3 (on en est à 3 requêtes)
  4. Je récupère un plan que je vais traiter (nettoyer la réponse et séparer les sous parties du plan)
  5. Ayant une idée précise pour les intro et conclu, je les vire également du plan… je ne garde que les sous-partie de l’article.
  6. Histoire d’avoir des trucs sympas pour les sous-parties, je vais générer l’introduction en premier (4ème requête)
  7. J’ai alors la possibilité de créer un prompt, comprenant un titre d’article, des mots-clés et une introduction, pour rédiger les sous parties une par une (dans une boucle)
  8. Vous me suivez ?
  9. Ok, alors on va faire un truc bizarre: demander à GPT3 de faire des recherches sur la sous-partie avant de la rédiger. Eh oui, GPT3, c’est pas GPT4…. il faut l’orienter un peu plus.
  10. Donc au lieu de rédiger tout de suite, on lui dit: « tiens, génère des faits intéressants sur tel sujet » et pour en rajouter une couche, on lui fait faire ça 2 ou 3 fois (une boucle) histoire d’avoir une liste intéressante de faits sur lesquels écrire
  11. On a donc, à ce stade, un titre, des mots clés, une intro, un sous titre et une liste à puces de faits intéressants: GPT3 peut maintenant pondre un texte sympa pour une des parties de l’article.
  12. Bon ok, en token, le prompt prend pas mal, mais comme on rédige par partie, c’est pas si gênant. GPT3 n’étant pas encore super compréhensif, il faut nettoyer ce qu’il renvoit. Parce qu’on a beau lui dire de ne pas faire de conclusion pour une sous-partie, des fois il ne pige pas. Idem pour une intro ou les patterns (premièrement, deuxièmement, etc…) … enfin bref…
  13. J’ai donc là mes sous-parties avec déjà quelques requêtes (on va dire 7 en arrivant à la rédaction de la première partie en ayant la liste des recherches) à mon actif… parce que rédiger un contenu sympa avec GPT3 ne se résume pas à envoyer un prompt avec une prière pour qu’il fasse des miracles :)
  14. J’ai donc un boucle qui répète le processus de recherche et de rédaction/nettoyage.
  15. Je rédige enfin mon intro comme j’ai pu le faire pour mon intro.
  16. J’envoie le tout sur mon site WordPress

Vous êtes toujours là ? Vous suivez toujours ? On se fait le code ?

Bon c’est la base…

J’ai oublié une ou deux choses avant de commencer:

  • Que serait un article sans personnalité ? On va générer pour intégrer un profil de rédacteur à notre article (un journaliste sportif qui a fait du foot dans sa jeunesse par exemple pour un article sur le sport)
  • Que serait un article sans de petites anecdotes persos ? sans FAQ, etc… GPT3 peut imaginer des histoires et tout un tas de trucs… autant s’en servir non ?
  • Et pis! Tant qu’à faire, je ne veux pas un seul article… je veux remplir un blog par jour de 100 articles. J’en ai 250+ à gérer quand même. Et comme ça doit aller vite, sans pour autant dépasser les limites de requêtes imposées par Openai, on va rédiger et poster les articles par lot de 5 ou 10 avec des temps de pause.

Alors on y va ?

OK… alors un truc doit (un peu) vous titiller à ce niveau de l’article: avec autant de requêtes, à combien se chiffre l’article au final ? Aujourd’hui ça peut paraitre un peu cher, mais 1€ pour le tout l’année dernière, c’était le pied! 1€ l’article de 2000+ mots rédigés en moins de 2 minutes (ça prenait du temps GPT3, on descend sous les 30 secondes avec GPT4), c’est un sacré réduction des coûts en rédaction quand on sait le tarif d’un rédacteur.

On pourrait s’arrêter ici et débattre de la qualité du contenu IA/Humain … mais à partir du moment où j’ai réussi à entrainer (avec 2000 exemples, c’est un autre sujet) GPT3 pour qu’il rédige mes emails à ma place, en employant ma façon de parler etc… ma position sur le sujet a été rapidement prise. Soit je rédige moi-même parce que c’est trop technique, soit j’utilise l’IA!

Bon, on va pouvoir commencer le code… chouette non ?

Alors on va faire ça sous NodeJS avec quelques librairies sympas telles que WPAPI (pour publier sur WordPress) et OPENAI (pour interagir avec GPT).

Il vous faudra également installer sur votre blog un plugin pour autoriser la publication distante:

JSON Basic Authentication

Allez, go pour le code:

On va déjà installer les librairies necessaires pour faire propre. A savoir qu’on va bosser notre « redacteur » sous NodeJS. Il nous faut donc la lib openai pour GPT, wpapi pour WordPress et momentJS pour gérer les dates (on ne va pas publier 50 articles à la même date sur son WP on est d’accord… il nous faut donc une gestion d’intervalles de publication):

npm install wpapi
npm install openai
npm install moment

Maintenant qu’on a nos bibliothèques, on va penser fûté: soit on a un seul site, soit on en a des dizaines… on va donc faire un fichier de config (config.js) sous cette forme:

const sites = [
    {
    prefix: 'mon_site_x',
    url: 'https://site.fr/',
    login: 'XXXXXX',
    pass: 'XXXXXX',
    profil_redacteur: `Je suis un expert en mode masculine avec une solide expérience dans l'industrie. J'ai une passion pour la mode et je suis constamment à l'affût des dernières tendances et des innovations dans le secteur. Je suis un créateur de contenu chevronné avec une excellente compréhension de ce qui intéresse les lecteurs, et je suis capable d'écrire sur une grande variété de sujets, qu'il s'agisse de la mode, des accessoires ou du style de vie en général`,
    // seo stuff
    keywords: `vêtements pour hommes, tendances de mode masculine, style masculin, accessoires pour hommes, chaussures pour hommes, marques de mode masculine, blog de mode masculine, streetwear masculin, costume masculin, chemises pour hommes, denim pour hommes, montres pour hommes, sacs pour hommes, bijoux pour hommes, chapeaux pour hommes, lunettes de soleil pour hommes, soins de la peau pour hommes, parfums pour hommes, coiffure masculine, barbes et moustaches pour hommes`,
    categories : `Fringues, Lifestyle, Accessoires, Marques, Streetwear`,
    tags : ``,
    //content stuff
    articlesNumber: 5,
    },
    {
        prefix: 'monsite_2',
        url: 'https://trucmuche.fr/',
        login: 'xxxxx',
        pass: 'xxxxxx',
        profil_redacteur: `Je suis une rédactrice spécialisée dans ....`,
        // seo stuff
        keywords: `kw1,kw2,kw3,etc...`,
        categories : `cat1,cat2,cat3,etc`,
        tags : ``,
        //content stuff
        articlesNumber: 5,
    },
    {
       etc...
}
];
//export
module.exports = sites;

On a donc ici « des » sites. Au lancement du script, on va s’arranger pour qu’il en tire un au sort. Qu’il y ait 1 site ou 50 dans ce fichier, ça tournera.

Très bien, on va en profiter pour se faire un utils.js avec des fonctions utiles comme la mise en forme en html (GPT3 n’était pas très bon pour faire du rendu html, j’ai du me demmerder), la séparation des sous-titres, le traitement des patterns, tout ça tout ça:

const fs = require("fs");
module.exports = {
    getFileContent: function (file) {
        return fs.readFileSync(file, 'utf8');
    },
    logThis: function (file,datas) {
        fs.appendFileSync(file, `${datas}\n===\n`);
    },
    stringToFilename: function (string, ext = 'txt') {
        return string.trim().toLowerCase()
        .replace(/[éêèë]/g, 'e')
        .replace(/[àâä]/g, 'a')
        .replace(/[îï]/g, 'i')
        .replace(/[ôö]/g, 'o')
        .replace(/[ùûü]/g, 'u')
        .replace(/[ç]/g, 'c')
        //remove punctuation ! ? . , ; : ' " ( ) [ ] { } / \ | & @ # $ % ^ * + = - _ ~ ` < > °
        .replace(/[!?.;:'"()\[\]{}\/\\|&@#$%^*+=\-_~`<>°]/g, '')
        .replace(/[^a-z0-9\-]/g, '-') + '.' + ext;
    },
    formatHtml: function (html) {
        var toReplace = [`&lt;`, `&gt;`, `&amp;`, `&quot;`, `&apos;`, `&nbsp;`, `&copy;`, `&reg;`, `&trade;`, `&cent;`, `&pound;`, `&yen;`, `&euro;`];
        var replaceWith = [`<`, `>`, `&`, `"`, `'`, ` `, `©`, `®`, `™`, `¢`, `£`, `¥`, `€`];
        for (let i = 0; i < toReplace.length; i++) {
          html = html.replace(new RegExp(toReplace[i], "g"), replaceWith[i]);
        }
        let formatted = html.replace(/(\.\s)?(En conclusion|De plus|En résumé|Ensuite|Enfin|Tout d\'abord),\s([a-z])/g, (match, p1, p2, p3) => {
          return `${p1 ? p1 : ''}${p3.toUpperCase()}`;
        });
        return formatted;
    },
    toHtml: function (text) {
        let tag = 'p';
        let html = text.split(/(?:\r?\n)+/).map((paragraph) => {
            if(paragraph.startsWith('-')){
                return `<li>${paragraph.replace('-','')}</li>`;
            }
            return `<${tag}>${paragraph.trim()}</${tag}>`;
        }).join(`\n`);
        html = html.replace(/<\/p>(?:\r?\n)+<li>/g, '</p>\n<ul>\n<li>')
        .replace(/<\/li>(?:\r?\n)+<p>/g, '</li>\n</ul>\n<p>');
        return html;
    },
    cleanLine: function (line = '',removeIntroConclu = false) {
      // whatever
       
      return line.replace(/([0-9]{0,2}|[a-z]{1})\.\s?/gi,'').trim();
    },
    wordCount: function (text) {
        return text.split(/\s+/).length;
    },
    removeIaPatterns: function (text) {
        let formatted = text.replace(/(\.\s)?(En conclusion|De plus|Ensuite|Enfin|Tout d\'abord),\s([a-z])/g, (match, p1, p2, p3) => {
            return `${p1 ? p1 : ''}${p3.toUpperCase()}`;
        });
        return formatted;
    },
  };

Bien, maintenant, on peut faire le script principal qui va tout gérer:

//DO NOT CHANGE
const { Configuration, OpenAIApi } = require("openai");
const fs = require("fs");
const moment = require("moment");
var WPAPI = require("wpapi");
var utils = require('../utils.js');
var configs = require('./config_solo.js');
//pick random item in config array
var config = configs[Math.floor(Math.random() * configs.length)];
console.log(`Posting ON: ${config.url}`);
const batchSize = 3; // nombre d'articles à traiter par lot (pour éviter de saturer l'API GPT3)
if(typeof utils.formatHtml != 'function'){
    console.log('utils not loaded');
}
const configuration = new Configuration({
  apiKey: "XXXXXXXXXXXXX",
});
const openai = new OpenAIApi(configuration);
//const currentTime = new Date().getTime();
const currentDay  = new Date().toISOString().slice(0, 10);
let tokensUsed = 0;
let prefix = config.prefix;
let logsFile = `./logs/logs_${currentDay}.txt`;
//Your code down here
async function gpt3_completion(prompt,temperature = 0.7) {
    try {
      const completion = await openai.createCompletion({
        model: 'text-davinci-003', //text-curie-001
        prompt: prompt,
        max_tokens: 2500,
        temperature: temperature,
        /*frequency_penalty:1.4,
        presence_penalty:1.2,*/
      });
      let result = completion.data.choices[0].text;
      tokensUsed += completion.data.usage.total_tokens;
      result = result.replace(/\n{2,}/g, '\n').trim();
  
      return result;
    } catch (error) {
      if (error.response) {
        console.log(error.response.status);
        console.log(error.response.data);
      } else {
        console.log(error.message);
      }
    }
}
//global function to call GPT3 tasks
async function callPrompt(promptFile, arrayReplacement) {
    let prompt = fs.readFileSync(promptFile, 'utf8');
    for (const [key, value] of Object.entries(arrayReplacement)) {
        //console.log(`${key}: ${value}`);
        prompt = prompt.replace(`<<${key}>>`, value);
        
      }
      utils.logThis(logsFile,`PROMPT:\n ${prompt}`);
    let result = await gpt3_completion(prompt);
    //console.log(result);
    return result;
}
//function delay
async function makePause(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
//--------------------------------------------------
//  Génération des sujets d'articles et champ lexical
//--------------------------------------------------
for (let i = 0; i < config.articlesNumber; i++) {
  (async (currentIteration) => {
    // Tous les 5 i, on attend 5 secondes
    if(currentIteration % batchSize == 0){
      console.log(`Sending ${batchSize} articles...`);
      //wait 5 minutes
      await makePause(1000*60*5);
    }
    await write(currentIteration);
  })(i);
    
}
async function write(i){
  let article = {};
  
  //set random num for date to publish
  let randomNum = Math.floor(Math.random() * 3) + 5;
  article.date = moment().subtract(i*randomNum, 'days').format();
  
  //find a subject
  console.log(`Generating subject ${i}...`);
  console.log(`Date to publish: ${article.date}`);
  //on tire au sort un des mots clés
  let randomKeyword = config.keywords.split(',').map((item) => item.trim())[Math.floor(Math.random() * config.keywords.split(',').length)];
  //on tire au sort une des catégories
  let randomCategory = config.categories.split(',').map((item) => item.trim())[Math.floor(Math.random() * config.categories.split(',').length)];
  let subject = await callPrompt('./prompts/prompt_subject.txt', {'KEYWORD': randomKeyword, 'CATEGORY': randomCategory});
  subject = utils.cleanLine(subject);
  console.log(`Subject: ${subject}`);
  //save subject to file
  fs.appendFileSync(`./logs/working_${currentDay}.txt`, `Sujet: ${subject}\n`);
  let datas = subject.split('|');
  let replaceStrings = {
      'SUBJECT': datas[0],
      'KEYWORDS': datas[1],
      'RANDOM' : Math.floor(Math.random() * 3) + 5,
  };
  article.subject = datas[0];
  replaceStrings['PROFIL'] = config.profil_redacteur;
  // create filename
  let filename = `./articles/${prefix}_${utils.stringToFilename(datas[0])}`;
 // let article = `${subject[0]}\n###\n`;
  fs.appendFileSync(filename, `${datas[0]}\n###\n`);
  console.log(replaceStrings);
  // 1. get outlines
  let outlines = await callPrompt('./prompts/prompt_outlines.txt', replaceStrings);
  // 2. write intro with outlines
  replaceStrings['SECTIONS'] = outlines = outlines.split('\n').map((line) => {
      line = utils.cleanLine(line).trim();
      //if line start with Intro or Conclusion, remove it
      if(line.startsWith('Introduction') || line.startsWith('Conclusion')){
        //unset line
        return '';
      }else{
        //add line
        return line;
      }
  }).join('\n');
  //log
  console.log(`OUTLINES: \n${replaceStrings['SECTIONS']}`);
  let intro = await callPrompt('./prompts/prompt_intro.txt', replaceStrings);
  fs.appendFileSync(filename, `${utils.toHtml(intro)}\n`);
  article.content = `${utils.toHtml(intro)}\n`;
  replaceStrings['INTRODUCTION'] = intro;
  console.log(`INTRO: \n${intro}`);
  utils.logThis(logsFile,intro);
  // 3. write each section with outlines and intro
  let sections = outlines.split('\n');
  for(var section in sections){
      let sectionTitle = utils.cleanLine(sections[section]);
      let previousSectionTitle = utils.cleanLine(sections[section-1]);
      //rand to create text or not
      let createAnecdote = Math.floor(Math.random() * 2);
     
      replaceStrings['SECTION'] = sectionTitle;
      replaceStrings['SECTION_PRECEDENTE'] = previousSectionTitle;
      console.log(`SECTION: ${sectionTitle}\n`);
      utils.logThis(logsFile,sectionTitle);
      //before writing section, make research
      replaceStrings['RESEARCH'] = '';
      for(var i = 0; i < 1; i++){
          let notes = await callPrompt('./prompts/prompt_research.txt', replaceStrings);
          //check if notes is defined and not empty
          if(notes && notes.trim() != ''){
          replaceStrings['RESEARCH'] += '\n' + '- ' + notes.split('\n').map((line) => {
              return utils.cleanLine(line).trim();
          }).join('\n');
        }
      }
      utils.logThis(logsFile,`RESEARCH : ${replaceStrings['RESEARCH']}`);    
      //console.log(`RESEARCH : ${replaceStrings['RESEARCH']}`);
      
       //insertion de l'anecdote
      if(createAnecdote){
        console.log('Writing Anecdote for this section...');
        replaceStrings['ANECDOTE'] = await callPrompt('./prompts/prompt_anecdote.txt', replaceStrings);
        replaceStrings['PROSE'] = await callPrompt('./prompts/prompt_prose_with_anecdote.txt', replaceStrings);
      }else{
        console.log('Not Writing Anecdote for this section...');
        replaceStrings['PROSE'] = await callPrompt('./prompts/prompt_prose.txt', replaceStrings);
      }
      console.log(`SECTION CONTENT: \n${replaceStrings['PROSE']}`);
      // improve prose
      // for (var i = 0; i < 2; i++) {
      //     replaceStrings['PROSE'] = await callPrompt('./prompt_improve_prose.txt', replaceStrings);
      // }
      utils.logThis(logsFile,replaceStrings['PROSE']);
      console.log(`SECTION CONTENT: \n${replaceStrings['PROSE']}`);
      //format in html
     //let sectionHtml = await callPrompt('./prompt_html_prose.txt', replaceStrings);
     let sectionHtml = utils.toHtml(replaceStrings['PROSE']);    
      //console.log(`SECTION HTML: \n${sectionHtml}`);
      article.content += `<h2>${sectionTitle}</h2>\n${utils.formatHtml(sectionHtml.trim())}\n`;
      fs.appendFileSync(filename, `<h2>${sectionTitle}</h2>\n${utils.formatHtml(sectionHtml.trim())}\n`);
  }
  // 4. write conclusion with intro and outlines
  // est-ce que je mets la dernière section dans le prompt de conclusion ?
  let conclusion = await callPrompt('./prompts/prompt_conclusion.txt', replaceStrings);
  //write article
  article.content += `${utils.toHtml(conclusion)}\n`;
  fs.appendFileSync(filename, `${utils.toHtml(conclusion)}\n`);
 // fs.appendFileSync(filename, article);
 //if article is not empty, send it to WordPress
  if(article.content != '' && utils.wordCount(article.content) > 700){
      //send article to wordpress
      
      //connection au blog
      var wp = new WPAPI({
          endpoint: `${config.url}wp-json`,
          // This assumes you are using basic auth, as described further below
          username: config.login,
          password: config.pass,
      });
      //pick random category
      //let categories = config.categories.split(',');
      let cat = randomCategory;
      getCat(wp, cat, function (wp,catId) {
          console.log(`Catégorie ${cat} trouvée, id: `,catId);
          send_article(wp, article.subject, article.content, catId, article.date);
      });
  }
}
async function getCat(wp, cat,callback) {
  wp.categories()
    .create({ name: cat })
    .then(function (response) {
      // "response" will hold all properties of your newly-created post,
      // including the unique `id` the post was assigned on creation
      console.log(`Successfully created categorie #${response.id}`);
      //console.log(`Response: `, response);
     callback(wp,[response.id]);
    })
    .catch(function (err) {
      // Something went wrong!
     console.log(err);
     
     callback(wp,[err.data.term_id || 1]);
    })
}
function send_article(wp, title, content, cat = [],date) {
  //console.log(`Datas to send: `, wp);
  wp.posts()
        .create({
          // "title" and "content" are the only required properties
          title: title,
          content: content,
          categories: cat,
          // Post will be created as a draft by default if a specific "status"
          // is not specified
          status: "publish",
          date: date,
        })
        .then(function (response) {
          // "response" will hold all properties of your newly-created post,
          // including the unique `id` the post was assigned on creation
          console.log(`Successfully created post #${response.id}`);
          console.log(`Article ok ${title}: `, response.link);
        })
        .catch(function (err) {
          // Something went wrong!
          console.log(err);
        });
}

Bien entendu, il y a tous les prompts à gérer. créez un dossier /prompts dans le lequel vous mettrez:

prompt_subject.txt

Agissez en tant que le profil ci-dessous. Trouvez un sujet d'article original et percutant qui incite à lire, en vous aidant des indications ci-dessous. Définissez le champ lexical de cet article pour le référencement web. Séparez le titre du champ lexical par |.
Les expressions longues du champs lexical sont séparés par des virgules
PROFIL: <<PROFIL>>
MOTS-CLÉ: <<KEYWORD>>
CATEGORIE : <<CATEGORY>>
Voici le titre d'article original trouvé grâce au profil et au mot clé ci-dessus ainsi que les expressions du champ sémantique.
TITRE:

prompt_intro.txt

Je dois rédiger un article sur un sujet précis. Je dois rédiger une introduction pour cet article d'après les instructions suivantes.Je peux parler de ma propre expérience.
BIOGRAPHIE:<<PROFIL>> 
TITRE: "<<SUBJECT>>"
PLAN:
<<SECTIONS>>
Voici l'introduction que j'ai rédigé.
INTRODUCTION:

prompt_outlines.txt

Je veux rédiger un article pour un client selon les instructions suivantes. J'ai besoin d'un plan élaboré, dont les sous-titres s'adressent au lecteur, mais sans introduction, ni conclusion.
SUJET: "<<SUBJECT>>"
MOTS-CLÉS: <<KEYWORDS>>
Réfléchissez à une liste de <<RANDOM>> sections pour cet article. Le plan doit répondre à la demande du client et chaque section doit avoir un titre percutant. Evitez les répétitions de mots.
PLAN DE L'ARTICLE:
1.

prompt_research.txt

J'ai la demande d'article suivante à écrire. Je me concentre sur l'écriture d'une section à la fois. Je dois prendre quelques notes sur cette section avant de continuer.
SUJET: "<<SUBJECT>>"
INTRODUCTION:
<<INTRODUCTION>>
SECTION: <<SECTION>>
Voici mes notes de recherche détaillées sur cette section. Je dois écrire tout ce que je sais à ce sujet:
-

prompt_anecdote.txt

D'après le profil ci-dessous, imaginez une anecdote de 2 ou 3 ligne, en commençant par "un jour..."
PROFIL: <<PROFIL>>
ANECDOTE:

prompt_prose_with_anecdote.txt

J'ai compilé les notes de recherche suivantes. Je dois réécrire ces notes dans une longue section de blog professionnel. Ça doit être engageant et intéressant, tout en évitant la répétition de connecteurs logiques qui prouve que c'est une intelligence artificielle qui rédige les textes. Agissez en vous mettant à la place de la personne du profil ci-dessous. Inspirez-vous de l'anecdote ci-dessous que vous partagerez avec le lecteur. Rédigez en vous adressant au lecteur, sans faire de répétitions inutiles. Prenez en compte la section précédente pour éviter les répétitions. N'introduisez pas le sujet. Ne concluez pas non plus.
PROFIL: <<PROFIL>>
ANECDOTE: <<ANECDOTE>>
SECTION: <<SECTION>>
SECTION PRECEDENTE: <<SECTION_PRECEDENTE>>
RECHERCHES:
<<RESEARCH>>
TEXTE ENGAGEANT DE LA SECTION :

prompt_prose.txt

J'ai compilé les notes de recherche suivantes. Je dois réécrire ces notes dans une longue section de blog professionnel. Ça doit être engageant et intéressant, tout en évitant la répétition de connecteurs logiques qui prouve que c'est une intelligence artificielle qui rédige les textes. Agissez en vous mettant à la place de la personne du profil ci-dessous. Rédigez en vous adressant au lecteur, sans faire de répétitions inutiles. Prenez en compte la section précédente pour éviter les répétitions. N'introduisez pas le sujet. Ne concluez pas non plus.
PROFIL: <<PROFIL>>
SECTION: <<SECTION>>
SECTION PRECEDENTE: <<SECTION_PRECEDENTE>>
RECHERCHES:
<<RESEARCH>>
TEXTE ENGAGEANT DE LA SECTION :

prompt_conclusion.txt

Je viens de rédiger un article sur un sujet précis. Je dois rédiger une conclusion pour cet article d'après les instructions suivantes.
TITRE: "<<SUBJECT>>"
PLAN:
<<SECTIONS>>
Voici la conclusion que j'ai rédigé.
CONCLUSION:

Je crois que j’ai tout mis…

Et woualà!

Il faut tout de même, vous l’aurez sûrement remarqué, créer 2 dossiers en plus: /logs pour loguer ce qui se passe histoire d’avoir des pistes d’améliorations et /articles pour enregistrer vos articles au cas où ça plante ou que ça n’envoie pas sur votre blog (ce serait dommage de tout perdre)

Il ne vous reste alors plus qu’à enregistrer votre fichier sous go.js et de lancer votre console dans votre dossier:

node go.js

Vous n’avez plus qu’à aller vous servir un café et regarder faire GPT.

Vous remarquerez quelques trucs bidons:

  • Il n’y a aucune interface tout mignonne, on s’en fout un peu
  • Le traitement des patterns peut être amélioré (mais je ne vais pas tout vous filer comme ça)
  • Le traitement par lot s’améliore aussi… ne demandez pas à l’API Openai de vous traiter 50 articles en même temps, vous allez vous retrouver avec 50 fichiers TXT d’articles qui ne contiendront qu’un titre, une intro et peut-être une première partie… jusqu’au plantage… gaspillage!
  • Les prompts peuvent être encore améliorés (encore une fois, je ne vais pas tout vous mâcher. Passé sous GPT3.5 puis 4 dernièrement, je n’utilise presque plus ce code qui date de quelques mois déjà)
  • Bref, il y a tout un tas de trucs à peaufiner… je ne suis pas une superstar du JS et donc le dév de métier y trouvera facilement à redire (développer ces outils me permet d’apprendre en même temps).

En gros, c’est une base de dev sur laquelle j’ai travaillé vite fait il y a quelque mois pour un besoin urgent de contenus.

Tout n’était pas parfait, mais ça faisait le job pour lequel c’était codé!

Maintenant, forcément avec GPT4, on faire des trucs un peu plus dingues (genre de la génération d’interviews, des listicles, des tutos etc…). Mais ça, c’est réservé aux membres de la Tribu™

Vous pouvez toujours aussi laisser votre adresse mail et recevoir, ne sait-on jamais, d’autres trucs dans ce style ;)

Bon code! N’hésitez pas à partager ;)

Vous souhaitez recevoir davantage de trucs & d'astuces ? Tous les jours j'envois un mail à mes 6000 (et quelques) abonnés. Si ça vous dit, cliquez-ici pour vous inscrire