Implement quiet mode (not responding to everything)

More README content
"Fixing" auto-joining
This commit is contained in:
Kumi 2023-05-01 16:47:01 +00:00
parent 1ed989c9b9
commit 85a04c4188
Signed by: kumi
GPG key ID: ECBCC9082395383F
4 changed files with 102 additions and 27 deletions

View file

@ -9,20 +9,20 @@ probably add more in the future, so the name is a bit misleading.
## Features ## Features
- AI-generated responses to all messages in a Matrix room (chatbot) - AI-generated responses to messages in a Matrix room (chatbot)
- Currently supports OpenAI (tested with `gpt-3.5-turbo` and `gpt-4`) - Currently supports OpenAI (tested with `gpt-3.5-turbo` and `gpt-4`)
- AI-generated pictures via the `!gptbot imagine` command - AI-generated pictures via the `!gptbot imagine` command
- Currently supports OpenAI (DALL-E) - Currently supports OpenAI (DALL-E)
- Mathematical calculations via the `!gptbot calculate` command - Mathematical calculations via the `!gptbot calculate` command
- Currently supports WolframAlpha - Currently supports WolframAlpha
- Automatic classification of messages (for `imagine`, `calculate`, etc.)
- Beta feature, see Usage section for details
- Really useful commands like `!gptbot help` and `!gptbot coin` - Really useful commands like `!gptbot help` and `!gptbot coin`
- DuckDB database to store room context - DuckDB database to store room context
## Planned features ## Planned features
- End-to-end encryption support (partly implemented, but not yet working) - End-to-end encryption support (partly implemented, but not yet working)
- Automatic classification of messages (for `imagine`, `calculate`, etc.)
- Beta feature, enable for a room using `!gptbot roomsettings classification true`
## Installation ## Installation
@ -57,12 +57,48 @@ to messages. If you want to create a new room, you can use the `!gptbot newroom`
command at any time, which will cause the bot to create a new room and invite command at any time, which will cause the bot to create a new room and invite
you to it. You may also specify a room name, e.g. `!gptbot newroom My new room`. you to it. You may also specify a room name, e.g. `!gptbot newroom My new room`.
Note that the bot will currently respond to _all_ messages in the room. So you ### Reply generation
shouldn't invite it to a room with other people in it.
It also supports the `!gptbot help` command, which will print a list of available Note that the bot will respond to _all_ messages in the room by default. If you
commands. Messages starting with `!` are considered commands and will not be don't want this, for example because you want to use the bot in a room with
considered for response generation. other people, you can use the `!gptbot roomsettings` command to change the
settings for the current room. For example, you can disable response generation
with `!gptbot roomsettings always_reply false`.
With this setting, the bot will only be triggered if a message begins with
`!gptbot chat`. For example, `!gptbot chat Hello, how are you?` will cause the
bot to generate a response to the message `Hello, how are you?`. The bot will
still get previous messages in the room as context for generating the response.
### Commands
There are a few commands that you can use to interact with the bot. For example,
if you want to generate an image from a text prompt, you can use the
`!gptbot imagine` command. For example, `!gptbot imagine a cat` will cause the
bot to generate an image of a cat.
To learn more about the available commands, `!gptbot help` will print a list of
available commands.
### Automatic classification
As a beta feature, the bot can automatically classify messages and use the
appropriate API to generate a response. For example, if you send a message
like "Draw me a picture of a cat", the bot will automatically use the
`imagine` command to generate an image of a cat.
This feature is disabled by default. To enable it, use the `!gptbot roomsettings`
command to change the settings for the current room. `!gptbot roomsettings classification true`
will enable automatic classification, and `!gptbot roomsettings classification false`
will disable it again.
Note that this feature is still in beta and may not work as expected. You can
always use the commands manually if the automatic classification doesn't work
for you (including `!gptbot chat` for a regular chat message).
Also note that this feature conflicts with the `always_reply false` setting -
or rather, it doesn't make sense then because you already have to explicitly
specify the command to use.
## Troubleshooting ## Troubleshooting

View file

@ -22,7 +22,9 @@ from nio import (
RoomMessageText, RoomMessageText,
RoomSendResponse, RoomSendResponse,
SyncResponse, SyncResponse,
RoomMessageNotice RoomMessageNotice,
JoinError,
RoomLeaveError,
) )
from nio.crypto import Olm from nio.crypto import Olm
@ -58,6 +60,7 @@ class GPTBot:
image_api: Optional[OpenAI] = None image_api: Optional[OpenAI] = None
classification_api: Optional[OpenAI] = None classification_api: Optional[OpenAI] = None
operator: Optional[str] = None operator: Optional[str] = None
room_ignore_list: List[str] = [] # List of rooms to ignore invites from
@classmethod @classmethod
def from_config(cls, config: ConfigParser): def from_config(cls, config: ConfigParser):
@ -276,8 +279,25 @@ class GPTBot:
invites = self.matrix_client.invited_rooms invites = self.matrix_client.invited_rooms
for invite in invites.keys(): for invite in invites.keys():
if invite in self.room_ignore_list:
self.logger.log(
f"Ignoring invite to room {invite} (room is in ignore list)")
continue
self.logger.log(f"Accepting invite to room {invite}") self.logger.log(f"Accepting invite to room {invite}")
await self.matrix_client.join(invite)
response = await self.matrix_client.join(invite)
if isinstance(response, JoinError):
self.logger.log(
f"Error joining room {invite}: {response.message}. Not trying again.", "error")
leave_response = await self.matrix_client.room_leave(invite)
if isinstance(leave_response, RoomLeaveError):
self.logger.log(
f"Error leaving room {invite}: {leave_response.message}", "error")
self.room_ignore_list.append(invite)
async def send_image(self, room: MatrixRoom, image: bytes, message: Optional[str] = None): async def send_image(self, room: MatrixRoom, image: bytes, message: Optional[str] = None):
"""Send an image to a room. """Send an image to a room.
@ -487,13 +507,13 @@ class GPTBot:
await self.matrix_client.sync(timeout=30000) await self.matrix_client.sync(timeout=30000)
def respond_to_room_messages(self, room: MatrixRoom | str) -> bool: def respond_to_room_messages(self, room: MatrixRoom | str) -> bool:
"""Check whether the bot should respond to messages sent in a room. """Check whether the bot should respond to all messages sent in a room.
Args: Args:
room (MatrixRoom | str): The room to check. room (MatrixRoom | str): The room to check.
Returns: Returns:
bool: Whether the bot should respond to messages sent in the room. bool: Whether the bot should respond to all messages sent in the room.
""" """
if isinstance(room, MatrixRoom): if isinstance(room, MatrixRoom):
@ -501,7 +521,7 @@ class GPTBot:
with self.database.cursor() as cursor: with self.database.cursor() as cursor:
cursor.execute( cursor.execute(
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "respond_to_messages")) "SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "always_reply"))
result = cursor.fetchone() result = cursor.fetchone()
return True if not result else bool(int(result[0])) return True if not result else bool(int(result[0]))

View file

@ -28,4 +28,4 @@ async def command_newroom(room: MatrixRoom, event: RoomMessageText, bot):
await bot.matrix_client.joined_rooms() await bot.matrix_client.joined_rooms()
await bot.send_message(room, f"Alright, I've created a new room called '{room_name}' and invited you to it. You can find it at {new_room.room_id}", True) await bot.send_message(room, f"Alright, I've created a new room called '{room_name}' and invited you to it. You can find it at {new_room.room_id}", True)
await bot.send_message(new_room.room_id, f"Welcome to the new room! What can I do for you?") await bot.send_message(new_room, f"Welcome to the new room! What can I do for you?")

View file

@ -3,10 +3,15 @@ from nio.rooms import MatrixRoom
async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot): async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
setting = event.body.split()[2] setting = event.body.split()[2] if len(event.body.split()) > 2 else None
value = " ".join(event.body.split()[3:]) if len( value = " ".join(event.body.split()[3:]) if len(
event.body.split()) > 3 else None event.body.split()) > 3 else None
if setting == "classification":
setting = "use_classification"
if setting == "systemmessage":
setting = "system_message"
if setting == "system_message": if setting == "system_message":
if value: if value:
bot.logger.log("Adding system message...") bot.logger.log("Adding system message...")
@ -28,38 +33,52 @@ async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
await bot.send_message(room, f"The current system message is: '{system_message}'.", True) await bot.send_message(room, f"The current system message is: '{system_message}'.", True)
return return
if setting == "classification": if setting in ("use_classification", "always_reply"):
if value: if value:
if value.lower() in ["true", "false"]: if value.lower() in ["true", "false"]:
value = value.lower() == "true" value = value.lower() == "true"
bot.logger.log("Setting classification status...") bot.logger.log(f"Setting {setting} status for {room.room_id} to {value}...")
with bot.database.cursor() as cur: with bot.database.cursor() as cur:
cur.execute( cur.execute(
"""INSERT INTO room_settings (room_id, setting, value) VALUES (?, ?, ?) """INSERT INTO room_settings (room_id, setting, value) VALUES (?, ?, ?)
ON CONFLICT (room_id, setting) DO UPDATE SET value = ?;""", ON CONFLICT (room_id, setting) DO UPDATE SET value = ?;""",
(room.room_id, "use_classification", "1" if value else "0", "1" if value else "0") (room.room_id, setting, "1" if value else "0", "1" if value else "0")
) )
await bot.send_message(room, f"Alright, I've set use_classification to: '{value}'.", True) await bot.send_message(room, f"Alright, I've set {setting} to: '{value}'.", True)
return return
await bot.send_message(room, "You need to provide a boolean value (true/false).", True) await bot.send_message(room, "You need to provide a boolean value (true/false).", True)
return return
bot.logger.log("Retrieving classification status...") bot.logger.log(f"Retrieving {setting} status for {room.room_id}...")
use_classification = bot.room_uses_classification(room) with bot.database.cursor() as cur:
cur.execute(
"""SELECT value FROM room_settings WHERE room_id = ? AND setting = ?;""",
(room.room_id, setting)
)
await bot.send_message(room, f"The current classification status is: '{use_classification}'.", True) value = cur.fetchone()[0]
if not value:
if setting == "use_classification":
value = False
elif setting == "always_reply":
value = True
else:
value = bool(int(value))
await bot.send_message(room, f"The current {setting} status is: '{value}'.", True)
return return
message = f""" message = f"""The following settings are available:
The following settings are available:
- system_message [message]: Get or set the system message to be sent to the chat model - system_message [message]: Get or set the system message to be sent to the chat model
- classification [true/false]: Get or set whether the room uses classification - classification [true/false]: Get or set whether the room uses classification
""" - always_reply [true/false]: Get or set whether the bot should reply to all messages (if false, only reply to mentions and commands)
"""
await bot.send_message(room, message, True) await bot.send_message(room, message, True)