# frozen_string_literal: true if defined?(DiscourseAutomation) module DiscourseAutomation::LlmTriage def self.handle( post:, model:, search_for_text:, system_prompt:, category_id: nil, tags: nil, canned_reply: nil, canned_reply_user: nil, hide_topic: nil ) if category_id.blank? && tags.blank? && canned_reply.blank? && hide_topic.blank? raise ArgumentError, "llm_triage: no action specified!" end post_template = +"" post_template << "title: #{post.topic.title}\n" post_template << "#{post.raw}" filled_system_prompt = system_prompt.sub("%%POST%%", post_template) if filled_system_prompt == system_prompt raise ArgumentError, "llm_triage: system_prompt does not contain %%POST%% placeholder" end result = DiscourseAi::Inference::OpenAiCompletions.perform!( [{ :role => "system", "content" => filled_system_prompt }], model, temperature: 0.7, top_p: 0.9, max_tokens: 40, ).dig(:choices, 0, :message, :content) if result.strip == search_for_text.strip user = User.find_by_username(canned_reply_user) if canned_reply_user.present? user = user || Discourse.system_user if canned_reply.present? PostCreator.create!( user, topic_id: post.topic_id, raw: canned_reply, reply_to_post_number: post.post_number, skip_validations: true, ) end changes = {} changes[:category_id] = category_id if category_id.present? changes[:tags] = tags if SiteSetting.tagging_enabled? && tags.present? if changes.present? first_post = post.topic.posts.where(post_number: 1).first changes[:bypass_bump] = true changes[:skip_validations] = true first_post.revise(Discourse.system_user, changes) end post.topic.update!(visible: false) if hide_topic end end end DiscourseAutomation::Scriptable::LLM_TRIAGE = "llm_triage" AVAILABLE_MODELS = [ { id: "gpt-4", name: "discourse_automation.scriptables.#{DiscourseAutomation::Scriptable::LLM_TRIAGE}.models.gpt_4", }, { id: "gpt-3-5-turbo", name: "discourse_automation.scriptables.#{DiscourseAutomation::Scriptable::LLM_TRIAGE}.models.gpt_3_5_turbo", }, { id: "claude-2", name: "discourse_automation.scriptables.#{DiscourseAutomation::Scriptable::LLM_TRIAGE}.models.claude_2", }, ] DiscourseAutomation::Scriptable.add(DiscourseAutomation::Scriptable::LLM_TRIAGE) do version 1 run_in_background placeholder :post triggerables %i[post_created_edited] field :system_prompt, component: :message, required: true, validator: ->(input) { if !input.include?("%%POST%%") I18n.t( "discourse_automation.scriptables.#{DiscourseAutomation::Scriptable::LLM_TRIAGE}.system_prompt_missing_post_placeholder", ) end }, accepts_placeholders: true field :search_for_text, component: :text, required: true field :model, component: :choices, required: true, extra: { content: AVAILABLE_MODELS } field :category, component: :category field :tags, component: :tags field :hide_topic, component: :boolean field :canned_reply, component: :message field :canned_reply_user, component: :user script do |context, fields, automation| post = context["post"] system_prompt = fields["system_prompt"]["value"] search_for_text = fields["search_for_text"]["value"] model = fields["model"]["value"] if !%w[gpt-4 gpt-3-5-turbo].include?(model) Rails.logger.warn("llm_triage: model #{model} is not supported") next end category_id = fields.dig("category", "value") tags = fields.dig("tags", "value") hide_topic = fields.dig("hide_topic", "value") canned_reply = fields.dig("canned_reply", "value") canned_reply_user = fields.dig("canned_reply_user", "value") if post.raw.strip == canned_reply.to_s.strip # nothing to do if we already replied next end begin DiscourseAutomation::LlmTriage.handle( post: post, model: model, search_for_text: search_for_text, system_prompt: system_prompt, category_id: category_id, tags: tags, canned_reply: canned_reply, canned_reply_user: canned_reply_user, hide_topic: hide_topic, ) rescue => e Discourse.warn_exception(e, message: "llm_triage: failed to run inference") end end end end