From 247b5d849dd18a637bacda05220b74e70f73631d Mon Sep 17 00:00:00 2001 From: Dustin Date: Wed, 14 Aug 2024 22:56:25 -0400 Subject: [PATCH 1/2] increased timeout (larger models take longer) --- ollamarama.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ollamarama.py b/ollamarama.py index b16e163..e470a40 100644 --- a/ollamarama.py +++ b/ollamarama.py @@ -112,7 +112,7 @@ class ollamarama: "repeat_penalty": self.repeat_penalty } } - response = requests.post(self.api_url, json=data, timeout=120) + response = requests.post(self.api_url, json=data, timeout=300) #may need to increase for larger models response.raise_for_status() data = response.json() From 5a430afb74e0e4aa8a176a892867d416aa85550e Mon Sep 17 00:00:00 2001 From: Dustin Date: Thu, 22 Aug 2024 01:57:41 -0400 Subject: [PATCH 2/2] overhaul --- README.md | 67 +++------ config.json | 4 - help.txt | 76 ++++------- ollamarama.py | 371 ++++++++++++++++++-------------------------------- 4 files changed, 183 insertions(+), 335 deletions(-) diff --git a/README.md b/README.md index d800602..2de7eb4 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ pip3 install matrix-nio Set up a [Matrix account](https://app.element.io/) for your bot. You'll need the server, username and password. -Plug those into the appropriate variables in the config.json file. +Add those to the config.json file. ``` python3 ollamarama.py @@ -35,60 +35,33 @@ python3 ollamarama.py ## Use -**.ai _message_ or botname: _message_** - Basic usage. - Personality is preset by bot operator. - -**.x _user message_** - This allows you to talk to another user's chat history. - _user_ is the display name of the user whose history you want to use - + +**.ai _message_** or **botname: _message_** + Basic usage. + +**.x _user_ _message_** + This allows you to talk to another user's chat history. + _user_ is the display name of the user whose history you want to use + **.persona _personality_** - Changes the personality. It can be a character, personality type, object, idea. - Don't use a custom system prompt here. + Changes the personality. It can be a character, personality type, object, idea, whatever. Use your imagination. **.custom _prompt_** - Allows use of a custom system prompt instead of the roleplaying prompt + Allows use of a custom system prompt instead of the roleplaying prompt **.reset** - Reset to preset personality + Clear history and reset to preset personality **.stock** - Remove personality and reset to standard settings - -**.help** - Show the built-in help menu + Clear history and use without a system prompt -**.models** - Show current model and available models (admin only) - -**.model _name_** - Set a model (admin only) - -**.model _reset_** - Reset to default model (admin only) - -**.temperature** - Set temperature value between 0 and 1. To reset to default, type reset instead of a number. (bot owner only) - -**.top_p** - Set top_p value between 0 and 1. To reset to default, type reset instead of a number. (bot owner only) - -**.repeat_penalty** - Set repeat_penalty between 0 and 2. To reset to default, type reset instead of a number. (bot owner only) +**Admin only commands** + +**.model _model_** + Omit model name to show current model and available models + Include model name to change model **.clear** - Resets all bot history and sets default model (bot owner only) - -**.auth _user_** - Add user to admins (bot owner only) - -**.deauth _user_** - Remove user from admins (bot owner only) - -**.gpersona _persona_** - Change global personality (bot owner only) - -**.gpersona reset** - Reset global personality (bot owner only) + Reset bot for everyone + diff --git a/config.json b/config.json index ddc0585..aa900b0 100644 --- a/config.json +++ b/config.json @@ -28,11 +28,7 @@ }, "models": { - "llama3": "llama3:8b-instruct-q5_K_M", "wizardlm2": "wizardlm2:7b-q5_K_M", - "phi3": "phi3:14b-medium-4k-instruct-q5_K_M", - "zephyr": "zephyr:7b-beta-q5_K_M", - "solar": "solar:10.7b-instruct-v1-q5_K_M", "mistral": "mistral:7b-instruct-v0.2-q5_K_M", "dolphin-mistral": "dolphin-mistral:7b-v2.8-q5_K_M", "dolphin-llama3": "dolphin-llama3:8b-v2.9-q5_K_M", diff --git a/help.txt b/help.txt index a58aebb..d18b753 100644 --- a/help.txt +++ b/help.txt @@ -1,55 +1,33 @@ -.ai or botname: - Basic usage. - Personality is preset by bot operator. - -.x - This allows you to talk to another user's chat history. - is the display name of the user whose history you want to use - -.persona - Changes the personality. It can be a character, personality type, object, idea. -.custom - Allows use of a custom system prompt instead of the roleplaying prompt - -.reset - Reset to preset personality +**.ai _message_** or **botname: _message_** + Basic usage. -.stock - Remove personality and reset to standard model settings - +**.x _user_ _message_** + This allows you to talk to another user's chat history. + _user_ is the display name of the user whose history you want to use -Available at https://github.com/h1ddenpr0cess20/ollamarama-matrix +**.persona _personality_** + Changes the personality. It can be a character, personality type, object, idea, whatever. Use your imagination. + +**.custom _prompt_** + Allows use of a custom system prompt instead of the roleplaying prompt + +**.reset** + Clear history and reset to preset personality + +**.stock** + Clear history and use without a system prompt + + +**Available at** https://github.com/h1ddenpr0cess20/ollamarama-matrix ~~~ -.admins - List of users authorized to use these commands - -.models - List available models -.model - Change the model - -.temperature <#> - Set temperature value between 0 and 1. To reset to default, type reset instead of a number. (bot owner only) - -.top_p <#> - Set top_p value between 0 and 1. To reset to default, type reset instead of a number. (bot owner only) - -.repeat_penalty <#> - Set repeat_penalty between 0 and 2. To reset to default, type reset instead of a number. (bot owner only) - -.clear - Reset bot for everyone (bot owner only) - -.gpersona - Change default global personality (bot owner only) - -.gpersona reset - Reset global personality (bot owner only) +**Admin only commands** -.auth - Add an admin (bot owner only) - -.deauth - Remove an admin (bot owner only) \ No newline at end of file +**.model _model_** + Omit model name to show current model and available models + Include model name to change model + +**.clear** + Reset bot for everyone + \ No newline at end of file diff --git a/ollamarama.py b/ollamarama.py index e470a40..3d5d336 100644 --- a/ollamarama.py +++ b/ollamarama.py @@ -22,11 +22,6 @@ class ollamarama: self.server, self.username, self.password, self.channels, self.admins = config["matrix"].values() - self.api_url = config["ollama"]["api_base"] + "/api/chat" - - self.default_personality = config["ollama"]["personality"] - self.personality = self.default_personality - self.client = AsyncClient(self.server, self.username) # time program started and joined channels @@ -35,8 +30,7 @@ class ollamarama: # store chat history self.messages = {} - #prompt parts - self.prompt = config["ollama"]["prompt"] + self.api_url = config["ollama"]["api_base"] + "/api/chat" self.models = config["ollama"]["models"] self.default_model = self.models[config["ollama"]["default_model"]] @@ -48,12 +42,10 @@ class ollamarama: "top_p": self.top_p, "repeat_penalty": self.repeat_penalty } - - #load help menu - with open("help.txt", "r") as f: - self.help, self.help_admin = f.read().split("~~~") - f.close() + self.default_personality = config["ollama"]["personality"] + self.personality = self.default_personality + self.prompt = config["ollama"]["prompt"] # get the display name for a user async def display_name(self, user): @@ -61,7 +53,7 @@ class ollamarama: name = await self.client.get_displayname(user) return name.displayname except Exception as e: - print(e) + return user # simplifies sending messages to the channel async def send_message(self, channel, message): @@ -77,27 +69,20 @@ class ollamarama: # add messages to the history dictionary async def add_history(self, role, channel, sender, message): - #check if channel is in the history yet - if channel in self.messages: - #check if user is in channel history - if sender in self.messages[channel]: - self.messages[channel][sender].append({"role": role, "content": message}) - + if channel not in self.messages: + self.messages[channel] = {} + if sender not in self.messages[channel]: + self.messages[channel][sender] = [ + {"role": "system", "content": self.prompt[0] + self.personality + self.prompt[1]} + ] + self.messages[channel][sender].append({"role": role, "content": message}) + + #trim history + if len(self.messages[channel][sender]) > 24: + if self.messages[channel][sender][0]["role"] == "system": + del self.messages[channel][sender][1:3] else: - self.messages[channel][sender] = [ - {"role": "system", "content": self.prompt[0] + self.personality + self.prompt[1]}, - {"role": role, "content": message}] - else: - #set up channel in history - self.messages[channel]= {} - self.messages[channel][sender] = {} - if role == "system": - self.messages[channel][sender] = [{"role": role, "content": message}] - else: - #add personality to the new user entry - self.messages[channel][sender] = [ - {"role": "system", "content": self.prompt[0] + self.personality + self.prompt[1]}, - {"role": role, "content": message}] + del self.messages[channel][sender][0:2] #generate Ollama model response async def respond(self, channel, sender, message, sender2=None): @@ -112,7 +97,7 @@ class ollamarama: "repeat_penalty": self.repeat_penalty } } - response = requests.post(self.api_url, json=data, timeout=300) #may need to increase for larger models + response = requests.post(self.api_url, json=data, timeout=300) #may need to increase for larger models, only tested on small models response.raise_for_status() data = response.json() @@ -120,231 +105,147 @@ class ollamarama: await self.send_message(channel, "Something went wrong") print(e) else: - #Extract response text response_text = data["message"]["content"] - - #add to history await self.add_history("assistant", channel, sender, response_text) + # .x function was used if sender2: display_name = await self.display_name(sender2) # .ai was used else: display_name = await self.display_name(sender) - response_text = display_name + ":\n" + response_text.strip() - #Send response to channel + + response_text = f"**{display_name}**:\n{response_text.strip()}" + try: await self.send_message(channel, response_text) except Exception as e: print(e) - #Shrink history list for token size management - if len(self.messages[channel][sender]) > 24: - if self.messages[channel][sender][0]["role"] == "system": - del self.messages[channel][sender][1:3] - else: - del self.messages[channel][sender][0:2] - - # change the personality of the bot - async def persona(self, channel, sender, persona): + + #set personality or custom system prompt + async def set_prompt(self, channel, sender, persona=None, custom=None, respond=True): #clear existing history try: await self.messages[channel][sender].clear() except: pass - personality = self.prompt[0] + persona + self.prompt[1] - #set system prompt - await self.add_history("system", channel, sender, personality) - await self.add_history("user", channel, sender, "introduce yourself") - - # use a custom prompt - async def custom(self, channel, sender, prompt): - try: - await self.messages[channel][sender].clear() - except: - pass + if persona != None: + # combine personality with prompt parts + prompt = self.prompt[0] + persona + self.prompt[1] + if custom != None: + prompt = custom await self.add_history("system", channel, sender, prompt) - await self.add_history("user", channel, sender, "introduce yourself") + if respond: + await self.add_history("user", channel, sender, "introduce yourself") + await self.respond(channel, sender, self.messages[channel][sender]) - # tracks the messages in channels - async def message_callback(self, room: MatrixRoom, event: RoomMessageText): - # Main bot functionality - if isinstance(event, RoomMessageText): - # convert timestamp - message_time = event.server_timestamp / 1000 - message_time = datetime.datetime.fromtimestamp(message_time) - # assign parts of event to variables - message = event.body - sender = event.sender - sender_display = await self.display_name(sender) - room_id = room.room_id - - - #check if the message was sent after joining and not by the bot - if message_time > self.join_time and sender != self.username: - user = await self.display_name(event.sender) - #admin commands - if message == ".admins": - await self.send_message(room_id, f"Bot admins: {', '.join(self.admins)}") - if sender_display in self.admins: - #model switching - if message.startswith(".model"): - with open(self.config_file, "r") as f: - config = json.load(f) - f.close() - self.models = config["ollama"]["models"] - if message == ".models": - current_model = f"Current model: {self.model}\nAvailable models: {', '.join(sorted(list(self.models)))}" - await self.send_message(room_id, current_model) - - if message.startswith(".model "): - m = message.split(" ", 1)[1] - if m != None: - if m in self.models: - self.model = self.models[m] - elif m == 'reset': - self.model = self.default_model - await self.send_message(room_id, f"Model set to {self.model}") - - #bot owner commands - if sender_display == self.admins[0]: - #add admins - if message.startswith(".auth "): - nick = message.split(" ", 1)[1].strip() - if nick != None: - self.admins.append(nick) - await self.send_message(room_id, f"{nick} added to admins") - - #remove admins - if message.startswith(".deauth "): - nick = message.split(" ", 1)[1].strip() - if nick != None: - self.admins.remove(nick) - await self.send_message(room_id, f"{nick} removed from admins") - - #set new global personality - if message.startswith(".gpersona "): - self.messages.clear() - m = message.split(" ", 1)[1] - if m != None: - if m == 'reset': - self.personality = self.default_personality - else: - self.personality = m.strip() - await self.send_message(room_id, f"Global personality set to {self.personality}") - - #remove personality globally - if message == ".gstock": - pass #i'll figure this out later - - #reset history for all users - if message == ".clear": - self.messages.clear() - self.model = self.default_model - self.personality = self.default_personality - self.temperature, self.top_p, self.repeat_penalty = self.defaults.values() - - await self.send_message(room_id, "Bot has been reset for everyone") - - if message.startswith((".temperature ", ".top_p ", ".repeat_penalty ")): - attr_name = message.split()[0][1:] - min_val, max_val, default_val = { - "temperature": (0, 1, self.defaults["temperature"]), - "top_p": (0, 1, self.defaults["top_p"]), - "repeat_penalty": (0, 2, self.defaults["repeat_penalty"]) - }[attr_name] - - if message.endswith(" reset"): - setattr(self, attr_name, default_val) - await self.send_message(room_id, f"{attr_name.capitalize()} set to {default_val}") - else: - try: - value = float(message.split(" ", 1)[1]) - if min_val <= value <= max_val: - setattr(self, attr_name, value) - await self.send_message(room_id, f"{attr_name.capitalize()} set to {value}") - else: - await self.send_message(room_id, f"Invalid input, {attr_name} is still {getattr(self, attr_name)}") - except: - await self.send_message(room_id, f"Invalid input, {attr_name} is still {getattr(self, attr_name)}") - - # main AI response functionality - if message.startswith(".ai ") or message.startswith(self.bot_id): - if message != ".ai reset": - m = message.split(" ", 1) - try: - m = m[1] - await self.add_history("user", room_id, sender, m) - await self.respond(room_id, sender, self.messages[room_id][sender]) - except: - pass - # collaborative functionality - if message.startswith(".x "): - m = message.split(" ", 2) - m.pop(0) - if len(m) > 1: - disp_name = m[0] - name_id = "" - m = m[1] - if room_id in self.messages: - for user in self.messages[room_id]: + async def ai(self, channel, message, sender, x=False): + try: + if x: + if len(message) > 2: + name = message[1] + if message[2]: + message = message[2:] + if channel in self.messages: + for user in self.messages[channel]: try: username = await self.display_name(user) - if disp_name == username: + if name == username: name_id = user except: - name_id = disp_name - - await self.add_history("user", room_id, name_id, m) - await self.respond(room_id, name_id, self.messages[room_id][name_id], sender) + name_id = name + await self.add_history("user", channel, name_id, ' '.join(message)) + await self.respond(channel, name_id, self.messages[channel][name_id], sender) + else: + await self.add_history("user", channel, sender, ' '.join(message[1:])) + await self.respond(channel, sender, self.messages[channel][sender]) + except: + pass + + async def reset(self, channel, sender, sender_display, stock=False): + if channel in self.messages: + try: + self.messages[channel][sender].clear() + except: + self.messages[channel] = {} + self.messages[channel][sender] = [] + if not stock: + await self.send_message(channel, f"{self.bot_id} reset to default for {sender_display}") + await self.set_prompt(channel, sender, persona=self.personality, respond=False) + else: + await self.send_message(channel, f"Stock settings applied for {sender_display}") + + async def help_menu(self, channel, sender_display): + with open("help.txt", "r") as f: + help_menu, help_admin = f.read().split("~~~") + f.close() + await self.send_message(channel, help_menu) + if sender_display in self.admins: + await self.send_message(channel, help_admin) - #change personality - if message.startswith(".persona "): - m = message.split(" ", 1) - m = m[1] - - await self.persona(room_id, sender, m) - await self.respond(room_id, sender, self.messages[room_id][sender]) + async def change_model(self, channel, model=False): + with open(self.config_file, "r") as f: + config = json.load(f) + f.close() + self.models = config["ollama"]["models"] + if model: + try: + if model in self.models: + self.model = self.models[model] + elif model == 'reset': + self.model = self.default_model + await self.send_message(channel, f"Model set to **{self.model}**") + except: + pass + else: + current_model = f"**Current model**: {self.model}\n**Available models**: {', '.join(sorted(list(self.models)))}" + await self.send_message(channel, current_model) - #custom prompt use - if message.startswith(".custom "): - m = message.split(" ", 1) - m = m[1] - await self.custom(room_id, sender, m) - await self.respond(room_id, sender, self.messages[room_id][sender]) + async def clear(self, channel): + self.messages.clear() + self.model = self.default_model + self.personality = self.default_personality + self.temperature, self.top_p, self.repeat_penalty = self.defaults.values() + await self.send_message(channel, "Bot has been reset for everyone") + + async def handle_message(self, message, sender, sender_display, channel): + user_commands = { + ".ai": lambda: self.ai(channel, message, sender), + f"{self.bot_id}:": lambda: self.ai(channel, message, sender), + ".x": lambda: self.ai(channel, message, sender, x=True), + ".persona": lambda: self.set_prompt(channel, sender, persona=' '.join(message[1:])), + ".custom": lambda: self.set_prompt(channel, sender, custom=' '.join(message[1:])), + ".reset": lambda: self.reset(channel, sender, sender_display), + ".stock": lambda: self.reset(channel, sender, sender_display, stock=True), + ".help": lambda: self.help_menu(channel, sender_display), + } + admin_commands = { + ".model": lambda: self.change_model(channel, model=message[1] if len(message) > 1 else False), + ".clear": lambda: self.clear(channel), + } + #may add back temperature controls later, per user, for now you can just change that in config on the fly - # reset bot to default personality - if message.startswith(".reset") or message == ".ai reset": #some users keep forgetting the correct command - if room_id in self.messages: - if sender in self.messages[room_id]: - self.messages[room_id][sender].clear() - await self.persona(room_id, sender, self.personality) - del self.messages[room_id][sender][1] - - try: - await self.send_message(room_id, f"{self.bot_id} reset to default for {sender_display}") - except: - await self.send_message(room_id, f"{self.bot_id} reset to default for {sender}") + command = message[0] + if command in user_commands: + action = user_commands[command] + if sender_display in self.admins and command in admin_commands: + action = admin_commands[command] + await action() + + async def message_callback(self, room: MatrixRoom, event: RoomMessageText): + if isinstance(event, RoomMessageText): + message_time = event.server_timestamp / 1000 + message_time = datetime.datetime.fromtimestamp(message_time) + message = event.body + message = message.split(" ") + sender = event.sender + sender_display = await self.display_name(sender) + channel = room.room_id + + #check if the message was sent after joining and not by the bot + if message_time > self.join_time and sender != self.username: + await self.handle_message(message, sender, sender_display, channel) - # Stock settings, no personality - if message.startswith(".stock"): - if room_id in self.messages: - if sender in self.messages[room_id]: - self.messages[room_id][sender].clear() - else: - self.messages[room_id] = {} - self.messages[room_id][sender] = [] - try: - await self.send_message(room_id, f"Stock settings applied for {sender_display}") - except: - await self.send_message(room_id, f"Stock settings applied for {sender}") - - # help menu - if message.startswith(".help"): - await self.send_message(room_id, self.help) - if sender_display in self.admins: - await self.send_message(room_id, self.help_admin) - - # main loop async def main(self): # Login, print "Logged in as @alice:example.org device id: RANDOMDID" print(await self.client.login(self.password)) @@ -364,9 +265,9 @@ class ollamarama: # start listening for messages self.client.add_event_callback(self.message_callback, RoomMessageText) - await self.client.sync_forever(timeout=30000) + await self.client.sync_forever(timeout=30000, full_state=True) if __name__ == "__main__": - bot = ollamarama() - asyncio.get_event_loop().run_until_complete(bot.main()) + ollamarama = ollamarama() + asyncio.get_event_loop().run_until_complete(ollamarama.main())