Использование ИИ для тегирования и категоризации постов на форуме

Я создал Persona, чтобы попытаться автоматически категоризировать и добавлять теги к новым темам. Вот промпт, который я использую для этой категории и теггера:

Ты — ИИ-ассистент моего форума Discourse. Твоя задача — классифицировать новые темы по одной из предопределённых категорий и применить до 5 релевантных тегов.

Категории:

  • Категория 1
  • Категория 2
  • Категория 3
  • Категория 4
  • Категория 5

Теги:

Твой выбор должен быть из этого предопределённого списка (выбери до 5):
tag1, tag2, tag3, tag4, tag5

Задача:

  • Выбери ровно одну категорию из списка выше, которая лучше всего подходит к теме.
  • Выбери до 5 тегов из списка выше в зависимости от релевантности.
  • Отформатируй свой ответ в JSON точно так:
    {
      "category": "Выбранное название категории",
      "tags": ["tag1", "tag2", "tag3"]
    }
    

При выборе этой персоны для использования в автоматизациях и создании поста ИИ корректно определяет категории и теги, что отражается в результате поста.

Список категорий
{
“category”: “Правильно определённая категория”,
“tags”: ["Все ", “валидные”, “теги”]
}

Вопрос: возможно ли на самом деле использовать ИИ и автоматизации для перемещения новых постов, их категоризации и назначения тегов для темы? Если да, то я уверен, что промпт можно настроить, чтобы улучшить результат.

Спасибо,

В процессе реализации… это немного сложно, так как нам не хватает двух небольших элементов:

  1. Пользовательские инструменты теперь поддерживают множество функций, но не поддерживают категоризацию и тегирование. Мы легко можем добавить эту возможность.
  2. Мне нужен ответчик, работающий в «тихом» режиме, чтобы он не отвечал на тему напрямую.

После того как оба эти элемента будут реализованы, вы предоставите доступ к двум пользовательским инструментам:

  1. Добавить тег к теме
  2. Категоризировать тему

(или к одному инструменту, который выполняет обе функции)

На самом деле, если вас устраивает функция шепота, вы уже можете что-то сделать.

Идея в том, что вы определяете пользовательский инструмент для категоризации темы (или добавления ей тега), а затем настраиваете персонажа на вызов этого инструмента.

Мне интересно посмотреть, как этот подход сработает для вас. Вам нужно будет протестировать инструмент — это должно быть довольно просто через интерфейс инструмента.

Всё это довольно громоздко в настройке, но при этом просто :exploding_head:, что это вообще возможно. Функция шепота на самом деле довольно полезна, так как она даёт представление о «процессе мышления», который привёл ответившего к тем или иным тегам или категориям.

Спасибо, Сэм,

Кому-то проще, кому-то сложнее. Я отношусь к последней категории :joy:

Я пытаюсь разобраться с этим, но у меня пока не получается. У меня нет опыта работы с инструментами, поэтому я с нетерпением жду, когда у меня всё заработает, чтобы подумать о других способах их использования.

Я пробовал использовать код из вашего блога с API-ключом, но это не сработало. ИИ предложил мне использовать жёстко заданный URL, я попробовал, но тоже без успеха.

API-ключ не вызывался, и не было создано никаких логов с ошибками.

Это используется для автоматической категоризации тем при создании

Я классифицирую тему «Уроки из ранних лет Иисуса и его чудеса» в категорию Новый Завет, так как она связана с жизнью и учением Иисуса.
Перемещаю тему сейчас…

Вот что я пробовал.

/**

  • Справочник по API инструментов
  • Входные функции
  • invoke(parameters): Основная функция. Принимает параметры (Объект). Должна возвращать значение, сериализуемое в 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): Сырой контент для добавления в сообщение.
    
  • Ограничения
  • Время выполнения: ≤ 2000 мс
  • Память: ≤ 10 МБ
  • 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” };
}

// Базовый URL вашего экземпляра Discourse
const baseUrl = “https://community.mysite.com”;

// Полный URL API для обновления темы
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 “Классифицирует тему, перемещая её в указанную категорию”;
}

@BrianC, ты в итоге разобрался с этим?

Discourse AI Tagger Category Quick Start.pdf (147,1 КБ)

Discourse AI Tagger Category.pdf (506,0 КБ)

@Sam,

Спасибо, что проверил. Я попытался, но у меня не получилось запустить, поэтому я приостановил работу. Я прогнал тестовый сценарий через Gemini 2.5 и сохранил вывод в формате PDF.

Я попробую ещё раз. Работа с инструментами пока не моя сильная сторона, но мне нужно стать в ней более компетентным. PDF-файлы во вложении — возможно, в них есть что-то полезное.

Мы добавили автоматизацию тегирования на основе ИИ, которая может вас заинтересовать: FEATURE: create AI tagging automation (#34587) · discourse/discourse@e470f3d · GitHub

Промпт, который я использую для тестирования
Вы — эксперт-классификатор контента и помощник по тегированию для этого форума.

Ваша задача — анализировать сообщения и предлагать подходящие теги на основе содержания, изображений и предоставленного списка доступных тегов.

Рекомендации:
- Предлагайте только теги из предоставленного списка доступных тегов
- Будьте осторожны — добавляйте теги только в том случае, если вы уверены
- Учитывайте как тему контента, так и цель сообщения

Вы всегда должны отвечать валидным JSON в точном формате:
{"tags": ["tag1", "tag2"], "confidence": 85}

- tags: массив названий тегов из доступного списка
- confidence: целое число от 0 до 100, отражающее уровень вашей уверенности

Если ни один тег не подходит, используйте: {"tags": [], "confidence": 0}

Формат ответа JSON для персонажа:

{
  "tags": "[строка]",
  "confidence": "целое число"
}

Доступные инструменты для персонажа: теги (опционально, если вы хотите, чтобы он использовал существующие теги на сайте)

Автоматизация имеет два режима:

  • Использовать существующие теги сайта
  • Использовать предоставленный список тегов

Также доступна настройка уровня уверенности. Вы можете включать/отключать ограниченные теги и изменять количество сообщений, используемых в теме для контекста.

Спасибо за эту замечательную функцию; однако я не смог её выполнить из-за следующей ошибки:

llm_tagger: не удалось обработать пост 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : приватный метод select’ вызван для экземпляра String`

Сообщение

llm_tagger: не удалось обработать пост 30550 /t/gecici-baslik-1756753838814/17361/1 : NoMethodError : приватный метод `select' вызван для экземпляра String

Трассировка стека

/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:184: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 несколько дней назад пометил тему.

Настройка может оказаться хлопотной, но если Discourse уже генерирует эмбеддинги (для семантического поиска) для новых тем, вы, вероятно, сможете генерировать эмбеддинги для описаний тегов, а затем использовать семантическое сходство для предложения тегов новым темам или автоматической разметки существующих тем.

Да, я запускал его несколько дней в Meta, чтобы провести дополнительное тестирование… работает нормально, но без описаний тегов он ошибочно использовал how-to для некоторых вопросов типа «как мне…» и применял moderation к постам, где упоминалось это слово, хотя модерация не всегда была полностью уместна для темы. Несколько корректировок в промптах, вероятно, помогут, а идеи @simon кажутся отличными вариантами для будущих улучшений.

Мне кажется, что эмбеддинги могут работать лучше и быть быстрее/дешевле. Мой реальный интерес к этому заключается скорее в создании динамической системы тегирования, которая могла бы заменить жесткое кодирование тегов в базу данных. Например, комбинация «how-to» + «sso» давала бы список тем, чьи эмбеддинги семантически близки к эмбеддингам описаний тегов «how-to» и «sso».