diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 987e5013..93442e74 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -113,6 +113,7 @@ en: description: "AI Bot with Google access that can research information for you" default_pm_prefix: "[Untitled AI bot PM]" topic_not_found: "Summary unavailable, topic not found!" + searching: "Searching for: '%{query}'" command_summary: categories: "List categories" search: "Search" diff --git a/lib/modules/ai_bot/bot.rb b/lib/modules/ai_bot/bot.rb index b62cc702..985cc0b4 100644 --- a/lib/modules/ai_bot/bot.rb +++ b/lib/modules/ai_bot/bot.rb @@ -52,7 +52,7 @@ module DiscourseAi attr_reader :bot_user BOT_NOT_FOUND = Class.new(StandardError) - MAX_COMPLETIONS = 6 + MAX_COMPLETIONS = 5 def self.as(bot_user) available_bots = [DiscourseAi::AiBot::OpenAiBot, DiscourseAi::AiBot::AnthropicBot] @@ -83,14 +83,6 @@ module DiscourseAi post.topic.save_custom_fields end - def max_commands_per_reply=(val) - @max_commands_per_reply = val - end - - def max_commands_per_reply - @max_commands_per_reply || 5 - end - def reply_to( post, total_completions: 0, @@ -100,11 +92,14 @@ module DiscourseAi ) return if total_completions > MAX_COMPLETIONS - @persona = DiscourseAi::AiBot::Personas::General.new + # do not allow commands when we are at the end of chain (total completions == MAX_COMPLETIONS) + allow_commands = (total_completions < MAX_COMPLETIONS) + + @persona = DiscourseAi::AiBot::Personas::General.new(allow_commands: allow_commands) if persona_name = post.topic.custom_fields["ai_persona"] persona_class = DiscourseAi::AiBot::Personas.all.find { |current| current.name == persona_name } - @persona = persona_class.new if persona_class + @persona = persona_class.new(allow_commands: allow_commands) if persona_class end prompt = diff --git a/lib/modules/ai_bot/commands/search_command.rb b/lib/modules/ai_bot/commands/search_command.rb index d4195bd3..528191a7 100644 --- a/lib/modules/ai_bot/commands/search_command.rb +++ b/lib/modules/ai_bot/commands/search_command.rb @@ -117,7 +117,7 @@ module DiscourseAi::AiBot::Commands @last_query = search_string - show_progress(localized_description) + show_progress(I18n.t("discourse_ai.ai_bot.searching", query: search_string)) results = Search.execute( @@ -145,16 +145,24 @@ module DiscourseAi::AiBot::Commands search = Search.new(search_string, guardian: Guardian.new) - results = semantic_search.search_for_topics(search.term) - results = search.apply_filters(results) + results = nil + begin + results = semantic_search.search_for_topics(search.term) + rescue => e + Discourse.warn_exception(e, message: "Semantic search failed") + end - results.each do |post| - next if topic_ids.include?(post.topic_id) + if results + results = search.apply_filters(results) - topic_ids << post.topic_id - posts << post + results.each do |post| + next if topic_ids.include?(post.topic_id) - break if posts.length >= MAX_RESULTS + topic_ids << post.topic_id + posts << post + + break if posts.length >= MAX_RESULTS + end end end diff --git a/lib/modules/ai_bot/open_ai_bot.rb b/lib/modules/ai_bot/open_ai_bot.rb index eb4bbcd3..9148c2af 100644 --- a/lib/modules/ai_bot/open_ai_bot.rb +++ b/lib/modules/ai_bot/open_ai_bot.rb @@ -16,8 +16,9 @@ module DiscourseAi # note this is about 100 tokens over, OpenAI have a more optimal representation @function_size ||= tokenize(available_functions.to_json).length - # provide a buffer of 50 tokens in case our counting is off - buffer = @function_size + reply_params[:max_tokens] + 50 + # provide a buffer of 80 tokens - our function counting is not + # 100% accurate so this is a trial and error number + buffer = @function_size + reply_params[:max_tokens] + 80 if bot_user.id == DiscourseAi::AiBot::EntryPoint::GPT4_ID 8192 - buffer @@ -90,7 +91,7 @@ module DiscourseAi fn = partial.dig(:choices, 0, :delta, :function_call) if fn functions.add_function(fn[:name]) if fn[:name].present? - functions.add_argument_fragment(fn[:arguments]) if fn[:arguments].present? + functions.add_argument_fragment(fn[:arguments]) if !fn[:arguments].nil? end end diff --git a/lib/modules/ai_bot/personas/persona.rb b/lib/modules/ai_bot/personas/persona.rb index 4ff40dfb..d3f07583 100644 --- a/lib/modules/ai_bot/personas/persona.rb +++ b/lib/modules/ai_bot/personas/persona.rb @@ -22,11 +22,17 @@ module DiscourseAi I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description") end + def initialize(allow_commands: true) + @allow_commands = allow_commands + end + def commands [] end def render_commands(render_function_instructions:) + return +"" if !@allow_commands + result = +"" if render_function_instructions result << "\n" @@ -55,12 +61,15 @@ module DiscourseAi end def available_commands + return [] if !@allow_commands + return @available_commands if @available_commands @available_commands = all_available_commands.filter { |cmd| commands.include?(cmd) } end def available_functions + return [] if !@allow_commands # note if defined? can be a problem in test # this can never be nil so it is safe return @available_functions if @available_functions diff --git a/lib/shared/inference/function_list.rb b/lib/shared/inference/function_list.rb index 598e47de..180f8336 100644 --- a/lib/shared/inference/function_list.rb +++ b/lib/shared/inference/function_list.rb @@ -84,7 +84,7 @@ module ::DiscourseAi - When you run a command/function you will gain access to real information in a subsequant call! - NEVER EVER pretend to know stuff, you ALWAYS lean on functions to discover the truth! - You have direct access to data on this forum using !functions - - You are not a lier, liers are bad bots, you are a good bot! + - You are not a liar, liars are bad bots, you are a good bot! - You always prefer to say "I don't know" as opposed to inventing a lie! { diff --git a/spec/lib/modules/ai_bot/bot_spec.rb b/spec/lib/modules/ai_bot/bot_spec.rb index 1d5e354b..657db3ca 100644 --- a/spec/lib/modules/ai_bot/bot_spec.rb +++ b/spec/lib/modules/ai_bot/bot_spec.rb @@ -42,7 +42,6 @@ RSpec.describe DiscourseAi::AiBot::Bot do describe "#reply_to" do it "can respond to a search command" do bot.system_prompt_style!(:simple) - bot.max_commands_per_reply = 2 expected_response = { function_call: { diff --git a/spec/lib/modules/ai_bot/personas/persona_spec.rb b/spec/lib/modules/ai_bot/personas/persona_spec.rb index 87a19b25..44bf1606 100644 --- a/spec/lib/modules/ai_bot/personas/persona_spec.rb +++ b/spec/lib/modules/ai_bot/personas/persona_spec.rb @@ -34,6 +34,18 @@ module DiscourseAi::AiBot::Personas topic end + it "can disable commands via constructor" do + persona = TestPersona.new(allow_commands: false) + + rendered = + persona.render_system_prompt(topic: topic_with_users, render_function_instructions: true) + + expect(rendered).not_to include("!tags") + expect(rendered).not_to include("!search") + + expect(persona.available_functions).to be_empty + end + it "renders the system prompt" do freeze_time