mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-07-08 15:22:47 +00:00
REFACTOR: Move personas into it's own module.
This commit is contained in:
parent
a6b08270c0
commit
5bfe231486
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 = [
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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[match[1..-2].to_sym]
|
||||
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[:conversation_context].to_a,
|
||||
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[:conversation_context].to_a,
|
||||
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
|
||||
<guidance>
|
||||
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")
|
||||
}
|
||||
</guidance>
|
||||
TEXT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -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,7 +196,7 @@ 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)
|
||||
bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona)
|
||||
playground = DiscourseAi::AiBot::Playground.new(bot)
|
||||
|
||||
playground.reply_to(
|
||||
@ -307,16 +305,52 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def title_playground(post, user)
|
||||
context = conversation_context(post)
|
||||
conversation =
|
||||
conversation_context(post).reduce(+"") do |memo, c|
|
||||
if c[:type] == :user || c[:type] == :model
|
||||
memo << "#{c[:type].to_s.humanize} said:\n#{c[:content]}\n\n"
|
||||
end
|
||||
|
||||
memo
|
||||
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,
|
||||
)
|
||||
|
||||
title =
|
||||
bot
|
||||
.get_updated_title(context, post, user)
|
||||
.tap do |new_title|
|
||||
.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/, ""),
|
||||
title: title.sub(/\A"/, "").sub(/"\Z/, ""),
|
||||
)
|
||||
end
|
||||
|
||||
allowed_users = post.topic.topic_allowed_users.pluck(:user_id)
|
||||
MessageBus.publish(
|
||||
@ -710,8 +744,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,
|
||||
|
@ -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 =
|
||||
{
|
||||
|
@ -333,7 +333,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]
|
||||
@ -482,7 +482,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|
|
||||
@ -511,7 +511,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,
|
||||
|
@ -9,12 +9,7 @@ module DiscourseAi
|
||||
.all_personas
|
||||
.find { |persona| persona.id == SiteSetting.ai_discord_search_persona.to_i }
|
||||
.new
|
||||
@bot =
|
||||
DiscourseAi::AiBot::Bot.as(
|
||||
Discourse.system_user,
|
||||
persona: @persona,
|
||||
model: LlmModel.find(@persona.class.default_llm_id),
|
||||
)
|
||||
@bot = DiscourseAi::Personas::Bot.as(Discourse.system_user, persona: @persona)
|
||||
super(body)
|
||||
end
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module ArtifactUpdateStrategies
|
||||
class InvalidFormatError < StandardError
|
||||
end
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module ArtifactUpdateStrategies
|
||||
class Diff < Base
|
||||
attr_reader :failed_searches
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module ArtifactUpdateStrategies
|
||||
class Full < Base
|
||||
private
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class Artist < Persona
|
||||
def tools
|
||||
@ -34,5 +33,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -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:, 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
|
||||
DiscourseAi::Completions::Llm.proxy(model)
|
||||
end
|
||||
|
||||
def force_tool_if_needed(prompt, context)
|
||||
@ -93,8 +52,8 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def reply(context, &update_blk)
|
||||
llm = DiscourseAi::Completions::Llm.proxy(model)
|
||||
prompt = persona.craft_prompt(context, llm: llm)
|
||||
current_llm = llm
|
||||
prompt = persona.craft_prompt(context, llm: current_llm)
|
||||
|
||||
total_completions = 0
|
||||
ongoing_chain = true
|
||||
@ -120,7 +79,7 @@ module DiscourseAi
|
||||
current_thinking = []
|
||||
|
||||
result =
|
||||
llm.generate(
|
||||
current_llm.generate(
|
||||
prompt,
|
||||
feature_name: "bot",
|
||||
partial_tool_calls: allow_partial_tool_calls,
|
||||
@ -131,7 +90,7 @@ module DiscourseAi
|
||||
persona.find_tool(
|
||||
partial,
|
||||
bot_user: user,
|
||||
llm: llm,
|
||||
llm: current_llm,
|
||||
context: context,
|
||||
existing_tools: existing_tools,
|
||||
)
|
||||
@ -158,7 +117,7 @@ module DiscourseAi
|
||||
process_tool(
|
||||
tool: tool,
|
||||
raw_context: raw_context,
|
||||
llm: llm,
|
||||
current_llm: current_llm,
|
||||
cancel: cancel,
|
||||
update_blk: update_blk,
|
||||
prompt: prompt,
|
||||
@ -242,7 +201,7 @@ module DiscourseAi
|
||||
def process_tool(
|
||||
tool:,
|
||||
raw_context:,
|
||||
llm:,
|
||||
current_llm:,
|
||||
cancel:,
|
||||
update_blk:,
|
||||
prompt:,
|
||||
@ -250,7 +209,7 @@ module DiscourseAi
|
||||
current_thinking:
|
||||
)
|
||||
tool_call_id = tool.tool_call_id
|
||||
invocation_result_json = invoke_tool(tool, llm, cancel, context, &update_blk).to_json
|
||||
invocation_result_json = invoke_tool(tool, cancel, context, &update_blk).to_json
|
||||
|
||||
tool_call_message = {
|
||||
type: :tool_call,
|
||||
@ -296,7 +255,7 @@ module DiscourseAi
|
||||
raw_context << [invocation_result_json, tool_call_id, "tool", tool.name]
|
||||
end
|
||||
|
||||
def invoke_tool(tool, llm, cancel, context, &update_blk)
|
||||
def invoke_tool(tool, cancel, context, &update_blk)
|
||||
show_placeholder = !context[:skip_tool_details] && !tool.class.allow_partial_tool_calls?
|
||||
|
||||
update_blk.call("", cancel, build_placeholder(tool.summary, "")) if show_placeholder
|
17
lib/personas/creative.rb
Normal file
17
lib/personas/creative.rb
Normal file
@ -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
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class DallE3 < Persona
|
||||
def tools
|
||||
@ -35,5 +34,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class DiscourseHelper < Persona
|
||||
def tools
|
||||
@ -44,5 +43,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class General < Persona
|
||||
def tools
|
||||
@ -30,5 +29,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class GithubHelper < Persona
|
||||
def tools
|
||||
@ -25,5 +24,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
395
lib/personas/persona.rb
Normal file
395
lib/personas/persona.rb
Normal file
@ -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 ||= {
|
||||
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[match[1..-2].to_sym]
|
||||
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[:conversation_context].to_a,
|
||||
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[:conversation_context].to_a,
|
||||
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
|
||||
<guidance>
|
||||
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")
|
||||
}
|
||||
</guidance>
|
||||
TEXT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class Researcher < Persona
|
||||
def tools
|
||||
@ -48,5 +47,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class SettingsExplorer < Persona
|
||||
def tools
|
||||
@ -22,5 +21,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class SqlHelper < Persona
|
||||
def self.schema
|
||||
@ -25,8 +24,7 @@ module DiscourseAi
|
||||
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, _|
|
||||
@ -103,5 +101,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class CreateArtifact < Tool
|
||||
def self.name
|
@ -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)
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class DallE < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class DbSchema < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class DiscourseMetaSearch < Tool
|
||||
class << self
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class GithubFileContent < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class GithubPullRequestDiff < Tool
|
||||
LARGE_OBJECT_THRESHOLD = 30_000
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class GithubSearchCode < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class GithubSearchFiles < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Google < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Image < Tool
|
||||
def self.signature
|
@ -4,7 +4,7 @@ require "mini_racer"
|
||||
require "json"
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class JavascriptEvaluator < Tool
|
||||
TIMEOUT = 500
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class ListCategories < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class ListTags < Tool
|
||||
def self.signature
|
@ -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
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class RandomPicker < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
MAX_POSTS = 100
|
||||
|
||||
module Tools
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class ReadArtifact < Tool
|
||||
MAX_HTML_SIZE = 30.kilobytes
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Search < Tool
|
||||
attr_reader :last_query
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class SearchSettings < Tool
|
||||
INCLUDE_DESCRIPTIONS_MAX_LENGTH = 10
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class SettingContext < Tool
|
||||
MAX_CONTEXT_TOKENS = 2000
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Summarize < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Time < Tool
|
||||
def self.signature
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class Tool
|
||||
# Why 30 mega bytes?
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class UpdateArtifact < Tool
|
||||
def self.name
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
module Tools
|
||||
class WebBrowser < Tool
|
||||
def self.signature
|
@ -1,7 +1,6 @@
|
||||
#frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module AiBot
|
||||
module Personas
|
||||
class WebArtifactCreator < Persona
|
||||
def tools
|
||||
@ -52,5 +51,4 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -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)
|
||||
|
@ -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!
|
||||
|
@ -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,7 +169,7 @@ 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)
|
||||
bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona_klass.new)
|
||||
playground = DiscourseAi::AiBot::Playground.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]
|
||||
@ -965,7 +961,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 =
|
||||
@ -1018,13 +1014,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=="
|
||||
|
@ -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) }
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseAi::AiBot::Bot do
|
||||
subject(:bot) { described_class.as(bot_user) }
|
||||
RSpec.describe DiscourseAi::Personas::Bot do
|
||||
subject(:bot) { described_class.as(bot_user, persona: DiscourseAi::Personas::General.new) }
|
||||
|
||||
fab!(:admin)
|
||||
fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") }
|
||||
@ -48,9 +48,9 @@ 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 = DiscourseAi::Personas::Bot.as(bot_user, persona: personaClass.new)
|
||||
bot.reply(
|
||||
{ conversation_context: [{ type: :user, content: "test" }] },
|
||||
) do |_partial, _cancel, _placeholder|
|
||||
@ -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)
|
||||
<details>
|
||||
<summary>#{tool.summary}</summary>
|
@ -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
|
||||
@ -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.merge(conversation_context: [{ content: "Tell me the time", type: :user }])
|
||||
@ -342,7 +315,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
|
||||
ctx =
|
||||
@ -417,7 +390,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)
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -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") }
|
@ -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}") }
|
@ -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) }
|
@ -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}") }
|
||||
|
@ -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}") }
|
@ -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}") }
|
@ -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}") }
|
||||
|
@ -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}") }
|
@ -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"] }
|
||||
|
@ -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}") }
|
@ -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}") }
|
@ -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}") }
|
@ -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 }
|
||||
|
@ -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)
|
@ -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}") }
|
@ -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}") }
|
@ -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 }
|
||||
|
@ -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) }
|
@ -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}") }
|
@ -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}") }
|
@ -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
|
@ -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)
|
@ -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}") }
|
@ -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
|
||||
|
@ -51,7 +51,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
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user