From 0518b9c7e69c8e2bc371111945213eac48de1200 Mon Sep 17 00:00:00 2001 From: merefield Date: Thu, 10 Oct 2024 17:15:42 +0100 Subject: [PATCH 1/4] wip --- config/locales/server.en.yml | 9 +++ lib/discourse_chatbot/bots/open_ai_bot_rag.rb | 15 +++++ .../functions/user_field_function.rb | 56 +++++++++++++++++++ plugin.rb | 1 + 4 files changed, 81 insertions(+) create mode 100644 lib/discourse_chatbot/functions/user_field_function.rb diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 75db617..1b9b34b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -110,6 +110,15 @@ en: private_message: title_creation: "Create a short Topic title from summarising the prior messages" function: + user_information: + name: "user_information_for_%{user_field}" + system_message: "You must be proactive and ask the user to tell you what their %{description} is. Do not wait to be prompted!" + description: | + get information about %{user_field} for the user + parameters: + answer: the answer to the question about what user's %{user_field} is + answer: "Confirm with the user the infomration they gave about %{user_field} has been saved." + error: "I couldn't save any information about %{user_field} for answer '%{answer}'" calculator: description: | Useful for getting the result of a math expression. It is a general purpose calculator. It works with Ruby expressions. diff --git a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb index ab087ef..28a3bbd 100644 --- a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb +++ b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb @@ -19,6 +19,7 @@ "DiscourseChatbot::WebSearchFunction", "DiscourseChatbot::WebCrawlerFunction", "DiscourseChatbot::NewsFunction", +"DiscourseChatbot::UserFieldFunction", "DiscourseChatbot::EscalateToStaffFunction", "DiscourseChatbot::CalculatorFunction"] @@ -40,6 +41,14 @@ def get_response(prompt, opts) if private_discussion system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.private", current_date_time: DateTime.current) } + + UserField.all.each do |user_field| + if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || + ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? + system_message[:content] += " " + I18n.t("chatbot.prompt.function.user_information.system_message", name: user_field.name, description: user_field.description) + break + end + end else system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.open", current_date_time: DateTime.current) } end @@ -98,6 +107,12 @@ def merge_functions(opts) functions = [calculator_function, wikipedia_function] + if opts[:private] + UserField.all.each do |user_field| + functions << ::DiscourseChatbot::UserFieldFunction.new(user_field.name, opts[:user_id]) + end + end + functions << forum_search_function if forum_search_function functions << vision_function if vision_function functions << paint_function if SiteSetting.chatbot_support_picture_creation diff --git a/lib/discourse_chatbot/functions/user_field_function.rb b/lib/discourse_chatbot/functions/user_field_function.rb new file mode 100644 index 0000000..356969a --- /dev/null +++ b/lib/discourse_chatbot/functions/user_field_function.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_relative '../function' + +module DiscourseChatbot + class UserFieldFunction < Function + def initialize(user_field, user_id) + super() + @user_field = user_field + @function_name = user_field.downcase.gsub(" ", "_") + @user_custom_field_name = "user_field_#{UserField.find_by(name: user_field).id}" + @user_id = user_id + end + + def name + I18n.t("chatbot.prompt.function.user_information.name", user_field: @function_name) + end + + def description + I18n.t("chatbot.prompt.function.user_information.description", user_field: @user_field) + end + + def parameters + [ + { name: "answer", type: String, description: I18n.t("chatbot.prompt.function.user_information.parameters.answer") } , + ] + end + + def required + ['answer'] + end + + def process(args) + begin + super(args) + + # ::UserField.find_by(name: @user_field) = args[parameters[0][:name]] + ucf = ::UserCustomField.where(user_id: @user_id, name: @user_custom_field_name).first + if ucf + ucf.value = args[parameters[0][:name]] + ucf.save! + else + ::UserCustomField.create!(user_id: @user_id, name: @user_custom_field_name, value: args[parameters[0][:name]]) + end + + + #, value: args[parameters[0][:name]]}, on_duplicate: :update, unique_by: [:user_id, :name]) + #::UserCustomField.upsert({user_id: @user_id, name: @user_field, value: args[parameters[0][:name]]}, on_duplicate: :update, unique_by: [:user_id, :name]) + + rescue StandardError => e + Rails.logger.error("Chatbot: Error occurred while attempting to store answer in a User Custom Field: #{e.message}") + I18n.t("chatbot.prompt.function.user_information.error", user_field: @user_field, answer: args[parameters[0][:name]]) + end + end + end +end diff --git a/plugin.rb b/plugin.rb index 74037b7..bb87b4c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -95,6 +95,7 @@ def progress_debug_message(message) ../lib/discourse_chatbot/bots/open_ai_bot_rag.rb ../lib/discourse_chatbot/safe_ruby/lib/safe_ruby.rb ../lib/discourse_chatbot/function.rb + ../lib/discourse_chatbot/functions/user_field_function.rb ../lib/discourse_chatbot/functions/calculator_function.rb ../lib/discourse_chatbot/functions/escalate_to_staff_function.rb ../lib/discourse_chatbot/functions/news_function.rb From ee9609a4ea85c17b49720b31d39063f408062c2e Mon Sep 17 00:00:00 2001 From: merefield Date: Thu, 10 Oct 2024 19:43:49 +0100 Subject: [PATCH 2/4] refine --- config/locales/server.en.yml | 11 +++++----- config/settings.yml | 3 +++ lib/discourse_chatbot/bots/open_ai_bot_rag.rb | 21 ++++++++++++------- plugin.rb | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 1b9b34b..63508c8 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -72,11 +72,12 @@ en: chatbot_forum_search_function_results_topic_max_posts_count: "The maximum number of Posts to be returned in the search results if content_type is 'topics'" chatbot_tool_choice_first_iteration: "Choose if a tool must be used for the first iteration of the bots response thinking cycle. If you choose 'force_local_forum_search' the bot will search the forum for information, if you choose 'force_a_function' the bot will use a function of its choice, if you choose 'not_forced' (default) the bot will not be forced to use a tool for its first cycle." chatbot_forum_search_function_hybrid_search: "(EXPERIMENTAL): Enable hybrid search mode. This will cause the bot to search using native keyword search in addition to embedding based semantic search and it will attempt to blend the results" - chatbot_url_integrity_check: "(EXPERIMENTAL): Enable URL integrity check. If the LLM has returned an invalid forum URL, this will be highlighted back to the LLM" - chatbot_locations_plugin_support: "(EXPERIMENTAL currently user locations only) Natural language querying capability for Locations Plugin (when using RAG mode, requires Locations Plugin to be installed)" - chatbot_escalate_to_staff_function: "(EXPERIMENTAL, Chat only) if user requests human assistance or gets irritated, escalate to staff via PM (requires staff group to be populated)" - chatbot_escalate_to_staff_groups: "(EXPERIMENTAL, Chat only) groups added to escalation PM, e.g. support team" - chatbot_escalate_to_staff_max_history: "(EXPERIMENTAL, Chat only) number of chat messages included in transcript added to escalation PM" + chatbot_url_integrity_check: "Enable URL integrity check. If the LLM has returned an invalid forum URL, this will be highlighted back to the LLM" + chatbot_locations_plugin_support: "(currently user locations only) Natural language querying capability for Locations Plugin (when using RAG mode, requires Locations Plugin to be installed)" + chatbot_escalate_to_staff_function: "(Chat only) if user requests human assistance or gets irritated, escalate to staff via PM (requires staff group to be populated)" + chatbot_escalate_to_staff_groups: "(Chat only) groups added to escalation PM, e.g. support team" + chatbot_escalate_to_staff_max_history: "(Chat only) number of chat messages included in transcript added to escalation PM" + chatbot_user_fields_collection: "(EXPERIMENTAL) Collect empty user fields from the user as part of the conversation" chatbot_news_api_token: "News API token for news (if left blank, news will never be searched)Get one at NewsAPI.org" chatbot_firecrawl_api_token: "Firecrawl API token for crawling remote websites. If left blank, crawling will not be available. Get one at https://www.firecrawl.dev/" chatbot_jina_api_token: "Jina API token for web crawl and search. Get one at https://jina.ai. Alternative to Firecrawl and Serp. If Firecrawl API token is populated, it will be used in preference to Jina for crawling. Ditto Serp API for Searching." diff --git a/config/settings.yml b/config/settings.yml index 7c53e32..3b5bff6 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -367,6 +367,9 @@ plugins: chatbot_escalate_to_staff_max_history: client: false default: 10 + chatbot_user_fields_collection: + client: false + default: false chatbot_news_api_token: client: false default: '' diff --git a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb index 28a3bbd..0ba07e2 100644 --- a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb +++ b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb @@ -42,18 +42,25 @@ def get_response(prompt, opts) if private_discussion system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.private", current_date_time: DateTime.current) } - UserField.all.each do |user_field| - if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || - ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? - system_message[:content] += " " + I18n.t("chatbot.prompt.function.user_information.system_message", name: user_field.name, description: user_field.description) - break + if SiteSetting.chatbot_user_fields_collection + UserField.all.each do |user_field| + if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || + ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? + system_message[:content] += " " + I18n.t("chatbot.prompt.function.user_information.system_message", name: user_field.name, description: user_field.description) + break + end end end else system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.open", current_date_time: DateTime.current) } end - prompt.unshift(system_message) + if SiteSetting.chatbot_user_fields_collection + # prioritize the system message + prompt << system_message + else + prompt.unshift(system_message) + end @inner_thoughts = [] @posts_ids_found = [] @@ -107,7 +114,7 @@ def merge_functions(opts) functions = [calculator_function, wikipedia_function] - if opts[:private] + if opts[:private] && SiteSetting.chatbot_user_fields_collection UserField.all.each do |user_field| functions << ::DiscourseChatbot::UserFieldFunction.new(user_field.name, opts[:user_id]) end diff --git a/plugin.rb b/plugin.rb index bb87b4c..d89f331 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-chatbot # about: a plugin that allows you to have a conversation with a configurable chatbot in Discourse Chat, Topics and Private Messages -# version: 1.1.2 +# version: 1.2.0 # authors: merefield # url: https://github.com/merefield/discourse-chatbot From ef1cdcf38f8a9992a1e257127654a7fec2c47029 Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 11 Oct 2024 15:26:42 +0100 Subject: [PATCH 3/4] add experimental dropdown and confirmation support --- config/locales/server.en.yml | 10 +++-- lib/discourse_chatbot/bots/open_ai_bot_rag.rb | 9 ++-- .../functions/user_field_function.rb | 44 ++++++++++++++----- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 63508c8..e3a32c0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -114,10 +114,14 @@ en: user_information: name: "user_information_for_%{user_field}" system_message: "You must be proactive and ask the user to tell you what their %{description} is. Do not wait to be prompted!" - description: | - get information about %{user_field} for the user + description: + general: get information about %{user_field} for the user + confirmation: confirm if the user accepts the following %{user_field} parameters: - answer: the answer to the question about what user's %{user_field} is + answer: + text: the answer to the question about what user's %{user_field} is + confirmation: "user's confirmation or otherwise (true or false) of: %{user_field}. If user does not confirm it is false. If user does confirm, it is true." + dropdown: "the user's selection of: %{user_field} which can be one of: %{options}" answer: "Confirm with the user the infomration they gave about %{user_field} has been saved." error: "I couldn't save any information about %{user_field} for answer '%{answer}'" calculator: diff --git a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb index 0ba07e2..4e6a32e 100644 --- a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb +++ b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb @@ -43,7 +43,7 @@ def get_response(prompt, opts) system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.private", current_date_time: DateTime.current) } if SiteSetting.chatbot_user_fields_collection - UserField.all.each do |user_field| + UserField.where(editable: true).each do |user_field| if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? system_message[:content] += " " + I18n.t("chatbot.prompt.function.user_information.system_message", name: user_field.name, description: user_field.description) @@ -115,8 +115,11 @@ def merge_functions(opts) functions = [calculator_function, wikipedia_function] if opts[:private] && SiteSetting.chatbot_user_fields_collection - UserField.all.each do |user_field| - functions << ::DiscourseChatbot::UserFieldFunction.new(user_field.name, opts[:user_id]) + UserField.where(editable: true).each do |user_field| + if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || + ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? + functions << ::DiscourseChatbot::UserFieldFunction.new(user_field.name, opts[:user_id]) + end end end diff --git a/lib/discourse_chatbot/functions/user_field_function.rb b/lib/discourse_chatbot/functions/user_field_function.rb index 356969a..a58444f 100644 --- a/lib/discourse_chatbot/functions/user_field_function.rb +++ b/lib/discourse_chatbot/functions/user_field_function.rb @@ -5,11 +5,20 @@ module DiscourseChatbot class UserFieldFunction < Function def initialize(user_field, user_id) - super() + @user_field_options = [] @user_field = user_field + @user_field_object = UserField.find_by(name: user_field) + @user_field_id = @user_field_object.id + @user_field_type = @user_field_object.field_type_enum + if @user_field_type == "dropdown" + UserFieldOption.where(user_field_id: @user_field_id).each do |option| + @user_field_options << option.value + end + end @function_name = user_field.downcase.gsub(" ", "_") - @user_custom_field_name = "user_field_#{UserField.find_by(name: user_field).id}" + @user_custom_field_name = "user_field_#{@user_field_id}" @user_id = user_id + super() end def name @@ -17,13 +26,29 @@ def name end def description - I18n.t("chatbot.prompt.function.user_information.description", user_field: @user_field) + case @user_field_type + when "confirm" + I18n.t("chatbot.prompt.function.user_information.description.confirmation", user_field: @user_field) + else + I18n.t("chatbot.prompt.function.user_information.description.general", user_field: @user_field) + end end def parameters - [ - { name: "answer", type: String, description: I18n.t("chatbot.prompt.function.user_information.parameters.answer") } , - ] + case @user_field_type + when "text" + [ + { name: "answer", type: String, description: I18n.t("chatbot.prompt.function.user_information.parameters.answer.text", user_field: @user_field) } , + ] + when "confirm" + [ + { name: "answer", type: String, enum: ["true", "false"], description: I18n.t("chatbot.prompt.function.user_information.parameters.answer.confirmation", user_field: @user_field) } , + ] + when "dropdown" + [ + { name: "answer", type: String, enum: @user_field_options, description: I18n.t("chatbot.prompt.function.user_information.parameters.answer.dropdown", user_field: @user_field, options: @user_field_options) } , + ] + end end def required @@ -33,9 +58,8 @@ def required def process(args) begin super(args) - - # ::UserField.find_by(name: @user_field) = args[parameters[0][:name]] ucf = ::UserCustomField.where(user_id: @user_id, name: @user_custom_field_name).first + if ucf ucf.value = args[parameters[0][:name]] ucf.save! @@ -43,10 +67,6 @@ def process(args) ::UserCustomField.create!(user_id: @user_id, name: @user_custom_field_name, value: args[parameters[0][:name]]) end - - #, value: args[parameters[0][:name]]}, on_duplicate: :update, unique_by: [:user_id, :name]) - #::UserCustomField.upsert({user_id: @user_id, name: @user_field, value: args[parameters[0][:name]]}, on_duplicate: :update, unique_by: [:user_id, :name]) - rescue StandardError => e Rails.logger.error("Chatbot: Error occurred while attempting to store answer in a User Custom Field: #{e.message}") I18n.t("chatbot.prompt.function.user_information.error", user_field: @user_field, answer: args[parameters[0][:name]]) From c6eaf59b4a2697106f1b2a73cef9d093e16e1aed Mon Sep 17 00:00:00 2001 From: merefield Date: Sat, 12 Oct 2024 11:20:17 +0100 Subject: [PATCH 4/4] influence kick-off statement --- .../discourse_chatbot/chatbot_controller.rb | 40 +++++++++++++++- config/locales/server.en.yml | 8 +++- .../bots/open_ai_bot_base.rb | 24 ++++++---- lib/discourse_chatbot/bots/open_ai_bot_rag.rb | 48 ++++++++++++++----- .../functions/user_field_function.rb | 1 + 5 files changed, 96 insertions(+), 25 deletions(-) diff --git a/app/controllers/discourse_chatbot/chatbot_controller.rb b/app/controllers/discourse_chatbot/chatbot_controller.rb index 6789a1e..30789cd 100644 --- a/app/controllers/discourse_chatbot/chatbot_controller.rb +++ b/app/controllers/discourse_chatbot/chatbot_controller.rb @@ -18,6 +18,42 @@ def start_bot_convo evaluation = ::DiscourseChatbot::EventEvaluation.new over_quota = evaluation.over_quota(current_user.id) + kick_off_statement = I18n.t("chatbot.quick_access_kick_off.announcement") + + if SiteSetting.chatbot_user_fields_collection + + trust_level = ::DiscourseChatbot::EventEvaluation.new.trust_level(current_user.id) + opts = { trust_level: trust_level, user_id: current_user.id } + + start_bot = ::DiscourseChatbot::OpenAiBotRag.new(opts, false) + + system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.private", current_date_time: DateTime.current) } + assistant_message = { "role": "assistant", "content": I18n.t("chatbot.prmopt.quick_access_kick_off.announcement") } + + system_message_suffix = start_bot.get_system_message_suffix(opts) + system_message[:content] += " " + system_message_suffix + + messages = [system_message, assistant_message] + + model = start_bot.model_name + + parameters = { + model: model, + messages: messages, + max_completion_tokens: SiteSetting.chatbot_max_response_tokens, + temperature: SiteSetting.chatbot_request_temperature / 100.0, + top_p: SiteSetting.chatbot_request_top_p / 100.0, + frequency_penalty: SiteSetting.chatbot_request_frequency_penalty / 100.0, + presence_penalty: SiteSetting.chatbot_request_presence_penalty / 100.0 + } + + res = start_bot.client.chat( + parameters: parameters + ) + + kick_off_statement = res.dig("choices", 0, "message", "content") + end + if channel_type == "chat" bot_author = ::User.find_by(username: SiteSetting.chatbot_bot_user) @@ -44,7 +80,7 @@ def start_bot_convo Chat::CreateMessage.call( chat_channel_id: chat_channel_id, guardian: guardian, - message: over_quota ? I18n.t('chatbot.errors.overquota') : I18n.t("chatbot.quick_access_kick_off.announcement"), + message: over_quota ? I18n.t('chatbot.errors.overquota') : kick_off_statement, ) end @@ -53,7 +89,7 @@ def start_bot_convo elsif channel_type == "personal message" default_opts = { post_alert_options: { skip_send_email: true }, - raw: over_quota ? I18n.t('chatbot.errors.overquota') : I18n.t("chatbot.quick_access_kick_off.announcement"), + raw: over_quota ? I18n.t('chatbot.errors.overquota') : kick_off_statement, skip_validations: true, title: I18n.t("chatbot.pm_prefix"), archetype: Archetype.private_message, diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index e3a32c0..374a14d 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -110,10 +110,16 @@ en: post: "%{username} said %{raw}" private_message: title_creation: "Create a short Topic title from summarising the prior messages" + quick_access_kick_off: + announcement: "I must greet the user warmly and kick off any questions I want answered" function: user_information: name: "user_information_for_%{user_field}" - system_message: "You must be proactive and ask the user to tell you what their %{description} is. Do not wait to be prompted!" + system_message: + general: "You must be proactive and ask the user to tell you what their %{name} is. Do not wait to be prompted!" + confirmation: "You must be proactive and ask the user to tell you what response is to the declaration: %{name}. Do not wait to be prompted!" + dropdown: "You must be proactive and ask the user to tell you what their selection of: %{name} is. The choices are %{options}. Do not wait to be prompted!" + closing_statement: "However, ONLY ask ONE question at a time. Do not ask multiple questions in one go. Ask last question first." description: general: get information about %{user_field} for the user confirmation: confirm if the user accepts the following %{user_field} diff --git a/lib/discourse_chatbot/bots/open_ai_bot_base.rb b/lib/discourse_chatbot/bots/open_ai_bot_base.rb index 04ad4d5..49a05ff 100644 --- a/lib/discourse_chatbot/bots/open_ai_bot_base.rb +++ b/lib/discourse_chatbot/bots/open_ai_bot_base.rb @@ -4,6 +4,7 @@ module ::DiscourseChatbot class OpenAIBotBase < Bot + attr_reader :client, :model_name def initialize(opts) ::OpenAI.configure do |config| @@ -39,21 +40,24 @@ def initialize(opts) end end - @model_name = - SiteSetting.chatbot_support_vision == "directly" ? SiteSetting.chatbot_open_ai_vision_model : - case opts[:trust_level] - when TRUST_LEVELS[0], TRUST_LEVELS[1], TRUST_LEVELS[2] - SiteSetting.send("chatbot_open_ai_model_custom_" + opts[:trust_level] + "_trust") ? - SiteSetting.send("chatbot_open_ai_model_custom_name_" + opts[:trust_level] + "_trust") : - SiteSetting.send("chatbot_open_ai_model_" + opts[:trust_level] + "_trust") - else - SiteSetting.chatbot_open_ai_model_custom_low_trust ? SiteSetting.chatbot_open_ai_model_custom_name_low_trust : SiteSetting.chatbot_open_ai_model_low_trust - end + @model_name = get_model(opts) end def get_response(prompt, opts) raise "Overwrite me!" end + def get_model(opts) + SiteSetting.chatbot_support_vision == "directly" ? SiteSetting.chatbot_open_ai_vision_model : + case opts[:trust_level] + when TRUST_LEVELS[0], TRUST_LEVELS[1], TRUST_LEVELS[2] + SiteSetting.send("chatbot_open_ai_model_custom_" + opts[:trust_level] + "_trust") ? + SiteSetting.send("chatbot_open_ai_model_custom_name_" + opts[:trust_level] + "_trust") : + SiteSetting.send("chatbot_open_ai_model_" + opts[:trust_level] + "_trust") + else + SiteSetting.chatbot_open_ai_model_custom_low_trust ? SiteSetting.chatbot_open_ai_model_custom_name_low_trust : SiteSetting.chatbot_open_ai_model_low_trust + end + end + end end diff --git a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb index 4e6a32e..c30847c 100644 --- a/lib/discourse_chatbot/bots/open_ai_bot_rag.rb +++ b/lib/discourse_chatbot/bots/open_ai_bot_rag.rb @@ -31,9 +31,9 @@ class OpenAiBotRag < OpenAIBotBase FORCE_A_FUNCTION = "force_a_function" FORCE_LOCAL_SEARCH_FUNCTION = "force_local_forum_search" - def initialize(opts) - super - merge_functions(opts) + def initialize(opts, tools = true) + super(opts) + merge_functions(opts) if tools end def get_response(prompt, opts) @@ -43,20 +43,13 @@ def get_response(prompt, opts) system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.private", current_date_time: DateTime.current) } if SiteSetting.chatbot_user_fields_collection - UserField.where(editable: true).each do |user_field| - if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || - ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? - system_message[:content] += " " + I18n.t("chatbot.prompt.function.user_information.system_message", name: user_field.name, description: user_field.description) - break - end - end + system_message[:content] += " " + get_system_message_suffix(opts) end else system_message = { "role": "system", "content": I18n.t("chatbot.prompt.system.rag.open", current_date_time: DateTime.current) } end if SiteSetting.chatbot_user_fields_collection - # prioritize the system message prompt << system_message else prompt.unshift(system_message) @@ -77,6 +70,35 @@ def get_response(prompt, opts) } end + def get_system_message_suffix(opts) + system_message_suffix = "" + system_message_suffix_array = [] + UserField.where(editable: true).order(:id).each do |user_field| + user_field_options = [] + user_field_id = user_field.id + user_field_type = user_field.field_type_enum + if user_field_type == "dropdown" + UserFieldOption.where(user_field_id: user_field_id).each do |option| + user_field_options << option.value + end + end + if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || + ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? + system_message_suffix_array << case user_field_type + when "confirm" + I18n.t("chatbot.prompt.function.user_information.system_message.confirmation", name: user_field.name, description: user_field.description) + when "dropdown" + I18n.t("chatbot.prompt.function.user_information.system_message.dropdown", name: user_field.name, options: user_field_options.to_sentence) + else + I18n.t("chatbot.prompt.function.user_information.system_message.general", name: user_field.name, description: user_field.description) + end + end + break if system_message_suffix_array.length > 1 + end + system_message_suffix = system_message_suffix_array.reverse.join(" ") + system_message_suffix += " " + I18n.t("chatbot.prompt.function.user_information.system_message.closing_statement") + end + def merge_functions(opts) calculator_function = ::DiscourseChatbot::CalculatorFunction.new wikipedia_function = ::DiscourseChatbot::WikipediaFunction.new @@ -115,11 +137,13 @@ def merge_functions(opts) functions = [calculator_function, wikipedia_function] if opts[:private] && SiteSetting.chatbot_user_fields_collection - UserField.where(editable: true).each do |user_field| + start_length = functions.length + UserField.where(editable: true).order(:id).each do |user_field| if !::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).exists? || ::UserCustomField.where(user_id: opts[:user_id], name: "user_field_#{UserField.find_by(name: user_field.name).id}" ).first.value.blank? functions << ::DiscourseChatbot::UserFieldFunction.new(user_field.name, opts[:user_id]) end + break if functions.length > start_length + 1 end end diff --git a/lib/discourse_chatbot/functions/user_field_function.rb b/lib/discourse_chatbot/functions/user_field_function.rb index a58444f..c3f33b3 100644 --- a/lib/discourse_chatbot/functions/user_field_function.rb +++ b/lib/discourse_chatbot/functions/user_field_function.rb @@ -67,6 +67,7 @@ def process(args) ::UserCustomField.create!(user_id: @user_id, name: @user_custom_field_name, value: args[parameters[0][:name]]) end + I18n.t("chatbot.prompt.function.user_information.answer", user_field: @user_field) rescue StandardError => e Rails.logger.error("Chatbot: Error occurred while attempting to store answer in a User Custom Field: #{e.message}") I18n.t("chatbot.prompt.function.user_information.error", user_field: @user_field, answer: args[parameters[0][:name]])