diff --git a/app/controllers/discourse_ai/admin/ai_personas_controller.rb b/app/controllers/discourse_ai/admin/ai_personas_controller.rb
index d272910b..b4fd66b1 100644
--- a/app/controllers/discourse_ai/admin/ai_personas_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_personas_controller.rb
@@ -96,7 +96,6 @@ module DiscourseAi
:temperature,
:default_llm,
:user_id,
- :mentionable,
:max_context_posts,
:vision_enabled,
:vision_max_pixels,
@@ -104,9 +103,13 @@ module DiscourseAi
:rag_chunk_overlap_tokens,
:rag_conversation_chunks,
:question_consolidator_llm,
- :allow_chat,
+ :allow_chat_channel_mentions,
+ :allow_chat_direct_messages,
+ :allow_topic_mentions,
+ :allow_personal_messages,
:tool_details,
:forced_tool_count,
+ :force_default_llm,
allowed_group_ids: [],
rag_uploads: [:id],
)
diff --git a/app/models/ai_persona.rb b/app/models/ai_persona.rb
index 350ec064..15c3c163 100644
--- a/app/models/ai_persona.rb
+++ b/app/models/ai_persona.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class AiPersona < ActiveRecord::Base
- # TODO remove this line 01-11-2024
- self.ignored_columns = [:commands]
+ # TODO remove this line 01-1-2025
+ self.ignored_columns = %i[commands allow_chat mentionable]
# places a hard limit, so per site we cache a maximum of 500 classes
MAX_PERSONAS_PER_SITE = 500
@@ -52,30 +52,47 @@ class AiPersona < ActiveRecord::Base
persona_cache[:persona_users] ||= AiPersona
.where(enabled: true)
.joins(:user)
- .pluck(
- "ai_personas.id, users.id, users.username_lower, allowed_group_ids, default_llm, mentionable, allow_chat",
- )
- .map do |id, user_id, username, allowed_group_ids, default_llm, mentionable, allow_chat|
+ .map do |persona|
{
- id: id,
- user_id: user_id,
- username: username,
- allowed_group_ids: allowed_group_ids,
- default_llm: default_llm,
- mentionable: mentionable,
- allow_chat: allow_chat,
+ id: persona.id,
+ user_id: persona.user_id,
+ username: persona.user.username_lower,
+ allowed_group_ids: persona.allowed_group_ids,
+ default_llm: persona.default_llm,
+ force_default_llm: persona.force_default_llm,
+ allow_chat_channel_mentions: persona.allow_chat_channel_mentions,
+ allow_chat_direct_messages: persona.allow_chat_direct_messages,
+ allow_topic_mentions: persona.allow_topic_mentions,
+ allow_personal_messages: persona.allow_personal_messages,
}
end
if user
- persona_users.select { |mentionable| user.in_any_groups?(mentionable[:allowed_group_ids]) }
+ persona_users.select { |persona_user| user.in_any_groups?(persona_user[:allowed_group_ids]) }
else
persona_users
end
end
- def self.allowed_chat(user: nil)
- personas = persona_cache[:allowed_chat] ||= persona_users.select { |u| u[:allow_chat] }
+ def self.allowed_modalities(
+ user: nil,
+ allow_chat_channel_mentions: false,
+ allow_chat_direct_messages: false,
+ allow_topic_mentions: false,
+ allow_personal_messages: false
+ )
+ index =
+ "modality-#{allow_chat_channel_mentions}-#{allow_chat_direct_messages}-#{allow_topic_mentions}-#{allow_personal_messages}"
+
+ personas =
+ persona_cache[index.to_sym] ||= persona_users.select do |persona|
+ next true if allow_chat_channel_mentions && persona[:allow_chat_channel_mentions]
+ next true if allow_chat_direct_messages && persona[:allow_chat_direct_messages]
+ next true if allow_topic_mentions && persona[:allow_topic_mentions]
+ next true if allow_personal_messages && persona[:allow_personal_messages]
+ false
+ end
+
if user
personas.select { |u| user.in_any_groups?(u[:allowed_group_ids]) }
else
@@ -83,18 +100,6 @@ class AiPersona < ActiveRecord::Base
end
end
- def self.mentionables(user: nil)
- all_mentionables =
- persona_cache[:mentionables] ||= persona_users.select do |mentionable|
- mentionable[:mentionable]
- end
- if user
- all_mentionables.select { |mentionable| user.in_any_groups?(mentionable[:allowed_group_ids]) }
- else
- all_mentionables
- end
- end
-
after_commit :bump_cache
def bump_cache
@@ -113,7 +118,11 @@ class AiPersona < ActiveRecord::Base
vision_max_pixels
rag_conversation_chunks
question_consolidator_llm
- allow_chat
+ allow_chat_channel_mentions
+ allow_chat_direct_messages
+ allow_topic_mentions
+ allow_personal_messages
+ force_default_llm
name
description
allowed_group_ids
@@ -128,6 +137,8 @@ class AiPersona < ActiveRecord::Base
instance_attributes[attr] = value
end
+ instance_attributes[:username] = user&.username_lower
+
if persona_class
instance_attributes.each do |key, value|
# description/name are localized
@@ -243,7 +254,10 @@ class AiPersona < ActiveRecord::Base
private
def chat_preconditions
- if allow_chat && !default_llm
+ if (
+ allow_chat_channel_mentions || allow_chat_direct_messages || allow_topic_mentions ||
+ force_default_llm
+ ) && !default_llm
errors.add(:default_llm, I18n.t("discourse_ai.ai_bot.personas.default_llm_required"))
end
end
@@ -281,7 +295,6 @@ end
# temperature :float
# top_p :float
# user_id :integer
-# mentionable :boolean default(FALSE), not null
# default_llm :text
# max_context_posts :integer
# max_post_context_tokens :integer
@@ -291,16 +304,15 @@ end
# rag_chunk_tokens :integer default(374), not null
# rag_chunk_overlap_tokens :integer default(10), not null
# rag_conversation_chunks :integer default(10), not null
-# role :enum default("bot"), not null
-# role_category_ids :integer default([]), not null, is an Array
-# role_tags :string default([]), not null, is an Array
-# role_group_ids :integer default([]), not null, is an Array
-# role_whispers :boolean default(FALSE), not null
-# role_max_responses_per_hour :integer default(50), not null
# question_consolidator_llm :text
-# allow_chat :boolean default(FALSE), not null
# tool_details :boolean default(TRUE), not null
# tools :json not null
+# forced_tool_count :integer default(-1), not null
+# allow_chat_channel_mentions :boolean default(FALSE), not null
+# allow_chat_direct_messages :boolean default(FALSE), not null
+# allow_topic_mentions :boolean default(FALSE), not null
+# allow_personal_message :boolean default(TRUE), not null
+# force_default_llm :boolean default(FALSE), not null
#
# Indexes
#
diff --git a/app/serializers/localized_ai_persona_serializer.rb b/app/serializers/localized_ai_persona_serializer.rb
index 69c9812b..81f6fa85 100644
--- a/app/serializers/localized_ai_persona_serializer.rb
+++ b/app/serializers/localized_ai_persona_serializer.rb
@@ -14,7 +14,6 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
:allowed_group_ids,
:temperature,
:top_p,
- :mentionable,
:default_llm,
:user_id,
:max_context_posts,
@@ -24,9 +23,13 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
:rag_chunk_overlap_tokens,
:rag_conversation_chunks,
:question_consolidator_llm,
- :allow_chat,
:tool_details,
- :forced_tool_count
+ :forced_tool_count,
+ :allow_chat_channel_mentions,
+ :allow_chat_direct_messages,
+ :allow_topic_mentions,
+ :allow_personal_messages,
+ :force_default_llm
has_one :user, serializer: BasicUserSerializer, embed: :object
has_many :rag_uploads, serializer: UploadSerializer, embed: :object
diff --git a/assets/javascripts/discourse/admin/models/ai-persona.js b/assets/javascripts/discourse/admin/models/ai-persona.js
index 53be8076..c649efbd 100644
--- a/assets/javascripts/discourse/admin/models/ai-persona.js
+++ b/assets/javascripts/discourse/admin/models/ai-persona.js
@@ -15,8 +15,8 @@ const CREATE_ATTRIBUTES = [
"top_p",
"temperature",
"user_id",
- "mentionable",
"default_llm",
+ "force_default_llm",
"user",
"max_context_posts",
"vision_enabled",
@@ -29,6 +29,10 @@ const CREATE_ATTRIBUTES = [
"allow_chat",
"tool_details",
"forced_tool_count",
+ "allow_personal_messages",
+ "allow_topic_mentions",
+ "allow_chat_channel_mentions",
+ "allow_chat_direct_messages",
];
const SYSTEM_ATTRIBUTES = [
@@ -38,8 +42,8 @@ const SYSTEM_ATTRIBUTES = [
"system",
"priority",
"user_id",
- "mentionable",
"default_llm",
+ "force_default_llm",
"user",
"max_context_posts",
"vision_enabled",
@@ -49,8 +53,11 @@ const SYSTEM_ATTRIBUTES = [
"rag_chunk_overlap_tokens",
"rag_conversation_chunks",
"question_consolidator_llm",
- "allow_chat",
"tool_details",
+ "allow_personal_messages",
+ "allow_topic_mentions",
+ "allow_chat_channel_mentions",
+ "allow_chat_direct_messages",
];
class ToolOption {
diff --git a/assets/javascripts/discourse/components/ai-persona-editor.gjs b/assets/javascripts/discourse/components/ai-persona-editor.gjs
index c2dd6182..0218b871 100644
--- a/assets/javascripts/discourse/components/ai-persona-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-persona-editor.gjs
@@ -44,6 +44,7 @@ export default class PersonaEditor extends Component {
@tracked selectedTools = [];
@tracked selectedToolNames = [];
@tracked forcedToolNames = [];
+ @tracked hasDefaultLlm = false;
get chatPluginEnabled() {
return this.siteSettings.chat_enabled;
@@ -81,6 +82,7 @@ export default class PersonaEditor extends Component {
@action
updateModel() {
this.editingModel = this.args.model.workingCopy();
+ this.hasDefaultLlm = !!this.editingModel.default_llm;
this.showDelete = !this.args.model.isNew && !this.args.model.system;
this.maxPixelsValue = this.findClosestPixelValue(
this.editingModel.vision_max_pixels
@@ -183,8 +185,10 @@ export default class PersonaEditor extends Component {
set mappedDefaultLlm(value) {
if (value === "blank") {
this.editingModel.default_llm = null;
+ this.hasDefaultLlm = false;
} else {
this.editingModel.default_llm = value;
+ this.hasDefaultLlm = true;
}
}
@@ -344,6 +348,16 @@ export default class PersonaEditor extends Component {
@content={{I18n.t "discourse_ai.ai_persona.default_llm_help"}}
/>
+ {{#if this.hasDefaultLlm}}
+
+
+
+ {{/if}}
{{#unless @model.isNew}}
@@ -429,33 +443,73 @@ export default class PersonaEditor extends Component {
disabled={{this.editingModel.system}}
/>
+
+
+
+
{{#if this.editingModel.user}}
- {{#if this.chatPluginEnabled}}
-
-
-
-
- {{/if}}
-
+
+ {{I18n.t "discourse_ai.ai_persona.allow_topic_mentions"}}
+ {{#if this.chatPluginEnabled}}
+
+
+
+
+
+
+
+
+ {{/if}}
{{/if}}
-
-
-
+ {{#if this.allowLLMSelector}}
+
+
+
+ {{/if}}
}
diff --git a/assets/stylesheets/modules/ai-bot/common/ai-persona.scss b/assets/stylesheets/modules/ai-bot/common/ai-persona.scss
index 26287529..424ffbef 100644
--- a/assets/stylesheets/modules/ai-bot/common/ai-persona.scss
+++ b/assets/stylesheets/modules/ai-bot/common/ai-persona.scss
@@ -68,9 +68,12 @@
&__tool-details,
&__vision_enabled,
- &__allow_chat,
- &__priority,
- &__mentionable {
+ &__allow_chat_direct_messages,
+ &__allow_chat_channel_mentions,
+ &__allow_topic_mentions,
+ &__allow_personal_messages,
+ &__force_default_llm,
+ &__priority {
display: flex;
align-items: center;
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ea83abb9..1dc8e6c0 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -148,8 +148,15 @@ en:
question_consolidator_llm_help: The language model to use for the question consolidator, you may choose a less powerful model to save costs.
system_prompt: System Prompt
forced_tool_strategy: Forced Tool Strategy
- allow_chat: "Allow Chat"
- allow_chat_help: "If enabled, users in allowed groups can DM this persona"
+ allow_chat_direct_messages: "Allow Chat Direct Messages"
+ allow_chat_direct_messages_help: "If enabled, users in allowed groups can send direct messages to this persona."
+ allow_chat_channel_mentions: "Allow Chat Channel Mentions"
+ allow_chat_channel_mentions_help: "If enabled, users in allowed groups can mention this persona in chat channels."
+ allow_personal_messages: "Allow Personal Messages"
+ allow_personal_messages_help: "If enabled, users in allowed groups can send personal messages to this persona."
+ allow_topic_mentions: "Allow Topic Mentions"
+ allow_topic_mentions_help: "If enabled, users in allowed groups can mention this persona in topics."
+ force_default_llm: "Always use default Language Model"
save: Save
saved: AI Persona Saved
enabled: "Enabled?"
diff --git a/db/migrate/20241014010245_ai_persona_chat_topic_refactor.rb b/db/migrate/20241014010245_ai_persona_chat_topic_refactor.rb
new file mode 100644
index 00000000..9a7e9a88
--- /dev/null
+++ b/db/migrate/20241014010245_ai_persona_chat_topic_refactor.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AiPersonaChatTopicRefactor < ActiveRecord::Migration[7.1]
+ def change
+ add_column :ai_personas, :allow_chat_channel_mentions, :boolean, default: false, null: false
+ add_column :ai_personas, :allow_chat_direct_messages, :boolean, default: false, null: false
+ add_column :ai_personas, :allow_topic_mentions, :boolean, default: false, null: false
+ add_column :ai_personas, :allow_personal_messages, :boolean, default: true, null: false
+ add_column :ai_personas, :force_default_llm, :boolean, default: false, null: false
+
+ execute <<~SQL
+ UPDATE ai_personas
+ SET allow_chat_channel_mentions = mentionable, allow_chat_direct_messages = true
+ WHERE allow_chat = true
+ SQL
+
+ execute <<~SQL
+ UPDATE ai_personas
+ SET allow_topic_mentions = true
+ WHERE mentionable = true
+ SQL
+ end
+end
diff --git a/db/post_migrate/20241014041242_ai_persona_post_migrate_drop_cols.rb b/db/post_migrate/20241014041242_ai_persona_post_migrate_drop_cols.rb
new file mode 100644
index 00000000..02d50537
--- /dev/null
+++ b/db/post_migrate/20241014041242_ai_persona_post_migrate_drop_cols.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+class AiPersonaPostMigrateDropCols < ActiveRecord::Migration[7.1]
+ def change
+ remove_columns :ai_personas, :allow_chat
+ remove_columns :ai_personas, :mentionable
+ end
+end
diff --git a/lib/ai_bot/entry_point.rb b/lib/ai_bot/entry_point.rb
index 5ff8ea36..77d9f377 100644
--- a/lib/ai_bot/entry_point.rb
+++ b/lib/ai_bot/entry_point.rb
@@ -8,11 +8,10 @@ module DiscourseAi
Bot = Struct.new(:id, :name, :llm)
def self.all_bot_ids
- mentionable_persona_user_ids =
- AiPersona.mentionables.map { |mentionable| mentionable[:user_id] }
- mentionable_bot_users = LlmModel.joins(:user).pluck("users.id")
-
- mentionable_bot_users + mentionable_persona_user_ids
+ AiPersona
+ .persona_users
+ .map { |persona| persona[:user_id] }
+ .concat(LlmModel.where(enabled_chat_bot: true).pluck(:user_id))
end
def self.find_participant_in(participant_ids)
@@ -109,7 +108,13 @@ module DiscourseAi
DiscourseAi::AiBot::Personas::Persona
.all(user: scope.user)
.map do |persona|
- { id: persona.id, name: persona.name, description: persona.description }
+ {
+ id: persona.id,
+ name: persona.name,
+ description: persona.description,
+ force_default_llm: persona.force_default_llm,
+ username: persona.username,
+ }
end
end
@@ -140,7 +145,7 @@ module DiscourseAi
{
"id" => persona_user[:user_id],
"username" => persona_user[:username],
- "mentionable" => persona_user[:mentionable],
+ "force_default_llm" => persona_user[:force_default_llm],
"is_persona" => true,
}
end,
diff --git a/lib/ai_bot/personas/persona.rb b/lib/ai_bot/personas/persona.rb
index dd2a729a..ca47df0a 100644
--- a/lib/ai_bot/personas/persona.rb
+++ b/lib/ai_bot/personas/persona.rb
@@ -21,7 +21,15 @@ module DiscourseAi
nil
end
- def allow_chat
+ def force_default_llm
+ false
+ end
+
+ def allow_chat_channel_mentions
+ false
+ end
+
+ def allow_chat_direct_messages
false
end
diff --git a/lib/ai_bot/playground.rb b/lib/ai_bot/playground.rb
index fb41b4f5..13834c92 100644
--- a/lib/ai_bot/playground.rb
+++ b/lib/ai_bot/playground.rb
@@ -11,17 +11,19 @@ module DiscourseAi
def self.find_chat_persona(message, channel, user)
if channel.direct_message_channel?
- AiPersona.allowed_chat.find do |p|
- p[:user_id].in?(channel.allowed_user_ids) && (user.group_ids & p[:allowed_group_ids])
- end
+ AiPersona
+ .allowed_modalities(allow_chat_direct_messages: true)
+ .find do |p|
+ p[:user_id].in?(channel.allowed_user_ids) && (user.group_ids & p[:allowed_group_ids])
+ end
else
# let's defer on the parse if there is no @ in the message
if message.message.include?("@")
mentions = message.parsed_mentions.parsed_direct_mentions
if mentions.present?
- AiPersona.allowed_chat.find do |p|
- p[:username].in?(mentions) && (user.group_ids & p[:allowed_group_ids])
- end
+ AiPersona
+ .allowed_modalities(allow_chat_channel_mentions: true)
+ .find { |p| p[:username].in?(mentions) && (user.group_ids & p[:allowed_group_ids]) }
end
end
end
@@ -29,8 +31,14 @@ module DiscourseAi
def self.schedule_chat_reply(message, channel, user, context)
return if !SiteSetting.ai_bot_enabled
- return if AiPersona.allowed_chat.blank?
- return if AiPersona.allowed_chat.any? { |m| m[:user_id] == user.id }
+
+ all_chat =
+ AiPersona.allowed_modalities(
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
+ )
+ return if all_chat.blank?
+ return if all_chat.any? { |m| m[:user_id] == user.id }
persona = find_chat_persona(message, channel, user)
return if !persona
@@ -56,15 +64,23 @@ module DiscourseAi
def self.schedule_reply(post)
return if is_bot_user_id?(post.user_id)
+ mentionables = nil
- bot_ids = LlmModel.joins(:user).pluck("users.id")
- mentionables = AiPersona.mentionables(user: post.user)
+ if post.topic.private_message?
+ mentionables =
+ AiPersona.allowed_modalities(user: post.user, allow_personal_messages: true)
+ else
+ mentionables = AiPersona.allowed_modalities(user: post.user, allow_topic_mentions: true)
+ end
bot_user = nil
mentioned = nil
+ all_llm_user_ids = LlmModel.joins(:user).pluck("users.id")
+
if post.topic.private_message?
- bot_user = post.topic.topic_allowed_users.where(user_id: bot_ids).first&.user
+ # this is an edge case, you started a PM with a different bot
+ bot_user = post.topic.topic_allowed_users.where(user_id: all_llm_user_ids).first&.user
bot_user ||=
post
.topic
@@ -114,6 +130,8 @@ module DiscourseAi
persona ||= DiscourseAi::AiBot::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)
new(bot).update_playground_with(post)
end
diff --git a/spec/lib/modules/ai_bot/entry_point_spec.rb b/spec/lib/modules/ai_bot/entry_point_spec.rb
index a4ebc99d..4f443280 100644
--- a/spec/lib/modules/ai_bot/entry_point_spec.rb
+++ b/spec/lib/modules/ai_bot/entry_point_spec.rb
@@ -35,15 +35,16 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do
expect(serializer[:current_user][:can_debug_ai_bot_conversations]).to eq(true)
end
- it "adds mentionables to current_user_serializer" do
+ it "adds information about forcing default llm to current_user_serializer" do
Group.refresh_automatic_groups!
persona =
Fabricate(
:ai_persona,
- mentionable: true,
enabled: true,
allowed_group_ids: [bot_allowed_group.id],
+ default_llm: "claude-2",
+ force_default_llm: true,
)
persona.create_user!
@@ -54,7 +55,7 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do
persona_bot = bots.find { |bot| bot["id"] == persona.user_id }
expect(persona_bot["username"]).to eq(persona.user.username)
- expect(persona_bot["mentionable"]).to eq(true)
+ expect(persona_bot["force_default_llm"]).to eq(true)
end
it "includes user ids for all personas in the serializer" do
@@ -69,7 +70,7 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do
persona_bot = bots.find { |bot| bot["id"] == persona.user_id }
expect(persona_bot["username"]).to eq(persona.user.username)
- expect(persona_bot["mentionable"]).to eq(false)
+ expect(persona_bot["force_default_llm"]).to eq(false)
end
it "queues a job to generate a reply by the AI" do
diff --git a/spec/lib/modules/ai_bot/playground_spec.rb b/spec/lib/modules/ai_bot/playground_spec.rb
index 47f529a4..08f5d56e 100644
--- a/spec/lib/modules/ai_bot/playground_spec.rb
+++ b/spec/lib/modules/ai_bot/playground_spec.rb
@@ -55,6 +55,11 @@ RSpec.describe DiscourseAi::AiBot::Playground do
)
end
+ after do
+ # we must reset cache on persona cause data can be rolled back
+ AiPersona.persona_cache.flush!
+ end
+
describe "is_bot_user_id?" do
it "properly detects ALL bots as bot users" do
persona = Fabricate(:ai_persona, enabled: false)
@@ -227,7 +232,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do
vision_enabled: true,
vision_max_pixels: 1_000,
default_llm: "custom:#{opus_model.id}",
- mentionable: true,
+ allow_topic_mentions: true,
)
end
@@ -277,7 +282,11 @@ RSpec.describe DiscourseAi::AiBot::Playground do
)
persona.create_user!
- persona.update!(default_llm: "custom:#{claude_2.id}", mentionable: true)
+ persona.update!(
+ default_llm: "custom:#{claude_2.id}",
+ allow_chat_channel_mentions: true,
+ allow_topic_mentions: true,
+ )
persona
end
@@ -294,7 +303,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do
SiteSetting.ai_bot_enabled = true
SiteSetting.chat_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}"
Group.refresh_automatic_groups!
- persona.update!(allow_chat: true, mentionable: true, default_llm: "custom:#{opus_model.id}")
+ persona.update!(allow_chat_channel_mentions: true, default_llm: "custom:#{opus_model.id}")
end
it "should behave in a sane way when threading is enabled" do
@@ -406,8 +415,9 @@ RSpec.describe DiscourseAi::AiBot::Playground do
SiteSetting.chat_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}"
Group.refresh_automatic_groups!
persona.update!(
- allow_chat: true,
- mentionable: false,
+ allow_chat_direct_messages: true,
+ allow_topic_mentions: false,
+ allow_chat_channel_mentions: false,
default_llm: "custom:#{opus_model.id}",
)
SiteSetting.ai_bot_enabled = true
@@ -481,7 +491,6 @@ RSpec.describe DiscourseAi::AiBot::Playground do
# it also needs to include history per config - first feed some history
persona.update!(enabled: false)
-
persona_guardian = Guardian.new(persona.user)
4.times do |i|
@@ -561,6 +570,8 @@ RSpec.describe DiscourseAi::AiBot::Playground do
# we still should be able to mention with no bots
toggle_enabled_bots(bots: [])
+ persona.update!(allow_topic_mentions: true)
+
post = nil
DiscourseAi::Completions::Llm.with_prepared_responses(["Yes I can"]) do
post =
@@ -574,6 +585,16 @@ RSpec.describe DiscourseAi::AiBot::Playground do
last_post = post.topic.posts.order(:post_number).last
expect(last_post.raw).to eq("Yes I can")
expect(last_post.user_id).to eq(persona.user_id)
+
+ persona.update!(allow_topic_mentions: false)
+
+ post =
+ create_post(
+ title: "My public topic ABC",
+ raw: "Hey @#{persona.user.username}, can you help me?",
+ )
+
+ expect(post.topic.posts.last.post_number).to eq(1)
end
it "allows PMing a persona even when no particular bots are enabled" do
@@ -603,6 +624,50 @@ RSpec.describe DiscourseAi::AiBot::Playground do
expect(last_post.topic.allowed_users.pluck(:user_id)).to include(persona.user_id)
expect(last_post.topic.participant_count).to eq(2)
+
+ # ensure it can be disabled
+ persona.update!(allow_personal_messages: false)
+
+ post =
+ create_post(
+ raw: "Hey there #{persona.user.username}, can you help me please",
+ topic_id: post.topic.id,
+ user: admin,
+ )
+
+ expect(post.post_number).to eq(3)
+ end
+
+ it "can tether a persona unconditionally to an llm" do
+ gpt_35_turbo = Fabricate(:llm_model, name: "gpt-3.5-turbo")
+
+ # If you start a PM with GPT 3.5 bot, replies should come from it, not from Claude
+ SiteSetting.ai_bot_enabled = true
+ toggle_enabled_bots(bots: [gpt_35_turbo, claude_2])
+
+ post = nil
+ persona.update!(force_default_llm: true, default_llm: "custom:#{gpt_35_turbo.id}")
+
+ DiscourseAi::Completions::Llm.with_prepared_responses(
+ ["Yes I can", "Magic Title"],
+ llm: "custom:#{gpt_35_turbo.id}",
+ ) do
+ post =
+ create_post(
+ title: "I just made a PM",
+ raw: "hello world",
+ target_usernames: "#{user.username},#{claude_2.user.username}",
+ archetype: Archetype.private_message,
+ user: admin,
+ custom_fields: {
+ "ai_persona_id" => persona.id,
+ },
+ )
+ end
+
+ last_post = post.topic.posts.order(:post_number).last
+ expect(last_post.raw).to eq("Yes I can")
+ expect(last_post.user_id).to eq(persona.user_id)
end
it "picks the correct llm for persona in PMs" do
diff --git a/spec/models/ai_persona_spec.rb b/spec/models/ai_persona_spec.rb
index 0d48b4cf..4783dbfb 100644
--- a/spec/models/ai_persona_spec.rb
+++ b/spec/models/ai_persona_spec.rb
@@ -71,9 +71,12 @@ RSpec.describe AiPersona do
forum_helper = AiPersona.find_by(name: "Forum Helper")
forum_helper.update!(
user_id: 1,
- mentionable: true,
default_llm: "anthropic:claude-2",
max_context_posts: 3,
+ allow_topic_mentions: true,
+ allow_personal_messages: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
)
klass = forum_helper.class_instance
@@ -83,9 +86,12 @@ RSpec.describe AiPersona do
# tl 0 by default
expect(klass.allowed_group_ids).to eq([10])
expect(klass.user_id).to eq(1)
- expect(klass.mentionable).to eq(true)
expect(klass.default_llm).to eq("anthropic:claude-2")
expect(klass.max_context_posts).to eq(3)
+ expect(klass.allow_topic_mentions).to eq(true)
+ expect(klass.allow_personal_messages).to eq(true)
+ expect(klass.allow_chat_channel_mentions).to eq(true)
+ expect(klass.allow_chat_direct_messages).to eq(true)
end
it "defines singleton methods non persona classes" do
@@ -98,7 +104,10 @@ RSpec.describe AiPersona do
allowed_group_ids: [],
default_llm: "anthropic:claude-2",
max_context_posts: 3,
- mentionable: true,
+ allow_topic_mentions: true,
+ allow_personal_messages: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
user_id: 1,
)
@@ -108,12 +117,15 @@ RSpec.describe AiPersona do
expect(klass.system).to eq(false)
expect(klass.allowed_group_ids).to eq([])
expect(klass.user_id).to eq(1)
- expect(klass.mentionable).to eq(true)
expect(klass.default_llm).to eq("anthropic:claude-2")
expect(klass.max_context_posts).to eq(3)
+ expect(klass.allow_topic_mentions).to eq(true)
+ expect(klass.allow_personal_messages).to eq(true)
+ expect(klass.allow_chat_channel_mentions).to eq(true)
+ expect(klass.allow_chat_direct_messages).to eq(true)
end
- it "does not allow setting allow_chat without a default_llm" do
+ it "does not allow setting allowing chat without a default_llm" do
persona =
AiPersona.create(
name: "test",
@@ -121,7 +133,37 @@ RSpec.describe AiPersona do
system_prompt: "test",
allowed_group_ids: [],
default_llm: nil,
- allow_chat: true,
+ allow_chat_channel_mentions: true,
+ )
+
+ expect(persona.valid?).to eq(false)
+ expect(persona.errors[:default_llm].first).to eq(
+ I18n.t("discourse_ai.ai_bot.personas.default_llm_required"),
+ )
+
+ persona =
+ AiPersona.create(
+ name: "test",
+ description: "test",
+ system_prompt: "test",
+ allowed_group_ids: [],
+ default_llm: nil,
+ allow_chat_direct_messages: true,
+ )
+
+ expect(persona.valid?).to eq(false)
+ expect(persona.errors[:default_llm].first).to eq(
+ I18n.t("discourse_ai.ai_bot.personas.default_llm_required"),
+ )
+
+ persona =
+ AiPersona.create(
+ name: "test",
+ description: "test",
+ system_prompt: "test",
+ allowed_group_ids: [],
+ default_llm: nil,
+ allow_topic_mentions: true,
)
expect(persona.valid?).to eq(false)
diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb
index a01a354a..0a203834 100644
--- a/spec/plugin_helper.rb
+++ b/spec/plugin_helper.rb
@@ -2,7 +2,10 @@
module DiscourseAi::ChatBotHelper
def toggle_enabled_bots(bots: [])
- LlmModel.update_all(enabled_chat_bot: false)
+ models = LlmModel.all
+ models = models.where("id not in (?)", bots.map(&:id)) if bots.present?
+ models.update_all(enabled_chat_bot: false)
+
bots.each { |b| b.update!(enabled_chat_bot: true) }
DiscourseAi::AiBot::SiteSettingsExtension.enable_or_disable_ai_bots
end
diff --git a/spec/requests/admin/ai_personas_controller_spec.rb b/spec/requests/admin/ai_personas_controller_spec.rb
index 61e831ce..1dee9b73 100644
--- a/spec/requests/admin/ai_personas_controller_spec.rb
+++ b/spec/requests/admin/ai_personas_controller_spec.rb
@@ -40,7 +40,10 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
:ai_persona,
name: "search2",
tools: [["SearchCommand", { base_query: "test" }, true]],
- mentionable: true,
+ allow_topic_mentions: true,
+ allow_personal_messages: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
default_llm: "anthropic:claude-2",
forced_tool_count: 2,
)
@@ -52,7 +55,11 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
serializer_persona1 = response.parsed_body["ai_personas"].find { |p| p["id"] == persona1.id }
serializer_persona2 = response.parsed_body["ai_personas"].find { |p| p["id"] == persona2.id }
- expect(serializer_persona2["mentionable"]).to eq(true)
+ expect(serializer_persona2["allow_topic_mentions"]).to eq(true)
+ expect(serializer_persona2["allow_personal_messages"]).to eq(true)
+ expect(serializer_persona2["allow_chat_channel_mentions"]).to eq(true)
+ expect(serializer_persona2["allow_chat_direct_messages"]).to eq(true)
+
expect(serializer_persona2["default_llm"]).to eq("anthropic:claude-2")
expect(serializer_persona2["user_id"]).to eq(persona2.user_id)
expect(serializer_persona2["user"]["id"]).to eq(persona2.user_id)
@@ -167,7 +174,10 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
tools: [["search", { "base_query" => "test" }, true]],
top_p: 0.1,
temperature: 0.5,
- mentionable: true,
+ allow_topic_mentions: true,
+ allow_personal_messages: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
default_llm: "anthropic:claude-2",
forced_tool_count: 2,
}
@@ -186,9 +196,12 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
expect(persona_json["name"]).to eq("superbot")
expect(persona_json["top_p"]).to eq(0.1)
expect(persona_json["temperature"]).to eq(0.5)
- expect(persona_json["mentionable"]).to eq(true)
expect(persona_json["default_llm"]).to eq("anthropic:claude-2")
expect(persona_json["forced_tool_count"]).to eq(2)
+ expect(persona_json["allow_topic_mentions"]).to eq(true)
+ expect(persona_json["allow_personal_messages"]).to eq(true)
+ expect(persona_json["allow_chat_channel_mentions"]).to eq(true)
+ expect(persona_json["allow_chat_direct_messages"]).to eq(true)
persona = AiPersona.find(persona_json["id"])
diff --git a/spec/requests/embeddings/embeddings_controller_spec.rb b/spec/requests/embeddings/embeddings_controller_spec.rb
index fa1c1b1c..2a411644 100644
--- a/spec/requests/embeddings/embeddings_controller_spec.rb
+++ b/spec/requests/embeddings/embeddings_controller_spec.rb
@@ -63,8 +63,6 @@ describe DiscourseAi::Embeddings::EmbeddingsController do
context "when rate limiting is enabled" do
before { RateLimiter.enable }
- use_redis_snapshotting
-
it "will rate limit correctly" do
stub_const(subject.class, :MAX_HYDE_SEARCHES_PER_MINUTE, 1) do
stub_const(subject.class, :MAX_SEARCHES_PER_MINUTE, 2) do
diff --git a/test/javascripts/unit/models/ai-persona-test.js b/test/javascripts/unit/models/ai-persona-test.js
index f9674f44..c8178f19 100644
--- a/test/javascripts/unit/models/ai-persona-test.js
+++ b/test/javascripts/unit/models/ai-persona-test.js
@@ -37,8 +37,8 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
description: "Description",
top_p: 0.8,
temperature: 0.7,
- mentionable: false,
default_llm: "Default LLM",
+ force_default_llm: false,
user: null,
user_id: null,
max_context_posts: 5,
@@ -52,6 +52,10 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
allow_chat: false,
tool_details: true,
forced_tool_count: -1,
+ allow_personal_messages: true,
+ allow_topic_mentions: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
};
const aiPersona = AiPersona.create({ ...properties });
@@ -82,7 +86,6 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
user: null,
user_id: null,
default_llm: "Default LLM",
- mentionable: false,
max_context_posts: 5,
vision_enabled: true,
vision_max_pixels: 100,
@@ -94,6 +97,11 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
allow_chat: false,
tool_details: true,
forced_tool_count: -1,
+ allow_personal_messages: true,
+ allow_topic_mentions: true,
+ allow_chat_channel_mentions: true,
+ allow_chat_direct_messages: true,
+ force_default_llm: false,
};
const aiPersona = AiPersona.create({ ...properties });