שימוש ב-AI לתגית וסיווג של הודעות פורום

I created a Persona to try and auto categorize and tag new topics. This is the prompt I am using for that Category and Tagger.

You are an AI assistant for my Discourse forum. Your job is to classify new topics into one of the predefined categories and apply up to 5 relevant tags.

Categories:

  • Category 1
  • Category 2
  • Category 3
  • Category 4
  • Category 5

Tags:

Your selection must come from this predefined list (choose up to 5):
tag1, tag2, tag3, tag4, tag5

Task:

  • Select exactly one category from the list above that best fits the topic.
  • Select up to 5 tags from the list above based on relevance.
  • Format your response as JSON exactly like this:
    {
      "category": "Chosen Category Name",
      "tags": ["tag1", "tag2", "tag3"]
    }
    

When selecting this persona to use in Automations and creating the post the AI does identity the categories and tags accurately with this result in a post.

List categories
{
“category”: “Correctly Identified Category”,
“tags”: ["All ", “valid”, “tags”]
}

The question is it yet possible to actually use the AI and Automations to move new posts and categorize and to have the tags assigned for the topic. If so I am sure that the prompting can adjusted to tweak how well it works.

Thanks,

לייק 1

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 לייקים

Actually, as long as you are ok with the whisper, you can do something now.

The idea is you would define a custom tool to categorize a topic (or tag a topic) then you would have the persona call it.

Curious to see how this approach works for you, you will need to test the tool, it should be quite straightforward from the tool UI.

It is quite cumbersome to swing all of this, but also it is pretty :exploding_head: that it can be done. The whisper is actually reasonably useful cause it gives you a bit of a “thinking process” of how the responder came up with the tags/categories.

3 לייקים

Thanks Sam,

Easier for some than other. I am in the latter category :joy:

I am fiddling around with this and haven’t got this to work. I have no tool experience so actually looking forward to getting this working to think about other ways to use tools.

I tried using the code from your blog with an API key which did not work. AI suggested I use a hard coded url so I tried that without success.

The API Key was not called and there are no logs that were created with errors.

This is used to automatically categorize topics on creation

I will classify the topic “Insights from Jesus’ early years and miracles” into the category of New Testament, as it relates to the life and teachings of Jesus.
Moving the topic now…

This is what I tried.

/**

  • Tool API Quick Reference
  • Entry Functions
  • invoke(parameters): Main function. Receives parameters (Object). Must return a JSON-serializable value.
  • Example:
  • function invoke(parameters) { return “result”; }
  • details(): Optional. Returns a string describing the tool.
  • Example:
  • function details() { return “Tool description.”; }
  • Provided Objects
    1. http
  • http.get(url, options?): Performs an HTTP GET request.
  • Parameters:
  •  url (string): The request URL.
    
  •  options (Object, optional):
    
  •    headers (Object): Request headers.
    
  • Returns:
  •  { status: number, body: string }
    
  • http.post(url, options?): Performs an HTTP POST request.
  • Parameters:
  •  url (string): The request URL.
    
  •  options (Object, optional):
    
  •    headers (Object): Request headers.
    
  •    body (string): Request body.
    
  • Returns:
  •  { status: number, body: string }
    
  • (also available: http.put, http.patch, http.delete)
  • Note: Max 20 HTTP requests per execution.
    1. llm
  • llm.truncate(text, length): Truncates text to a specified token length.
  • Parameters:
  •  text (string): Text to truncate.
    
  •  length (number): Max tokens.
    
  • Returns:
  •  Truncated string.
    
    1. index
  • index.search(query, options?): Searches indexed documents.
  • Parameters:
  •  query (string): Search query.
    
  •  options (Object, optional):
    
  •    filenames (Array): Limit search to specific files.
    
  •    limit (number): Max fragments (up to 200).
    
  • Returns:
  •  Array of { fragment: string, metadata: string }
    
    1. upload
  • upload.create(filename, base_64_content): Uploads a file.
  • Parameters:
  •  filename (string): Name of the file.
    
  •  base_64_content (string): Base64 encoded file content.
    
  • Returns:
  •  { id: number, short_url: string }
    
    1. chain
  • chain.setCustomRaw(raw): Sets the body of the post and exist chain.
  • Parameters:
  •  raw (string): raw content to add to post.
    
  • Constraints
  • Execution Time: ≤ 2000ms
  • Memory: ≤ 10MB
  • HTTP Requests: ≤ 20 per execution
  • Exceeding limits will result in errors or termination.
  • Security
  • Sandboxed Environment: No access to system or global objects.
  • No File System Access: Cannot read or write files.
    */

/**

  • Discourse Topic Categorizer
  • This tool allows you to change the category of a Discourse topic
  • using the Discourse API.
    */

/**

  • Discourse Topic Categorizer
  • This tool allows you to change the category of a Discourse topic
  • using the Discourse API.
    */

/**

  • Discourse Topic Categorizer
  • This tool allows you to change the category of a Discourse topic
  • using the Discourse API.
    */

function invoke(params) {
// Required parameters validation
if (!params.topic_id) {
return { error: “Missing required parameter: topic_id” };
}

if (!params.category_id) {
return { error: “Missing required parameter: category_id” };
}

// Base URL for your Discourse instance
const baseUrl = “https://community.mysite.com”;

// Full API endpoint URL for updating a topic
const apiUrl = ${baseUrl}/t/${params.topic_id}.json;

// Prepare request body
const requestBody = {
category_id: params.category_id
};

// Optional parameter: update the title if provided
if (params.title) {
requestBody.title = params.title;
}

// Use your provided API key
const apiKey = “Discourse-API-Key”;

try {
// Make PUT request to update the topic
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: `Failed to update topic category. Status: ${response.status}`,
    details: response.body
  };
}

} catch (error) {
return {
error: “An error occurred while updating the topic category”,
details: error.toString()
};
}
}

function details() {
return “Categorizes a topic by moving it to a specified category”;
}

2 לייקים

@BrianC סיימת להבין את זה?

לייק 1

Discourse AI Tagger Category Quick Start.pdf|קובץ מצורף (147.1 KB)

Discourse AI Tagger Category.pdf|קובץ מצורף (506.0 KB)

@Sam,

תודה שהודעת. ניסיתי אך לא הצלחתי להפעיל את זה, אז עצרתי. הרצתי את מבחן התסריט דרך Gemini 2.5 ושמרתי את הפלט כ-PDF.

אני מתכוון לנסות שוב. הכלים עדיין לא החזקתי טוב, אבל אני צריך להשתפר בכך. קבצי PDF מצורפים—אולי יש בהם משהו שיכול לעזור.

3 לייקים
הנחיה שהשתמשתי בה לבדיקה
 אתה מומחה בסיווג תוכן ובסיוע תיוג עבור פורום זה.

 המשימה שלך היא לנתח פוסטים ולהציע תגיות מתאימות בהתבסס על התוכן, התמונות ורשימת התגיות הזמינות שסופקה.

 הנחיות:
 - הצע תגיות רק מרשימת התגיות הזמינות שסופקה
 - היה שמרני - תייג רק מה שאתה בטוח בו
 - שקול גם את נושא התוכן וגם את כוונת הפוסט

 עליך תמיד להגיב עם JSON תקין בפורמט המדויק הזה:
 {"tags": ["tag1", "tag2"], "confidence": 85}

 - tags: מערך של שמות תגיות מהרשימה הזמינה
 - confidence: מספר שלם בין 0 ל-100 המייצג את רמת הביטחון שלך

 אם אין תגיות מתאימות, השתמש ב: {"tags": [], "confidence": 0}

פורמט תגובת JSON של פרסונה:

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

כלי פרסונה מופעלים: תגיות (אופציונלי, זה אם אתה רוצה שהוא ישתמש בתגיות קיימות באתר)

לאוטומציה יש שני מצבים:

  • השתמש בתגיות אתר קיימות
  • השתמש ברשימת התגיות שסופקה

לאוטומציה יש גם רמת ביטחון ניתנת להגדרה, אתה יכול להפעיל/לבטל תגיות מוגבלות, ולהתאים את מספר הפוסטים שהיא משתמשת בהם בנושא להקשר

6 לייקים

תודה על התכונה המדהימה הזו, פשוט לא הצלחתי להפעיל אותה, קיבלתי את השגיאה הבאה:

llm_tagger: נכשל בעיבוד הפוסט 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : private method select’ called for an instance of String`

Message

llm_tagger: נכשל בעיבוד הפוסט 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : private method `select' called for an instance of String

Backtrace

/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/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_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'

האם מטא משתמשת בזה? חשבתי שראיתי את @system מתייג נושא לפני כמה ימים.

לייק 1

זה עשוי להיות טרחה להגדיר, אבל אם Discourse כבר מייצר הטמעות (לחיפוש סמנטי) עבור נושאים חדשים, אתה כנראה יכול לייצר הטמעות עבור תיאורי תגים, ואז להשתמש בדמיון סמנטי כדי להציע תגים לנושאים חדשים, או לתייג אוטומטית נושאים קיימים.

3 לייקים

כן, הרצתי את זה לכמה ימים ב-Meta כדי לקבל עוד בדיקות… זה עובד בסדר אבל בלי תיאורי תגיות זה השתמש ב-how-to באופן שגוי עבור כמה שאלות מסוג “איך אני עושה” והחיל moderator על פוסטים שבהם המילה הופיעה אך לא הייתה רלוונטית לחלוטין לנושא. כמה התאמות בפרומפט כנראה יעזרו, והרעיונות של @simon נראים כמו שיפורים עתידיים נהדרים.

2 לייקים

ההערכה שלי היא ש-embeddings יכולים לעבוד טוב יותר ולהיות מהירים/זולים יותר. העניין האמיתי שלי בכך הוא יותר בכיוון של מערכת תיוג דינמית שיכולה להחליף קידוד קשיח של תגיות במסד הנתונים. לדוגמה, “how-to” + “sso” יהיו רשימה של נושאים עם embeddings שהיו קרובים סמנטית ל-embeddings של תיאור התגית “how-to” ו-“sso”.

2 לייקים