Bump version to 0.1.0
Improve logging Black formatting
This commit is contained in:
parent
c188223cea
commit
cce082ef67
7 changed files with 349 additions and 165 deletions
105
config.dist.ini
105
config.dist.ini
|
@ -3,6 +3,61 @@
|
||||||
# The values that are not commented have to be set, everything else comes with
|
# The values that are not commented have to be set, everything else comes with
|
||||||
# sensible defaults.
|
# sensible defaults.
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
[GPTBot]
|
||||||
|
|
||||||
|
# Some way for the user to contact you.
|
||||||
|
# Ideally, either your personal user ID or a support room
|
||||||
|
# If this is your user ID and Debug is 1, any errors that occur when using the script will be reported to you in detail
|
||||||
|
#
|
||||||
|
Operator = Contact details not set
|
||||||
|
|
||||||
|
# Enable debug mode
|
||||||
|
# Will send error tracebacks to you (= Operator above) if an error occurs processing a message from you
|
||||||
|
# Defaults to 0 (= off)
|
||||||
|
#
|
||||||
|
# Debug = 1
|
||||||
|
|
||||||
|
# The default room name used by the !newroom command
|
||||||
|
# Defaults to GPTBot if not set
|
||||||
|
#
|
||||||
|
# DefaultRoomName = GPTBot
|
||||||
|
|
||||||
|
# Contents of a special message sent to the GPT API with every request.
|
||||||
|
# Can be used to give the bot some context about the environment it's running in
|
||||||
|
#
|
||||||
|
# SystemMessage = You are a helpful bot.
|
||||||
|
|
||||||
|
# Force inclusion of the SystemMessage defined above if one is defined on per-room level
|
||||||
|
# If no custom message is defined for the room, SystemMessage is always included
|
||||||
|
#
|
||||||
|
# ForceSystemMessage = 0
|
||||||
|
|
||||||
|
# Path to a custom logo
|
||||||
|
# Used as room/space image and profile picture
|
||||||
|
# Defaults to logo.png in assets directory
|
||||||
|
#
|
||||||
|
# Logo = assets/logo.png
|
||||||
|
|
||||||
|
# Display name for the bot
|
||||||
|
#
|
||||||
|
# DisplayName = GPTBot
|
||||||
|
|
||||||
|
# A list of allowed users
|
||||||
|
# If not defined, everyone is allowed to use the bot
|
||||||
|
# Use the "*:homeserver.matrix" syntax to allow everyone on a given homeserver
|
||||||
|
#
|
||||||
|
# AllowedUsers = ["*:matrix.local"]
|
||||||
|
|
||||||
|
# Minimum level of log messages that should be printed
|
||||||
|
# Available log levels in ascending order: trace, debug, info, warning, error, critical
|
||||||
|
# Defaults to info
|
||||||
|
#
|
||||||
|
LogLevel = info
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[OpenAI]
|
[OpenAI]
|
||||||
|
|
||||||
# The Chat Completion model you want to use.
|
# The Chat Completion model you want to use.
|
||||||
|
@ -38,6 +93,8 @@ APIKey = sk-yoursecretkey
|
||||||
#
|
#
|
||||||
# MaxMessages = 20
|
# MaxMessages = 20
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[WolframAlpha]
|
[WolframAlpha]
|
||||||
|
|
||||||
# An API key for Wolfram|Alpha
|
# An API key for Wolfram|Alpha
|
||||||
|
@ -47,6 +104,8 @@ APIKey = sk-yoursecretkey
|
||||||
#
|
#
|
||||||
#APIKey = YOUR-APIKEY
|
#APIKey = YOUR-APIKEY
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[Matrix]
|
[Matrix]
|
||||||
|
|
||||||
# The URL to your Matrix homeserver
|
# The URL to your Matrix homeserver
|
||||||
|
@ -66,43 +125,7 @@ AccessToken = syt_yoursynapsetoken
|
||||||
#
|
#
|
||||||
# UserID = @gptbot:matrix.local
|
# UserID = @gptbot:matrix.local
|
||||||
|
|
||||||
[GPTBot]
|
###############################################################################
|
||||||
|
|
||||||
# Some way for the user to contact you.
|
|
||||||
# Ideally, either your personal user ID or a support room
|
|
||||||
#
|
|
||||||
Operator = Contact details not set
|
|
||||||
|
|
||||||
# The default room name used by the !newroom command
|
|
||||||
# Defaults to GPTBot if not set
|
|
||||||
#
|
|
||||||
# DefaultRoomName = GPTBot
|
|
||||||
|
|
||||||
# Contents of a special message sent to the GPT API with every request.
|
|
||||||
# Can be used to give the bot some context about the environment it's running in
|
|
||||||
#
|
|
||||||
# SystemMessage = You are a helpful bot.
|
|
||||||
|
|
||||||
# Force inclusion of the SystemMessage defined above if one is defined on per-room level
|
|
||||||
# If no custom message is defined for the room, SystemMessage is always included
|
|
||||||
#
|
|
||||||
# ForceSystemMessage = 0
|
|
||||||
|
|
||||||
# Path to a custom logo
|
|
||||||
# Used as room/space image and profile picture
|
|
||||||
# Defaults to logo.png in assets directory
|
|
||||||
#
|
|
||||||
# Logo = assets/logo.png
|
|
||||||
|
|
||||||
# Display name for the bot
|
|
||||||
#
|
|
||||||
# DisplayName = GPTBot
|
|
||||||
|
|
||||||
# A list of allowed users
|
|
||||||
# If not defined, everyone is allowed to use the bot
|
|
||||||
# Use the "*:homeserver.matrix" syntax to allow everyone on a given homeserver
|
|
||||||
#
|
|
||||||
# AllowedUsers = ["*:matrix.local"]
|
|
||||||
|
|
||||||
[Database]
|
[Database]
|
||||||
|
|
||||||
|
@ -116,6 +139,8 @@ Path = database.db
|
||||||
#
|
#
|
||||||
CryptoStore = store.db
|
CryptoStore = store.db
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[TrackingMore]
|
[TrackingMore]
|
||||||
|
|
||||||
# API key for TrackingMore
|
# API key for TrackingMore
|
||||||
|
@ -123,6 +148,8 @@ CryptoStore = store.db
|
||||||
#
|
#
|
||||||
# APIKey = abcde-fghij-klmnop
|
# APIKey = abcde-fghij-klmnop
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[Replicate]
|
[Replicate]
|
||||||
|
|
||||||
# API key for replicate.com
|
# API key for replicate.com
|
||||||
|
@ -131,6 +158,8 @@ CryptoStore = store.db
|
||||||
#
|
#
|
||||||
# APIKey = r8_alotoflettersandnumbershere
|
# APIKey = r8_alotoflettersandnumbershere
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
[HuggingFace]
|
[HuggingFace]
|
||||||
|
|
||||||
# API key for Hugging Face
|
# API key for Hugging Face
|
||||||
|
@ -138,3 +167,5 @@ CryptoStore = store.db
|
||||||
# If not defined, the features that depend on it are not available
|
# If not defined, the features that depend on it are not available
|
||||||
#
|
#
|
||||||
# APIKey = __________________________
|
# APIKey = __________________________
|
||||||
|
|
||||||
|
###############################################################################
|
|
@ -7,7 +7,7 @@ allow-direct-references = true
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "matrix-gptbot"
|
name = "matrix-gptbot"
|
||||||
version = "0.1.0-alpha1"
|
version = "0.1.0"
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Kumi Mitterer", email="gptbot@kumi.email" },
|
{ name="Kumi Mitterer", email="gptbot@kumi.email" },
|
||||||
|
|
|
@ -6,4 +6,5 @@ duckdb
|
||||||
python-magic
|
python-magic
|
||||||
pillow
|
pillow
|
||||||
wolframalpha
|
wolframalpha
|
||||||
|
|
||||||
git+https://kumig.it/kumitterer/trackingmore-api-tool.git
|
git+https://kumig.it/kumitterer/trackingmore-api-tool.git
|
|
@ -16,9 +16,17 @@ if __name__ == "__main__":
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
|
"-c",
|
||||||
help="Path to config file (default: config.ini in working directory)",
|
help="Path to config file (default: config.ini in working directory)",
|
||||||
default="config.ini",
|
default="config.ini",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
help="Print version and exit",
|
||||||
|
action="version",
|
||||||
|
version="GPTBot v0.1.0",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Read config file
|
# Read config file
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
async def test_response_callback(response, bot):
|
from nio import ErrorResponse
|
||||||
bot.logger.log(
|
|
||||||
f"{response.__class__} response received", "debug")
|
|
||||||
|
|
||||||
|
|
||||||
|
async def test_response_callback(response, bot):
|
||||||
|
if isinstance(response, ErrorResponse):
|
||||||
|
bot.logger.log(
|
||||||
|
f"Error response received ({response.__class__.__name__}): {response.message}",
|
||||||
|
"warning",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
bot.logger.log(f"{response.__class__} response received", "debug")
|
||||||
|
|
|
@ -53,10 +53,11 @@ from .openai import OpenAI
|
||||||
from .wolframalpha import WolframAlpha
|
from .wolframalpha import WolframAlpha
|
||||||
from .trackingmore import TrackingMore
|
from .trackingmore import TrackingMore
|
||||||
|
|
||||||
|
|
||||||
class GPTBot:
|
class GPTBot:
|
||||||
# Default values
|
# Default values
|
||||||
database: Optional[sqlite3.Connection] = None
|
database: Optional[sqlite3.Connection] = None
|
||||||
crypto_store_path: Optional[str|Path] = None
|
crypto_store_path: Optional[str | Path] = None
|
||||||
# Default name of rooms created by the bot
|
# Default name of rooms created by the bot
|
||||||
display_name = default_room_name = "GPTBot"
|
display_name = default_room_name = "GPTBot"
|
||||||
default_system_message: str = "You are a helpful assistant."
|
default_system_message: str = "You are a helpful assistant."
|
||||||
|
@ -93,51 +94,64 @@ class GPTBot:
|
||||||
bot = cls()
|
bot = cls()
|
||||||
|
|
||||||
# Set the database connection
|
# Set the database connection
|
||||||
bot.database = sqlite3.connect(
|
bot.database = (
|
||||||
config["Database"]["Path"]) if "Database" in config and "Path" in config["Database"] else None
|
sqlite3.connect(config["Database"]["Path"])
|
||||||
|
if "Database" in config and "Path" in config["Database"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
bot.crypto_store_path = config["Database"]["CryptoStore"] if "Database" in config and "CryptoStore" in config["Database"] else None
|
bot.crypto_store_path = (
|
||||||
|
config["Database"]["CryptoStore"]
|
||||||
|
if "Database" in config and "CryptoStore" in config["Database"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
# Override default values
|
# Override default values
|
||||||
if "GPTBot" in config:
|
if "GPTBot" in config:
|
||||||
bot.operator = config["GPTBot"].get("Operator", bot.operator)
|
bot.operator = config["GPTBot"].get("Operator", bot.operator)
|
||||||
bot.default_room_name = config["GPTBot"].get(
|
bot.default_room_name = config["GPTBot"].get(
|
||||||
"DefaultRoomName", bot.default_room_name)
|
"DefaultRoomName", bot.default_room_name
|
||||||
|
)
|
||||||
bot.default_system_message = config["GPTBot"].get(
|
bot.default_system_message = config["GPTBot"].get(
|
||||||
"SystemMessage", bot.default_system_message)
|
"SystemMessage", bot.default_system_message
|
||||||
|
)
|
||||||
bot.force_system_message = config["GPTBot"].getboolean(
|
bot.force_system_message = config["GPTBot"].getboolean(
|
||||||
"ForceSystemMessage", bot.force_system_message)
|
"ForceSystemMessage", bot.force_system_message
|
||||||
|
)
|
||||||
bot.debug = config["GPTBot"].getboolean("Debug", bot.debug)
|
bot.debug = config["GPTBot"].getboolean("Debug", bot.debug)
|
||||||
|
|
||||||
logo_path = config["GPTBot"].get("Logo", str(
|
if "LogLevel" in config["GPTBot"]:
|
||||||
Path(__file__).parent.parent / "assets/logo.png"))
|
bot.logger = Logger(config["GPTBot"]["LogLevel"])
|
||||||
|
|
||||||
bot.logger.log(f"Loading logo from {logo_path}")
|
logo_path = config["GPTBot"].get(
|
||||||
|
"Logo", str(Path(__file__).parent.parent / "assets/logo.png")
|
||||||
|
)
|
||||||
|
|
||||||
|
bot.logger.log(f"Loading logo from {logo_path}", "debug")
|
||||||
|
|
||||||
if Path(logo_path).exists() and Path(logo_path).is_file():
|
if Path(logo_path).exists() and Path(logo_path).is_file():
|
||||||
bot.logo = Image.open(logo_path)
|
bot.logo = Image.open(logo_path)
|
||||||
|
|
||||||
bot.display_name = config["GPTBot"].get(
|
bot.display_name = config["GPTBot"].get("DisplayName", bot.display_name)
|
||||||
"DisplayName", bot.display_name)
|
|
||||||
|
|
||||||
if "AllowedUsers" in config["GPTBot"]:
|
if "AllowedUsers" in config["GPTBot"]:
|
||||||
bot.allowed_users = json.loads(config["GPTBot"]["AllowedUsers"])
|
bot.allowed_users = json.loads(config["GPTBot"]["AllowedUsers"])
|
||||||
|
|
||||||
bot.chat_api = bot.image_api = bot.classification_api = OpenAI(
|
bot.chat_api = bot.image_api = bot.classification_api = OpenAI(
|
||||||
config["OpenAI"]["APIKey"], config["OpenAI"].get("Model"), bot.logger)
|
config["OpenAI"]["APIKey"], config["OpenAI"].get("Model"), bot.logger
|
||||||
|
)
|
||||||
bot.max_tokens = config["OpenAI"].getint("MaxTokens", bot.max_tokens)
|
bot.max_tokens = config["OpenAI"].getint("MaxTokens", bot.max_tokens)
|
||||||
bot.max_messages = config["OpenAI"].getint(
|
bot.max_messages = config["OpenAI"].getint("MaxMessages", bot.max_messages)
|
||||||
"MaxMessages", bot.max_messages)
|
|
||||||
|
|
||||||
# Set up WolframAlpha
|
# Set up WolframAlpha
|
||||||
if "WolframAlpha" in config:
|
if "WolframAlpha" in config:
|
||||||
bot.calculation_api = WolframAlpha(
|
bot.calculation_api = WolframAlpha(
|
||||||
config["WolframAlpha"]["APIKey"], bot.logger)
|
config["WolframAlpha"]["APIKey"], bot.logger
|
||||||
|
)
|
||||||
|
|
||||||
# Set up TrackingMore
|
# Set up TrackingMore
|
||||||
if "TrackingMore" in config:
|
if "TrackingMore" in config:
|
||||||
bot.parcel_api = TrackingMore(
|
bot.parcel_api = TrackingMore(config["TrackingMore"]["APIKey"], bot.logger)
|
||||||
config["TrackingMore"]["APIKey"], bot.logger)
|
|
||||||
|
|
||||||
# Set up the Matrix client
|
# Set up the Matrix client
|
||||||
|
|
||||||
|
@ -182,17 +196,21 @@ class GPTBot:
|
||||||
room_id = room.room_id if isinstance(room, MatrixRoom) else room
|
room_id = room.room_id if isinstance(room, MatrixRoom) else room
|
||||||
|
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Fetching last {2*n} messages from room {room_id} (starting at {self.sync_token})...")
|
f"Fetching last {2*n} messages from room {room_id} (starting at {self.sync_token})...",
|
||||||
|
"debug",
|
||||||
|
)
|
||||||
|
|
||||||
response = await self.matrix_client.room_messages(
|
response = await self.matrix_client.room_messages(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
start=self.sync_token,
|
start=self.sync_token,
|
||||||
limit=2*n,
|
limit=2 * n,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(response, RoomMessagesError):
|
if isinstance(response, RoomMessagesError):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Error fetching messages: {response.message} (status code {response.status_code})", "error")
|
f"Error fetching messages: {response.message} (status code {response.status_code})",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
|
|
||||||
for event in response.chunk:
|
for event in response.chunk:
|
||||||
if len(messages) >= n:
|
if len(messages) >= n:
|
||||||
|
@ -202,34 +220,48 @@ class GPTBot:
|
||||||
event = await self.matrix_client.decrypt_event(event)
|
event = await self.matrix_client.decrypt_event(event)
|
||||||
except (GroupEncryptionError, EncryptionError):
|
except (GroupEncryptionError, EncryptionError):
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Could not decrypt message {event.event_id} in room {room_id}", "error")
|
f"Could not decrypt message {event.event_id} in room {room_id}",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
if isinstance(event, (RoomMessageText, RoomMessageNotice)):
|
if isinstance(event, (RoomMessageText, RoomMessageNotice)):
|
||||||
if event.body.startswith("!gptbot ignoreolder"):
|
if event.body.startswith("!gptbot ignoreolder"):
|
||||||
break
|
break
|
||||||
if (not event.body.startswith("!")) or (event.body.startswith("!gptbot")):
|
if (not event.body.startswith("!")) or (
|
||||||
|
event.body.startswith("!gptbot")
|
||||||
|
):
|
||||||
messages.append(event)
|
messages.append(event)
|
||||||
|
|
||||||
self.logger.log(f"Found {len(messages)} messages (limit: {n})")
|
self.logger.log(f"Found {len(messages)} messages (limit: {n})", "debug")
|
||||||
|
|
||||||
# Reverse the list so that messages are in chronological order
|
# Reverse the list so that messages are in chronological order
|
||||||
return messages[::-1]
|
return messages[::-1]
|
||||||
|
|
||||||
def _truncate(self, messages: list, max_tokens: Optional[int] = None,
|
def _truncate(
|
||||||
model: Optional[str] = None, system_message: Optional[str] = None):
|
self,
|
||||||
|
messages: list,
|
||||||
|
max_tokens: Optional[int] = None,
|
||||||
|
model: Optional[str] = None,
|
||||||
|
system_message: Optional[str] = None,
|
||||||
|
):
|
||||||
max_tokens = max_tokens or self.max_tokens
|
max_tokens = max_tokens or self.max_tokens
|
||||||
model = model or self.chat_api.chat_model
|
model = model or self.chat_api.chat_model
|
||||||
system_message = self.default_system_message if system_message is None else system_message
|
system_message = (
|
||||||
|
self.default_system_message if system_message is None else system_message
|
||||||
|
)
|
||||||
|
|
||||||
encoding = tiktoken.encoding_for_model(model)
|
encoding = tiktoken.encoding_for_model(model)
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
|
|
||||||
system_message_tokens = 0 if not system_message else (
|
system_message_tokens = (
|
||||||
len(encoding.encode(system_message)) + 1)
|
0 if not system_message else (len(encoding.encode(system_message)) + 1)
|
||||||
|
)
|
||||||
|
|
||||||
if system_message_tokens > max_tokens:
|
if system_message_tokens > max_tokens:
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"System message is too long to fit within token limit ({system_message_tokens} tokens) - cannot proceed", "error")
|
f"System message is too long to fit within token limit ({system_message_tokens} tokens) - cannot proceed",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
total_tokens += system_message_tokens
|
total_tokens += system_message_tokens
|
||||||
|
@ -279,7 +311,9 @@ class GPTBot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Received command {event.body} from {event.sender} in room {room.room_id}")
|
f"Received command {event.body} from {event.sender} in room {room.room_id}",
|
||||||
|
"debug",
|
||||||
|
)
|
||||||
command = event.body.split()[1] if event.body.split()[1:] else None
|
command = event.body.split()[1] if event.body.split()[1:] else None
|
||||||
|
|
||||||
await COMMANDS.get(command, COMMANDS[None])(room, event, self)
|
await COMMANDS.get(command, COMMANDS[None])(room, event, self)
|
||||||
|
@ -297,7 +331,9 @@ class GPTBot:
|
||||||
|
|
||||||
with closing(self.database.cursor()) as cursor:
|
with closing(self.database.cursor()) as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room_id, "use_classification"))
|
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?",
|
||||||
|
(room_id, "use_classification"),
|
||||||
|
)
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
|
|
||||||
return False if not result else bool(int(result[0]))
|
return False if not result else bool(int(result[0]))
|
||||||
|
@ -310,10 +346,13 @@ class GPTBot:
|
||||||
await callback(room, event, self)
|
await callback(room, event, self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Error in event callback for {event.__class__}: {e}", "error")
|
f"Error in event callback for {event.__class__}: {e}", "error"
|
||||||
|
)
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
await self.send_message(room, f"Error: {e}\n\n```\n{traceback.format_exc()}\n```", True)
|
await self.send_message(
|
||||||
|
room, f"Error: {e}\n\n```\n{traceback.format_exc()}\n```", True
|
||||||
|
)
|
||||||
|
|
||||||
def user_is_allowed(self, user_id: str) -> bool:
|
def user_is_allowed(self, user_id: str) -> bool:
|
||||||
"""Check if a user is allowed to use the bot.
|
"""Check if a user is allowed to use the bot.
|
||||||
|
@ -326,10 +365,14 @@ class GPTBot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
user_id in self.allowed_users or
|
(
|
||||||
f"*:{user_id.split(':')[1]}" in self.allowed_users or
|
user_id in self.allowed_users
|
||||||
f"@*:{user_id.split(':')[1]}" in self.allowed_users
|
or f"*:{user_id.split(':')[1]}" in self.allowed_users
|
||||||
) if self.allowed_users else True
|
or f"@*:{user_id.split(':')[1]}" in self.allowed_users
|
||||||
|
)
|
||||||
|
if self.allowed_users
|
||||||
|
else True
|
||||||
|
)
|
||||||
|
|
||||||
async def event_callback(self, room: MatrixRoom, event: Event):
|
async def event_callback(self, room: MatrixRoom, event: Event):
|
||||||
"""Callback for events.
|
"""Callback for events.
|
||||||
|
@ -349,8 +392,8 @@ class GPTBot:
|
||||||
"m.room.message",
|
"m.room.message",
|
||||||
{
|
{
|
||||||
"msgtype": "m.notice",
|
"msgtype": "m.notice",
|
||||||
"body": f"You are not allowed to use this bot. Please contact {self.operator} for more information."
|
"body": f"You are not allowed to use this bot. Please contact {self.operator} for more information.",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -369,7 +412,9 @@ class GPTBot:
|
||||||
|
|
||||||
with closing(self.database.cursor()) as cursor:
|
with closing(self.database.cursor()) as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room_id, "use_timing"))
|
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?",
|
||||||
|
(room_id, "use_timing"),
|
||||||
|
)
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
|
|
||||||
return False if not result else bool(int(result[0]))
|
return False if not result else bool(int(result[0]))
|
||||||
|
@ -392,7 +437,9 @@ class GPTBot:
|
||||||
for invite in invites.keys():
|
for invite in invites.keys():
|
||||||
if invite in self.room_ignore_list:
|
if invite in self.room_ignore_list:
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Ignoring invite to room {invite} (room is in ignore list)")
|
f"Ignoring invite to room {invite} (room is in ignore list)",
|
||||||
|
"debug",
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.log(f"Accepting invite to room {invite}")
|
self.logger.log(f"Accepting invite to room {invite}")
|
||||||
|
@ -401,16 +448,25 @@ class GPTBot:
|
||||||
|
|
||||||
if isinstance(response, JoinError):
|
if isinstance(response, JoinError):
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Error joining room {invite}: {response.message}. Not trying again.", "error")
|
f"Error joining room {invite}: {response.message}. Not trying again.",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
|
|
||||||
leave_response = await self.matrix_client.room_leave(invite)
|
leave_response = await self.matrix_client.room_leave(invite)
|
||||||
|
|
||||||
if isinstance(leave_response, RoomLeaveError):
|
if isinstance(leave_response, RoomLeaveError):
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Error leaving room {invite}: {leave_response.message}", "error")
|
f"Error leaving room {invite}: {leave_response.message}",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
self.room_ignore_list.append(invite)
|
self.room_ignore_list.append(invite)
|
||||||
|
|
||||||
async def upload_file(self, file: bytes, filename: str = "file", mime: str = "application/octet-stream") -> str:
|
async def upload_file(
|
||||||
|
self,
|
||||||
|
file: bytes,
|
||||||
|
filename: str = "file",
|
||||||
|
mime: str = "application/octet-stream",
|
||||||
|
) -> str:
|
||||||
"""Upload a file to the homeserver.
|
"""Upload a file to the homeserver.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -426,15 +482,14 @@ class GPTBot:
|
||||||
bio.seek(0)
|
bio.seek(0)
|
||||||
|
|
||||||
response, _ = await self.matrix_client.upload(
|
response, _ = await self.matrix_client.upload(
|
||||||
bio,
|
bio, content_type=mime, filename=filename, filesize=len(file)
|
||||||
content_type=mime,
|
|
||||||
filename=filename,
|
|
||||||
filesize=len(file)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return response.content_uri
|
return response.content_uri
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -444,7 +499,8 @@ class GPTBot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Sending image of size {len(image)} bytes to room {room.room_id}")
|
f"Sending image of size {len(image)} bytes to room {room.room_id}", "debug"
|
||||||
|
)
|
||||||
|
|
||||||
bio = BytesIO(image)
|
bio = BytesIO(image)
|
||||||
img = Image.open(bio)
|
img = Image.open(bio)
|
||||||
|
@ -453,11 +509,13 @@ class GPTBot:
|
||||||
(width, height) = img.size
|
(width, height) = img.size
|
||||||
|
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Uploading - Image size: {width}x{height} pixels, MIME type: {mime}")
|
f"Uploading - Image size: {width}x{height} pixels, MIME type: {mime}",
|
||||||
|
"debug",
|
||||||
|
)
|
||||||
|
|
||||||
content_uri = await self.upload_file(image, "image", mime)
|
content_uri = await self.upload_file(image, "image", mime)
|
||||||
|
|
||||||
self.logger.log("Uploaded image - sending message...")
|
self.logger.log("Uploaded image - sending message...", "debug")
|
||||||
|
|
||||||
content = {
|
content = {
|
||||||
"body": message or "",
|
"body": message or "",
|
||||||
|
@ -468,20 +526,18 @@ class GPTBot:
|
||||||
"h": height,
|
"h": height,
|
||||||
},
|
},
|
||||||
"msgtype": "m.image",
|
"msgtype": "m.image",
|
||||||
"url": content_uri
|
"url": content_uri,
|
||||||
}
|
}
|
||||||
|
|
||||||
status = await self.matrix_client.room_send(
|
status = await self.matrix_client.room_send(
|
||||||
room.room_id,
|
room.room_id, "m.room.message", content
|
||||||
"m.room.message",
|
|
||||||
content
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.log(str(status), "debug")
|
self.logger.log("Sent image", "debug")
|
||||||
|
|
||||||
self.logger.log("Sent image")
|
async def send_message(
|
||||||
|
self, room: MatrixRoom | str, message: str, notice: bool = False
|
||||||
async def send_message(self, room: MatrixRoom | str, message: str, notice: bool = False):
|
):
|
||||||
"""Send a message to a room.
|
"""Send a message to a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -498,8 +554,12 @@ class GPTBot:
|
||||||
|
|
||||||
msgtype = "m.notice" if notice else "m.text"
|
msgtype = "m.notice" if notice else "m.text"
|
||||||
|
|
||||||
msgcontent = {"msgtype": msgtype, "body": message,
|
msgcontent = {
|
||||||
"format": "org.matrix.custom.html", "formatted_body": formatted_body}
|
"msgtype": msgtype,
|
||||||
|
"body": message,
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": formatted_body,
|
||||||
|
}
|
||||||
|
|
||||||
content = None
|
content = None
|
||||||
|
|
||||||
|
@ -507,7 +567,9 @@ class GPTBot:
|
||||||
try:
|
try:
|
||||||
if not room.members_synced:
|
if not room.members_synced:
|
||||||
responses = []
|
responses = []
|
||||||
responses.append(await self.matrix_client.joined_members(room.room_id))
|
responses.append(
|
||||||
|
await self.matrix_client.joined_members(room.room_id)
|
||||||
|
)
|
||||||
|
|
||||||
if self.matrix_client.olm.should_share_group_session(room.room_id):
|
if self.matrix_client.olm.should_share_group_session(room.room_id):
|
||||||
try:
|
try:
|
||||||
|
@ -521,12 +583,14 @@ class GPTBot:
|
||||||
|
|
||||||
if msgtype != "m.reaction":
|
if msgtype != "m.reaction":
|
||||||
response = self.matrix_client.encrypt(
|
response = self.matrix_client.encrypt(
|
||||||
room.room_id, "m.room.message", msgcontent)
|
room.room_id, "m.room.message", msgcontent
|
||||||
|
)
|
||||||
msgtype, content = response
|
msgtype, content = response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
f"Error encrypting message: {e} - sending unencrypted", "error")
|
f"Error encrypting message: {e} - sending unencrypted", "warning"
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
|
@ -534,17 +598,24 @@ class GPTBot:
|
||||||
content = msgcontent
|
content = msgcontent
|
||||||
|
|
||||||
method, path, data = Api.room_send(
|
method, path, data = Api.room_send(
|
||||||
self.matrix_client.access_token, room.room_id, msgtype, content, uuid.uuid4()
|
self.matrix_client.access_token,
|
||||||
|
room.room_id,
|
||||||
|
msgtype,
|
||||||
|
content,
|
||||||
|
uuid.uuid4(),
|
||||||
)
|
)
|
||||||
|
|
||||||
response = await self.matrix_client._send(RoomSendResponse, method, path, data, (room.room_id,))
|
response = await self.matrix_client._send(
|
||||||
|
RoomSendResponse, method, path, data, (room.room_id,)
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(response, RoomSendError):
|
if isinstance(response, RoomSendError):
|
||||||
self.logger.log(
|
self.logger.log(f"Error sending message: {response.message}", "error")
|
||||||
f"Error sending message: {response.message}", "error")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def log_api_usage(self, message: Event | str, room: MatrixRoom | str, api: str, tokens: int):
|
def log_api_usage(
|
||||||
|
self, message: Event | str, room: MatrixRoom | str, api: str, tokens: int
|
||||||
|
):
|
||||||
"""Log API usage to the database.
|
"""Log API usage to the database.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -565,7 +636,7 @@ class GPTBot:
|
||||||
|
|
||||||
self.database.execute(
|
self.database.execute(
|
||||||
"INSERT INTO token_usage (message_id, room_id, tokens, api, timestamp) VALUES (?, ?, ?, ?, ?)",
|
"INSERT INTO token_usage (message_id, room_id, tokens, api, timestamp) VALUES (?, ?, ?, ?, ?)",
|
||||||
(message, room, tokens, api, datetime.now())
|
(message, room, tokens, api, datetime.now()),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
@ -587,7 +658,9 @@ class GPTBot:
|
||||||
IN_MEMORY = False
|
IN_MEMORY = False
|
||||||
if not self.database:
|
if not self.database:
|
||||||
self.logger.log(
|
self.logger.log(
|
||||||
"No database connection set up, using in-memory database. Data will be lost on bot shutdown.")
|
"No database connection set up, using in-memory database. Data will be lost on bot shutdown.",
|
||||||
|
"warning",
|
||||||
|
)
|
||||||
IN_MEMORY = True
|
IN_MEMORY = True
|
||||||
self.database = sqlite3.connect(":memory:")
|
self.database = sqlite3.connect(":memory:")
|
||||||
|
|
||||||
|
@ -596,8 +669,12 @@ class GPTBot:
|
||||||
try:
|
try:
|
||||||
before, after = migrate(self.database)
|
before, after = migrate(self.database)
|
||||||
except sqlite3.DatabaseError as e:
|
except sqlite3.DatabaseError as e:
|
||||||
self.logger.log(f"Error migrating database: {e}", "fatal")
|
self.logger.log(f"Error migrating database: {e}", "critical")
|
||||||
self.logger.log("If you have just updated the bot, the previous version of the database may be incompatible with this version. Please delete the database file and try again.", "fatal")
|
|
||||||
|
self.logger.log(
|
||||||
|
"If you have just updated the bot, the previous version of the database may be incompatible with this version. Please delete the database file and try again.",
|
||||||
|
"critical",
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
if before != after:
|
if before != after:
|
||||||
|
@ -607,66 +684,73 @@ class GPTBot:
|
||||||
|
|
||||||
if IN_MEMORY:
|
if IN_MEMORY:
|
||||||
client_config = AsyncClientConfig(
|
client_config = AsyncClientConfig(
|
||||||
store_sync_tokens=True, encryption_enabled=False)
|
store_sync_tokens=True, encryption_enabled=False
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
matrix_store = SqliteStore
|
matrix_store = SqliteStore
|
||||||
client_config = AsyncClientConfig(
|
client_config = AsyncClientConfig(
|
||||||
store_sync_tokens=True, encryption_enabled=True, store=matrix_store)
|
store_sync_tokens=True, encryption_enabled=True, store=matrix_store
|
||||||
|
)
|
||||||
self.matrix_client.config = client_config
|
self.matrix_client.config = client_config
|
||||||
self.matrix_client.store = matrix_store(
|
self.matrix_client.store = matrix_store(
|
||||||
self.matrix_client.user_id,
|
self.matrix_client.user_id,
|
||||||
self.matrix_client.device_id,
|
self.matrix_client.device_id,
|
||||||
self.crypto_store_path or ""
|
self.crypto_store_path or "",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.matrix_client.olm = Olm(
|
self.matrix_client.olm = Olm(
|
||||||
self.matrix_client.user_id,
|
self.matrix_client.user_id,
|
||||||
self.matrix_client.device_id,
|
self.matrix_client.device_id,
|
||||||
self.matrix_client.store
|
self.matrix_client.store,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.matrix_client.encrypted_rooms = self.matrix_client.store.load_encrypted_rooms()
|
self.matrix_client.encrypted_rooms = (
|
||||||
|
self.matrix_client.store.load_encrypted_rooms()
|
||||||
|
)
|
||||||
|
|
||||||
# Run initial sync (now includes joining rooms)
|
# Run initial sync (now includes joining rooms)
|
||||||
sync = await self.matrix_client.sync(timeout=30000)
|
sync = await self.matrix_client.sync(timeout=30000)
|
||||||
if isinstance(sync, SyncResponse):
|
if isinstance(sync, SyncResponse):
|
||||||
await self.response_callback(sync)
|
await self.response_callback(sync)
|
||||||
else:
|
else:
|
||||||
self.logger.log(f"Initial sync failed, aborting: {sync}", "error")
|
self.logger.log(f"Initial sync failed, aborting: {sync}", "critical")
|
||||||
return
|
exit(1)
|
||||||
|
|
||||||
# Set up callbacks
|
# Set up callbacks
|
||||||
|
|
||||||
self.matrix_client.add_event_callback(self.event_callback, Event)
|
self.matrix_client.add_event_callback(self.event_callback, Event)
|
||||||
self.matrix_client.add_response_callback(
|
self.matrix_client.add_response_callback(self.response_callback, Response)
|
||||||
self.response_callback, Response)
|
|
||||||
|
|
||||||
# Set custom name / logo
|
# Set custom name / logo
|
||||||
|
|
||||||
if self.display_name:
|
if self.display_name:
|
||||||
self.logger.log(f"Setting display name to {self.display_name}")
|
self.logger.log(f"Setting display name to {self.display_name}", "debug")
|
||||||
await self.matrix_client.set_displayname(self.display_name)
|
asyncio.create_task(self.matrix_client.set_displayname(self.display_name))
|
||||||
if self.logo:
|
if self.logo:
|
||||||
self.logger.log("Setting avatar...")
|
self.logger.log("Setting avatar...")
|
||||||
logo_bio = BytesIO()
|
logo_bio = BytesIO()
|
||||||
self.logo.save(logo_bio, format=self.logo.format)
|
self.logo.save(logo_bio, format=self.logo.format)
|
||||||
uri = await self.upload_file(logo_bio.getvalue(), "logo", Image.MIME[self.logo.format])
|
uri = await self.upload_file(
|
||||||
|
logo_bio.getvalue(), "logo", Image.MIME[self.logo.format]
|
||||||
|
)
|
||||||
self.logo_uri = uri
|
self.logo_uri = uri
|
||||||
|
|
||||||
asyncio.create_task(self.matrix_client.set_avatar(uri))
|
asyncio.create_task(self.matrix_client.set_avatar(uri))
|
||||||
|
|
||||||
for room in self.matrix_client.rooms.keys():
|
for room in self.matrix_client.rooms.keys():
|
||||||
self.logger.log(f"Setting avatar for {room}...", "debug")
|
self.logger.log(f"Setting avatar for {room}...", "debug")
|
||||||
asyncio.create_task(self.matrix_client.room_put_state(room, "m.room.avatar", {
|
asyncio.create_task(
|
||||||
"url": uri
|
self.matrix_client.room_put_state(
|
||||||
}, ""))
|
room, "m.room.avatar", {"url": uri}, ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Start syncing events
|
# Start syncing events
|
||||||
self.logger.log("Starting sync loop...")
|
self.logger.log("Starting sync loop...", "warning")
|
||||||
try:
|
try:
|
||||||
await self.matrix_client.sync_forever(timeout=30000)
|
await self.matrix_client.sync_forever(timeout=30000)
|
||||||
finally:
|
finally:
|
||||||
self.logger.log("Syncing one last time...")
|
self.logger.log("Syncing one last time...", "warning")
|
||||||
await self.matrix_client.sync(timeout=30000)
|
await self.matrix_client.sync(timeout=30000)
|
||||||
|
|
||||||
async def create_space(self, name, visibility=RoomVisibility.private) -> str:
|
async def create_space(self, name, visibility=RoomVisibility.private) -> str:
|
||||||
|
@ -681,16 +765,18 @@ class GPTBot:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = await self.matrix_client.room_create(
|
response = await self.matrix_client.room_create(
|
||||||
name=name, visibility=visibility, space=True)
|
name=name, visibility=visibility, space=True
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(response, RoomCreateError):
|
if isinstance(response, RoomCreateError):
|
||||||
self.logger.log(
|
self.logger.log(f"Error creating space: {response.message}", "error")
|
||||||
f"Error creating space: {response.message}", "error")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
return response.room_id
|
return response.room_id
|
||||||
|
|
||||||
async def add_rooms_to_space(self, space: MatrixRoom | str, rooms: List[MatrixRoom | str]):
|
async def add_rooms_to_space(
|
||||||
|
self, space: MatrixRoom | str, rooms: List[MatrixRoom | str]
|
||||||
|
):
|
||||||
"""Add rooms to a space.
|
"""Add rooms to a space.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -706,20 +792,26 @@ class GPTBot:
|
||||||
room = room.room_id
|
room = room.room_id
|
||||||
|
|
||||||
if space == room:
|
if space == room:
|
||||||
self.logger.log(
|
self.logger.log(f"Refusing to add {room} to itself", "warning")
|
||||||
f"Refusing to add {room} to itself", "warning")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.log(f"Adding {room} to {space}...")
|
self.logger.log(f"Adding {room} to {space}...", "debug")
|
||||||
|
|
||||||
await self.matrix_client.room_put_state(space, "m.space.child", {
|
await self.matrix_client.room_put_state(
|
||||||
|
space,
|
||||||
|
"m.space.child",
|
||||||
|
{
|
||||||
"via": [room.split(":")[1], space.split(":")[1]],
|
"via": [room.split(":")[1], space.split(":")[1]],
|
||||||
}, room)
|
},
|
||||||
|
room,
|
||||||
|
)
|
||||||
|
|
||||||
await self.matrix_client.room_put_state(room, "m.room.parent", {
|
await self.matrix_client.room_put_state(
|
||||||
"via": [space.split(":")[1], room.split(":")[1]],
|
room,
|
||||||
"canonical": True
|
"m.room.parent",
|
||||||
}, space)
|
{"via": [space.split(":")[1], room.split(":")[1]], "canonical": True},
|
||||||
|
space,
|
||||||
|
)
|
||||||
|
|
||||||
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 all messages sent in a room.
|
"""Check whether the bot should respond to all messages sent in a room.
|
||||||
|
@ -736,12 +828,16 @@ class GPTBot:
|
||||||
|
|
||||||
with closing(self.database.cursor()) as cursor:
|
with closing(self.database.cursor()) as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "always_reply"))
|
"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]))
|
||||||
|
|
||||||
async def process_query(self, room: MatrixRoom, event: RoomMessageText, from_chat_command: bool = False):
|
async def process_query(
|
||||||
|
self, room: MatrixRoom, event: RoomMessageText, from_chat_command: bool = False
|
||||||
|
):
|
||||||
"""Process a query message. Generates a response and sends it to the room.
|
"""Process a query message. Generates a response and sends it to the room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -750,7 +846,11 @@ class GPTBot:
|
||||||
from_chat_command (bool, optional): Whether the query was sent via the `!gptbot chat` command. Defaults to False.
|
from_chat_command (bool, optional): Whether the query was sent via the `!gptbot chat` command. Defaults to False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not (from_chat_command or self.respond_to_room_messages(room) or self.matrix_client.user_id in event.body):
|
if not (
|
||||||
|
from_chat_command
|
||||||
|
or self.respond_to_room_messages(room)
|
||||||
|
or self.matrix_client.user_id in event.body
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.matrix_client.room_typing(room.room_id, True)
|
await self.matrix_client.room_typing(room.room_id, True)
|
||||||
|
@ -760,18 +860,26 @@ class GPTBot:
|
||||||
if (not from_chat_command) and self.room_uses_classification(room):
|
if (not from_chat_command) and self.room_uses_classification(room):
|
||||||
try:
|
try:
|
||||||
classification, tokens = await self.classification_api.classify_message(
|
classification, tokens = await self.classification_api.classify_message(
|
||||||
event.body, room.room_id)
|
event.body, room.room_id
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log(f"Error classifying message: {e}", "error")
|
self.logger.log(f"Error classifying message: {e}", "error")
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
room, "Something went wrong. Please try again.", True)
|
room, "Something went wrong. Please try again.", True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log_api_usage(
|
self.log_api_usage(
|
||||||
event, room, f"{self.classification_api.api_code}-{self.classification_api.classification_api}", tokens)
|
event,
|
||||||
|
room,
|
||||||
|
f"{self.classification_api.api_code}-{self.classification_api.classification_api}",
|
||||||
|
tokens,
|
||||||
|
)
|
||||||
|
|
||||||
if not classification["type"] == "chat":
|
if not classification["type"] == "chat":
|
||||||
event.body = f"!gptbot {classification['type']} {classification['prompt']}"
|
event.body = (
|
||||||
|
f"!gptbot {classification['type']} {classification['prompt']}"
|
||||||
|
)
|
||||||
await self.process_command(room, event)
|
await self.process_command(room, event)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -780,7 +888,8 @@ class GPTBot:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log(f"Error getting last messages: {e}", "error")
|
self.logger.log(f"Error getting last messages: {e}", "error")
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
room, "Something went wrong. Please try again.", True)
|
room, "Something went wrong. Please try again.", True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
system_message = self.get_system_message(room)
|
system_message = self.get_system_message(room)
|
||||||
|
@ -788,7 +897,9 @@ class GPTBot:
|
||||||
chat_messages = [{"role": "system", "content": system_message}]
|
chat_messages = [{"role": "system", "content": system_message}]
|
||||||
|
|
||||||
for message in last_messages:
|
for message in last_messages:
|
||||||
role = "assistant" if message.sender == self.matrix_client.user_id else "user"
|
role = (
|
||||||
|
"assistant" if message.sender == self.matrix_client.user_id else "user"
|
||||||
|
)
|
||||||
if not message.event_id == event.event_id:
|
if not message.event_id == event.event_id:
|
||||||
chat_messages.append({"role": role, "content": message.body})
|
chat_messages.append({"role": role, "content": message.body})
|
||||||
|
|
||||||
|
@ -796,20 +907,27 @@ class GPTBot:
|
||||||
|
|
||||||
# Truncate messages to fit within the token limit
|
# Truncate messages to fit within the token limit
|
||||||
truncated_messages = self._truncate(
|
truncated_messages = self._truncate(
|
||||||
chat_messages, self.max_tokens - 1, system_message=system_message)
|
chat_messages, self.max_tokens - 1, system_message=system_message
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response, tokens_used = await self.chat_api.generate_chat_response(
|
response, tokens_used = await self.chat_api.generate_chat_response(
|
||||||
chat_messages, user=room.room_id)
|
chat_messages, user=room.room_id
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log(f"Error generating response: {e}", "error")
|
self.logger.log(f"Error generating response: {e}", "error")
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
room, "Something went wrong. Please try again.", True)
|
room, "Something went wrong. Please try again.", True
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
self.log_api_usage(
|
self.log_api_usage(
|
||||||
event, room, f"{self.chat_api.api_code}-{self.chat_api.chat_api}", tokens_used)
|
event,
|
||||||
|
room,
|
||||||
|
f"{self.chat_api.api_code}-{self.chat_api.chat_api}",
|
||||||
|
tokens_used,
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.log(f"Sending response to room {room.room_id}...")
|
self.logger.log(f"Sending response to room {room.room_id}...")
|
||||||
|
|
||||||
|
@ -821,7 +939,8 @@ class GPTBot:
|
||||||
# Send a notice to the room if there was an error
|
# Send a notice to the room if there was an error
|
||||||
self.logger.log("Didn't get a response from GPT API", "error")
|
self.logger.log("Didn't get a response from GPT API", "error")
|
||||||
await self.send_message(
|
await self.send_message(
|
||||||
room, "Something went wrong. Please try again.", True)
|
room, "Something went wrong. Please try again.", True
|
||||||
|
)
|
||||||
|
|
||||||
await self.matrix_client.room_typing(room.room_id, False)
|
await self.matrix_client.room_typing(room.room_id, False)
|
||||||
|
|
||||||
|
@ -845,12 +964,14 @@ class GPTBot:
|
||||||
with closing(self.database.cursor()) as cur:
|
with closing(self.database.cursor()) as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?",
|
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?",
|
||||||
(room_id, "system_message")
|
(room_id, "system_message"),
|
||||||
)
|
)
|
||||||
system_message = cur.fetchone()
|
system_message = cur.fetchone()
|
||||||
|
|
||||||
complete = ((default if ((not system_message) or self.force_system_message) else "") + (
|
complete = (
|
||||||
"\n\n" + system_message[0] if system_message else "")).strip()
|
(default if ((not system_message) or self.force_system_message) else "")
|
||||||
|
+ ("\n\n" + system_message[0] if system_message else "")
|
||||||
|
).strip()
|
||||||
|
|
||||||
return complete
|
return complete
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,23 @@ from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
|
LOG_LEVELS = ["trace", "debug", "info", "warning", "error", "critical"]
|
||||||
|
|
||||||
|
def __init__(self, log_level: str = "warning"):
|
||||||
|
if log_level not in self.LOG_LEVELS:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid log level {log_level}. Valid levels are {', '.join(self.LOG_LEVELS)}")
|
||||||
|
|
||||||
|
self.log_level = log_level
|
||||||
|
|
||||||
def log(self, message: str, log_level: str = "info"):
|
def log(self, message: str, log_level: str = "info"):
|
||||||
|
if log_level not in self.LOG_LEVELS:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid log level {log_level}. Valid levels are {', '.join(self.LOG_LEVELS)}")
|
||||||
|
|
||||||
|
if self.LOG_LEVELS.index(log_level) < self.LOG_LEVELS.index(self.log_level):
|
||||||
|
return
|
||||||
|
|
||||||
caller = inspect.currentframe().f_back.f_code.co_name
|
caller = inspect.currentframe().f_back.f_code.co_name
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f")
|
||||||
print(f"[{timestamp}] - {caller} - [{log_level.upper()}] {message}")
|
print(f"[{timestamp}] - {caller} - [{log_level.upper()}] {message}")
|
||||||
|
|
Loading…
Reference in a new issue