Я хотел бы предложить возможность отправки вебхуков AI-артефактами всякий раз, когда обновляются хранящиеся в них данные. Существует множество причин, по которым это было бы выгодно, но я постараюсь изложить свою аргументацию кратко.
Многие организации, использующие Discourse, также применяют различные инструменты автоматизации, которые становятся всё более популярными, простыми в настройке и удобными для начинающих. AI-артефакты могут содержать ценные данные, которые можно очень эффективно использовать в таких автоматизациях, плюс бонус в том, что они уже представлены в формате JSON!
В моём случае я использую n8n в составе Docker Compose рядом с моим контейнером Discourse и уже применяю его для автоматизации широкого спектра задач, включая операции на моих экземплярах Discourse через Docker-сеть. Однако один из экземпляров Discourse, который я поддерживаю, принадлежит образовательной организации/бизнесу, где AI-артефакты используются для отслеживания заметок учащихся, дневников уроков и т.д. Такие данные были бы чрезвычайно полезны для улучшения рабочих процессов преподавателей, создания сводок и прочего через инструменты автоматизации, такие как n8n.
Технически возможно опрашивать артефакты на предмет обновлений, но это может быстро начать нагружать системные ресурсы, а в лучшем случае будет пустой тратой ресурсов, к тому же потребует обширных технических навыков для настройки, которыми обладают не все администраторы.
Излишне говорить, что это также было бы крайне выгодно для корпоративных клиентов Discourse.
Обходным путём могло бы стать обращение JavaScript-кода артефакта к внешнему вебхуку после вызова window.discourseArtifact.set(...).
Однако у этого подхода есть некоторые ограничения:
-
Это происходит на стороне браузера и, следовательно, не является авторитетным источником.
-
Срабатывает только в том случае, если JavaScript артефакта успешно выполняется в браузере пользователя.
-
Может быть затронут CORS, инструментами приватности браузера, сетевыми блокировщиками или ограничениями песочницы.
-
Нагрузку (payload) нельзя считать заслуживающей доверия и исходящей от Discourse, если не реализована дополнительная серверная проверка.
-
Секреты нельзя безопасно встраивать в JavaScript артефакта.
Событие/вебхук на стороне сервера был бы гораздо более надёжным и безопасным.
Я не разработчик на Ruby, поэтому я обсуждал это с GPT-5.5, и он также предложил некоторые интересные идеи…
Основываясь на текущей архитектуре вебхуков в Discourse, чистая реализация, вероятно, не должна заключаться в создании специальной функции «вызов этого URL» внутри AI-артефактов. Это должно быть реализовано как обычный тип события вебхука Discourse, поддерживаемый стандартным внутренним DiscourseEvent.
Оптимальная форма реализации
Я бы предложил разработчикам Discourse реализовать это в два слоя:
-
Внутренние события, генерируемые при изменении записей KV артефакта.
-
Типы событий вебхука, которые подписываются на эти внутренние события и используют существующую систему доставки вебхуков.
Это обеспечит согласованность с остальной частью Discourse. Существующие вебхуки уже работают путём сопоставления внутренних DiscourseEvent с вызовами WebHook.enqueue_* в config/initializers/012-web_hook_events.rb; например, события тем, постов, пользователей, категорий, тегов, reviewable, уведомлений и лайков подключены именно так.
Путь к хранилищу артефактов достаточно прост: ArtifactKeyValuesController#set находит или инициализирует запись ключ-значение, присваивает key/value/public и сохраняет её; destroy находит текущую запись пользователя по ключу и удаляет её. Сама модель — это AiArtifactKeyValue, она принадлежит артефакту и пользователю, имеет поля key, value и public, а также обеспечивает уникальность по артефакту/пользователю/ключу.
Предлагаемые имена событий
Я бы, вероятно, использовал три конкретных события вебхука:
ai_artifact_key_value_created
ai_artifact_key_value_updated
ai_artifact_key_value_deleted
Альтернативно, могло бы подойти одно событие:
ai_artifact_key_value_changed
…но три события лучше соответствуют существующему стилю Discourse. В Discourse уже есть отдельные имена событий вебхука, такие как post_created, post_edited, post_destroyed, calendar_event_created, calendar_event_updated и так далее.
Файлы/классы, которые, скорее всего, будут затронуты
1. Добавление новых типов событий вебхука
WebHookEventType в настоящее время определяет числовые константы, перечисление group и хэш TYPES с именами событий и их ID.
Они могли бы добавить что-то вроде:
AI_ARTIFACT = 20
enum :group,
{
# существующие группы...
ai_artifact: 18,
},
scopes: false
TYPES = {
# существующие типы...
ai_artifact_key_value_created: 2001,
ai_artifact_key_value_updated: 2002,
ai_artifact_key_value_deleted: 2003,
}
Точные ID будут на усмотрение разработчиков Discourse; им просто нужно избегать конфликтов.
Они также добавят записи инициализации (seed entries) в db/fixtures/007_web_hook_event_types.rb, так как существующие типы событий вебхука инициализируются там с ID, именами и группами.
Пример:
WebHookEventType.seed do |b|
b.id = WebHookEventType::TYPES[:ai_artifact_key_value_created]
b.name = "ai_artifact_key_value_created"
b.group = WebHookEventType.groups[:ai_artifact]
end
WebHookEventType.seed do |b|
b.id = WebHookEventType::TYPES[:ai_artifact_key_value_updated]
b.name = "ai_artifact_key_value_updated"
b.group = WebHookEventType.groups[:ai_artifact]
end
WebHookEventType.seed do |b|
b.id = WebHookEventType::TYPES[:ai_artifact_key_value_deleted]
b.name = "ai_artifact_key_value_deleted"
b.group = WebHookEventType.groups[:ai_artifact]
end
Административный интерфейс вебхуков должен затем автоматически подхватить их, так как Admin::WebHooksController#index уже сериализует активные типы событий для UI. Сериализатор типов событий уже экспонирует id, name и group.
2. Скрытие событий, когда Discourse AI отключен
WebHookEventType.active уже скрывает события вебхуков, зависящие от плагинов, когда их функции отключены, например, события solved, assign, голосования за темы, чата и календаря.
Таким образом, они могли бы добавить:
unless defined?(SiteSetting.discourse_ai_enabled) && SiteSetting.discourse_ai_enabled
ids_to_exclude.concat(
[
TYPES[:ai_artifact_key_value_created],
TYPES[:ai_artifact_key_value_updated],
TYPES[:ai_artifact_key_value_deleted],
],
)
end
Также, возможно, стоит проверить специфичную для артефактов настройку, если она существует или будет добавлена.
3. Генерация внутренних событий из модели, а не только из контроллера
Хотя текущий путь записи основан на контроллере, более надёжным местом, вероятно, является модель, с использованием обратных вызовов коммита:
class AiArtifactKeyValue < ActiveRecord::Base
after_create_commit :trigger_created_event
after_update_commit :trigger_updated_event
after_destroy_commit :trigger_deleted_event
private
def trigger_created_event
DiscourseEvent.trigger(:ai_artifact_key_value_created, self)
end
def trigger_updated_event
return if previous_changes.slice("value", "public").blank?
DiscourseEvent.trigger(:ai_artifact_key_value_updated, self)
end
def trigger_deleted_event
DiscourseEvent.trigger(:ai_artifact_key_value_deleted, self)
end
end
Обратные вызовы на уровне модели также перехватят будущие пути записи, а не только ArtifactKeyValuesController#set. Часть after_commit важна, потому что доставка вебхуков должна быть поставлена в очередь только после того, как изменение в базе данных безопасно закоммичено.
Один нюанс: вероятно, не следует генерировать событие «updated», когда артефакт вызывает set() с тем же значением и фактически ничего не меняется. Проверка previous_changes позволяет избежать шумных вебхуков.
4. Добавление сериализатора полезной нагрузки вебхука
Вебхуки Discourse генерируют полезную нагрузку через сериализаторы. Общий метод WebHook.enqueue_object_hooks может принимать сериализатор, а WebHook.generate_payload сериализует объект с использованием guard-объекта системного пользователя.
Они могли бы добавить что-то вроде:
class WebHookAiArtifactKeyValueSerializer < ApplicationSerializer
attributes :id,
:ai_artifact_id,
:post_id,
:topic_id,
:user_id,
:key,
:public,
:value_included,
:created_at,
:updated_at
def post_id
object.ai_artifact.post_id
end
def topic_id
object.ai_artifact.post.topic_id
end
def value_included
false
end
end
Я настоятельно рекомендую не включать value по умолчанию. Данные KV артефакта могут быть приватными для каждого пользователя, и у модели есть флаг public. Если Discourse захочет поддерживать значения, они могли бы сделать это явно:
include_value: false
include_public_values: true
include_private_values: false
Но безопасный вариант v1, вероятно, должен полностью опускать значения.
5. Подключение внутренних событий к доставке вебхуков
Они могли бы добавить обработчики в config/initializers/012-web_hook_events.rb, повторяя существующие паттерны.
Что-то вроде:
%i[
ai_artifact_key_value_created
ai_artifact_key_value_updated
ai_artifact_key_value_deleted
].each do |event|
DiscourseEvent.on(event) do |key_value|
artifact = key_value.ai_artifact
post = artifact.post
topic = post.topic
payload =
WebHook.generate_payload(
:ai_artifact_key_value,
key_value,
WebHookAiArtifactKeyValueSerializer
)
WebHook.enqueue_hooks(
:ai_artifact_key_value,
event,
id: key_value.id,
category_id: topic&.category_id,
tag_ids: topic&.tags&.pluck(:id),
payload: payload
)
end
end
Это позволило бы переиспользовать существующий конвейер задач вебхуков. WebHook.enqueue_hooks находит активные вебхуки для события и ставит в очередь Jobs::EmitWebHookEvent. Задача уже обрабатывает доставку, повторные попытки, логирование, заголовки, подписи и видимость для администраторов.
Включение category_id и tag_ids — это хороший штрих, так как существующие задачи вебхуков могут фильтровать по категории и тегу. Задача вебхука уже проверяет ограничения по категории и тегу перед отправкой. Поскольку AI-артефакт принадлежит посту, а модель связывает артефакт с постом, вывод контекста категории/темы должен быть возможен.
Как может выглядеть доставляемый вебхук
Поскольку тело вебхука Discourse использует event_type как корневой элемент верхнего уровня, это, вероятно, будет выглядеть так:
{
"ai_artifact_key_value": {
"id": 456,
"ai_artifact_id": 123,
"post_id": 789,
"topic_id": 321,
"user_id": 42,
"key": "score",
"public": true,
"value_included": false,
"created_at": "2026-05-26T12:00:00Z",
"updated_at": "2026-05-26T12:05:00Z"
}
}
Имя события будет в заголовках, что соответствует существующей доставке вебхуков:
X-Discourse-Event-Type: ai_artifact_key_value
X-Discourse-Event: ai_artifact_key_value_updated
X-Discourse-Event-Signature: sha256=...
Эти заголовки соответствуют тому, как EmitWebHookEvent формирует заголовки вебхуков сегодня.
Разработчикам, вероятно, придется решить следующие вопросы:
Должны ли включаться значения?
Мой голос: нет по умолчанию. Возможно, разрешить только публичные значения или сделать включение значений настройкой администратора. Приватные данные артефакта для конкретного пользователя не должны незаметно покидать сайт.
Должно быть одно событие или три?
Три события чище для подписчиков вебхуков. Одно событие с полем action проще внутренне. Существующий стиль Discourse склоняется к использованию нескольких имён событий.
Должно ли применяться фильтрация по категории/тегу?
Я думаю, да. Артефакт прикреплен к посту/теме, поэтому фильтры по категории и тегу имеют смысл.
Должна ли эта функция быть реализована в ядре Discourse или в плагине Discourse AI?
Модель данных находится в плагине Discourse AI, но реестр событий вебхука — в ядре. Поскольку связанные плагины, такие как solved/chat/calendar, уже имеют типы событий вебхука в общем списке WebHookEventType, Discourse, возможно, не против добавить события AI-артефактов туда же. Метод active может скрывать их, когда AI отключен.
Должно ли это также генерировать внутреннее событие DiscourseEvent, даже если вебхука не существует?
Да. Это даст авторам плагинов чистую точку подключения даже отдельно от вебхуков.
Сложность
Я бы охарактеризовал это как умеренную, но очень ограниченную.
Вероятный объём работы:
-
Добавить 3 константы типа события.
-
Добавить 1 группу вебхуков.
-
Добавить 3 записи инициализации.
-
Добавить 1 сериализатор.
-
Добавить 3 обратных вызова модели или триггера событий на уровне сервиса.
-
Добавить обработчики инициализатора вебхука.
-
Добавить спецификации для создания/обновления/удаления.
-
Принять решение о конфиденциальности относительно включения
value.
Система доставки, повторные попытки, подписи, лог событий, интерфейс пинга/повторной доставки и конфигурация вебхуков администратора уже существуют. Функция в основном заключается в том, чтобы сделать мутации KV артефакта видимыми для этой существующей системы.