discourse-ai/lib/modules/ai_bot/personas/persona.rb

163 lines
4.8 KiB
Ruby

#frozen_string_literal: true
module DiscourseAi
module AiBot
module Personas
def self.system_personas
@system_personas ||= {
Personas::General => -1,
Personas::SqlHelper => -2,
Personas::Artist => -3,
Personas::SettingsExplorer => -4,
Personas::Researcher => -5,
Personas::Creative => -6,
}
end
def self.system_personas_by_id
@system_personas_by_id ||= system_personas.invert
end
def self.all(user:)
personas =
AiPersona.all_personas.filter { |persona| user.in_any_groups?(persona.allowed_group_ids) }
# this needs to be dynamic cause site settings may change
all_available_commands = Persona.all_available_commands
personas.filter do |persona|
if persona.system
instance = persona.new
(
instance.required_commands == [] ||
(instance.required_commands - all_available_commands).empty?
)
else
true
end
end
end
def self.find_by(id: nil, name: nil, user:)
all(user: user).find { |persona| persona.id == id || persona.name == name }
end
class Persona
def self.name
I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.name")
end
def self.description
I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description")
end
def commands
[]
end
def required_commands
[]
end
def render_commands(render_function_instructions:)
return +"" if available_commands.empty?
result = +""
if render_function_instructions
result << "\n"
result << function_list.system_prompt
result << "\n"
end
result << available_commands.map(&:custom_system_message).compact.join("\n")
result
end
def render_system_prompt(
topic: nil,
render_function_instructions: true,
allow_commands: true
)
substitutions = {
site_url: Discourse.base_url,
site_title: SiteSetting.title,
site_description: SiteSetting.site_description,
time: Time.zone.now,
}
substitutions[:participants] = topic.allowed_users.map(&:username).join(", ") if topic
prompt =
system_prompt.gsub(/\{(\w+)\}/) do |match|
found = substitutions[match[1..-2].to_sym]
found.nil? ? match : found.to_s
end
if allow_commands
prompt += render_commands(render_function_instructions: render_function_instructions)
end
prompt
end
def available_commands
return @available_commands if @available_commands
@available_commands = all_available_commands.filter { |cmd| commands.include?(cmd) }
end
def available_functions
# note if defined? can be a problem in test
# this can never be nil so it is safe
return @available_functions if @available_functions
functions = []
functions =
available_commands.map do |command|
function =
DiscourseAi::Inference::Function.new(name: command.name, description: command.desc)
command.parameters.each { |parameter| function.add_parameter(parameter) }
function
end
@available_functions = functions
end
def function_list
return @function_list if @function_list
@function_list = DiscourseAi::Inference::FunctionList.new
available_functions.each { |function| @function_list << function }
@function_list
end
def self.all_available_commands
all_commands = [
Commands::CategoriesCommand,
Commands::TimeCommand,
Commands::SearchCommand,
Commands::SummarizeCommand,
Commands::ReadCommand,
Commands::DbSchemaCommand,
Commands::SearchSettingsCommand,
Commands::SummarizeCommand,
Commands::SettingContextCommand,
]
all_commands << Commands::TagsCommand if SiteSetting.tagging_enabled
all_commands << Commands::ImageCommand if SiteSetting.ai_stability_api_key.present?
if SiteSetting.ai_google_custom_search_api_key.present? &&
SiteSetting.ai_google_custom_search_cx.present?
all_commands << Commands::GoogleCommand
end
all_commands
end
def all_available_commands
@cmds ||= self.class.all_available_commands
end
end
end
end
end