| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module DiscourseAi | 
					
						
							|  |  |  |   module AiBot | 
					
						
							|  |  |  |     class Bot | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |       class FunctionCalls | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         def initialize | 
					
						
							|  |  |  |           @functions = [] | 
					
						
							|  |  |  |           @current_function = nil | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |           @found = false | 
					
						
							| 
									
										
										
										
											2023-09-05 10:37:58 +10:00
										 |  |  |           @cancel_completion = false | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def found? | 
					
						
							|  |  |  |           !@functions.empty? || @found | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def found! | 
					
						
							|  |  |  |           @found = true | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-05 10:37:58 +10:00
										 |  |  |         def cancel_completion? | 
					
						
							|  |  |  |           @cancel_completion | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def cancel_completion! | 
					
						
							|  |  |  |           @cancel_completion = true | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         def add_function(name) | 
					
						
							|  |  |  |           @current_function = { name: name, arguments: +"" } | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |           @functions << @current_function | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def add_argument_fragment(fragment) | 
					
						
							|  |  |  |           @current_function[:arguments] << fragment | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def length | 
					
						
							|  |  |  |           @functions.length | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def each | 
					
						
							|  |  |  |           @functions.each { |function| yield function } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def to_a | 
					
						
							|  |  |  |           @functions | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  |       attr_reader :bot_user | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |       BOT_NOT_FOUND = Class.new(StandardError) | 
					
						
							| 
									
										
										
										
											2023-09-14 16:46:56 +10:00
										 |  |  |       MAX_COMPLETIONS = 5
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       def self.as(bot_user) | 
					
						
							|  |  |  |         available_bots = [DiscourseAi::AiBot::OpenAiBot, DiscourseAi::AiBot::AnthropicBot] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         bot = | 
					
						
							|  |  |  |           available_bots.detect(-> { raise BOT_NOT_FOUND }) do |bot_klass| | 
					
						
							|  |  |  |             bot_klass.can_reply_as?(bot_user) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         bot.new(bot_user) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def initialize(bot_user) | 
					
						
							|  |  |  |         @bot_user = bot_user | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         @persona = DiscourseAi::AiBot::Personas::General.new | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |       def update_pm_title(post) | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         prompt = title_prompt(post) | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-24 07:20:24 +10:00
										 |  |  |         new_title = get_updated_title(prompt).strip.split("\n").last | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         PostRevisor.new(post.topic.first_post, post.topic).revise!( | 
					
						
							|  |  |  |           bot_user, | 
					
						
							|  |  |  |           title: new_title.sub(/\A"/, "").sub(/"\Z/, ""), | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-08-24 07:20:24 +10:00
										 |  |  |         post.topic.custom_fields.delete(DiscourseAi::AiBot::EntryPoint::REQUIRE_TITLE_UPDATE) | 
					
						
							|  |  |  |         post.topic.save_custom_fields | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |       def reply_to( | 
					
						
							|  |  |  |         post, | 
					
						
							|  |  |  |         total_completions: 0, | 
					
						
							|  |  |  |         bot_reply_post: nil, | 
					
						
							|  |  |  |         prefer_low_cost: false, | 
					
						
							|  |  |  |         standalone: false | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |         return if total_completions > MAX_COMPLETIONS | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-14 16:46:56 +10:00
										 |  |  |         # 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) | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         if persona_name = post.topic.custom_fields["ai_persona"] | 
					
						
							|  |  |  |           persona_class = | 
					
						
							|  |  |  |             DiscourseAi::AiBot::Personas.all.find { |current| current.name == persona_name } | 
					
						
							| 
									
										
										
										
											2023-09-14 16:46:56 +10:00
										 |  |  |           @persona = persona_class.new(allow_commands: allow_commands) if persona_class | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         prompt = | 
					
						
							|  |  |  |           if standalone && post.post_custom_prompt | 
					
						
							|  |  |  |             username, standalone_prompt = post.post_custom_prompt.custom_prompt.last | 
					
						
							|  |  |  |             [build_message(username, standalone_prompt)] | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             bot_prompt_with_topic_context(post) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         redis_stream_key = nil | 
					
						
							| 
									
										
										
										
											2023-06-20 15:44:03 +10:00
										 |  |  |         partial_reply = +"" | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  |         reply = +(bot_reply_post ? bot_reply_post.raw.dup : "") | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         start = Time.now | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         setup_cancel = false | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  |         context = {} | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |         functions = FunctionCalls.new | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  |         submit_prompt(prompt, prefer_low_cost: prefer_low_cost) do |partial, cancel| | 
					
						
							| 
									
										
										
										
											2023-06-20 15:44:03 +10:00
										 |  |  |           current_delta = get_delta(partial, context) | 
					
						
							|  |  |  |           partial_reply << current_delta | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if !available_functions.empty? | 
					
						
							|  |  |  |             populate_functions( | 
					
						
							|  |  |  |               partial: partial, | 
					
						
							|  |  |  |               reply: partial_reply, | 
					
						
							|  |  |  |               functions: functions, | 
					
						
							|  |  |  |               done: false, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2023-09-05 10:37:58 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |             cancel&.call if functions.cancel_completion? | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           reply << current_delta if !functions.found? | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if redis_stream_key && !Discourse.redis.get(redis_stream_key) | 
					
						
							|  |  |  |             cancel&.call | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             bot_reply_post.update!(raw: reply, cooked: PrettyText.cook(reply)) if bot_reply_post | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           next if reply.length < SiteSetting.min_personal_message_post_length | 
					
						
							|  |  |  |           # Minor hack to skip the delay during tests. | 
					
						
							|  |  |  |           next if (Time.now - start < 0.5) && !Rails.env.test? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if bot_reply_post | 
					
						
							|  |  |  |             Discourse.redis.expire(redis_stream_key, 60) | 
					
						
							|  |  |  |             start = Time.now | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             publish_update(bot_reply_post, raw: reply.dup) | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             bot_reply_post = | 
					
						
							|  |  |  |               PostCreator.create!( | 
					
						
							|  |  |  |                 bot_user, | 
					
						
							|  |  |  |                 topic_id: post.topic_id, | 
					
						
							|  |  |  |                 raw: reply, | 
					
						
							|  |  |  |                 skip_validations: false, | 
					
						
							|  |  |  |               ) | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if !setup_cancel && bot_reply_post | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |             redis_stream_key = "gpt_cancel:#{bot_reply_post.id}" | 
					
						
							|  |  |  |             Discourse.redis.setex(redis_stream_key, 60, 1) | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |             setup_cancel = true | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if bot_reply_post | 
					
						
							|  |  |  |           publish_update(bot_reply_post, done: true) | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |           bot_reply_post.revise( | 
					
						
							|  |  |  |             bot_user, | 
					
						
							|  |  |  |             { raw: reply }, | 
					
						
							|  |  |  |             skip_validations: true, | 
					
						
							|  |  |  |             skip_revision: true, | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |           bot_reply_post.post_custom_prompt ||= post.build_post_custom_prompt(custom_prompt: []) | 
					
						
							|  |  |  |           prompt = post.post_custom_prompt.custom_prompt || [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 15:44:03 +10:00
										 |  |  |           prompt << [partial_reply, bot_user.username] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |           post.post_custom_prompt.update!(custom_prompt: prompt) | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2023-06-06 07:09:33 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |         if !available_functions.empty? | 
					
						
							|  |  |  |           populate_functions(partial: nil, reply: partial_reply, functions: functions, done: true) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if functions.length > 0
 | 
					
						
							| 
									
										
										
										
											2023-06-06 07:09:33 +10:00
										 |  |  |           chain = false | 
					
						
							|  |  |  |           standalone = false | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |           functions.each do |function| | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |             name, args = function[:name], function[:arguments] | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |             if command_klass = available_commands.detect { |cmd| cmd.invoked?(name) } | 
					
						
							| 
									
										
										
										
											2023-08-14 16:30:12 +10:00
										 |  |  |               command = | 
					
						
							|  |  |  |                 command_klass.new( | 
					
						
							|  |  |  |                   bot_user: bot_user, | 
					
						
							|  |  |  |                   args: args, | 
					
						
							|  |  |  |                   post: bot_reply_post, | 
					
						
							|  |  |  |                   parent_post: post, | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |               chain_intermediate, bot_reply_post = command.invoke! | 
					
						
							| 
									
										
										
										
											2023-06-06 07:09:33 +10:00
										 |  |  |               chain ||= chain_intermediate | 
					
						
							|  |  |  |               standalone ||= command.standalone? | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |             end | 
					
						
							| 
									
										
										
										
											2023-06-06 07:09:33 +10:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if chain | 
					
						
							|  |  |  |             reply_to( | 
					
						
							|  |  |  |               bot_reply_post, | 
					
						
							|  |  |  |               total_completions: total_completions + 1, | 
					
						
							|  |  |  |               bot_reply_post: bot_reply_post, | 
					
						
							|  |  |  |               standalone: standalone, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         end | 
					
						
							|  |  |  |       rescue => e | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         if Rails.env.development? | 
					
						
							|  |  |  |           p e | 
					
						
							|  |  |  |           puts e.backtrace | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         raise e if Rails.env.test? | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         Discourse.warn_exception(e, message: "ai-bot: Reply failed") | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 08:36:41 +10:00
										 |  |  |       def extra_tokens_per_message | 
					
						
							|  |  |  |         0
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |       def bot_prompt_with_topic_context(post, prompt: "topic") | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         messages = [] | 
					
						
							|  |  |  |         conversation = conversation_context(post) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         rendered_system_prompt = system_prompt(post) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 08:36:41 +10:00
										 |  |  |         total_prompt_tokens = tokenize(rendered_system_prompt).length + extra_tokens_per_message | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         messages = | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |           conversation.reduce([]) do |memo, (raw, username, function)| | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |             break(memo) if total_prompt_tokens >= prompt_limit | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |             tokens = tokenize(raw.to_s) | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 08:36:41 +10:00
										 |  |  |             while !raw.blank? && | 
					
						
							|  |  |  |                     tokens.length + total_prompt_tokens + extra_tokens_per_message > prompt_limit | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |               raw = raw[0..-100] || "" | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |               tokens = tokenize(raw.to_s) | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |             next(memo) if raw.blank? | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 08:36:41 +10:00
										 |  |  |             total_prompt_tokens += tokens.length + extra_tokens_per_message | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |             memo.unshift(build_message(username, raw, function: !!function)) | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         messages.unshift(build_message(bot_user.username, rendered_system_prompt, system: true)) | 
					
						
							| 
									
										
										
										
											2023-08-22 08:36:41 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |         messages | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def prompt_limit | 
					
						
							|  |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |       def title_prompt(post) | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |         [build_message(bot_user.username, <<~TEXT)] | 
					
						
							| 
									
										
										
										
											2023-08-24 07:20:24 +10:00
										 |  |  |           You are titlebot. Given a topic you will figure out a title. | 
					
						
							|  |  |  |           You will never respond with anything but a topic title. | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |           Suggest a 7 word title for the following topic without quoting any of it: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           #{post.topic.posts[1..-1].map(&:raw).join("\n\n")[0..prompt_limit]} | 
					
						
							|  |  |  |         TEXT | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |       def available_commands | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         @persona.available_commands | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def system_prompt_style!(style) | 
					
						
							|  |  |  |         @style = style | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def system_prompt(post) | 
					
						
							|  |  |  |         return "You are a helpful Bot" if @style == :simple | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         @persona.render_system_prompt( | 
					
						
							|  |  |  |           topic: post.topic, | 
					
						
							|  |  |  |           render_function_instructions: include_function_instructions_in_system_prompt?, | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def include_function_instructions_in_system_prompt? | 
					
						
							|  |  |  |         true | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def function_list | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         @persona.function_list | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-29 10:43:58 +10:00
										 |  |  |       def tokenizer | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-29 10:43:58 +10:00
										 |  |  |       def tokenize(text) | 
					
						
							|  |  |  |         tokenizer.tokenize(text) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  |       def submit_prompt(prompt, prefer_low_cost: false, &blk) | 
					
						
							|  |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  |       def get_delta(partial, context) | 
					
						
							|  |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |       def populate_functions(partial:, reply:, functions:, done:) | 
					
						
							|  |  |  |         if !done | 
					
						
							| 
									
										
										
										
											2023-09-05 10:37:58 +10:00
										 |  |  |           if functions.found? | 
					
						
							|  |  |  |             functions.cancel_completion! if reply.split("\n")[-1].match?(/^\s*[^!]+/) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2023-08-23 07:49:36 +10:00
										 |  |  |           functions.found! if reply.match?(/^!/i) | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           reply | 
					
						
							|  |  |  |             .scan(/^!.*$/i) | 
					
						
							|  |  |  |             .each do |line| | 
					
						
							|  |  |  |               function_list | 
					
						
							|  |  |  |                 .parse_prompt(line) | 
					
						
							|  |  |  |                 .each do |function| | 
					
						
							|  |  |  |                   functions.add_function(function[:name]) | 
					
						
							|  |  |  |                   functions.add_argument_fragment(function[:arguments].to_json) | 
					
						
							|  |  |  |                 end | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def available_functions | 
					
						
							| 
									
										
										
										
											2023-08-30 16:15:03 +10:00
										 |  |  |         @persona.available_functions | 
					
						
							| 
									
										
										
										
											2023-06-20 08:45:31 +10:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 23:08:17 +10:00
										 |  |  |       protected | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-16 14:38:21 -03:00
										 |  |  |       def get_updated_title(prompt) | 
					
						
							|  |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |       def model_for(bot) | 
					
						
							|  |  |  |         raise NotImplemented | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def conversation_context(post) | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         context = | 
					
						
							|  |  |  |           post | 
					
						
							|  |  |  |             .topic | 
					
						
							|  |  |  |             .posts | 
					
						
							|  |  |  |             .includes(:user) | 
					
						
							|  |  |  |             .joins("LEFT JOIN post_custom_prompts ON post_custom_prompts.post_id = posts.id") | 
					
						
							|  |  |  |             .where("post_number <= ?", post.post_number) | 
					
						
							|  |  |  |             .order("post_number desc") | 
					
						
							|  |  |  |             .where("post_type = ?", Post.types[:regular]) | 
					
						
							|  |  |  |             .limit(50) | 
					
						
							|  |  |  |             .pluck(:raw, :username, "post_custom_prompts.custom_prompt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         result = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  |         first = true | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |         context.each do |raw, username, custom_prompt| | 
					
						
							|  |  |  |           if custom_prompt.present? | 
					
						
							| 
									
										
										
										
											2023-05-22 12:09:14 +10:00
										 |  |  |             if first | 
					
						
							|  |  |  |               custom_prompt.reverse_each { |message| result << message } | 
					
						
							|  |  |  |               first = false | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               result << custom_prompt.first | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2023-05-20 17:45:54 +10:00
										 |  |  |           else | 
					
						
							|  |  |  |             result << [raw, username] | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         result | 
					
						
							| 
									
										
										
										
											2023-05-11 10:03:03 -03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def publish_update(bot_reply_post, payload) | 
					
						
							|  |  |  |         MessageBus.publish( | 
					
						
							|  |  |  |           "discourse-ai/ai-bot/topic/#{bot_reply_post.topic_id}", | 
					
						
							|  |  |  |           payload.merge(post_id: bot_reply_post.id, post_number: bot_reply_post.post_number), | 
					
						
							|  |  |  |           user_ids: bot_reply_post.topic.allowed_user_ids, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |