From c294b6d39494a00c18a4b734c441cda027d9854c Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 4 Oct 2024 15:11:30 +0900 Subject: [PATCH] FEATURE: allow llm triage to automatically hide posts (#820) Previous to this change we could flag, but there was no way to hide content and treat the flag as spam. We had the option to hide topics, but this is not desirable for a spam reply. New option allows triage to hide a post if it is a reply, if the post happens to be the first post on the topic, the topic will be hidden. --- config/locales/client.en.yml | 7 +++-- config/locales/server.en.yml | 4 +++ discourse_automation/llm_triage.rb | 9 ++++++ lib/automation.rb | 6 ++++ lib/automation/llm_triage.rb | 29 +++++++++++++------ .../lib/modules/automation/llm_triage_spec.rb | 18 ++++++++++++ 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d5b555fc..7b6d7f35 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -99,9 +99,12 @@ en: hide_topic: label: "Hide topic" description: "Make topic non visible to the public if triggered" + flag_type: + label: "Flag type" + description: "Type of flag to apply to the post (spam or simply raise for review)" flag_post: - label: "Send to review" - description: "Puts the post into the review queue if triggered, for moderators to triage" + label: "Flag post" + description: "Flags post (either as spam or for review)" model: label: "Model" description: "Language model used for triage" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 76a83e33..c42deb03 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1,5 +1,9 @@ en: discourse_automation: + ai: + flag_types: + review: "Add post to review queue" + spam: "Flag as spam and hide post" scriptables: llm_triage: title: Triage posts using AI diff --git a/discourse_automation/llm_triage.rb b/discourse_automation/llm_triage.rb index b8b52fb4..ad90d361 100644 --- a/discourse_automation/llm_triage.rb +++ b/discourse_automation/llm_triage.rb @@ -21,6 +21,13 @@ if defined?(DiscourseAutomation) field :tags, component: :tags field :hide_topic, component: :boolean field :flag_post, component: :boolean + field :flag_type, + component: :choices, + required: false, + extra: { + content: DiscourseAi::Automation.flag_types, + }, + default: "review" field :canned_reply, component: :message field :canned_reply_user, component: :user @@ -41,6 +48,7 @@ if defined?(DiscourseAutomation) tags = fields.dig("tags", "value") hide_topic = fields.dig("hide_topic", "value") flag_post = fields.dig("flag_post", "value") + flag_type = fields.dig("flag_type", "value") begin RateLimiter.new( @@ -68,6 +76,7 @@ if defined?(DiscourseAutomation) canned_reply_user: canned_reply_user, hide_topic: hide_topic, flag_post: flag_post, + flag_type: flag_type.to_s.to_sym, automation: self.automation, ) rescue => e diff --git a/lib/automation.rb b/lib/automation.rb index 590bbbe2..e6ed22a2 100644 --- a/lib/automation.rb +++ b/lib/automation.rb @@ -2,6 +2,12 @@ module DiscourseAi module Automation + def self.flag_types + [ + { id: "review", translated_name: I18n.t("discourse_automation.ai.flag_types.review") }, + { id: "spam", translated_name: I18n.t("discourse_automation.ai.flag_types.spam") }, + ] + end def self.available_models values = DB.query_hash(<<~SQL) SELECT display_name AS translated_name, id AS id diff --git a/lib/automation/llm_triage.rb b/lib/automation/llm_triage.rb index 9e7c57b9..103119e7 100644 --- a/lib/automation/llm_triage.rb +++ b/lib/automation/llm_triage.rb @@ -14,6 +14,7 @@ module DiscourseAi canned_reply_user: nil, hide_topic: nil, flag_post: nil, + flag_type: nil, automation: nil ) if category_id.blank? && tags.blank? && canned_reply.blank? && hide_topic.blank? && @@ -65,9 +66,6 @@ module DiscourseAi post.topic.update!(visible: false) if hide_topic if flag_post - reviewable = - ReviewablePost.needs_review!(target: post, created_by: Discourse.system_user) - score_reason = I18n .t("discourse_automation.scriptables.llm_triage.flagged_post") @@ -75,12 +73,25 @@ module DiscourseAi .sub("%%AUTOMATION_ID%%", automation&.id.to_s) .sub("%%AUTOMATION_NAME%%", automation&.name.to_s) - reviewable.add_score( - Discourse.system_user, - ReviewableScore.types[:needs_approval], - reason: score_reason, - force_review: true, - ) + if flag_type == :spam + PostActionCreator.new( + Discourse.system_user, + post, + PostActionType.types[:spam], + message: score_reason, + queue_for_review: true, + ).perform + else + reviewable = + ReviewablePost.needs_review!(target: post, created_by: Discourse.system_user) + + reviewable.add_score( + Discourse.system_user, + ReviewableScore.types[:needs_approval], + reason: score_reason, + force_review: true, + ) + end end end end diff --git a/spec/lib/modules/automation/llm_triage_spec.rb b/spec/lib/modules/automation/llm_triage_spec.rb index 36bc5f32..5fa76309 100644 --- a/spec/lib/modules/automation/llm_triage_spec.rb +++ b/spec/lib/modules/automation/llm_triage_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true describe DiscourseAi::Automation::LlmTriage do fab!(:post) + fab!(:reply) { Fabricate(:post, topic: post.topic, user: Fabricate(:user)) } fab!(:llm_model) def triage(**args) @@ -92,6 +93,23 @@ describe DiscourseAi::Automation::LlmTriage do expect(reviewable.reviewable_scores.first.reason).to include("bad") end + it "can handle spam flags" do + DiscourseAi::Completions::Llm.with_prepared_responses(["bad"]) do + triage( + post: post, + model: "custom:#{llm_model.id}", + system_prompt: "test %%POST%%", + search_for_text: "bad", + flag_post: true, + flag_type: :spam, + automation: nil, + ) + end + + expect(post.reload).to be_hidden + expect(post.topic.reload.visible).to eq(false) + end + it "can handle garbled output from LLM" do DiscourseAi::Completions::Llm.with_prepared_responses(["Bad.\n\nYo"]) do triage(