From 30242a27e6296b97163ad6a92581bc4429574508 Mon Sep 17 00:00:00 2001 From: Roman Rizzi Date: Mon, 31 Mar 2025 14:42:33 -0300 Subject: [PATCH] REFACTOR: Move personas into its own module. (#1233) This change moves all the personas code into its own module. We want to treat them as a building block features can built on top of, same as `Completions::Llm`. The code to title a message was moved from `Bot` to `Playground`. --- .../admin/ai_personas_controller.rb | 2 +- app/jobs/regular/create_ai_chat_reply.rb | 4 +- app/jobs/regular/create_ai_reply.rb | 8 +- app/jobs/regular/stream_discover_reply.rb | 4 +- app/models/ai_persona.rb | 8 +- app/models/ai_tool.rb | 2 +- .../603_ai_personas.rb} | 6 +- lib/ai_bot/entry_point.rb | 5 +- lib/ai_bot/personas/creative.rb | 19 - lib/ai_bot/personas/persona.rb | 397 ------------------ lib/ai_bot/playground.rb | 75 +++- lib/ai_bot/response_http_streamer.rb | 4 +- lib/automation/llm_tool_triage.rb | 2 +- lib/discord/bot/persona_replier.rb | 2 +- lib/discord/bot/search.rb | 2 +- .../artifact_update_strategies/base.rb | 2 +- .../artifact_update_strategies/diff.rb | 2 +- .../artifact_update_strategies/full.rb | 2 +- lib/{ai_bot => }/personas/artist.rb | 22 +- lib/{ai_bot => personas}/bot.rb | 49 +-- lib/{ai_bot => personas}/bot_context.rb | 2 +- lib/personas/creative.rb | 17 + lib/{ai_bot => }/personas/dall_e_3.rb | 22 +- lib/{ai_bot => }/personas/discourse_helper.rb | 16 +- lib/{ai_bot => }/personas/general.rb | 30 +- lib/{ai_bot => }/personas/github_helper.rb | 26 +- lib/personas/persona.rb | 395 +++++++++++++++++ .../question_consolidator.rb | 2 +- lib/{ai_bot => }/personas/researcher.rb | 22 +- .../personas/settings_explorer.rb | 16 +- lib/{ai_bot => }/personas/sql_helper.rb | 67 ++- lib/{ai_bot => personas}/tool_runner.rb | 12 +- .../tools/create_artifact.rb | 2 +- lib/{ai_bot => personas}/tools/custom.rb | 2 +- lib/{ai_bot => personas}/tools/dall_e.rb | 2 +- lib/{ai_bot => personas}/tools/db_schema.rb | 2 +- .../tools/discourse_meta_search.rb | 2 +- .../tools/github_file_content.rb | 2 +- .../tools/github_pull_request_diff.rb | 2 +- .../tools/github_search_code.rb | 2 +- .../tools/github_search_files.rb | 2 +- lib/{ai_bot => personas}/tools/google.rb | 2 +- lib/{ai_bot => personas}/tools/image.rb | 2 +- .../tools/javascript_evaluator.rb | 2 +- .../tools/list_categories.rb | 2 +- lib/{ai_bot => personas}/tools/list_tags.rb | 2 +- lib/{ai_bot => personas}/tools/option.rb | 2 +- .../tools/random_picker.rb | 2 +- lib/{ai_bot => personas}/tools/read.rb | 2 +- .../tools/read_artifact.rb | 2 +- lib/{ai_bot => personas}/tools/search.rb | 2 +- .../tools/search_settings.rb | 2 +- .../tools/setting_context.rb | 2 +- lib/{ai_bot => personas}/tools/summarize.rb | 2 +- lib/{ai_bot => personas}/tools/time.rb | 2 +- lib/{ai_bot => personas}/tools/tool.rb | 8 +- .../tools/update_artifact.rb | 2 +- lib/{ai_bot => personas}/tools/web_browser.rb | 2 +- .../personas/web_artifact_creator.rb | 22 +- plugin.rb | 2 + spec/lib/discord/bot/persona_replier_spec.rb | 2 +- spec/lib/discord/bot/search_spec.rb | 2 +- spec/lib/modules/ai_bot/playground_spec.rb | 24 +- .../artifact_update_strategies/diff_spec.rb | 2 +- .../{modules/ai_bot => personas}/bot_spec.rb | 12 +- .../ai_bot => }/personas/persona_spec.rb | 131 +++--- .../question_consolidator_spec.rb | 2 +- .../ai_bot => }/personas/researcher_spec.rb | 4 +- .../personas/settings_explorer_spec.rb | 4 +- .../ai_bot => }/personas/sql_helper_spec.rb | 4 +- .../tools/create_artifact_spec.rb | 4 +- .../ai_bot => personas}/tools/dall_e_spec.rb | 2 +- .../tools/db_schema_spec.rb | 2 +- .../tools/discourse_meta_search_spec.rb | 2 +- .../tools/github_file_content_spec.rb | 2 +- .../tools/github_pull_request_diff_spec.rb | 2 +- .../tools/github_search_code_spec.rb | 2 +- .../tools/github_search_files_spec.rb | 2 +- .../ai_bot => personas}/tools/google_spec.rb | 2 +- .../ai_bot => personas}/tools/image_spec.rb | 4 +- .../tools/javascript_evaluator_spec.rb | 2 +- .../tools/list_categories_spec.rb | 2 +- .../tools/list_tags_spec.rb | 2 +- .../tools/random_picker_spec.rb | 2 +- .../tools/read_artifact_spec.rb | 12 +- .../ai_bot => personas}/tools/read_spec.rb | 6 +- .../tools/search_settings_spec.rb | 2 +- .../ai_bot => personas}/tools/search_spec.rb | 4 +- .../tools/setting_context_spec.rb | 2 +- .../tools/summarize_spec.rb | 2 +- .../ai_bot => personas}/tools/time_spec.rb | 2 +- .../ai_bot => personas}/tools/tool_spec.rb | 2 +- .../tools/update_artifact_spec.rb | 20 +- .../tools/web_browser_spec.rb | 2 +- spec/models/ai_tool_spec.rb | 2 +- .../admin/ai_personas_controller_spec.rb | 19 +- spec/system/admin_ai_persona_spec.rb | 2 +- spec/system/ai_bot/persona_spec.rb | 2 +- 98 files changed, 781 insertions(+), 843 deletions(-) rename db/fixtures/{ai_bot/603_bot_ai_personas.rb => personas/603_ai_personas.rb} (86%) delete mode 100644 lib/ai_bot/personas/creative.rb delete mode 100644 lib/ai_bot/personas/persona.rb rename lib/{ai_bot => personas}/artifact_update_strategies/base.rb (98%) rename lib/{ai_bot => personas}/artifact_update_strategies/diff.rb (99%) rename lib/{ai_bot => personas}/artifact_update_strategies/full.rb (99%) rename lib/{ai_bot => }/personas/artist.rb (83%) rename lib/{ai_bot => personas}/bot.rb (86%) rename lib/{ai_bot => personas}/bot_context.rb (99%) create mode 100644 lib/personas/creative.rb rename lib/{ai_bot => }/personas/dall_e_3.rb (83%) rename lib/{ai_bot => }/personas/discourse_helper.rb (90%) rename lib/{ai_bot => }/personas/general.rb (62%) rename lib/{ai_bot => }/personas/github_helper.rb (60%) create mode 100644 lib/personas/persona.rb rename lib/{ai_bot => personas}/question_consolidator.rb (99%) rename lib/{ai_bot => }/personas/researcher.rb (88%) rename lib/{ai_bot => }/personas/settings_explorer.rb (71%) rename lib/{ai_bot => }/personas/sql_helper.rb (72%) rename lib/{ai_bot => personas}/tool_runner.rb (97%) rename lib/{ai_bot => personas}/tools/create_artifact.rb (99%) rename lib/{ai_bot => personas}/tools/custom.rb (98%) rename lib/{ai_bot => personas}/tools/dall_e.rb (99%) rename lib/{ai_bot => personas}/tools/db_schema.rb (98%) rename lib/{ai_bot => personas}/tools/discourse_meta_search.rb (99%) rename lib/{ai_bot => personas}/tools/github_file_content.rb (99%) rename lib/{ai_bot => personas}/tools/github_pull_request_diff.rb (99%) rename lib/{ai_bot => personas}/tools/github_search_code.rb (99%) rename lib/{ai_bot => personas}/tools/github_search_files.rb (99%) rename lib/{ai_bot => personas}/tools/google.rb (99%) rename lib/{ai_bot => personas}/tools/image.rb (99%) rename lib/{ai_bot => personas}/tools/javascript_evaluator.rb (99%) rename lib/{ai_bot => personas}/tools/list_categories.rb (98%) rename lib/{ai_bot => personas}/tools/list_tags.rb (97%) rename lib/{ai_bot => personas}/tools/option.rb (97%) rename lib/{ai_bot => personas}/tools/random_picker.rb (99%) rename lib/{ai_bot => personas}/tools/read.rb (99%) rename lib/{ai_bot => personas}/tools/read_artifact.rb (99%) rename lib/{ai_bot => personas}/tools/search.rb (99%) rename lib/{ai_bot => personas}/tools/search_settings.rb (99%) rename lib/{ai_bot => personas}/tools/setting_context.rb (99%) rename lib/{ai_bot => personas}/tools/summarize.rb (99%) rename lib/{ai_bot => personas}/tools/time.rb (98%) rename lib/{ai_bot => personas}/tools/tool.rb (97%) rename lib/{ai_bot => personas}/tools/update_artifact.rb (99%) rename lib/{ai_bot => personas}/tools/web_browser.rb (99%) rename lib/{ai_bot => }/personas/web_artifact_creator.rb (84%) rename spec/lib/{modules/ai_bot => personas}/artifact_update_strategies/diff_spec.rb (98%) rename spec/lib/{modules/ai_bot => personas}/bot_spec.rb (84%) rename spec/lib/{modules/ai_bot => }/personas/persona_spec.rb (76%) rename spec/lib/{modules/ai_bot => personas}/question_consolidator_spec.rb (94%) rename spec/lib/{modules/ai_bot => }/personas/researcher_spec.rb (51%) rename spec/lib/{modules/ai_bot => }/personas/settings_explorer_spec.rb (69%) rename spec/lib/{modules/ai_bot => }/personas/sql_helper_spec.rb (73%) rename spec/lib/{modules/ai_bot => personas}/tools/create_artifact_spec.rb (92%) rename spec/lib/{modules/ai_bot => personas}/tools/dall_e_spec.rb (98%) rename spec/lib/{modules/ai_bot => personas}/tools/db_schema_spec.rb (92%) rename spec/lib/{modules/ai_bot => personas}/tools/discourse_meta_search_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/github_file_content_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/github_pull_request_diff_spec.rb (98%) rename spec/lib/{modules/ai_bot => personas}/tools/github_search_code_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/github_search_files_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/google_spec.rb (98%) rename spec/lib/{modules/ai_bot => personas}/tools/image_spec.rb (94%) rename spec/lib/{modules/ai_bot => personas}/tools/javascript_evaluator_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/list_categories_spec.rb (90%) rename spec/lib/{modules/ai_bot => personas}/tools/list_tags_spec.rb (92%) rename spec/lib/{modules/ai_bot => personas}/tools/random_picker_spec.rb (96%) rename spec/lib/{modules/ai_bot => personas}/tools/read_artifact_spec.rb (90%) rename spec/lib/{modules/ai_bot => personas}/tools/read_spec.rb (94%) rename spec/lib/{modules/ai_bot => personas}/tools/search_settings_spec.rb (97%) rename spec/lib/{modules/ai_bot => personas}/tools/search_spec.rb (98%) rename spec/lib/{modules/ai_bot => personas}/tools/setting_context_spec.rb (95%) rename spec/lib/{modules/ai_bot => personas}/tools/summarize_spec.rb (96%) rename spec/lib/{modules/ai_bot => personas}/tools/time_spec.rb (92%) rename spec/lib/{modules/ai_bot => personas}/tools/tool_spec.rb (95%) rename spec/lib/{modules/ai_bot => personas}/tools/update_artifact_spec.rb (92%) rename spec/lib/{modules/ai_bot => personas}/tools/web_browser_spec.rb (98%) diff --git a/app/controllers/discourse_ai/admin/ai_personas_controller.rb b/app/controllers/discourse_ai/admin/ai_personas_controller.rb index b982677b..ca3059fc 100644 --- a/app/controllers/discourse_ai/admin/ai_personas_controller.rb +++ b/app/controllers/discourse_ai/admin/ai_personas_controller.rb @@ -15,7 +15,7 @@ module DiscourseAi LocalizedAiPersonaSerializer.new(persona, root: false) end tools = - DiscourseAi::AiBot::Personas::Persona.all_available_tools.map do |tool| + DiscourseAi::Personas::Persona.all_available_tools.map do |tool| AiToolSerializer.new(tool, root: false) end AiTool diff --git a/app/jobs/regular/create_ai_chat_reply.rb b/app/jobs/regular/create_ai_chat_reply.rb index 41776a3b..61f824b6 100644 --- a/app/jobs/regular/create_ai_chat_reply.rb +++ b/app/jobs/regular/create_ai_chat_reply.rb @@ -12,11 +12,11 @@ module ::Jobs return if message.blank? personaClass = - DiscourseAi::AiBot::Personas::Persona.find_by(id: args[:persona_id], user: message.user) + DiscourseAi::Personas::Persona.find_by(id: args[:persona_id], user: message.user) return if personaClass.blank? user = User.find_by(id: personaClass.user_id) - bot = DiscourseAi::AiBot::Bot.as(user, persona: personaClass.new) + bot = DiscourseAi::Personas::Bot.as(user, persona: personaClass.new) DiscourseAi::AiBot::Playground.new(bot).reply_to_chat_message( message, diff --git a/app/jobs/regular/create_ai_reply.rb b/app/jobs/regular/create_ai_reply.rb index 16f24b2e..e8a0f0ea 100644 --- a/app/jobs/regular/create_ai_reply.rb +++ b/app/jobs/regular/create_ai_reply.rb @@ -10,13 +10,13 @@ module ::Jobs persona_id = args[:persona_id] begin - persona = DiscourseAi::AiBot::Personas::Persona.find_by(user: post.user, id: persona_id) - raise DiscourseAi::AiBot::Bot::BOT_NOT_FOUND if persona.nil? + persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id) + raise DiscourseAi::Personas::Bot::BOT_NOT_FOUND if persona.nil? - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.new) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new) DiscourseAi::AiBot::Playground.new(bot).reply_to(post) - rescue DiscourseAi::AiBot::Bot::BOT_NOT_FOUND + rescue DiscourseAi::Personas::Bot::BOT_NOT_FOUND Rails.logger.warn( "Bot not found for post #{post.id} - perhaps persona was deleted or bot was disabled", ) diff --git a/app/jobs/regular/stream_discover_reply.rb b/app/jobs/regular/stream_discover_reply.rb index 48183f6e..f8b6d256 100644 --- a/app/jobs/regular/stream_discover_reply.rb +++ b/app/jobs/regular/stream_discover_reply.rb @@ -19,7 +19,7 @@ module Jobs return if (llm_model = LlmModel.find_by(id: ai_persona_klass.default_llm_id)).nil? bot = - DiscourseAi::AiBot::Bot.as( + DiscourseAi::Personas::Bot.as( Discourse.system_user, persona: ai_persona_klass.new, model: llm_model, @@ -31,7 +31,7 @@ module Jobs base = { query: query, model_used: llm_model.display_name } context = - DiscourseAi::AiBot::BotContext.new( + DiscourseAi::Personas::BotContext.new( messages: [{ type: :user, content: query }], skip_tool_details: true, ) diff --git a/app/models/ai_persona.rb b/app/models/ai_persona.rb index 867d44ce..9c1399ef 100644 --- a/app/models/ai_persona.rb +++ b/app/models/ai_persona.rb @@ -201,14 +201,14 @@ class AiPersona < ActiveRecord::Base if inner_name.start_with?("custom-") custom_tool_id = inner_name.split("-", 2).last.to_i if AiTool.exists?(id: custom_tool_id, enabled: true) - klass = DiscourseAi::AiBot::Tools::Custom.class_instance(custom_tool_id) + klass = DiscourseAi::Personas::Tools::Custom.class_instance(custom_tool_id) end else inner_name = inner_name.gsub("Tool", "") inner_name = "List#{inner_name}" if %w[Categories Tags].include?(inner_name) begin - klass = "DiscourseAi::AiBot::Tools::#{inner_name}".constantize + klass = "DiscourseAi::Personas::Tools::#{inner_name}".constantize options[klass] = current_options if current_options rescue StandardError end @@ -218,7 +218,7 @@ class AiPersona < ActiveRecord::Base klass end - persona_class = DiscourseAi::AiBot::Personas::Persona.system_personas_by_id[self.id] + persona_class = DiscourseAi::Personas::Persona.system_personas_by_id[self.id] if persona_class instance_attributes.each do |key, value| # description/name are localized @@ -230,7 +230,7 @@ class AiPersona < ActiveRecord::Base ai_persona_id = self.id - Class.new(DiscourseAi::AiBot::Personas::Persona) do + Class.new(DiscourseAi::Personas::Persona) do instance_attributes.each { |key, value| define_singleton_method(key) { value } } define_singleton_method(:to_s) do diff --git a/app/models/ai_tool.rb b/app/models/ai_tool.rb index d6fde8b2..6ff5582a 100644 --- a/app/models/ai_tool.rb +++ b/app/models/ai_tool.rb @@ -36,7 +36,7 @@ class AiTool < ActiveRecord::Base end def runner(parameters, llm:, bot_user:, context: nil) - DiscourseAi::AiBot::ToolRunner.new( + DiscourseAi::Personas::ToolRunner.new( parameters: parameters, llm: llm, bot_user: bot_user, diff --git a/db/fixtures/ai_bot/603_bot_ai_personas.rb b/db/fixtures/personas/603_ai_personas.rb similarity index 86% rename from db/fixtures/ai_bot/603_bot_ai_personas.rb rename to db/fixtures/personas/603_ai_personas.rb index 06c11169..eaea14ab 100644 --- a/db/fixtures/ai_bot/603_bot_ai_personas.rb +++ b/db/fixtures/personas/603_ai_personas.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -DiscourseAi::AiBot::Personas::Persona.system_personas.each do |persona_class, id| +DiscourseAi::Personas::Persona.system_personas.each do |persona_class, id| persona = AiPersona.find_by(id: id) if !persona persona = AiPersona.new persona.id = id - if persona_class == DiscourseAi::AiBot::Personas::WebArtifactCreator + if persona_class == DiscourseAi::Personas::WebArtifactCreator # this is somewhat sensitive, so we default it to staff persona.allowed_group_ids = [Group::AUTO_GROUPS[:staff]] else persona.allowed_group_ids = [Group::AUTO_GROUPS[:trust_level_0]] end persona.enabled = true - persona.priority = true if persona_class == DiscourseAi::AiBot::Personas::General + persona.priority = true if persona_class == DiscourseAi::Personas::General end names = [ diff --git a/lib/ai_bot/entry_point.rb b/lib/ai_bot/entry_point.rb index 70d4bac5..3f9120a3 100644 --- a/lib/ai_bot/entry_point.rb +++ b/lib/ai_bot/entry_point.rb @@ -110,7 +110,7 @@ module DiscourseAi scope.user.in_any_groups?(SiteSetting.ai_bot_allowed_groups_map) end, ) do - DiscourseAi::AiBot::Personas::Persona + DiscourseAi::Personas::Persona .all(user: scope.user) .map do |persona| { @@ -205,8 +205,7 @@ module DiscourseAi include_condition: -> { SiteSetting.ai_bot_enabled && object.topic.private_message? }, ) do id = topic.custom_fields["ai_persona_id"] - name = - DiscourseAi::AiBot::Personas::Persona.find_by(user: scope.user, id: id.to_i)&.name if id + name = DiscourseAi::Personas::Persona.find_by(user: scope.user, id: id.to_i)&.name if id name || topic.custom_fields["ai_persona"] end diff --git a/lib/ai_bot/personas/creative.rb b/lib/ai_bot/personas/creative.rb deleted file mode 100644 index 338577b5..00000000 --- a/lib/ai_bot/personas/creative.rb +++ /dev/null @@ -1,19 +0,0 @@ -#frozen_string_literal: true - -module DiscourseAi - module AiBot - module Personas - class Creative < Persona - def tools - [] - end - - def system_prompt - <<~PROMPT - You are a helpful bot - PROMPT - end - end - end - end -end diff --git a/lib/ai_bot/personas/persona.rb b/lib/ai_bot/personas/persona.rb deleted file mode 100644 index 0d6745a3..00000000 --- a/lib/ai_bot/personas/persona.rb +++ /dev/null @@ -1,397 +0,0 @@ -#frozen_string_literal: true - -module DiscourseAi - module AiBot - module Personas - class Persona - class << self - def rag_conversation_chunks - 10 - end - - def vision_enabled - false - end - - def vision_max_pixels - 1_048_576 - end - - def question_consolidator_llm_id - nil - end - - def force_default_llm - false - end - - def allow_chat_channel_mentions - false - end - - def allow_chat_direct_messages - false - end - - def system_personas - @system_personas ||= { - Personas::General => -1, - Personas::SqlHelper => -2, - Personas::Artist => -3, - Personas::SettingsExplorer => -4, - Personas::Researcher => -5, - Personas::Creative => -6, - Personas::DallE3 => -7, - Personas::DiscourseHelper => -8, - Personas::GithubHelper => -9, - Personas::WebArtifactCreator => -10, - } - end - - def system_personas_by_id - @system_personas_by_id ||= system_personas.invert - end - - def all(user:) - # listing tools has to be dynamic cause site settings may change - AiPersona.all_personas.filter do |persona| - next false if !user.in_any_groups?(persona.allowed_group_ids) - - if persona.system - instance = persona.new - ( - instance.required_tools == [] || - (instance.required_tools - all_available_tools).empty? - ) - else - true - end - end - end - - def find_by(id: nil, name: nil, user:) - all(user: user).find { |persona| persona.id == id || persona.name == name } - end - - def name - I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.name") - end - - def description - I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description") - end - - def all_available_tools - tools = [ - Tools::ListCategories, - Tools::Time, - Tools::Search, - Tools::Read, - Tools::DbSchema, - Tools::SearchSettings, - Tools::SettingContext, - Tools::RandomPicker, - Tools::DiscourseMetaSearch, - Tools::GithubFileContent, - Tools::GithubPullRequestDiff, - Tools::GithubSearchFiles, - Tools::WebBrowser, - Tools::JavascriptEvaluator, - ] - - if SiteSetting.ai_artifact_security.in?(%w[lax strict]) - tools << Tools::CreateArtifact - tools << Tools::UpdateArtifact - tools << Tools::ReadArtifact - end - - tools << Tools::GithubSearchCode if SiteSetting.ai_bot_github_access_token.present? - - tools << Tools::ListTags if SiteSetting.tagging_enabled - tools << Tools::Image if SiteSetting.ai_stability_api_key.present? - - tools << Tools::DallE if SiteSetting.ai_openai_api_key.present? - if SiteSetting.ai_google_custom_search_api_key.present? && - SiteSetting.ai_google_custom_search_cx.present? - tools << Tools::Google - end - - tools - end - end - - def id - @ai_persona&.id || self.class.system_personas[self.class] - end - - def tools - [] - end - - def force_tool_use - [] - end - - def forced_tool_count - -1 - end - - def required_tools - [] - end - - def temperature - nil - end - - def top_p - nil - end - - def options - {} - end - - def available_tools - self - .class - .all_available_tools - .filter { |tool| tools.include?(tool) } - .concat(tools.filter(&:custom?)) - end - - def craft_prompt(context, llm: nil) - system_insts = - system_prompt.gsub(/\{(\w+)\}/) do |match| - found = context.lookup_template_param(match[1..-2]) - found.nil? ? match : found.to_s - end - - prompt_insts = <<~TEXT.strip - #{system_insts} - #{available_tools.map(&:custom_system_message).compact_blank.join("\n")} - TEXT - - question_consolidator_llm = llm - if self.class.question_consolidator_llm_id.present? - question_consolidator_llm ||= - DiscourseAi::Completions::Llm.proxy( - LlmModel.find_by(id: self.class.question_consolidator_llm_id), - ) - end - - if context.custom_instructions.present? - prompt_insts << "\n" - prompt_insts << context.custom_instructions - end - - fragments_guidance = - rag_fragments_prompt( - context.messages, - llm: question_consolidator_llm, - user: context.user, - )&.strip - - prompt_insts << fragments_guidance if fragments_guidance.present? - - prompt = - DiscourseAi::Completions::Prompt.new( - prompt_insts, - messages: context.messages, - topic_id: context.topic_id, - post_id: context.post_id, - ) - - prompt.max_pixels = self.class.vision_max_pixels if self.class.vision_enabled - prompt.tools = available_tools.map(&:signature) if available_tools - available_tools.each do |tool| - tool.inject_prompt(prompt: prompt, context: context, persona: self) - end - prompt - end - - def find_tool(partial, bot_user:, llm:, context:, existing_tools: []) - return nil if !partial.is_a?(DiscourseAi::Completions::ToolCall) - tool_instance( - partial, - bot_user: bot_user, - llm: llm, - context: context, - existing_tools: existing_tools, - ) - end - - def allow_partial_tool_calls? - available_tools.any? { |tool| tool.allow_partial_tool_calls? } - end - - protected - - def tool_instance(tool_call, bot_user:, llm:, context:, existing_tools:) - function_id = tool_call.id - function_name = tool_call.name - return nil if function_name.nil? - - tool_klass = available_tools.find { |c| c.signature.dig(:name) == function_name } - return nil if tool_klass.nil? - - arguments = {} - tool_klass.signature[:parameters].to_a.each do |param| - name = param[:name] - value = tool_call.parameters[name.to_sym] - - if param[:type] == "array" && value - value = - begin - JSON.parse(value) - rescue JSON::ParserError - [value.to_s] - end - elsif param[:type] == "string" && value - value = strip_quotes(value).to_s - elsif param[:type] == "integer" && value - value = strip_quotes(value).to_i - end - - if param[:enum] && value && !param[:enum].include?(value) - # invalid enum value - value = nil - end - - arguments[name.to_sym] = value if value - end - - tool_instance = - existing_tools.find { |t| t.name == function_name && t.tool_call_id == function_id } - - if tool_instance - tool_instance.parameters = arguments - tool_instance - else - tool_klass.new( - arguments, - tool_call_id: function_id || function_name, - persona_options: options[tool_klass].to_h, - bot_user: bot_user, - llm: llm, - context: context, - ) - end - end - - def strip_quotes(value) - if value.is_a?(String) - if value.start_with?('"') && value.end_with?('"') - value = value[1..-2] - elsif value.start_with?("'") && value.end_with?("'") - value = value[1..-2] - else - value - end - else - value - end - end - - def rag_fragments_prompt(conversation_context, llm:, user:) - upload_refs = - UploadReference.where(target_id: id, target_type: "AiPersona").pluck(:upload_id) - - return nil if !DiscourseAi::Embeddings.enabled? - return nil if conversation_context.blank? || upload_refs.blank? - - latest_interactions = - conversation_context.select { |ctx| %i[model user].include?(ctx[:type]) }.last(10) - - return nil if latest_interactions.empty? - - # first response - if latest_interactions.length == 1 - consolidated_question = latest_interactions[0][:content] - else - consolidated_question = - DiscourseAi::AiBot::QuestionConsolidator.consolidate_question( - llm, - latest_interactions, - user, - ) - end - - return nil if !consolidated_question - - vector = DiscourseAi::Embeddings::Vector.instance - reranker = DiscourseAi::Inference::HuggingFaceTextEmbeddings - - interactions_vector = vector.vector_from(consolidated_question) - - rag_conversation_chunks = self.class.rag_conversation_chunks - search_limit = - if reranker.reranker_configured? - rag_conversation_chunks * 5 - else - rag_conversation_chunks - end - - schema = DiscourseAi::Embeddings::Schema.for(RagDocumentFragment) - - candidate_fragment_ids = - schema - .asymmetric_similarity_search( - interactions_vector, - limit: search_limit, - offset: 0, - ) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiPersona") } - rag_document_fragments ON - rag_document_fragments.id = rag_document_fragment_id AND - rag_document_fragments.target_id = :target_id AND - rag_document_fragments.target_type = :target_type - SQL - .map(&:rag_document_fragment_id) - - fragments = - RagDocumentFragment.where(upload_id: upload_refs, id: candidate_fragment_ids).pluck( - :fragment, - :metadata, - ) - - if reranker.reranker_configured? - guidance = fragments.map { |fragment, _metadata| fragment } - ranks = - DiscourseAi::Inference::HuggingFaceTextEmbeddings - .rerank(conversation_context.last[:content], guidance) - .to_a - .take(rag_conversation_chunks) - .map { _1[:index] } - - if ranks.empty? - fragments = fragments.take(rag_conversation_chunks) - else - fragments = ranks.map { |idx| fragments[idx] } - end - end - - <<~TEXT - - The following texts will give you additional guidance for your response. - We included them because we believe they are relevant to this conversation topic. - - Texts: - - #{ - fragments - .map do |fragment, metadata| - if metadata.present? - ["# #{metadata}", fragment].join("\n") - else - fragment - end - end - .join("\n") - } - - TEXT - end - end - end - end -end diff --git a/lib/ai_bot/playground.rb b/lib/ai_bot/playground.rb index 685683fd..eea64f0e 100644 --- a/lib/ai_bot/playground.rb +++ b/lib/ai_bot/playground.rb @@ -150,13 +150,11 @@ module DiscourseAi persona = nil if persona_id - persona = - DiscourseAi::AiBot::Personas::Persona.find_by(user: post.user, id: persona_id.to_i) + persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id.to_i) end if !persona && persona_name = post.topic.custom_fields["ai_persona"] - persona = - DiscourseAi::AiBot::Personas::Persona.find_by(user: post.user, name: persona_name) + persona = DiscourseAi::Personas::Persona.find_by(user: post.user, name: persona_name) end # edge case, llm was mentioned in an ai persona conversation @@ -172,11 +170,11 @@ module DiscourseAi end end - persona ||= DiscourseAi::AiBot::Personas::General + persona ||= DiscourseAi::Personas::General bot_user = User.find(persona.user_id) if persona && persona.force_default_llm - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.new) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new) new(bot).update_playground_with(post) end end @@ -198,8 +196,8 @@ module DiscourseAi bot_user = user || ai_persona.user raise Discourse::InvalidParameters.new(:user) if bot_user.nil? - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona) - playground = DiscourseAi::AiBot::Playground.new(bot) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona) + playground = new(bot) playground.reply_to( post, @@ -236,14 +234,54 @@ module DiscourseAi include_uploads: bot.persona.class.vision_enabled, ) - bot - .get_updated_title(messages, post, user) - .tap do |new_title| - PostRevisor.new(post.topic.first_post, post.topic).revise!( - bot.bot_user, - title: new_title.sub(/\A"/, "").sub(/"\Z/, ""), - ) + # conversation context may contain tool calls, and confusing user names + # clean it up + conversation = +"" + messages.each do |context| + if context[:type] == :user + conversation << "User said:\n#{context[:content]}\n\n" + elsif context[:type] == :model + conversation << "Model said:\n#{context[:content]}\n\n" end + end + + system_insts = <<~TEXT.strip + You are titlebot. Given a conversation, you will suggest a title. + + - You will never respond with anything but the suggested title. + - You will always match the conversation language in your title suggestion. + - Title will capture the essence of the conversation. + TEXT + + instruction = <<~TEXT.strip + Given the following conversation: + + {{{ + #{conversation} + }}} + + Reply only with a title that is 7 words or less. + TEXT + + title_prompt = + DiscourseAi::Completions::Prompt.new( + system_insts, + messages: [type: :user, content: instruction], + topic_id: post.topic_id, + ) + + new_title = + bot + .llm + .generate(title_prompt, user: user, feature_name: "bot_title") + .strip + .split("\n") + .last + + PostRevisor.new(post.topic.first_post, post.topic).revise!( + bot.bot_user, + title: new_title.sub(/\A"/, "").sub(/"\Z/, ""), + ) allowed_users = post.topic.topic_allowed_users.pluck(:user_id) MessageBus.publish( @@ -271,7 +309,7 @@ module DiscourseAi end context = - BotContext.new( + DiscourseAi::Personas::BotContext.new( participants: participants, message_id: message.id, channel_id: channel.id, @@ -372,7 +410,7 @@ module DiscourseAi end context = - BotContext.new( + DiscourseAi::Personas::BotContext.new( post: post, custom_instructions: custom_instructions, messages: @@ -575,8 +613,7 @@ module DiscourseAi def schedule_bot_reply(post) persona_id = - DiscourseAi::AiBot::Personas::Persona.system_personas[bot.persona.class] || - bot.persona.class.id + DiscourseAi::Personas::Persona.system_personas[bot.persona.class] || bot.persona.class.id ::Jobs.enqueue( :create_ai_reply, post_id: post.id, diff --git a/lib/ai_bot/response_http_streamer.rb b/lib/ai_bot/response_http_streamer.rb index 125a8bbe..cfb2fbeb 100644 --- a/lib/ai_bot/response_http_streamer.rb +++ b/lib/ai_bot/response_http_streamer.rb @@ -77,8 +77,8 @@ module DiscourseAi io.flush persona_class = - DiscourseAi::AiBot::Personas::Persona.find_by(id: persona.id, user: current_user) - bot = DiscourseAi::AiBot::Bot.as(persona.user, persona: persona_class.new) + DiscourseAi::Personas::Persona.find_by(id: persona.id, user: current_user) + bot = DiscourseAi::Personas::Bot.as(persona.user, persona: persona_class.new) data = { diff --git a/lib/automation/llm_tool_triage.rb b/lib/automation/llm_tool_triage.rb index 7d7622ad..58b9210d 100644 --- a/lib/automation/llm_tool_triage.rb +++ b/lib/automation/llm_tool_triage.rb @@ -7,7 +7,7 @@ module DiscourseAi return if !tool return if !tool.parameters.blank? - context = DiscourseAi::AiBot::BotContext.new(post: post) + context = DiscourseAi::Personas::BotContext.new(post: post) runner = tool.runner({}, llm: nil, bot_user: Discourse.system_user, context: context) runner.invoke diff --git a/lib/discord/bot/persona_replier.rb b/lib/discord/bot/persona_replier.rb index ad9a7841..425451bb 100644 --- a/lib/discord/bot/persona_replier.rb +++ b/lib/discord/bot/persona_replier.rb @@ -10,7 +10,7 @@ module DiscourseAi .find { |persona| persona.id == SiteSetting.ai_discord_search_persona.to_i } .new @bot = - DiscourseAi::AiBot::Bot.as( + DiscourseAi::Personas::Bot.as( Discourse.system_user, persona: @persona, model: LlmModel.find(@persona.class.default_llm_id), diff --git a/lib/discord/bot/search.rb b/lib/discord/bot/search.rb index 051639ef..e33e35e7 100644 --- a/lib/discord/bot/search.rb +++ b/lib/discord/bot/search.rb @@ -4,7 +4,7 @@ module DiscourseAi module Discord::Bot class Search < Base def initialize(body) - @search = DiscourseAi::AiBot::Tools::Search + @search = DiscourseAi::Personas::Tools::Search super(body) end diff --git a/lib/ai_bot/artifact_update_strategies/base.rb b/lib/personas/artifact_update_strategies/base.rb similarity index 98% rename from lib/ai_bot/artifact_update_strategies/base.rb rename to lib/personas/artifact_update_strategies/base.rb index 91bcb306..68e0b761 100644 --- a/lib/ai_bot/artifact_update_strategies/base.rb +++ b/lib/personas/artifact_update_strategies/base.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module ArtifactUpdateStrategies class InvalidFormatError < StandardError end diff --git a/lib/ai_bot/artifact_update_strategies/diff.rb b/lib/personas/artifact_update_strategies/diff.rb similarity index 99% rename from lib/ai_bot/artifact_update_strategies/diff.rb rename to lib/personas/artifact_update_strategies/diff.rb index 39ba8549..af96c0fb 100644 --- a/lib/ai_bot/artifact_update_strategies/diff.rb +++ b/lib/personas/artifact_update_strategies/diff.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module ArtifactUpdateStrategies class Diff < Base attr_reader :failed_searches diff --git a/lib/ai_bot/artifact_update_strategies/full.rb b/lib/personas/artifact_update_strategies/full.rb similarity index 99% rename from lib/ai_bot/artifact_update_strategies/full.rb rename to lib/personas/artifact_update_strategies/full.rb index 26f02a84..3942cdf8 100644 --- a/lib/ai_bot/artifact_update_strategies/full.rb +++ b/lib/personas/artifact_update_strategies/full.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module ArtifactUpdateStrategies class Full < Base private diff --git a/lib/ai_bot/personas/artist.rb b/lib/personas/artist.rb similarity index 83% rename from lib/ai_bot/personas/artist.rb rename to lib/personas/artist.rb index 03271424..93e2361e 100644 --- a/lib/ai_bot/personas/artist.rb +++ b/lib/personas/artist.rb @@ -1,19 +1,18 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class Artist < Persona - def tools - [Tools::Image] - end + module Personas + class Artist < Persona + def tools + [Tools::Image] + end - def required_tools - [Tools::Image] - end + def required_tools + [Tools::Image] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are artistbot and you are here to help people generate images. You generate images using stable diffusion. @@ -31,7 +30,6 @@ module DiscourseAi - Be creative with your prompts, offer diverse options - You can use the seeds to regenerate the same image and amend the prompt keeping general style PROMPT - end end end end diff --git a/lib/ai_bot/bot.rb b/lib/personas/bot.rb similarity index 86% rename from lib/ai_bot/bot.rb rename to lib/personas/bot.rb index 3248d031..62797f72 100644 --- a/lib/ai_bot/bot.rb +++ b/lib/personas/bot.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas class Bot attr_reader :model @@ -13,7 +13,7 @@ module DiscourseAi # limit is arbitrary, but 5 which was used in the past was too low MAX_TOOLS = 20 - def self.as(bot_user, persona: DiscourseAi::AiBot::Personas::General.new, model: nil) + def self.as(bot_user, persona: DiscourseAi::Personas::General.new, model: nil) new(bot_user, persona, model) end @@ -27,49 +27,8 @@ module DiscourseAi attr_reader :bot_user attr_accessor :persona - def get_updated_title(conversation_context, post, user) - system_insts = <<~TEXT.strip - You are titlebot. Given a conversation, you will suggest a title. - - - You will never respond with anything but the suggested title. - - You will always match the conversation language in your title suggestion. - - Title will capture the essence of the conversation. - TEXT - - # conversation context may contain tool calls, and confusing user names - # clean it up - conversation = +"" - conversation_context.each do |context| - if context[:type] == :user - conversation << "User said:\n#{context[:content]}\n\n" - elsif context[:type] == :model - conversation << "Model said:\n#{context[:content]}\n\n" - end - end - - instruction = <<~TEXT.strip - Given the following conversation: - - {{{ - #{conversation} - }}} - - Reply only with a title that is 7 words or less. - TEXT - - title_prompt = - DiscourseAi::Completions::Prompt.new( - system_insts, - messages: [type: :user, content: instruction], - topic_id: post.topic_id, - ) - - DiscourseAi::Completions::Llm - .proxy(model) - .generate(title_prompt, user: user, feature_name: "bot_title") - .strip - .split("\n") - .last + def llm + @llm ||= DiscourseAi::Completions::Llm.proxy(model) end def force_tool_if_needed(prompt, context) diff --git a/lib/ai_bot/bot_context.rb b/lib/personas/bot_context.rb similarity index 99% rename from lib/ai_bot/bot_context.rb rename to lib/personas/bot_context.rb index ca32262a..5ac5b05c 100644 --- a/lib/ai_bot/bot_context.rb +++ b/lib/personas/bot_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas class BotContext attr_accessor :messages, :topic_id, diff --git a/lib/personas/creative.rb b/lib/personas/creative.rb new file mode 100644 index 00000000..407c2240 --- /dev/null +++ b/lib/personas/creative.rb @@ -0,0 +1,17 @@ +#frozen_string_literal: true + +module DiscourseAi + module Personas + class Creative < Persona + def tools + [] + end + + def system_prompt + <<~PROMPT + You are a helpful bot + PROMPT + end + end + end +end diff --git a/lib/ai_bot/personas/dall_e_3.rb b/lib/personas/dall_e_3.rb similarity index 83% rename from lib/ai_bot/personas/dall_e_3.rb rename to lib/personas/dall_e_3.rb index 65666284..851756c8 100644 --- a/lib/ai_bot/personas/dall_e_3.rb +++ b/lib/personas/dall_e_3.rb @@ -1,19 +1,18 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class DallE3 < Persona - def tools - [Tools::DallE] - end + module Personas + class DallE3 < Persona + def tools + [Tools::DallE] + end - def required_tools - [Tools::DallE] - end + def required_tools + [Tools::DallE] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT As a DALL-E-3 bot, you're tasked with generating images based on user prompts. - Be specific and detailed in your prompts. Include elements like subject, medium (e.g., oil on canvas), artist style, lighting, time of day, and website style (e.g., ArtStation, DeviantArt). @@ -32,7 +31,6 @@ module DiscourseAi Just generate the images PROMPT - end end end end diff --git a/lib/ai_bot/personas/discourse_helper.rb b/lib/personas/discourse_helper.rb similarity index 90% rename from lib/ai_bot/personas/discourse_helper.rb rename to lib/personas/discourse_helper.rb index 6c03a0a3..2e9db142 100644 --- a/lib/ai_bot/personas/discourse_helper.rb +++ b/lib/personas/discourse_helper.rb @@ -1,15 +1,14 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class DiscourseHelper < Persona - def tools - [Tools::DiscourseMetaSearch] - end + module Personas + class DiscourseHelper < Persona + def tools + [Tools::DiscourseMetaSearch] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are Discourse Helper Bot - Discourse Helper Bot understands *markdown* and responds in Discourse **markdown**. @@ -41,7 +40,6 @@ module DiscourseAi The date now is: {time}, much has changed since you were trained. PROMPT - end end end end diff --git a/lib/ai_bot/personas/general.rb b/lib/personas/general.rb similarity index 62% rename from lib/ai_bot/personas/general.rb rename to lib/personas/general.rb index 3e06a3f7..01c05e08 100644 --- a/lib/ai_bot/personas/general.rb +++ b/lib/personas/general.rb @@ -1,22 +1,21 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class General < Persona - def tools - [ - Tools::Search, - Tools::Google, - Tools::Image, - Tools::Read, - Tools::ListCategories, - Tools::ListTags, - ] - end + module Personas + class General < Persona + def tools + [ + Tools::Search, + Tools::Google, + Tools::Image, + Tools::Read, + Tools::ListCategories, + Tools::ListTags, + ] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are a helpful Discourse assistant. You _understand_ and **generate** Discourse Markdown. You live in a Discourse Forum Message. @@ -27,7 +26,6 @@ module DiscourseAi The participants in this conversation are: {participants} The date now is: {time}, much has changed since you were trained. PROMPT - end end end end diff --git a/lib/ai_bot/personas/github_helper.rb b/lib/personas/github_helper.rb similarity index 60% rename from lib/ai_bot/personas/github_helper.rb rename to lib/personas/github_helper.rb index a0a2e499..a9798065 100644 --- a/lib/ai_bot/personas/github_helper.rb +++ b/lib/personas/github_helper.rb @@ -1,20 +1,19 @@ # frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class GithubHelper < Persona - def tools - [ - Tools::GithubFileContent, - Tools::GithubPullRequestDiff, - Tools::GithubSearchCode, - Tools::GithubSearchFiles, - ] - end + module Personas + class GithubHelper < Persona + def tools + [ + Tools::GithubFileContent, + Tools::GithubPullRequestDiff, + Tools::GithubSearchCode, + Tools::GithubSearchFiles, + ] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are a helpful GitHub assistant. You _understand_ and **generate** Discourse Flavored Markdown. You live in a Discourse Forum Message. @@ -22,7 +21,6 @@ module DiscourseAi Your purpose is to assist users with GitHub-related tasks and questions. When asked about a specific repository, pull request, or file, try to use the available tools to provide accurate and helpful information. PROMPT - end end end end diff --git a/lib/personas/persona.rb b/lib/personas/persona.rb new file mode 100644 index 00000000..fb3eb155 --- /dev/null +++ b/lib/personas/persona.rb @@ -0,0 +1,395 @@ +#frozen_string_literal: true + +module DiscourseAi + module Personas + class Persona + class << self + def rag_conversation_chunks + 10 + end + + def vision_enabled + false + end + + def vision_max_pixels + 1_048_576 + end + + def question_consolidator_llm_id + nil + end + + def force_default_llm + false + end + + def allow_chat_channel_mentions + false + end + + def allow_chat_direct_messages + false + end + + def system_personas + @system_personas ||= { + General => -1, + SqlHelper => -2, + Artist => -3, + SettingsExplorer => -4, + Researcher => -5, + Creative => -6, + DallE3 => -7, + DiscourseHelper => -8, + GithubHelper => -9, + WebArtifactCreator => -10, + } + end + + def system_personas_by_id + @system_personas_by_id ||= system_personas.invert + end + + def all(user:) + # listing tools has to be dynamic cause site settings may change + AiPersona.all_personas.filter do |persona| + next false if !user.in_any_groups?(persona.allowed_group_ids) + + if persona.system + instance = persona.new + ( + instance.required_tools == [] || + (instance.required_tools - all_available_tools).empty? + ) + else + true + end + end + end + + def find_by(id: nil, name: nil, user:) + all(user: user).find { |persona| persona.id == id || persona.name == name } + end + + def name + I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.name") + end + + def description + I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description") + end + + def all_available_tools + tools = [ + Tools::ListCategories, + Tools::Time, + Tools::Search, + Tools::Read, + Tools::DbSchema, + Tools::SearchSettings, + Tools::SettingContext, + Tools::RandomPicker, + Tools::DiscourseMetaSearch, + Tools::GithubFileContent, + Tools::GithubPullRequestDiff, + Tools::GithubSearchFiles, + Tools::WebBrowser, + Tools::JavascriptEvaluator, + ] + + if SiteSetting.ai_artifact_security.in?(%w[lax strict]) + tools << Tools::CreateArtifact + tools << Tools::UpdateArtifact + tools << Tools::ReadArtifact + end + + tools << Tools::GithubSearchCode if SiteSetting.ai_bot_github_access_token.present? + + tools << Tools::ListTags if SiteSetting.tagging_enabled + tools << Tools::Image if SiteSetting.ai_stability_api_key.present? + + tools << Tools::DallE if SiteSetting.ai_openai_api_key.present? + if SiteSetting.ai_google_custom_search_api_key.present? && + SiteSetting.ai_google_custom_search_cx.present? + tools << Tools::Google + end + + tools + end + end + + def id + @ai_persona&.id || self.class.system_personas[self.class] + end + + def tools + [] + end + + def force_tool_use + [] + end + + def forced_tool_count + -1 + end + + def required_tools + [] + end + + def temperature + nil + end + + def top_p + nil + end + + def options + {} + end + + def available_tools + self + .class + .all_available_tools + .filter { |tool| tools.include?(tool) } + .concat(tools.filter(&:custom?)) + end + + def craft_prompt(context, llm: nil) + system_insts = + system_prompt.gsub(/\{(\w+)\}/) do |match| + found = context.lookup_template_param(match[1..-2]) + found.nil? ? match : found.to_s + end + + prompt_insts = <<~TEXT.strip + #{system_insts} + #{available_tools.map(&:custom_system_message).compact_blank.join("\n")} + TEXT + + question_consolidator_llm = llm + if self.class.question_consolidator_llm_id.present? + question_consolidator_llm ||= + DiscourseAi::Completions::Llm.proxy( + LlmModel.find_by(id: self.class.question_consolidator_llm_id), + ) + end + + if context.custom_instructions.present? + prompt_insts << "\n" + prompt_insts << context.custom_instructions + end + + fragments_guidance = + rag_fragments_prompt( + context.messages, + llm: question_consolidator_llm, + user: context.user, + )&.strip + + prompt_insts << fragments_guidance if fragments_guidance.present? + + prompt = + DiscourseAi::Completions::Prompt.new( + prompt_insts, + messages: context.messages, + topic_id: context.topic_id, + post_id: context.post_id, + ) + + prompt.max_pixels = self.class.vision_max_pixels if self.class.vision_enabled + prompt.tools = available_tools.map(&:signature) if available_tools + available_tools.each do |tool| + tool.inject_prompt(prompt: prompt, context: context, persona: self) + end + prompt + end + + def find_tool(partial, bot_user:, llm:, context:, existing_tools: []) + return nil if !partial.is_a?(DiscourseAi::Completions::ToolCall) + tool_instance( + partial, + bot_user: bot_user, + llm: llm, + context: context, + existing_tools: existing_tools, + ) + end + + def allow_partial_tool_calls? + available_tools.any? { |tool| tool.allow_partial_tool_calls? } + end + + protected + + def tool_instance(tool_call, bot_user:, llm:, context:, existing_tools:) + function_id = tool_call.id + function_name = tool_call.name + return nil if function_name.nil? + + tool_klass = available_tools.find { |c| c.signature.dig(:name) == function_name } + return nil if tool_klass.nil? + + arguments = {} + tool_klass.signature[:parameters].to_a.each do |param| + name = param[:name] + value = tool_call.parameters[name.to_sym] + + if param[:type] == "array" && value + value = + begin + JSON.parse(value) + rescue JSON::ParserError + [value.to_s] + end + elsif param[:type] == "string" && value + value = strip_quotes(value).to_s + elsif param[:type] == "integer" && value + value = strip_quotes(value).to_i + end + + if param[:enum] && value && !param[:enum].include?(value) + # invalid enum value + value = nil + end + + arguments[name.to_sym] = value if value + end + + tool_instance = + existing_tools.find { |t| t.name == function_name && t.tool_call_id == function_id } + + if tool_instance + tool_instance.parameters = arguments + tool_instance + else + tool_klass.new( + arguments, + tool_call_id: function_id || function_name, + persona_options: options[tool_klass].to_h, + bot_user: bot_user, + llm: llm, + context: context, + ) + end + end + + def strip_quotes(value) + if value.is_a?(String) + if value.start_with?('"') && value.end_with?('"') + value = value[1..-2] + elsif value.start_with?("'") && value.end_with?("'") + value = value[1..-2] + else + value + end + else + value + end + end + + def rag_fragments_prompt(conversation_context, llm:, user:) + upload_refs = + UploadReference.where(target_id: id, target_type: "AiPersona").pluck(:upload_id) + + return nil if !DiscourseAi::Embeddings.enabled? + return nil if conversation_context.blank? || upload_refs.blank? + + latest_interactions = + conversation_context.select { |ctx| %i[model user].include?(ctx[:type]) }.last(10) + + return nil if latest_interactions.empty? + + # first response + if latest_interactions.length == 1 + consolidated_question = latest_interactions[0][:content] + else + consolidated_question = + DiscourseAi::Personas::QuestionConsolidator.consolidate_question( + llm, + latest_interactions, + user, + ) + end + + return nil if !consolidated_question + + vector = DiscourseAi::Embeddings::Vector.instance + reranker = DiscourseAi::Inference::HuggingFaceTextEmbeddings + + interactions_vector = vector.vector_from(consolidated_question) + + rag_conversation_chunks = self.class.rag_conversation_chunks + search_limit = + if reranker.reranker_configured? + rag_conversation_chunks * 5 + else + rag_conversation_chunks + end + + schema = DiscourseAi::Embeddings::Schema.for(RagDocumentFragment) + + candidate_fragment_ids = + schema + .asymmetric_similarity_search( + interactions_vector, + limit: search_limit, + offset: 0, + ) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiPersona") } + rag_document_fragments ON + rag_document_fragments.id = rag_document_fragment_id AND + rag_document_fragments.target_id = :target_id AND + rag_document_fragments.target_type = :target_type + SQL + .map(&:rag_document_fragment_id) + + fragments = + RagDocumentFragment.where(upload_id: upload_refs, id: candidate_fragment_ids).pluck( + :fragment, + :metadata, + ) + + if reranker.reranker_configured? + guidance = fragments.map { |fragment, _metadata| fragment } + ranks = + DiscourseAi::Inference::HuggingFaceTextEmbeddings + .rerank(conversation_context.last[:content], guidance) + .to_a + .take(rag_conversation_chunks) + .map { _1[:index] } + + if ranks.empty? + fragments = fragments.take(rag_conversation_chunks) + else + fragments = ranks.map { |idx| fragments[idx] } + end + end + + <<~TEXT + + The following texts will give you additional guidance for your response. + We included them because we believe they are relevant to this conversation topic. + + Texts: + + #{ + fragments + .map do |fragment, metadata| + if metadata.present? + ["# #{metadata}", fragment].join("\n") + else + fragment + end + end + .join("\n") + } + + TEXT + end + end + end +end diff --git a/lib/ai_bot/question_consolidator.rb b/lib/personas/question_consolidator.rb similarity index 99% rename from lib/ai_bot/question_consolidator.rb rename to lib/personas/question_consolidator.rb index 59159148..f1e0c476 100644 --- a/lib/ai_bot/question_consolidator.rb +++ b/lib/personas/question_consolidator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas class QuestionConsolidator attr_reader :llm, :messages, :user, :max_tokens diff --git a/lib/ai_bot/personas/researcher.rb b/lib/personas/researcher.rb similarity index 88% rename from lib/ai_bot/personas/researcher.rb rename to lib/personas/researcher.rb index 983d4b22..c79e8ee4 100644 --- a/lib/ai_bot/personas/researcher.rb +++ b/lib/personas/researcher.rb @@ -1,19 +1,18 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class Researcher < Persona - def tools - [Tools::Google, Tools::WebBrowser] - end + module Personas + class Researcher < Persona + def tools + [Tools::Google, Tools::WebBrowser] + end - def required_tools - [Tools::Google] - end + def required_tools + [Tools::Google] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are a research assistant with access to two powerful tools: 1. Google search - for finding relevant information across the internet. @@ -45,7 +44,6 @@ module DiscourseAi Remember, efficient use of your tools not only saves time but also ensures the high quality and relevance of the information provided. PROMPT - end end end end diff --git a/lib/ai_bot/personas/settings_explorer.rb b/lib/personas/settings_explorer.rb similarity index 71% rename from lib/ai_bot/personas/settings_explorer.rb rename to lib/personas/settings_explorer.rb index 018df156..77ae45e3 100644 --- a/lib/ai_bot/personas/settings_explorer.rb +++ b/lib/personas/settings_explorer.rb @@ -1,15 +1,14 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class SettingsExplorer < Persona - def tools - [Tools::SettingContext, Tools::SearchSettings] - end + module Personas + class SettingsExplorer < Persona + def tools + [Tools::SettingContext, Tools::SearchSettings] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are Discourse Site settings bot. - You are able to find information about all the site settings. @@ -19,7 +18,6 @@ module DiscourseAi Current time is: {time} PROMPT - end end end end diff --git a/lib/ai_bot/personas/sql_helper.rb b/lib/personas/sql_helper.rb similarity index 72% rename from lib/ai_bot/personas/sql_helper.rb rename to lib/personas/sql_helper.rb index 7d602192..720bc145 100644 --- a/lib/ai_bot/personas/sql_helper.rb +++ b/lib/personas/sql_helper.rb @@ -1,52 +1,50 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class SqlHelper < Persona - def self.schema - return @schema if defined?(@schema) + module Personas + class SqlHelper < Persona + def self.schema + return @schema if defined?(@schema) - tables = Hash.new - priority_tables = %w[ - posts - topics - notifications - users - user_actions - user_emails - categories - groups - ] + tables = Hash.new + priority_tables = %w[ + posts + topics + notifications + users + user_actions + user_emails + categories + groups + ] - DB.query(<<~SQL).each { |row| (tables[row.table_name] ||= []) << row.column_name } + DB.query(<<~SQL).each { |row| (tables[row.table_name] ||= []) << row.column_name } select table_name, column_name from information_schema.columns where table_schema = 'public' order by table_name SQL - priority = - +(priority_tables.map { |name| "#{name}(#{tables[name].join(",")})" }.join("\n")) + priority = +(priority_tables.map { |name| "#{name}(#{tables[name].join(",")})" }.join("\n")) - other_tables = +"" - tables.each do |table_name, _| - next if priority_tables.include?(table_name) - other_tables << "#{table_name} " - end - - @schema = { priority_tables: priority, other_tables: other_tables } + other_tables = +"" + tables.each do |table_name, _| + next if priority_tables.include?(table_name) + other_tables << "#{table_name} " end - def tools - [Tools::DbSchema] - end + @schema = { priority_tables: priority, other_tables: other_tables } + end - def temperature - 0.2 - end + def tools + [Tools::DbSchema] + end - def system_prompt - <<~PROMPT + def temperature + 0.2 + end + + def system_prompt + <<~PROMPT You are a PostgreSQL expert. - Avoid returning any text to the user prior to a tool call. - You understand and generate Discourse Markdown but specialize in creating queries. @@ -100,7 +98,6 @@ module DiscourseAi NEVER look up schema for the tables listed above, as their full schema is already provided. PROMPT - end end end end diff --git a/lib/ai_bot/tool_runner.rb b/lib/personas/tool_runner.rb similarity index 97% rename from lib/ai_bot/tool_runner.rb rename to lib/personas/tool_runner.rb index 8a42a494..33a5a657 100644 --- a/lib/ai_bot/tool_runner.rb +++ b/lib/personas/tool_runner.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas class ToolRunner attr_reader :tool, :parameters, :llm attr_accessor :running_attached_function, :timeout, :custom_raw @@ -14,11 +14,11 @@ module DiscourseAi MAX_HTTP_REQUESTS = 20 def initialize(parameters:, llm:, bot_user:, context: nil, tool:, timeout: nil) - if context && !context.is_a?(DiscourseAi::AiBot::BotContext) + if context && !context.is_a?(DiscourseAi::Personas::BotContext) raise ArgumentError, "context must be a BotContext object" end - context ||= DiscourseAi::AiBot::BotContext.new + context ||= DiscourseAi::Personas::BotContext.new @parameters = parameters @llm = llm @@ -339,7 +339,7 @@ module DiscourseAi return { error: "Persona not found" } if persona_class.nil? persona = persona_class.new - bot = DiscourseAi::AiBot::Bot.as(@bot_user || persona.user, persona: persona) + bot = DiscourseAi::Personas::Bot.as(@bot_user || persona.user, persona: persona) playground = DiscourseAi::AiBot::Playground.new(bot) if @context.post_id @@ -488,7 +488,7 @@ module DiscourseAi headers = (options && options["headers"]) || {} result = {} - DiscourseAi::AiBot::Tools::Tool.send_http_request( + DiscourseAi::Personas::Tools::Tool.send_http_request( url, headers: headers, ) do |response| @@ -517,7 +517,7 @@ module DiscourseAi body = options && options["body"] result = {} - DiscourseAi::AiBot::Tools::Tool.send_http_request( + DiscourseAi::Personas::Tools::Tool.send_http_request( url, method: method, headers: headers, diff --git a/lib/ai_bot/tools/create_artifact.rb b/lib/personas/tools/create_artifact.rb similarity index 99% rename from lib/ai_bot/tools/create_artifact.rb rename to lib/personas/tools/create_artifact.rb index 54221b40..c12577ff 100644 --- a/lib/ai_bot/tools/create_artifact.rb +++ b/lib/personas/tools/create_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class CreateArtifact < Tool def self.name diff --git a/lib/ai_bot/tools/custom.rb b/lib/personas/tools/custom.rb similarity index 98% rename from lib/ai_bot/tools/custom.rb rename to lib/personas/tools/custom.rb index 8a4fbc99..505b051e 100644 --- a/lib/ai_bot/tools/custom.rb +++ b/lib/personas/tools/custom.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Custom < Tool def self.class_instance(tool_id) diff --git a/lib/ai_bot/tools/dall_e.rb b/lib/personas/tools/dall_e.rb similarity index 99% rename from lib/ai_bot/tools/dall_e.rb rename to lib/personas/tools/dall_e.rb index 4564cfbb..e8957b51 100644 --- a/lib/ai_bot/tools/dall_e.rb +++ b/lib/personas/tools/dall_e.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class DallE < Tool def self.signature diff --git a/lib/ai_bot/tools/db_schema.rb b/lib/personas/tools/db_schema.rb similarity index 98% rename from lib/ai_bot/tools/db_schema.rb rename to lib/personas/tools/db_schema.rb index 1c11fb68..b0a6f296 100644 --- a/lib/ai_bot/tools/db_schema.rb +++ b/lib/personas/tools/db_schema.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class DbSchema < Tool def self.signature diff --git a/lib/ai_bot/tools/discourse_meta_search.rb b/lib/personas/tools/discourse_meta_search.rb similarity index 99% rename from lib/ai_bot/tools/discourse_meta_search.rb rename to lib/personas/tools/discourse_meta_search.rb index d787eb79..5fdfb76e 100644 --- a/lib/ai_bot/tools/discourse_meta_search.rb +++ b/lib/personas/tools/discourse_meta_search.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class DiscourseMetaSearch < Tool class << self diff --git a/lib/ai_bot/tools/github_file_content.rb b/lib/personas/tools/github_file_content.rb similarity index 99% rename from lib/ai_bot/tools/github_file_content.rb rename to lib/personas/tools/github_file_content.rb index c3cde356..aef00c1e 100644 --- a/lib/ai_bot/tools/github_file_content.rb +++ b/lib/personas/tools/github_file_content.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class GithubFileContent < Tool def self.signature diff --git a/lib/ai_bot/tools/github_pull_request_diff.rb b/lib/personas/tools/github_pull_request_diff.rb similarity index 99% rename from lib/ai_bot/tools/github_pull_request_diff.rb rename to lib/personas/tools/github_pull_request_diff.rb index 21bb71cf..afbe51f9 100644 --- a/lib/ai_bot/tools/github_pull_request_diff.rb +++ b/lib/personas/tools/github_pull_request_diff.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class GithubPullRequestDiff < Tool LARGE_OBJECT_THRESHOLD = 30_000 diff --git a/lib/ai_bot/tools/github_search_code.rb b/lib/personas/tools/github_search_code.rb similarity index 99% rename from lib/ai_bot/tools/github_search_code.rb rename to lib/personas/tools/github_search_code.rb index a0e1acbd..3bb93d02 100644 --- a/lib/ai_bot/tools/github_search_code.rb +++ b/lib/personas/tools/github_search_code.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class GithubSearchCode < Tool def self.signature diff --git a/lib/ai_bot/tools/github_search_files.rb b/lib/personas/tools/github_search_files.rb similarity index 99% rename from lib/ai_bot/tools/github_search_files.rb rename to lib/personas/tools/github_search_files.rb index 97af3bdf..c97cbd7c 100644 --- a/lib/ai_bot/tools/github_search_files.rb +++ b/lib/personas/tools/github_search_files.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class GithubSearchFiles < Tool def self.signature diff --git a/lib/ai_bot/tools/google.rb b/lib/personas/tools/google.rb similarity index 99% rename from lib/ai_bot/tools/google.rb rename to lib/personas/tools/google.rb index 77158882..bf90fcdb 100644 --- a/lib/ai_bot/tools/google.rb +++ b/lib/personas/tools/google.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Google < Tool def self.signature diff --git a/lib/ai_bot/tools/image.rb b/lib/personas/tools/image.rb similarity index 99% rename from lib/ai_bot/tools/image.rb rename to lib/personas/tools/image.rb index 34e5e3f2..3ab2b705 100644 --- a/lib/ai_bot/tools/image.rb +++ b/lib/personas/tools/image.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Image < Tool def self.signature diff --git a/lib/ai_bot/tools/javascript_evaluator.rb b/lib/personas/tools/javascript_evaluator.rb similarity index 99% rename from lib/ai_bot/tools/javascript_evaluator.rb rename to lib/personas/tools/javascript_evaluator.rb index 77aadd04..23d7c1b1 100644 --- a/lib/ai_bot/tools/javascript_evaluator.rb +++ b/lib/personas/tools/javascript_evaluator.rb @@ -4,7 +4,7 @@ require "mini_racer" require "json" module DiscourseAi - module AiBot + module Personas module Tools class JavascriptEvaluator < Tool TIMEOUT = 500 diff --git a/lib/ai_bot/tools/list_categories.rb b/lib/personas/tools/list_categories.rb similarity index 98% rename from lib/ai_bot/tools/list_categories.rb rename to lib/personas/tools/list_categories.rb index 4a32ce13..895eaae5 100644 --- a/lib/ai_bot/tools/list_categories.rb +++ b/lib/personas/tools/list_categories.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class ListCategories < Tool def self.signature diff --git a/lib/ai_bot/tools/list_tags.rb b/lib/personas/tools/list_tags.rb similarity index 97% rename from lib/ai_bot/tools/list_tags.rb rename to lib/personas/tools/list_tags.rb index 6ff702bf..852c9abc 100644 --- a/lib/ai_bot/tools/list_tags.rb +++ b/lib/personas/tools/list_tags.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class ListTags < Tool def self.signature diff --git a/lib/ai_bot/tools/option.rb b/lib/personas/tools/option.rb similarity index 97% rename from lib/ai_bot/tools/option.rb rename to lib/personas/tools/option.rb index b772ab8c..777475b8 100644 --- a/lib/ai_bot/tools/option.rb +++ b/lib/personas/tools/option.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Option attr_reader :tool, :name, :type, :values, :default diff --git a/lib/ai_bot/tools/random_picker.rb b/lib/personas/tools/random_picker.rb similarity index 99% rename from lib/ai_bot/tools/random_picker.rb rename to lib/personas/tools/random_picker.rb index c1ed16f9..1abd537f 100644 --- a/lib/ai_bot/tools/random_picker.rb +++ b/lib/personas/tools/random_picker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class RandomPicker < Tool def self.signature diff --git a/lib/ai_bot/tools/read.rb b/lib/personas/tools/read.rb similarity index 99% rename from lib/ai_bot/tools/read.rb rename to lib/personas/tools/read.rb index d7a0186f..3077de47 100644 --- a/lib/ai_bot/tools/read.rb +++ b/lib/personas/tools/read.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas MAX_POSTS = 100 module Tools diff --git a/lib/ai_bot/tools/read_artifact.rb b/lib/personas/tools/read_artifact.rb similarity index 99% rename from lib/ai_bot/tools/read_artifact.rb rename to lib/personas/tools/read_artifact.rb index 98b7944f..e3074d71 100644 --- a/lib/ai_bot/tools/read_artifact.rb +++ b/lib/personas/tools/read_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class ReadArtifact < Tool MAX_HTML_SIZE = 30.kilobytes diff --git a/lib/ai_bot/tools/search.rb b/lib/personas/tools/search.rb similarity index 99% rename from lib/ai_bot/tools/search.rb rename to lib/personas/tools/search.rb index 402eca05..869cff58 100644 --- a/lib/ai_bot/tools/search.rb +++ b/lib/personas/tools/search.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Search < Tool attr_reader :last_query diff --git a/lib/ai_bot/tools/search_settings.rb b/lib/personas/tools/search_settings.rb similarity index 99% rename from lib/ai_bot/tools/search_settings.rb rename to lib/personas/tools/search_settings.rb index ad0af7fa..fc66c926 100644 --- a/lib/ai_bot/tools/search_settings.rb +++ b/lib/personas/tools/search_settings.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class SearchSettings < Tool INCLUDE_DESCRIPTIONS_MAX_LENGTH = 10 diff --git a/lib/ai_bot/tools/setting_context.rb b/lib/personas/tools/setting_context.rb similarity index 99% rename from lib/ai_bot/tools/setting_context.rb rename to lib/personas/tools/setting_context.rb index c8acf5aa..7fb7c6b7 100644 --- a/lib/ai_bot/tools/setting_context.rb +++ b/lib/personas/tools/setting_context.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class SettingContext < Tool MAX_CONTEXT_TOKENS = 2000 diff --git a/lib/ai_bot/tools/summarize.rb b/lib/personas/tools/summarize.rb similarity index 99% rename from lib/ai_bot/tools/summarize.rb rename to lib/personas/tools/summarize.rb index 5076635f..94f6cf49 100644 --- a/lib/ai_bot/tools/summarize.rb +++ b/lib/personas/tools/summarize.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Summarize < Tool def self.signature diff --git a/lib/ai_bot/tools/time.rb b/lib/personas/tools/time.rb similarity index 98% rename from lib/ai_bot/tools/time.rb rename to lib/personas/tools/time.rb index bd88eeed..da3d4f43 100644 --- a/lib/ai_bot/tools/time.rb +++ b/lib/personas/tools/time.rb @@ -1,7 +1,7 @@ #frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Time < Tool def self.signature diff --git a/lib/ai_bot/tools/tool.rb b/lib/personas/tools/tool.rb similarity index 97% rename from lib/ai_bot/tools/tool.rb rename to lib/personas/tools/tool.rb index ba0bc69c..4bafe0af 100644 --- a/lib/ai_bot/tools/tool.rb +++ b/lib/personas/tools/tool.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class Tool # Why 30 mega bytes? @@ -63,9 +63,9 @@ module DiscourseAi @persona_options = persona_options @bot_user = bot_user @llm = llm - @context = context.nil? ? DiscourseAi::AiBot::BotContext.new(messages: []) : context - if !@context.is_a?(DiscourseAi::AiBot::BotContext) - raise ArgumentError, "context must be a DiscourseAi::AiBot::Context" + @context = context.nil? ? DiscourseAi::Personas::BotContext.new(messages: []) : context + if !@context.is_a?(DiscourseAi::Personas::BotContext) + raise ArgumentError, "context must be a DiscourseAi::Personas::Context" end end diff --git a/lib/ai_bot/tools/update_artifact.rb b/lib/personas/tools/update_artifact.rb similarity index 99% rename from lib/ai_bot/tools/update_artifact.rb rename to lib/personas/tools/update_artifact.rb index 2c9e544b..ccf39a8d 100644 --- a/lib/ai_bot/tools/update_artifact.rb +++ b/lib/personas/tools/update_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class UpdateArtifact < Tool def self.name diff --git a/lib/ai_bot/tools/web_browser.rb b/lib/personas/tools/web_browser.rb similarity index 99% rename from lib/ai_bot/tools/web_browser.rb rename to lib/personas/tools/web_browser.rb index eb4cf020..3e917cb3 100644 --- a/lib/ai_bot/tools/web_browser.rb +++ b/lib/personas/tools/web_browser.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module DiscourseAi - module AiBot + module Personas module Tools class WebBrowser < Tool def self.signature diff --git a/lib/ai_bot/personas/web_artifact_creator.rb b/lib/personas/web_artifact_creator.rb similarity index 84% rename from lib/ai_bot/personas/web_artifact_creator.rb rename to lib/personas/web_artifact_creator.rb index ec99a778..8fe5ef1c 100644 --- a/lib/ai_bot/personas/web_artifact_creator.rb +++ b/lib/personas/web_artifact_creator.rb @@ -1,19 +1,18 @@ #frozen_string_literal: true module DiscourseAi - module AiBot - module Personas - class WebArtifactCreator < Persona - def tools - [Tools::CreateArtifact, Tools::UpdateArtifact, Tools::ReadArtifact] - end + module Personas + class WebArtifactCreator < Persona + def tools + [Tools::CreateArtifact, Tools::UpdateArtifact, Tools::ReadArtifact] + end - def required_tools - [Tools::CreateArtifact, Tools::UpdateArtifact, Tools::ReadArtifact] - end + def required_tools + [Tools::CreateArtifact, Tools::UpdateArtifact, Tools::ReadArtifact] + end - def system_prompt - <<~PROMPT + def system_prompt + <<~PROMPT You are the Web Creator, an AI assistant specializing in building interactive web components. You create engaging and functional web experiences using HTML, CSS, and JavaScript. You live in a Discourse PM and communicate using Markdown. Core Principles: @@ -49,7 +48,6 @@ module DiscourseAi Remember: Great components combine structure (HTML), presentation (CSS), and behavior (JavaScript) to create memorable user experiences. PROMPT - end end end end diff --git a/plugin.rb b/plugin.rb index 7f6e1389..77bdde7b 100644 --- a/plugin.rb +++ b/plugin.rb @@ -83,6 +83,8 @@ after_initialize do add_admin_route("discourse_ai.title", "discourse-ai", { use_new_show_route: true }) + register_seedfu_fixtures(Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "personas")) + [ DiscourseAi::Embeddings::EntryPoint.new, DiscourseAi::Sentiment::EntryPoint.new, diff --git a/spec/lib/discord/bot/persona_replier_spec.rb b/spec/lib/discord/bot/persona_replier_spec.rb index 00bb4859..9228e1bf 100644 --- a/spec/lib/discord/bot/persona_replier_spec.rb +++ b/spec/lib/discord/bot/persona_replier_spec.rb @@ -13,7 +13,7 @@ RSpec.describe DiscourseAi::Discord::Bot::PersonaReplier do before do SiteSetting.ai_discord_search_persona = persona.id.to_s - allow_any_instance_of(DiscourseAi::AiBot::Bot).to receive(:reply).and_return( + allow_any_instance_of(DiscourseAi::Personas::Bot).to receive(:reply).and_return( "This is a reply from bot!", ) allow(persona_replier).to receive(:create_reply) diff --git a/spec/lib/discord/bot/search_spec.rb b/spec/lib/discord/bot/search_spec.rb index 7d4cebff..36233f60 100644 --- a/spec/lib/discord/bot/search_spec.rb +++ b/spec/lib/discord/bot/search_spec.rb @@ -20,7 +20,7 @@ RSpec.describe DiscourseAi::Discord::Bot::Search do describe "#handle_interaction!" do it "creates a reply with search results" do - allow_any_instance_of(DiscourseAi::AiBot::Tools::Search).to receive(:invoke).and_return( + allow_any_instance_of(DiscourseAi::Personas::Tools::Search).to receive(:invoke).and_return( { rows: [%w[Title /link]] }, ) search.handle_interaction! diff --git a/spec/lib/modules/ai_bot/playground_spec.rb b/spec/lib/modules/ai_bot/playground_spec.rb index 4df2ae5d..e825ebc2 100644 --- a/spec/lib/modules/ai_bot/playground_spec.rb +++ b/spec/lib/modules/ai_bot/playground_spec.rb @@ -22,14 +22,10 @@ RSpec.describe DiscourseAi::AiBot::Playground do fab!(:bot) do persona = AiPersona - .find( - DiscourseAi::AiBot::Personas::Persona.system_personas[ - DiscourseAi::AiBot::Personas::General - ], - ) + .find(DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General]) .class_instance .new - DiscourseAi::AiBot::Bot.as(bot_user, persona: persona) + DiscourseAi::Personas::Bot.as(bot_user, persona: persona) end fab!(:admin) { Fabricate(:admin, refresh_auto_groups: true) } @@ -103,7 +99,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do ) end - let(:bot) { DiscourseAi::AiBot::Bot.as(bot_user, persona: ai_persona.class_instance.new) } + let(:bot) { DiscourseAi::Personas::Bot.as(bot_user, persona: ai_persona.class_instance.new) } let(:playground) { DiscourseAi::AiBot::Playground.new(bot) } @@ -173,8 +169,8 @@ RSpec.describe DiscourseAi::AiBot::Playground do it "uses custom tool in conversation" do persona_klass = AiPersona.all_personas.find { |p| p.name == ai_persona.name } - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona_klass.new) - playground = DiscourseAi::AiBot::Playground.new(bot) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona_klass.new) + playground = described_class.new(bot) responses = [tool_call, "custom tool did stuff (maybe)"] @@ -213,7 +209,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do custom_tool.update!(enabled: false) # so we pick up new cache persona_klass = AiPersona.all_personas.find { |p| p.name == ai_persona.name } - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona_klass.new) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona_klass.new) playground = DiscourseAi::AiBot::Playground.new(bot) responses = ["custom tool did stuff (maybe)", tool_call] @@ -968,7 +964,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do it "supports disabling tool details" do persona = Fabricate(:ai_persona, tool_details: false, tools: ["Search"]) - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.class_instance.new) + bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.class_instance.new) playground = described_class.new(bot) response1 = @@ -1021,13 +1017,11 @@ RSpec.describe DiscourseAi::AiBot::Playground do let(:persona) do AiPersona.find( - DiscourseAi::AiBot::Personas::Persona.system_personas[ - DiscourseAi::AiBot::Personas::DallE3 - ], + DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::DallE3], ) end - let(:bot) { DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.class_instance.new) } + let(:bot) { DiscourseAi::Personas::Bot.as(bot_user, persona: persona.class_instance.new) } let(:data) do image = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" diff --git a/spec/lib/modules/ai_bot/artifact_update_strategies/diff_spec.rb b/spec/lib/personas/artifact_update_strategies/diff_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/artifact_update_strategies/diff_spec.rb rename to spec/lib/personas/artifact_update_strategies/diff_spec.rb index 689984cf..d07813ff 100644 --- a/spec/lib/modules/ai_bot/artifact_update_strategies/diff_spec.rb +++ b/spec/lib/personas/artifact_update_strategies/diff_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::ArtifactUpdateStrategies::Diff do +RSpec.describe DiscourseAi::Personas::ArtifactUpdateStrategies::Diff do fab!(:user) fab!(:post) fab!(:artifact) { Fabricate(:ai_artifact) } diff --git a/spec/lib/modules/ai_bot/bot_spec.rb b/spec/lib/personas/bot_spec.rb similarity index 84% rename from spec/lib/modules/ai_bot/bot_spec.rb rename to spec/lib/personas/bot_spec.rb index 025a37f9..03619957 100644 --- a/spec/lib/modules/ai_bot/bot_spec.rb +++ b/spec/lib/personas/bot_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Bot do +RSpec.describe DiscourseAi::Personas::Bot do subject(:bot) { described_class.as(bot_user) } fab!(:admin) @@ -48,11 +48,11 @@ RSpec.describe DiscourseAi::AiBot::Bot do allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - personaClass = DiscourseAi::AiBot::Personas::Persona.find_by(user: admin, name: "TestPersona") + personaClass = DiscourseAi::Personas::Persona.find_by(user: admin, name: "TestPersona") - bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: personaClass.new) + bot = described_class.as(bot_user, persona: personaClass.new) bot.reply( - DiscourseAi::AiBot::BotContext.new(messages: [{ type: :user, content: "test" }]), + DiscourseAi::Personas::BotContext.new(messages: [{ type: :user, content: "test" }]), ) do |_partial, _cancel, _placeholder| # we just need the block so bot has something to call with results end @@ -64,7 +64,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do context "when using function chaining" do it "yields a loading placeholder while proceeds to invoke the command" do - tool = DiscourseAi::AiBot::Tools::ListCategories.new({}, bot_user: nil, llm: nil) + tool = DiscourseAi::Personas::Tools::ListCategories.new({}, bot_user: nil, llm: nil) partial_placeholder = +(<<~HTML)
#{tool.summary} @@ -75,7 +75,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do HTML context = - DiscourseAi::AiBot::BotContext.new( + DiscourseAi::Personas::BotContext.new( messages: [{ type: :user, content: "Does my site has tags?" }], ) diff --git a/spec/lib/modules/ai_bot/personas/persona_spec.rb b/spec/lib/personas/persona_spec.rb similarity index 76% rename from spec/lib/modules/ai_bot/personas/persona_spec.rb rename to spec/lib/personas/persona_spec.rb index 8bd6634d..70f4c327 100644 --- a/spec/lib/modules/ai_bot/personas/persona_spec.rb +++ b/spec/lib/personas/persona_spec.rb @@ -1,11 +1,11 @@ #frozen_string_literal: true -class TestPersona < DiscourseAi::AiBot::Personas::Persona +class TestPersona < DiscourseAi::Personas::Persona def tools [ - DiscourseAi::AiBot::Tools::ListTags, - DiscourseAi::AiBot::Tools::Search, - DiscourseAi::AiBot::Tools::Image, + DiscourseAi::Personas::Tools::ListTags, + DiscourseAi::Personas::Tools::Search, + DiscourseAi::Personas::Tools::Image, ] end def system_prompt @@ -19,7 +19,7 @@ class TestPersona < DiscourseAi::AiBot::Personas::Persona end end -RSpec.describe DiscourseAi::AiBot::Personas::Persona do +RSpec.describe DiscourseAi::Personas::Persona do let :persona do TestPersona.new end @@ -36,7 +36,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do end let(:context) do - DiscourseAi::AiBot::BotContext.new( + DiscourseAi::Personas::BotContext.new( site_url: Discourse.base_url, site_title: "test site title", site_description: "test site description", @@ -84,12 +84,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do ) tool_instance = - DiscourseAi::AiBot::Personas::Artist.new.find_tool( - tool_call, - bot_user: nil, - llm: nil, - context: nil, - ) + DiscourseAi::Personas::Artist.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:prompts]).to eq(["cat oil painting", "big car"]) expect(tool_instance.parameters[:aspect_ratio]).to eq("16:9") @@ -108,12 +103,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do ) tool_instance = - DiscourseAi::AiBot::Personas::General.new.find_tool( - tool_call, - bot_user: nil, - llm: nil, - context: nil, - ) + DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters.key?(:status)).to eq(false) @@ -129,12 +119,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do ) tool_instance = - DiscourseAi::AiBot::Personas::General.new.find_tool( - tool_call, - bot_user: nil, - llm: nil, - context: nil, - ) + DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:status]).to eq("open") end @@ -152,12 +137,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do ) search = - DiscourseAi::AiBot::Personas::General.new.find_tool( - tool_call, - bot_user: nil, - llm: nil, - context: nil, - ) + DiscourseAi::Personas::General.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(search.parameters[:max_posts]).to eq(3) expect(search.parameters[:search_query]).to eq("hello world") @@ -177,12 +157,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do ) tool_instance = - DiscourseAi::AiBot::Personas::DallE3.new.find_tool( - tool_call, - bot_user: nil, - llm: nil, - context: nil, - ) + DiscourseAi::Personas::DallE3.new.find_tool(tool_call, bot_user: nil, llm: nil, context: nil) expect(tool_instance.parameters[:prompts]).to eq(["cat oil painting", "big car"]) end @@ -200,29 +175,29 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]], ) - custom_persona = DiscourseAi::AiBot::Personas::Persona.all(user: user).last + custom_persona = DiscourseAi::Personas::Persona.all(user: user).last expect(custom_persona.name).to eq("zzzpun_bot") expect(custom_persona.description).to eq("you write puns") instance = custom_persona.new - expect(instance.tools).to eq([DiscourseAi::AiBot::Tools::Image]) + expect(instance.tools).to eq([DiscourseAi::Personas::Tools::Image]) expect(instance.craft_prompt(context).messages.first[:content]).to eq("you are pun bot") # should update persona.update!(name: "zzzpun_bot2") - custom_persona = DiscourseAi::AiBot::Personas::Persona.all(user: user).last + custom_persona = DiscourseAi::Personas::Persona.all(user: user).last expect(custom_persona.name).to eq("zzzpun_bot2") # can be disabled persona.update!(enabled: false) - last_persona = DiscourseAi::AiBot::Personas::Persona.all(user: user).last + last_persona = DiscourseAi::Personas::Persona.all(user: user).last expect(last_persona.name).not_to eq("zzzpun_bot2") persona.update!(enabled: true) # no groups have access persona.update!(allowed_group_ids: []) - last_persona = DiscourseAi::AiBot::Personas::Persona.all(user: user).last + last_persona = DiscourseAi::Personas::Persona.all(user: user).last expect(last_persona.name).not_to eq("zzzpun_bot2") end end @@ -237,31 +212,31 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do SiteSetting.ai_google_custom_search_cx = "abc123" # should be ordered by priority and then alpha - expect(DiscourseAi::AiBot::Personas::Persona.all(user: user)).to eq( + expect(DiscourseAi::Personas::Persona.all(user: user)).to eq( [ - DiscourseAi::AiBot::Personas::General, - DiscourseAi::AiBot::Personas::Artist, - DiscourseAi::AiBot::Personas::Creative, - DiscourseAi::AiBot::Personas::DiscourseHelper, - DiscourseAi::AiBot::Personas::GithubHelper, - DiscourseAi::AiBot::Personas::Researcher, - DiscourseAi::AiBot::Personas::SettingsExplorer, - DiscourseAi::AiBot::Personas::SqlHelper, + DiscourseAi::Personas::General, + DiscourseAi::Personas::Artist, + DiscourseAi::Personas::Creative, + DiscourseAi::Personas::DiscourseHelper, + DiscourseAi::Personas::GithubHelper, + DiscourseAi::Personas::Researcher, + DiscourseAi::Personas::SettingsExplorer, + DiscourseAi::Personas::SqlHelper, ], ) # it should allow staff access to WebArtifactCreator - expect(DiscourseAi::AiBot::Personas::Persona.all(user: admin)).to eq( + expect(DiscourseAi::Personas::Persona.all(user: admin)).to eq( [ - DiscourseAi::AiBot::Personas::General, - DiscourseAi::AiBot::Personas::Artist, - DiscourseAi::AiBot::Personas::Creative, - DiscourseAi::AiBot::Personas::DiscourseHelper, - DiscourseAi::AiBot::Personas::GithubHelper, - DiscourseAi::AiBot::Personas::Researcher, - DiscourseAi::AiBot::Personas::SettingsExplorer, - DiscourseAi::AiBot::Personas::SqlHelper, - DiscourseAi::AiBot::Personas::WebArtifactCreator, + DiscourseAi::Personas::General, + DiscourseAi::Personas::Artist, + DiscourseAi::Personas::Creative, + DiscourseAi::Personas::DiscourseHelper, + DiscourseAi::Personas::GithubHelper, + DiscourseAi::Personas::Researcher, + DiscourseAi::Personas::SettingsExplorer, + DiscourseAi::Personas::SqlHelper, + DiscourseAi::Personas::WebArtifactCreator, ], ) @@ -270,27 +245,25 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do SiteSetting.ai_google_custom_search_api_key = "" SiteSetting.ai_artifact_security = "disabled" - expect(DiscourseAi::AiBot::Personas::Persona.all(user: admin)).to contain_exactly( - DiscourseAi::AiBot::Personas::General, - DiscourseAi::AiBot::Personas::SqlHelper, - DiscourseAi::AiBot::Personas::SettingsExplorer, - DiscourseAi::AiBot::Personas::Creative, - DiscourseAi::AiBot::Personas::DiscourseHelper, - DiscourseAi::AiBot::Personas::GithubHelper, + expect(DiscourseAi::Personas::Persona.all(user: admin)).to contain_exactly( + DiscourseAi::Personas::General, + DiscourseAi::Personas::SqlHelper, + DiscourseAi::Personas::SettingsExplorer, + DiscourseAi::Personas::Creative, + DiscourseAi::Personas::DiscourseHelper, + DiscourseAi::Personas::GithubHelper, ) AiPersona.find( - DiscourseAi::AiBot::Personas::Persona.system_personas[ - DiscourseAi::AiBot::Personas::General - ], + DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General], ).update!(enabled: false) - expect(DiscourseAi::AiBot::Personas::Persona.all(user: user)).to contain_exactly( - DiscourseAi::AiBot::Personas::SqlHelper, - DiscourseAi::AiBot::Personas::SettingsExplorer, - DiscourseAi::AiBot::Personas::Creative, - DiscourseAi::AiBot::Personas::DiscourseHelper, - DiscourseAi::AiBot::Personas::GithubHelper, + expect(DiscourseAi::Personas::Persona.all(user: user)).to contain_exactly( + DiscourseAi::Personas::SqlHelper, + DiscourseAi::Personas::SettingsExplorer, + DiscourseAi::Personas::Creative, + DiscourseAi::Personas::DiscourseHelper, + DiscourseAi::Personas::GithubHelper, ) end end @@ -304,7 +277,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do SiteSetting.ai_embeddings_enabled = true end - let(:ai_persona) { DiscourseAi::AiBot::Personas::Persona.all(user: user).first.new } + let(:ai_persona) { DiscourseAi::Personas::Persona.all(user: user).first.new } let(:with_cc) do context.messages = [{ content: "Tell me the time", type: :user }] @@ -343,7 +316,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do UploadReference.ensure_exist!(target: custom_ai_persona, upload_ids: [upload.id]) custom_persona = - DiscourseAi::AiBot::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new + DiscourseAi::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new # this means that we will consolidate context.messages = [ @@ -415,7 +388,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do UploadReference.ensure_exist!(target: custom_ai_persona, upload_ids: [upload.id]) custom_persona = - DiscourseAi::AiBot::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new + DiscourseAi::Personas::Persona.find_by(id: custom_ai_persona.id, user: user).new expect(custom_persona.class.rag_conversation_chunks).to eq(3) diff --git a/spec/lib/modules/ai_bot/question_consolidator_spec.rb b/spec/lib/personas/question_consolidator_spec.rb similarity index 94% rename from spec/lib/modules/ai_bot/question_consolidator_spec.rb rename to spec/lib/personas/question_consolidator_spec.rb index ece7c973..7fe54399 100644 --- a/spec/lib/modules/ai_bot/question_consolidator_spec.rb +++ b/spec/lib/personas/question_consolidator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::QuestionConsolidator do +RSpec.describe DiscourseAi::Personas::QuestionConsolidator do let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{Fabricate(:fake_model).id}") } let(:fake_endpoint) { DiscourseAi::Completions::Endpoints::Fake } diff --git a/spec/lib/modules/ai_bot/personas/researcher_spec.rb b/spec/lib/personas/researcher_spec.rb similarity index 51% rename from spec/lib/modules/ai_bot/personas/researcher_spec.rb rename to spec/lib/personas/researcher_spec.rb index c3c65888..d216d6c0 100644 --- a/spec/lib/modules/ai_bot/personas/researcher_spec.rb +++ b/spec/lib/personas/researcher_spec.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Personas::Researcher do +RSpec.describe DiscourseAi::Personas::Researcher do let :researcher do subject end it "renders schema" do expect(researcher.tools).to eq( - [DiscourseAi::AiBot::Tools::Google, DiscourseAi::AiBot::Tools::WebBrowser], + [DiscourseAi::Personas::Tools::Google, DiscourseAi::Personas::Tools::WebBrowser], ) end end diff --git a/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb b/spec/lib/personas/settings_explorer_spec.rb similarity index 69% rename from spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb rename to spec/lib/personas/settings_explorer_spec.rb index d7a60cd1..24f15224 100644 --- a/spec/lib/modules/ai_bot/personas/settings_explorer_spec.rb +++ b/spec/lib/personas/settings_explorer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Personas::SettingsExplorer do +RSpec.describe DiscourseAi::Personas::SettingsExplorer do let :settings_explorer do subject end @@ -14,7 +14,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::SettingsExplorer do expect(prompt).to include("site_description") expect(settings_explorer.tools).to eq( - [DiscourseAi::AiBot::Tools::SettingContext, DiscourseAi::AiBot::Tools::SearchSettings], + [DiscourseAi::Personas::Tools::SettingContext, DiscourseAi::Personas::Tools::SearchSettings], ) end end diff --git a/spec/lib/modules/ai_bot/personas/sql_helper_spec.rb b/spec/lib/personas/sql_helper_spec.rb similarity index 73% rename from spec/lib/modules/ai_bot/personas/sql_helper_spec.rb rename to spec/lib/personas/sql_helper_spec.rb index 37d645bc..d2f1614e 100644 --- a/spec/lib/modules/ai_bot/personas/sql_helper_spec.rb +++ b/spec/lib/personas/sql_helper_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Personas::SqlHelper do +RSpec.describe DiscourseAi::Personas::SqlHelper do let :sql_helper do subject end @@ -12,6 +12,6 @@ RSpec.describe DiscourseAi::AiBot::Personas::SqlHelper do expect(prompt).not_to include("translation_key") # not a priority table expect(prompt).to include("user_api_keys") # not a priority table - expect(sql_helper.tools).to eq([DiscourseAi::AiBot::Tools::DbSchema]) + expect(sql_helper.tools).to eq([DiscourseAi::Personas::Tools::DbSchema]) end end diff --git a/spec/lib/modules/ai_bot/tools/create_artifact_spec.rb b/spec/lib/personas/tools/create_artifact_spec.rb similarity index 92% rename from spec/lib/modules/ai_bot/tools/create_artifact_spec.rb rename to spec/lib/personas/tools/create_artifact_spec.rb index 26d47528..929e54ef 100644 --- a/spec/lib/modules/ai_bot/tools/create_artifact_spec.rb +++ b/spec/lib/personas/tools/create_artifact_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::CreateArtifact do +RSpec.describe DiscourseAi::Personas::Tools::CreateArtifact do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } fab!(:post) @@ -34,7 +34,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::CreateArtifact do { html_body: "hello" }, bot_user: Fabricate(:user), llm: llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) tool.parameters = { name: "hello", specification: "hello spec" } diff --git a/spec/lib/modules/ai_bot/tools/dall_e_spec.rb b/spec/lib/personas/tools/dall_e_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/tools/dall_e_spec.rb rename to spec/lib/personas/tools/dall_e_spec.rb index dfb4e9d3..fff8ee61 100644 --- a/spec/lib/modules/ai_bot/tools/dall_e_spec.rb +++ b/spec/lib/personas/tools/dall_e_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::DallE do +RSpec.describe DiscourseAi::Personas::Tools::DallE do let(:prompts) { ["a pink cow", "a red cow"] } fab!(:gpt_35_turbo) { Fabricate(:llm_model, name: "gpt-3.5-turbo") } diff --git a/spec/lib/modules/ai_bot/tools/db_schema_spec.rb b/spec/lib/personas/tools/db_schema_spec.rb similarity index 92% rename from spec/lib/modules/ai_bot/tools/db_schema_spec.rb rename to spec/lib/personas/tools/db_schema_spec.rb index f0f3ae29..643e3fe7 100644 --- a/spec/lib/modules/ai_bot/tools/db_schema_spec.rb +++ b/spec/lib/personas/tools/db_schema_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::DbSchema do +RSpec.describe DiscourseAi::Personas::Tools::DbSchema do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/discourse_meta_search_spec.rb b/spec/lib/personas/tools/discourse_meta_search_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/discourse_meta_search_spec.rb rename to spec/lib/personas/tools/discourse_meta_search_spec.rb index 375f6d0c..1ccc4d4d 100644 --- a/spec/lib/modules/ai_bot/tools/discourse_meta_search_spec.rb +++ b/spec/lib/personas/tools/discourse_meta_search_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::DiscourseMetaSearch do +RSpec.describe DiscourseAi::Personas::Tools::DiscourseMetaSearch do before { SiteSetting.ai_bot_enabled = true } fab!(:llm_model) { Fabricate(:llm_model, max_prompt_tokens: 8192) } diff --git a/spec/lib/modules/ai_bot/tools/github_file_content_spec.rb b/spec/lib/personas/tools/github_file_content_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/github_file_content_spec.rb rename to spec/lib/personas/tools/github_file_content_spec.rb index 8018813b..4186dd01 100644 --- a/spec/lib/modules/ai_bot/tools/github_file_content_spec.rb +++ b/spec/lib/personas/tools/github_file_content_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::AiBot::Tools::GithubFileContent do +RSpec.describe DiscourseAi::Personas::Tools::GithubFileContent do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/github_pull_request_diff_spec.rb b/spec/lib/personas/tools/github_pull_request_diff_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/tools/github_pull_request_diff_spec.rb rename to spec/lib/personas/tools/github_pull_request_diff_spec.rb index 428bd72b..e8b3d226 100644 --- a/spec/lib/modules/ai_bot/tools/github_pull_request_diff_spec.rb +++ b/spec/lib/personas/tools/github_pull_request_diff_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::AiBot::Tools::GithubPullRequestDiff do +RSpec.describe DiscourseAi::Personas::Tools::GithubPullRequestDiff do let(:bot_user) { Fabricate(:user) } fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/github_search_code_spec.rb b/spec/lib/personas/tools/github_search_code_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/github_search_code_spec.rb rename to spec/lib/personas/tools/github_search_code_spec.rb index b9cebe36..b8fbca27 100644 --- a/spec/lib/modules/ai_bot/tools/github_search_code_spec.rb +++ b/spec/lib/personas/tools/github_search_code_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::AiBot::Tools::GithubSearchCode do +RSpec.describe DiscourseAi::Personas::Tools::GithubSearchCode do let(:bot_user) { Fabricate(:user) } fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/github_search_files_spec.rb b/spec/lib/personas/tools/github_search_files_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/github_search_files_spec.rb rename to spec/lib/personas/tools/github_search_files_spec.rb index a0352a38..cc6926fd 100644 --- a/spec/lib/modules/ai_bot/tools/github_search_files_spec.rb +++ b/spec/lib/personas/tools/github_search_files_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::AiBot::Tools::GithubSearchFiles do +RSpec.describe DiscourseAi::Personas::Tools::GithubSearchFiles do fab!(:llm_model) let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/google_spec.rb b/spec/lib/personas/tools/google_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/tools/google_spec.rb rename to spec/lib/personas/tools/google_spec.rb index c39f94b0..5062cea9 100644 --- a/spec/lib/modules/ai_bot/tools/google_spec.rb +++ b/spec/lib/personas/tools/google_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Google do +RSpec.describe DiscourseAi::Personas::Tools::Google do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/image_spec.rb b/spec/lib/personas/tools/image_spec.rb similarity index 94% rename from spec/lib/modules/ai_bot/tools/image_spec.rb rename to spec/lib/personas/tools/image_spec.rb index aea98dd9..342c9f67 100644 --- a/spec/lib/modules/ai_bot/tools/image_spec.rb +++ b/spec/lib/personas/tools/image_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Image do +RSpec.describe DiscourseAi::Personas::Tools::Image do let(:progress_blk) { Proc.new {} } let(:prompts) { ["a pink cow", "a red cow"] } @@ -9,7 +9,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::Image do { prompts: prompts, seeds: [99, 32] }, bot_user: bot_user, llm: llm, - context: DiscourseAi::AiBot::BotContext.new, + context: DiscourseAi::Personas::BotContext.new, ) end diff --git a/spec/lib/modules/ai_bot/tools/javascript_evaluator_spec.rb b/spec/lib/personas/tools/javascript_evaluator_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/javascript_evaluator_spec.rb rename to spec/lib/personas/tools/javascript_evaluator_spec.rb index 8b2a544c..cae05ee9 100644 --- a/spec/lib/modules/ai_bot/tools/javascript_evaluator_spec.rb +++ b/spec/lib/personas/tools/javascript_evaluator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::JavascriptEvaluator do +RSpec.describe DiscourseAi::Personas::Tools::JavascriptEvaluator do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/list_categories_spec.rb b/spec/lib/personas/tools/list_categories_spec.rb similarity index 90% rename from spec/lib/modules/ai_bot/tools/list_categories_spec.rb rename to spec/lib/personas/tools/list_categories_spec.rb index a2e92b86..bcda2123 100644 --- a/spec/lib/modules/ai_bot/tools/list_categories_spec.rb +++ b/spec/lib/personas/tools/list_categories_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::ListCategories do +RSpec.describe DiscourseAi::Personas::Tools::ListCategories do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/list_tags_spec.rb b/spec/lib/personas/tools/list_tags_spec.rb similarity index 92% rename from spec/lib/modules/ai_bot/tools/list_tags_spec.rb rename to spec/lib/personas/tools/list_tags_spec.rb index 0019a884..b8f4ed5c 100644 --- a/spec/lib/modules/ai_bot/tools/list_tags_spec.rb +++ b/spec/lib/personas/tools/list_tags_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::ListTags do +RSpec.describe DiscourseAi::Personas::Tools::ListTags do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/random_picker_spec.rb b/spec/lib/personas/tools/random_picker_spec.rb similarity index 96% rename from spec/lib/modules/ai_bot/tools/random_picker_spec.rb rename to spec/lib/personas/tools/random_picker_spec.rb index 6e5930bf..65f7c7f2 100644 --- a/spec/lib/modules/ai_bot/tools/random_picker_spec.rb +++ b/spec/lib/personas/tools/random_picker_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe DiscourseAi::AiBot::Tools::RandomPicker do +RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do describe "#invoke" do subject { described_class.new({ options: options }, bot_user: nil, llm: nil).invoke } diff --git a/spec/lib/modules/ai_bot/tools/read_artifact_spec.rb b/spec/lib/personas/tools/read_artifact_spec.rb similarity index 90% rename from spec/lib/modules/ai_bot/tools/read_artifact_spec.rb rename to spec/lib/personas/tools/read_artifact_spec.rb index 8247c528..279dd18e 100644 --- a/spec/lib/modules/ai_bot/tools/read_artifact_spec.rb +++ b/spec/lib/personas/tools/read_artifact_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do +RSpec.describe DiscourseAi::Personas::Tools::ReadArtifact do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } fab!(:post) @@ -25,7 +25,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do { url: "#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/#{artifact.id}" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) result = tool.invoke {} @@ -44,7 +44,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do { url: "invalid-url" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) result = tool.invoke {} @@ -58,7 +58,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do { url: "#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/99999" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) result = tool.invoke {} @@ -91,7 +91,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do { url: "https://example.com" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) result = tool.invoke {} @@ -120,7 +120,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::ReadArtifact do { url: "https://example.com" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(post: post), + context: DiscourseAi::Personas::BotContext.new(post: post), ) result = tool.invoke {} diff --git a/spec/lib/modules/ai_bot/tools/read_spec.rb b/spec/lib/personas/tools/read_spec.rb similarity index 94% rename from spec/lib/modules/ai_bot/tools/read_spec.rb rename to spec/lib/personas/tools/read_spec.rb index 88dc7906..2affc1f4 100644 --- a/spec/lib/modules/ai_bot/tools/read_spec.rb +++ b/spec/lib/personas/tools/read_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Read do +RSpec.describe DiscourseAi::Personas::Tools::Read do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } @@ -56,7 +56,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::Read do persona_options: { "read_private" => true, }, - context: DiscourseAi::AiBot::BotContext.new(user: admin), + context: DiscourseAi::Personas::BotContext.new(user: admin), ) results = tool.invoke expect(results[:content]).to include("hello there") @@ -66,7 +66,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::Read do { topic_id: topic_with_tags.id, post_numbers: [post1.post_number] }, bot_user: bot_user, llm: llm, - context: DiscourseAi::AiBot::BotContext.new(user: admin), + context: DiscourseAi::Personas::BotContext.new(user: admin), ) results = tool.invoke diff --git a/spec/lib/modules/ai_bot/tools/search_settings_spec.rb b/spec/lib/personas/tools/search_settings_spec.rb similarity index 97% rename from spec/lib/modules/ai_bot/tools/search_settings_spec.rb rename to spec/lib/personas/tools/search_settings_spec.rb index 0796e89a..f3cd4356 100644 --- a/spec/lib/modules/ai_bot/tools/search_settings_spec.rb +++ b/spec/lib/personas/tools/search_settings_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::SearchSettings do +RSpec.describe DiscourseAi::Personas::Tools::SearchSettings do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/search_spec.rb b/spec/lib/personas/tools/search_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/tools/search_spec.rb rename to spec/lib/personas/tools/search_spec.rb index c7be7522..c28696f9 100644 --- a/spec/lib/modules/ai_bot/tools/search_spec.rb +++ b/spec/lib/personas/tools/search_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Search do +RSpec.describe DiscourseAi::Personas::Tools::Search do before { SearchIndexer.enable } after { SearchIndexer.disable } @@ -60,7 +60,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::Search do persona_options: persona_options, bot_user: bot_user, llm: llm, - context: DiscourseAi::AiBot::BotContext.new(user: user), + context: DiscourseAi::Personas::BotContext.new(user: user), ) expect(search.options[:base_query]).to eq("#funny") diff --git a/spec/lib/modules/ai_bot/tools/setting_context_spec.rb b/spec/lib/personas/tools/setting_context_spec.rb similarity index 95% rename from spec/lib/modules/ai_bot/tools/setting_context_spec.rb rename to spec/lib/personas/tools/setting_context_spec.rb index 36240ffb..20e26b64 100644 --- a/spec/lib/modules/ai_bot/tools/setting_context_spec.rb +++ b/spec/lib/personas/tools/setting_context_spec.rb @@ -8,7 +8,7 @@ def has_rg? end end -RSpec.describe DiscourseAi::AiBot::Tools::SettingContext, if: has_rg? do +RSpec.describe DiscourseAi::Personas::Tools::SettingContext, if: has_rg? do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } diff --git a/spec/lib/modules/ai_bot/tools/summarize_spec.rb b/spec/lib/personas/tools/summarize_spec.rb similarity index 96% rename from spec/lib/modules/ai_bot/tools/summarize_spec.rb rename to spec/lib/personas/tools/summarize_spec.rb index 2e48b196..2bda3cd3 100644 --- a/spec/lib/modules/ai_bot/tools/summarize_spec.rb +++ b/spec/lib/personas/tools/summarize_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Summarize do +RSpec.describe DiscourseAi::Personas::Tools::Summarize do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/time_spec.rb b/spec/lib/personas/tools/time_spec.rb similarity index 92% rename from spec/lib/modules/ai_bot/tools/time_spec.rb rename to spec/lib/personas/tools/time_spec.rb index e389ea99..e92a32ad 100644 --- a/spec/lib/modules/ai_bot/tools/time_spec.rb +++ b/spec/lib/personas/tools/time_spec.rb @@ -1,6 +1,6 @@ #frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Time do +RSpec.describe DiscourseAi::Personas::Tools::Time do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/lib/modules/ai_bot/tools/tool_spec.rb b/spec/lib/personas/tools/tool_spec.rb similarity index 95% rename from spec/lib/modules/ai_bot/tools/tool_spec.rb rename to spec/lib/personas/tools/tool_spec.rb index e7eca272..5896d8e3 100644 --- a/spec/lib/modules/ai_bot/tools/tool_spec.rb +++ b/spec/lib/personas/tools/tool_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::Tool do +RSpec.describe DiscourseAi::Personas::Tools::Tool do let :tool_class do described_class end diff --git a/spec/lib/modules/ai_bot/tools/update_artifact_spec.rb b/spec/lib/personas/tools/update_artifact_spec.rb similarity index 92% rename from spec/lib/modules/ai_bot/tools/update_artifact_spec.rb rename to spec/lib/personas/tools/update_artifact_spec.rb index f3d2a28f..3f8b7f06 100644 --- a/spec/lib/modules/ai_bot/tools/update_artifact_spec.rb +++ b/spec/lib/personas/tools/update_artifact_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do +RSpec.describe DiscourseAi::Personas::Tools::UpdateArtifact do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } fab!(:post) @@ -47,7 +47,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do persona_options: { "update_algorithm" => "full", }, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -91,7 +91,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do persona_options: { "update_algorithm" => "full", }, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -115,7 +115,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do { artifact_id: artifact.id, instructions: "Invalid update" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -129,7 +129,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do { artifact_id: -1, instructions: "Update something" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -155,7 +155,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do persona_options: { "update_algorithm" => "full", }, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) tool.invoke {} @@ -186,7 +186,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do persona_options: { "update_algorithm" => "full", }, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) .invoke {} end @@ -212,7 +212,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do persona_options: { "update_algorithm" => "full", }, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), ) result = tool.invoke {} @@ -262,7 +262,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), persona_options: { "update_algorithm" => "diff", }, @@ -330,7 +330,7 @@ RSpec.describe DiscourseAi::AiBot::Tools::UpdateArtifact do { artifact_id: artifact.id, instructions: "Change the text to Updated and color to red" }, bot_user: bot_user, llm: llm_model.to_llm, - context: DiscourseAi::AiBot::BotContext.new(messages: [], post: post), + context: DiscourseAi::Personas::BotContext.new(messages: [], post: post), persona_options: { "update_algorithm" => "diff", }, diff --git a/spec/lib/modules/ai_bot/tools/web_browser_spec.rb b/spec/lib/personas/tools/web_browser_spec.rb similarity index 98% rename from spec/lib/modules/ai_bot/tools/web_browser_spec.rb rename to spec/lib/personas/tools/web_browser_spec.rb index 40e17c8c..aebd4e66 100644 --- a/spec/lib/modules/ai_bot/tools/web_browser_spec.rb +++ b/spec/lib/personas/tools/web_browser_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe DiscourseAi::AiBot::Tools::WebBrowser do +RSpec.describe DiscourseAi::Personas::Tools::WebBrowser do fab!(:llm_model) let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) } let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") } diff --git a/spec/models/ai_tool_spec.rb b/spec/models/ai_tool_spec.rb index 45ec3175..30aa8a23 100644 --- a/spec/models/ai_tool_spec.rb +++ b/spec/models/ai_tool_spec.rb @@ -121,7 +121,7 @@ RSpec.describe AiTool do }, ) - expect { runner.invoke }.to raise_error(DiscourseAi::AiBot::ToolRunner::TooManyRequestsError) + expect { runner.invoke }.to raise_error(DiscourseAi::Personas::ToolRunner::TooManyRequestsError) end it "can perform GET HTTP requests" do diff --git a/spec/requests/admin/ai_personas_controller_spec.rb b/spec/requests/admin/ai_personas_controller_spec.rb index c9c036c0..c7989e28 100644 --- a/spec/requests/admin/ai_personas_controller_spec.rb +++ b/spec/requests/admin/ai_personas_controller_spec.rb @@ -19,7 +19,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do expect(response.parsed_body["ai_personas"].length).to eq(AiPersona.count) expect(response.parsed_body["meta"]["tools"].length).to eq( - DiscourseAi::AiBot::Personas::Persona.all_available_tools.length, + DiscourseAi::Personas::Persona.all_available_tools.length, ) end @@ -136,10 +136,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do it "returns localized persona names and descriptions" do get "/admin/plugins/discourse-ai/ai-personas.json" - id = - DiscourseAi::AiBot::Personas::Persona.system_personas[ - DiscourseAi::AiBot::Personas::General - ] + id = DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General] persona = response.parsed_body["ai_personas"].find { |p| p["id"] == id } expect(persona["name"]).to eq("Général") @@ -301,7 +298,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do end it "does not allow temperature and top p changes on stock personas" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", params: { ai_persona: { top_p: 0.5, @@ -335,7 +332,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do context "with system personas" do it "does not allow editing of system prompts" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", params: { ai_persona: { system_prompt: "you are not a helpful bot", @@ -348,7 +345,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do end it "does not allow editing of tools" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", params: { ai_persona: { tools: %w[SearchCommand ImageCommand], @@ -361,7 +358,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do end it "does not allow editing of name and description cause it is localized" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", params: { ai_persona: { name: "bob", @@ -375,7 +372,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do end it "does allow some actions" do - put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json", + put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json", params: { ai_persona: { allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_1]], @@ -413,7 +410,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do it "is not allowed to delete system personas" do expect { - delete "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json" + delete "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}.json" expect(response).to have_http_status(:unprocessable_entity) expect(response.parsed_body["errors"].join).not_to be_blank # let's make sure this is translated diff --git a/spec/system/admin_ai_persona_spec.rb b/spec/system/admin_ai_persona_spec.rb index 79fd57e0..1fc48508 100644 --- a/spec/system/admin_ai_persona_spec.rb +++ b/spec/system/admin_ai_persona_spec.rb @@ -55,7 +55,7 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do end it "will not allow deletion or editing of system personas" do - visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}/edit" + visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::Personas::Persona.system_personas.values.first}/edit" expect(page).not_to have_selector(".ai-persona-editor__delete") expect(form.field("system_prompt")).to be_disabled end diff --git a/spec/system/ai_bot/persona_spec.rb b/spec/system/ai_bot/persona_spec.rb index 6d1edf4b..1cf5e231 100644 --- a/spec/system/ai_bot/persona_spec.rb +++ b/spec/system/ai_bot/persona_spec.rb @@ -16,7 +16,7 @@ RSpec.describe "AI personas", type: :system, js: true do persona_selector = PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown") - id = DiscourseAi::AiBot::Personas::Persona.all(user: admin).first.id + id = DiscourseAi::Personas::Persona.all(user: admin).first.id expect(persona_selector).to have_selected_value(id)