Utiliser l'IA pour taguer et catégoriser les publications du forum

J’ai créé un Persona pour tenter de catégoriser et d’étiqueter automatiquement les nouveaux sujets. Voici le prompt que j’utilise pour cette catégorie et cet étiqueteur.

Vous êtes un assistant IA pour mon forum Discourse. Votre travail consiste à classer les nouveaux sujets dans l’une des catégories prédéfinies et à appliquer jusqu’à 5 étiquettes pertinentes.

Catégories :

  • Catégorie 1
  • Catégorie 2
  • Catégorie 3
  • Catégorie 4
  • Catégorie 5

Étiquettes :

Votre sélection doit provenir de cette liste prédéfinie (choisissez jusqu’à 5) :
tag1, tag2, tag3, tag4, tag5

Tâche :

  • Sélectionnez exactement une catégorie dans la liste ci-dessus qui correspond le mieux au sujet.
  • Sélectionnez jusqu’à 5 étiquettes dans la liste ci-dessus en fonction de leur pertinence.
  • Formatez votre réponse en JSON exactement comme ceci :
{
  "category": "Nom de la catégorie choisie",
  "tags": ["tag1", "tag2", "tag3"]
}

Lorsque je sélectionne ce persona à utiliser dans les automatisations et que je crée le post, l’IA identifie correctement les catégories et les étiquettes avec ce résultat dans un post.

Lister les catégories

{
  "category": "Catégorie Correctement Identifiée",
  "tags": ["Toutes ", "valides", "étiquettes"]
}

La question est : est-il possible d’utiliser réellement l’IA et les automatisations pour déplacer les nouveaux posts, les catégoriser et leur attribuer des étiquettes ? Si oui, je suis sûr que le prompting peut être ajusté pour améliorer son efficacité.

Merci.

1 « J'aime »

Getting there… it is a bit tricky we are missing 2 little pieces:

  1. Custom tools now support lots of stuff, but they do not support categorizing and tagging, we can easily add that.
  2. I need a responder that works in “silent” mode, so it does not actually respond to the topic.

Once both of those are in place you would give access to 2 custom tools

  1. tag topic
  2. categorize topic

(or a single tool that does both)

5 « J'aime »

En fait, tant que cela vous convient avec le murmure, vous pouvez faire quelque chose maintenant.

L’idée est que vous définiriez un outil personnalisé pour catégoriser un sujet (ou étiqueter un sujet), puis vous feriez en sorte que le persona l’appelle.

Curieux de voir comment cette approche fonctionne pour vous, vous devrez tester l’outil, cela devrait être assez simple à partir de l’interface utilisateur de l’outil.
C’est assez fastidieux de gérer tout cela, mais c’est aussi assez :exploding_head: que cela puisse être fait. Le murmure est en fait raisonnablement utile car il vous donne un peu le « processus de réflexion » sur la façon dont le répondeur a trouvé les étiquettes/catégories.

3 « J'aime »

Merci Sam,

Plus facile pour certains que pour d’autres. Je fais partie de la seconde catégorie :joy:

Je bidouille avec ça et je n’arrive pas à le faire fonctionner. Je n’ai aucune expérience des outils, j’ai donc hâte de faire fonctionner cela pour réfléchir à d’autres façons d’utiliser les outils.

J’ai essayé d’utiliser le code de votre blog avec une clé API, ce qui n’a pas fonctionné. L’IA m’a suggéré d’utiliser une URL codée en dur, alors j’ai essayé cela sans succès.

La clé API n’a pas été appelée et aucun journal n’a été créé avec des erreurs.

Ceci est utilisé pour catégoriser automatiquement les sujets lors de leur création

Je classerai le sujet « Insights from Jesus’ early years and miracles » dans la catégorie Nouveau Testament, car il concerne la vie et les enseignements de Jésus.
Passage du sujet maintenant…

Voici ce que j’ai essayé.

/**

  • Référence rapide de l’API de l’outil
  • Fonctions d’entrée
  • invoke(parameters): Fonction principale. Reçoit des paramètres (Objet). Doit retourner une valeur sérialisable en JSON.
  • Exemple :
  • function invoke(parameters) { return “result”; }
  • details(): Optionnel. Retourne une chaîne décrivant l’outil.
  • Exemple :
  • function details() { return “Description de l’outil.”; }
  • Objets fournis
    1. http
  • http.get(url, options?): Effectue une requête HTTP GET.
  • Paramètres :
  •  url (string): L'URL de la requête.
    
  •  options (Object, optionnel) :
    
  •    headers (Object): En-têtes de la requête.
    
  • Retourne :
  •  { status: number, body: string }
    
  • http.post(url, options?): Effectue une requête HTTP POST.
  • Paramètres :
  •  url (string): L'URL de la requête.
    
  •  options (Object, optionnel) :
    
  •    headers (Object): En-têtes de la requête.
    
  •    body (string): Corps de la requête.
    
  • Retourne :
  •  { status: number, body: string }
    
  • (également disponible : http.put, http.patch, http.delete)
  • Note : Max 20 requêtes HTTP par exécution.
    1. llm
  • llm.truncate(text, length): Tronque le texte à une longueur de jeton spécifiée.
  • Paramètres :
  •  text (string): Texte à tronquer.
    
  •  length (number): Jetons maximum.
    
  • Retourne :
  •  Chaîne tronquée.
    
    1. index
  • index.search(query, options?): Recherche des documents indexés.
  • Paramètres :
  •  query (string): Requête de recherche.
    
  •  options (Object, optionnel) :
    
  •    filenames (Array): Limite la recherche à des fichiers spécifiques.
    
  •    limit (number): Fragments maximum (jusqu'à 200).
    
  • Retourne :
  •  Tableau de { fragment: string, metadata: string }
    
    1. upload
  • upload.create(filename, base_64_content): Télécharge un fichier.
  • Paramètres :
  •  filename (string): Nom du fichier.
    
  •  base_64_content (string): Contenu du fichier encodé en Base64.
    
  • Retourne :
  •  { id: number, short_url: string }
    
    1. chain
  • chain.setCustomRaw(raw): Définit le corps du post et quitte la chaîne.
  • Paramètres :
  •  raw (string): Contenu brut à ajouter au post.
    
  • Contraintes
  • Temps d’exécution : ≤ 2000 ms
  • Mémoire : ≤ 10 Mo
  • Requêtes HTTP : ≤ 20 par exécution
  • Le dépassement des limites entraînera des erreurs ou une terminaison.
  • Sécurité
  • Environnement sandboxé : Pas d’accès aux objets système ou globaux.
  • Pas d’accès au système de fichiers : Impossible de lire ou d’écrire des fichiers.
    */

/**

  • Catégoriseur de sujets Discourse
  • Cet outil vous permet de changer la catégorie d’un sujet Discourse
  • en utilisant l’API Discourse.
    */

/**

  • Catégoriseur de sujets Discourse
  • Cet outil vous permet de changer la catégorie d’un sujet Discourse
  • en utilisant l’API Discourse.
    */

/**

  • Catégoriseur de sujets Discourse
  • Cet outil vous permet de changer la catégorie d’un sujet Discourse
  • en utilisant l’API Discourse.
    */

function invoke(params) {
// Validation des paramètres requis
if (!params.topic_id) {
return { error: “Paramètre requis manquant : topic_id” };
}

if (!params.category_id) {
return { error: “Paramètre requis manquant : category_id” };
}

// URL de base pour votre instance Discourse
const baseUrl = “https://community.mysite.com”;

// URL complète du point d’accès API pour la mise à jour d’un sujet
const apiUrl = ${baseUrl}/t/${params.topic_id}.json;

// Préparation du corps de la requête
const requestBody = {
category_id: params.category_id
};

// Paramètre optionnel : mise à jour du titre si fourni
if (params.title) {
requestBody.title = params.title;
}

// Utilisez votre clé API fournie
const apiKey = “Discourse-API-Key”;

try {
// Effectuer une requête PUT pour mettre à jour le sujet
const response = http.put(apiUrl, {
headers: {
“Content-Type”: “application/json”,
“Api-Key”: apiKey,
“Api-Username”: params.api_username || “system”
},
body: JSON.stringify(requestBody)
});

if (response.status >= 200 && response.status < 300) {
  return {
    success: true,
    topic_id: params.topic_id,
    category_id: params.category_id,
    response: JSON.parse(response.body)
  };
} else {
  return {
    error: `Échec de la mise à jour de la catégorie du sujet. Statut : ${response.status}`,
    details: response.body
  };
}

} catch (error) {
return {
error: “Une erreur s’est produite lors de la mise à jour de la catégorie du sujet”,
details: error.toString()
};
}
}

function details() {
return “Catégorise un sujet en le déplaçant vers une catégorie spécifiée”;
}

2 « J'aime »

@BrianC as-tu fini par comprendre cela ?

1 « J'aime »

Discourse AI Tagger Catégorie Démarrage Rapide.pdf (147,1 Ko)

Discourse AI Tagger Catégorie.pdf (506,0 Ko)

@Sam,

Merci pour votre suivi. J’ai essayé, mais je n’ai pas réussi à le faire fonctionner, alors j’ai mis en pause. J’ai passé le test du scénario via Gemini 2.5 et j’ai enregistré la sortie en PDF.

Je vais réessayer. Les outils ne sont pas encore ma force, mais je dois m’y habituer davantage. PDF en pièce jointe — peut-être y a-t-il quelque chose d’utile dedans.

3 « J'aime »

Nous avons ajouté une automatisation de balisage par IA que vous pourriez être intéressé à essayer : FEATURE: create AI tagging automation (#34587) · discourse/discourse@e470f3d · GitHub

Invite que j'ai utilisée pour le tester
 Vous êtes un expert en classification de contenu et un assistant de balisage pour ce forum.

 Votre tâche consiste à analyser les publications et à suggérer des balises appropriées en fonction du contenu, des images et de la liste de balises disponibles fournie.

 Lignes directrices :
 - Ne suggérez que des balises de la liste de balises disponibles fournie
 - Soyez prudent - ne balisez que ce dont vous êtes sûr
 - Tenez compte à la fois du sujet du contenu et de l'intention de la publication

 Vous devez toujours répondre avec un JSON valide dans ce format exact :
 {"tags": ["tag1", "tag2"], "confidence": 85}

 - tags : tableau de noms de balises de la liste disponible
 - confidence : entier de 0 à 100 représentant votre niveau de confiance

 Si aucune balise n'est appropriée, utilisez : {"tags": [], "confidence": 0}

Format de réponse JSON Persona :

{
  "tags": "[string]",
  "confidence": "[integer]"
}

Outils Persona activés : balises (facultatif, c’est si vous voulez qu’il utilise les balises existantes sur le site)

L’automatisation a deux modes :

  • Utiliser les balises du site existantes
  • Utiliser la liste de balises fournie

L’automatisation a également un niveau de confiance configurable, vous pouvez activer/désactiver les balises restreintes et ajuster le nombre de publications qu’elle utilise dans un sujet pour le contexte.

6 « J'aime »

Merci pour cette fonctionnalité géniale, je n’arrive juste pas à la faire fonctionner, j’obtiens l’erreur suivante :

llm_tagger : échec du traitement du message 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : la méthode privée select’ a été appelée sur une instance de String`

Message

llm_tagger : échec du traitement du message 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : la méthode privée select’ a été appelée sur une instance de String`

Trace

/var/www/discourse/plugins/discourse-ai/lib/automation/llm_tagger.rb:140:in handle’ /var/www/discourse/plugins/discourse-ai/discourse_automation/llm_tagger.rb:110:in block (2 levels) in <main>'
/var/www/discourse/plugins/automation/app/models/discourse_automation/automation.rb:158:in block in trigger!’ /var/www/discourse/plugins/automation/app/models/discourse_automation/stat.rb:11:in log'
/var/www/discourse/plugins/automation/app/models/discourse_automation/automation.rb:156:in trigger!’ /var/www/discourse/plugins/automation/app/jobs/regular/discourse_automation/trigger.rb:29:in execute'
/var/www/discourse/app/jobs/base.rb:318:in block (2 levels) in perform’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management/null_instance.rb:49:in with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management.rb:17:in with_connection’ /var/www/discourse/app/jobs/base.rb:305:in block in perform'
/var/www/discourse/app/jobs/base.rb:301:in each’ /var/www/discourse/app/jobs/base.rb:301:in perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:220:in execute_job’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:185:in block (4 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:180:in traverse’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in block in traverse'
/var/www/discourse/lib/sidekiq/discourse_event.rb:6:in call’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in block in traverse’ /var/www/discourse/lib/sidekiq/pausable.rb:131:in call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in traverse’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job/interrupt_handler.rb:9:in call’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in block in traverse’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:26:in track'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:134:in call’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:173:in invoke’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in block (3 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:145:in block (6 levels) in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:118:in local'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:144:in block (5 levels) in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:39:in block in <class:Config>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:139:in block (4 levels) in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:281:in stats'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:134:in block (3 levels) in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:15:in call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:133:in block (2 levels) in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:85:in global'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:132:in block in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:40:in prepare'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:131:in dispatch’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in block (2 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in handle_interrupt’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in block in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in handle_interrupt’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:86:in process_one’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:76:in run'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:10:in watchdog’ /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:19:in block in safe_thread'

Meta l’utilise-t-il ? Je pensais avoir vu @system taguer un sujet il y a quelques jours.

1 « J'aime »

Cela pourrait être compliqué à configurer, mais si Discourse génère déjà des embeddings (pour la recherche sémantique) pour de nouveaux sujets, vous pourriez probablement générer des embeddings pour les descriptions de tags, puis utiliser la similarité sémantique pour suggérer des tags pour de nouveaux sujets, ou taguer automatiquement les sujets existants.

3 « J'aime »

Oui, je l’ai exécuté pendant quelques jours sur Meta pour faire plus de tests… ça fonctionne à peu près mais sans descriptions de tags, il utilisait mal how-to pour certaines questions du type « comment faire » et appliquait moderator aux publications où le mot était utilisé mais pas entièrement pertinent pour le sujet. Quelques ajustements de prompt aideront probablement, et les idées de @simon semblent être d’excellentes améliorations futures.

2 « J'aime »

Ma supposition est que les embeddings pourraient mieux fonctionner et être plus rapides/moins chers. Mon intérêt réel pour cela se situe davantage dans le cadre d’un système de balisage dynamique qui pourrait remplacer le codage en dur des balises dans la base de données. Par exemple, « comment faire » + « sso » serait une liste de sujets avec des embeddings sémantiquement proches des embeddings de description des balises « comment faire » et « sso ».

2 « J'aime »