使用 AI 来标记和分类论坛帖子

我创建了一个 Persona 来尝试自动分类和标记新主题。这是我用于该类别和标记器的提示。

你是我 Discourse 论坛的 AI 助手。你的工作是将新主题分类到一个预定义的类别中,并应用最多 5 个相关的标签。

类别:

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

标签:

你的选择必须来自这个预定义的列表(最多选择 5 个):
tag1, tag2, tag3, tag4, tag5

任务:

  • 从上面的列表中选择正好一个类别,最适合该主题。
  • 根据相关性从上面的列表中选择最多 5 个标签
  • 按照此格式将你的响应格式化为 JSON:
{
  "category": "选择的类别名称",
  "tags": ["tag1", "tag2", "tag3"]
}

当使用此 Persona 在 Automations 中创建帖子时,AI 确实能准确识别类别和标签,并产生以下帖子结果。

列出类别
{
“category”: “正确识别的类别”,
“tags”: [“所有”, “有效”, “标签”]
}

问题是,是否可以实际使用 AI 和 Automations 来移动新帖子并进行分类,以及为主题分配标签。如果可以,我相信提示可以进行调整以优化其效果。

谢谢。

1 个赞

正在进行中……有点棘手,我们还缺少两件小东西:

  1. 自定义工具现在支持很多功能,但不支持分类和标记,我们可以轻松添加这些功能。
  2. 我需要一个可以“静默”模式运行的响应器,这样它就不会实际响应主题。

一旦这两项都到位,您将可以访问两个自定义工具:

  1. 标记主题
  2. 分类主题

(或者一个同时执行这两个功能的工具)

5 个赞

实际上,只要你对whisper(语音转文本)没意见,现在就可以做些事情了。

这个想法是,你可以定义一个自定义工具来对一个主题进行分类(或标记一个主题),然后让这个角色(persona)调用它。

我很想看看这种方法对你来说效果如何,你需要测试一下这个工具,从工具界面来看应该相当直接。

要实现这一切相当麻烦,但它也能做到,这真是太 :exploding_head: 了。whisper(语音转文本)实际上相当有用,因为它能让你大致了解响应者是如何想出标签/类别的“思考过程”。

3 个赞

谢谢 Sam,

对一些人来说比对另一些人容易。我属于后者 :joy:

我正在研究这个问题,但还没有成功。我没有任何工具使用经验,所以实际上很期待能让它工作起来,然后考虑其他使用工具的方法。

我尝试使用你博客中的代码,但没有 API 密钥,也没有成功。AI 建议我使用硬编码的 URL,所以我尝试了,但也没有成功。

API 密钥没有被调用,也没有创建任何日志来显示错误。

这用于在创建时自动对主题进行分类

我将把主题“耶稣早年和奇迹的见解”归类到“新约”类别,因为它与耶稣的生平和教导有关。
正在移动主题…

这是我尝试过的。

/**

  • 工具 API 快速参考
  • 入口函数
  • invoke(parameters): 主函数。接收参数 (Object)。必须返回一个 JSON 可序列化的值。
  • 示例:
  • function invoke(parameters) { return “result”; }
  • details(): 可选。返回一个描述工具的字符串。
  • 示例:
  • function details() { return “工具描述。”; }
  • 提供的对象
    1. http
  • http.get(url, options?): 执行 HTTP GET 请求。
  • 参数:
  •  url (string): 请求 URL。
    
  •  options (Object, optional):
    
  •    headers (Object): 请求头。
    
  • 返回:
  •  { status: number, body: string }
    
  • http.post(url, options?): 执行 HTTP POST 请求。
  • 参数:
  •  url (string): 请求 URL。
    
  •  options (Object, optional):
    
  •    headers (Object): 请求头。
    
  •    body (string): 请求体。
    
  • 返回:
  •  { status: number, body: string }
    
  • (还可用:http.put, http.patch, http.delete)
  • 注意:每次执行最多 20 个 HTTP 请求。
    1. llm
  • llm.truncate(text, length): 将文本截断到指定的令牌长度。
  • 参数:
  •  text (string): 要截断的文本。
    
  •  length (number): 最大令牌数。
    
  • 返回:
  •  截断后的字符串。
    
    1. index
  • index.search(query, options?): 搜索索引文档。
  • 参数:
  •  query (string): 搜索查询。
    
  •  options (Object, optional):
    
  •    filenames (Array): 将搜索限制在特定文件。
    
  •    limit (number): 最大片段数 (最多 200)。
    
  • 返回:
  •  { fragment: string, metadata: string } 数组
    
    1. upload
  • upload.create(filename, base_64_content): 上传文件。
  • 参数:
  •  filename (string): 文件名。
    
  •  base_64_content (string): Base64 编码的文件内容。
    
  • 返回:
  •  { id: number, short_url: string }
    
    1. chain
  • chain.setCustomRaw(raw): 设置帖子的正文并退出链。
  • 参数:
  •  raw (string): 要添加到帖子的原始内容。
    
  • 约束
  • 执行时间:≤ 2000ms
  • 内存:≤ 10MB
  • HTTP 请求:每次执行 ≤ 20 次
  • 超过限制将导致错误或终止。
  • 安全
  • 沙盒环境:无法访问系统或全局对象。
  • 无文件系统访问:无法读取或写入文件。
    */

/**

  • Discourse 主题分类器
  • 此工具允许您使用 Discourse API 更改 Discourse 主题的类别。
    */

/**

  • Discourse 主题分类器
  • 此工具允许您使用 Discourse API 更改 Discourse 主题的类别。
    */

/**

  • Discourse 主题分类器
  • 此工具允许您使用 Discourse API 更改 Discourse 主题的类别。
    */

function invoke(params) {
// 必需参数验证
if (!params.topic_id) {
return { error: “缺少必需参数: topic_id” };
}

if (!params.category_id) {
return { error: “缺少必需参数: category_id” };
}

// 您的 Discourse 实例的基础 URL
const baseUrl = “https://community.mysite.com”;

// 更新主题的完整 API 端点 URL
const apiUrl = ${baseUrl}/t/${params.topic_id}.json;

// 准备请求体
const requestBody = {
category_id: params.category_id
};

// 可选参数:如果提供了标题,则更新标题
if (params.title) {
requestBody.title = params.title;
}

// 使用您提供的 API 密钥
const apiKey = “Discourse-API-Key”;

try {
// 发送 PUT 请求以更新主题
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: `更新主题类别失败。状态码:${response.status}`,
    details: response.body
  };
}

} catch (error) {
return {
error: “更新主题类别时发生错误”,
details: error.toString()
};
}
}

function details() {
return “通过将主题移动到指定类别来对其进行分类”;
}

2 个赞

@BrianC 你最终搞清楚了吗?

1 个赞

Discourse AI Tagger 分类快速入门.pdf|附件 (147.1 KB)

Discourse AI Tagger 分类.pdf|附件 (506.0 KB)

@Sam

感谢您的询问。我尝试了一下,但未能成功,所以暂停了。我使用 Gemini 2.5 进行了场景测试并将结果保存为PDF。

我打算再次尝试。工具还不是我的强项,但我需要更熟练地使用它们。附件中是PDF——也许里面有一些有用的信息。

3 个赞

我们添加了一个您可能感兴趣的 AI 标记自动化功能:FEATURE: create AI tagging automation (#34587) · discourse/discourse@e470f3d · GitHub

我用来测试它的提示
您是本论坛的专家内容分类器和标记助手。

您的任务是根据内容、图像和提供的可用标签列表来分析帖子并建议适当的标签。

指南:
- 仅建议来自提供的可用标签列表的标签
- 保持保守 - 只标记您有信心的内容
- 同时考虑内容主题和帖子意图

您必须始终以这种确切的格式响应有效的 JSON:
{"tags": ["tag1", "tag2"], "confidence": 85}

- tags:来自可用列表的标签名称数组
- confidence:表示您置信度的 0 到 100 的整数

如果没有合适的标签,请使用:{"tags": [], "confidence": 0}

Persona JSON 响应格式:

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

Persona 已启用工具:tags(可选,如果您希望它使用站点上现有的标签)
该自动化有两种模式:

  • 使用现有的站点标签
  • 使用提供的标签列表
    该自动化还具有可配置的置信度级别,您可以启用/禁用受限标签,并调整它在主题中使用的帖子数量以获取上下文
6 个赞

感谢您提供此出色的功能,我只是无法运行它,收到了以下错误:

llm_tagger: failed to process post 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : private method select’ called for an instance of String`

Message

llm_tagger: failed to process post 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/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 使用它吗?我以为几天前我看到 @system 标记了一个主题。

1 个赞

设置起来可能有点麻烦,但如果 Discourse 已经在为新主题生成嵌入(用于语义搜索),那么您或许可以为标签描述生成嵌入,然后使用语义相似性来为新主题建议标签,或者自动标记现有主题。

3 个赞

是的,我在 Meta 上运行了几次,以获得更多测试……它运行得还可以,但如果没有标签描述,它会将“how-to”误用于一些“how do I”类型的问题,并且会将“moderator”应用于包含该词但与主题不完全相关的帖子。一些提示调整可能会有帮助,@simon 的想法似乎是未来可以进行的很棒的增强。

2 个赞

我的猜测是,嵌入式可能效果更好,速度更快/成本更低。我对此的真正兴趣在于一个动态标签系统,可以取代将标签硬编码到数据库中。例如,“how-to”+“sso”将是主题列表,其嵌入式在语义上接近“how-to”和“sso”标签描述的嵌入式。

2 个赞