diff --git a/lib/modules/ai_bot/bot.rb b/lib/modules/ai_bot/bot.rb index a5657c58..74f12061 100644 --- a/lib/modules/ai_bot/bot.rb +++ b/lib/modules/ai_bot/bot.rb @@ -73,7 +73,11 @@ module DiscourseAi MAX_COMPLETIONS = 5 def self.as(bot_user) - available_bots = [DiscourseAi::AiBot::OpenAiBot, DiscourseAi::AiBot::AnthropicBot] + available_bots = [ + DiscourseAi::AiBot::OpenAiBot, + DiscourseAi::AiBot::AnthropicBot, + DiscourseAi::AiBot::OpenLlmBot, + ] bot = available_bots.detect(-> { raise BOT_NOT_FOUND }) do |bot_klass| diff --git a/lib/modules/ai_bot/entry_point.rb b/lib/modules/ai_bot/entry_point.rb index e9117c7c..8bf8113a 100644 --- a/lib/modules/ai_bot/entry_point.rb +++ b/lib/modules/ai_bot/entry_point.rb @@ -8,7 +8,14 @@ module DiscourseAi GPT4_ID = -110 GPT3_5_TURBO_ID = -111 CLAUDE_V2_ID = -112 - BOTS = [[GPT4_ID, "gpt4_bot"], [GPT3_5_TURBO_ID, "gpt3.5_bot"], [CLAUDE_V2_ID, "claude_bot"]] + OPEN_LLM_ID = -113 + + BOTS = [ + [GPT4_ID, "gpt4_bot"], + [GPT3_5_TURBO_ID, "gpt3.5_bot"], + [CLAUDE_V2_ID, "claude_bot"], + [OPEN_LLM_ID, "open_llm_bot"], + ] def self.map_bot_model_to_user_id(model_name) case model_name @@ -18,6 +25,8 @@ module DiscourseAi GPT4_ID in "claude-2" CLAUDE_V2_ID + in "open-llm" + OPEN_LLM_ID else nil end @@ -29,6 +38,7 @@ module DiscourseAi require_relative "bot" require_relative "anthropic_bot" require_relative "open_ai_bot" + require_relative "open_llm_bot" require_relative "commands/command" require_relative "commands/search_command" require_relative "commands/categories_command" diff --git a/lib/modules/ai_bot/open_llm_bot.rb b/lib/modules/ai_bot/open_llm_bot.rb new file mode 100644 index 00000000..83ea7f02 --- /dev/null +++ b/lib/modules/ai_bot/open_llm_bot.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module DiscourseAi + module AiBot + class OpenLlmBot < Bot + # format of thebloke chat models is: + # [INST] <> sys message <> {prompt} [/INST] {model_reply} [INST] {prompt} [/INST] + + def self.can_reply_as?(bot_user) + bot_user.id == DiscourseAi::AiBot::EntryPoint::OPEN_LLM_ID + end + + def bot_prompt_with_topic_context(post) + messages = super(post) + + # start with system + result = +"" + result << "[INST] <>\n #{messages.shift[:content]} <>\n\n #{messages.shift[:content]} [/INST]" + + messages.each do |message| + result << "\n\n[INST]#{message[:bot] ? "" : message[:username] + ":"} #{message[:content]} [/INST]" + end + + result + end + + def prompt_limit + 2000 + end + + def title_prompt(post) + super(post).join("\n\n") + "\n\nAssistant:" + end + + def get_delta(partial, context) + partial.dig(:token, :text) || "" + end + + private + + def populate_functions(partial, function) + # nothing to do here, no proper function support quite yet + end + + def build_message(poster_username, content, system: false, function: nil) + { bot: poster_username == bot_user.username, username: poster_username, content: content } + end + + def model_for + # we only support single model hosting for huggingface api for now + "random-string-for-now" + end + + def get_updated_title(prompt) + DiscourseAi::Inference::HuggingFaceTextGeneration.perform!( + prompt, + model_for, + temperature: 0.7, + max_tokens: 40, + ).dig(:completion) + end + + def submit_prompt(prompt, prefer_low_cost: false, &blk) + DiscourseAi::Inference::HuggingFaceTextGeneration.perform!( + prompt, + model_for, + temperature: 0.4, + max_tokens: 200, + &blk + ) + end + + def tokenize(text) + DiscourseAi::Tokenizer::AnthropicTokenizer.tokenize(text) + end + end + end +end