From 37beca0dd61fee148f4c5ad022134a7e1a9d30e7 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 18:24:27 +0500 Subject: [PATCH 01/12] Modified typing for InputImage --- reginaldCog/llm_clients/openai_data_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reginaldCog/llm_clients/openai_data_models.py b/reginaldCog/llm_clients/openai_data_models.py index 21ab7c8..171e7c4 100644 --- a/reginaldCog/llm_clients/openai_data_models.py +++ b/reginaldCog/llm_clients/openai_data_models.py @@ -23,8 +23,8 @@ class OutputText(Content): @dataclass(frozen=True) class InputImage(Content): type: str = field(init=False, default='input_image') - image_url: str = field(default=None) - file_id: str = field(default=None) + image_url: str | None = field(default=None) + file_id: str | None = field(default=None) def __post_init__(self): if self.image_url is None and self.file_id is None: From 8255d54e569be52392ca081df95a8f74cb3639dc Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 18:25:16 +0500 Subject: [PATCH 02/12] Added empty attachments list for test message --- reginaldCog/messenger_clients/messenger_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reginaldCog/messenger_clients/messenger_client.py b/reginaldCog/messenger_clients/messenger_client.py index cfc0b5e..685c898 100644 --- a/reginaldCog/messenger_clients/messenger_client.py +++ b/reginaldCog/messenger_clients/messenger_client.py @@ -78,6 +78,7 @@ if __name__ == "__main__": class TestMessage: content = 'Hello!' author = TestAuthor() + attachments = [] message = DiscordMessageBuilder(raw_message=TestMessage).create_message() From 7dc1d485b1c4596d9a8d4621ca40246782058766 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 18:26:08 +0500 Subject: [PATCH 03/12] Finished some basic OpenAI interaction. Next are adapters. --- reginaldCog/llm_clients/llm_client.py | 154 ++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 reginaldCog/llm_clients/llm_client.py diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py new file mode 100644 index 0000000..7ecfe24 --- /dev/null +++ b/reginaldCog/llm_clients/llm_client.py @@ -0,0 +1,154 @@ +from itertools import zip_longest +from abc import ABC, abstractmethod +from enum import Enum +from openai import OpenAI + + +class ILLMContent(ABC): + pass + + +class OpenAIContent(ILLMContent): + def __init__(self): + self._content_items: list[dict[str, str]] = [] + + @property + def content_items(self) -> list[dict]: + return self._content_items + + @content_items.setter + def content_items(self, value: list[dict[str, str]]): + self._content_items = value + + +class ILLMContentBuilder(ABC): + pass + + +class OpenAIContentBuilder(ILLMContentBuilder): + def __init__(self, content: OpenAIContent): + self.content = content + + def add_output_text(self, text: str): + item = {"type": "output_text", "text": text} + self.content.content_items.append(item) + return self + + def add_input_text(self, text: str): + item = {"type": "input_text", "text": text} + self.content.content_items.append(item) + return self + + def add_input_image(self, image_url: str): + item = {"type": "input_image", "image_url": image_url} + self.content.content_items.append(item) + return self + + +class ILLMMessage(ABC): + pass + + +class OpenAIMessage(ILLMMessage): + def __init__(self): + self.content = OpenAIContent() + self.role = "" + + @property + def to_dict(self): + return {"role": self.role, "content": self.content.content_items} + + +class ILLMMessageBuilder(ABC): + pass + + +class OpenAIMessageBuilder(ILLMMessageBuilder): + def __init__(self, message: OpenAIMessage): + self.message = message + + def set_role(self, role: str): + self.message.role = role + return self + + def set_content(self, content: OpenAIContent): + self.message.content = content + return self + + +class ILLMPrompt(ABC): + pass + + +class OpenAIPrompt(ILLMPrompt): + def __init__(self): + self.messages = [] + + @property + def to_list(self): + return [i_message.to_dict for i_message in self.messages] + + +class ILLMPromptBuilder(ABC): + @abstractmethod + def __init__(self, prompt: ILLMPrompt): + pass + + @abstractmethod + def add_message(self, message: ILLMMessage): + pass + + +class OpenAIPromptBuilder(ILLMPromptBuilder): + def __init__(self, prompt: OpenAIPrompt): + self.prompt = prompt + + def add_message(self, message: OpenAIMessage): + self.prompt.messages.append(message) + + +class ILLMClient(ABC): + @abstractmethod + def get_response(self, prompt: ILLMPrompt): + pass + + +class OpenAIClient(ILLMClient): + def __init__(self): + self.model = 'gpt-4.1-mini' + self.client = OpenAI() + + def get_response(self, prompt: OpenAIPrompt): + response_input = {"model": self.model, "input": prompt.to_list} + return self.client.responses.create(**response_input) + + +if __name__ == "__main__": + dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." + user_message_text = "Hi! Tell me about yourself!" + + # Create developer message content and message + dev_content = OpenAIContent() + dev_content_builder = OpenAIContentBuilder(dev_content).add_input_text(dev_message_text) + dev_message = OpenAIMessage() + OpenAIMessageBuilder(dev_message).set_role("developer").set_content(dev_content) + + # Create user message content and message + user_content = OpenAIContent() + OpenAIContentBuilder(user_content).add_input_text(user_message_text) + user_message = OpenAIMessage() + OpenAIMessageBuilder(user_message).set_role("user").set_content(user_content) + + # Create prompt and add messages + test_prompt = OpenAIPrompt() + prompt_builder = OpenAIPromptBuilder(test_prompt) + prompt_builder.add_message(dev_message) + prompt_builder.add_message(user_message) + + # Create client and get response + client = OpenAIClient() + response = client.get_response(test_prompt) + + # Print messages and response + print(test_prompt.to_list) + print(response) From a5bfccb19a0b2bb00e21329ed3ca3cc4f06e29b7 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 19:43:02 +0500 Subject: [PATCH 04/12] Added OpenAI response -> OpenAIMessage object adapter --- reginaldCog/llm_clients/llm_client.py | 42 +++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index 7ecfe24..ce66553 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -44,6 +44,10 @@ class OpenAIContentBuilder(ILLMContentBuilder): self.content.content_items.append(item) return self + def add_from_dict(self, item: dict): + self.content.content_items.append(item) + return self + class ILLMMessage(ABC): pass @@ -123,6 +127,33 @@ class OpenAIClient(ILLMClient): return self.client.responses.create(**response_input) +class IMessageAdapter(ABC): + @abstractmethod + def to_message(self) -> ILLMMessage: + pass + + +class OpenAIResponseAdapter(IMessageAdapter): + def __init__(self, response): + self.response = response + self.response_output = response.output[0] + + def to_message(self) -> OpenAIMessage: + content = OpenAIContent() + content_builder = OpenAIContentBuilder(content) + message = OpenAIMessage() + message_builder = OpenAIMessageBuilder(message) + + message_builder.set_role(self.response_output.role)\ + .set_content(content) + + for i_content_item in self.response_output.content: + item = i_content_item.to_dict() + content_builder.add_from_dict(item) + + return message + + if __name__ == "__main__": dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." user_message_text = "Hi! Tell me about yourself!" @@ -147,8 +178,13 @@ if __name__ == "__main__": # Create client and get response client = OpenAIClient() - response = client.get_response(test_prompt) + test_response = client.get_response(test_prompt) - # Print messages and response + # Create response adapter and add response to prompt + response_adapter = OpenAIResponseAdapter(test_response) + response_message = response_adapter.to_message() + prompt_builder.add_message(response_message) + + # Print messages print(test_prompt.to_list) - print(response) + From c024d85db5ebabafbdf0460479fe2bfec3fb77b2 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 19:59:07 +0500 Subject: [PATCH 05/12] Added ClientMessage -> OpenAIMessage object adapter --- reginaldCog/llm_clients/llm_client.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index ce66553..3591df5 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -2,6 +2,7 @@ from itertools import zip_longest from abc import ABC, abstractmethod from enum import Enum from openai import OpenAI +from ..messenger_clients.messenger_client import ClientMessage class ILLMContent(ABC): @@ -154,6 +155,27 @@ class OpenAIResponseAdapter(IMessageAdapter): return message +class MessengerMessageAdapter(IMessageAdapter): + def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later + self.message = message + + def to_message(self) -> ILLMMessage: + content = OpenAIContent() + content_builder = OpenAIContentBuilder(content) + message = OpenAIMessage() + message_builder = OpenAIMessageBuilder(message) + + message_builder.set_role("user")\ + .set_content(content) + + if self.message: + content_builder.add_input_text(self.message.content) + for i_image_url in self.message.image_urls: + content_builder.add_input_image(i_image_url) + + return message + + if __name__ == "__main__": dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." user_message_text = "Hi! Tell me about yourself!" From 3b6fc28d4c1081ec3a65c14e9908944d8dabaff9 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 19:59:07 +0500 Subject: [PATCH 06/12] Added ClientMessage -> OpenAIMessage object adapter --- reginaldCog/llm_clients/llm_client.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index ce66553..3591df5 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -2,6 +2,7 @@ from itertools import zip_longest from abc import ABC, abstractmethod from enum import Enum from openai import OpenAI +from ..messenger_clients.messenger_client import ClientMessage class ILLMContent(ABC): @@ -154,6 +155,27 @@ class OpenAIResponseAdapter(IMessageAdapter): return message +class MessengerMessageAdapter(IMessageAdapter): + def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later + self.message = message + + def to_message(self) -> ILLMMessage: + content = OpenAIContent() + content_builder = OpenAIContentBuilder(content) + message = OpenAIMessage() + message_builder = OpenAIMessageBuilder(message) + + message_builder.set_role("user")\ + .set_content(content) + + if self.message: + content_builder.add_input_text(self.message.content) + for i_image_url in self.message.image_urls: + content_builder.add_input_image(i_image_url) + + return message + + if __name__ == "__main__": dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." user_message_text = "Hi! Tell me about yourself!" From 4fd5e06eaa0a18764174459276fb11050f109436 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Sun, 24 Aug 2025 20:01:21 +0500 Subject: [PATCH 07/12] Purged unused imports --- reginaldCog/llm_clients/llm_client.py | 4 +--- reginaldCog/llm_clients/openai_data_models.py | 2 +- reginaldCog/messenger_clients/discord_data_models.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index 3591df5..c51f3d3 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -1,6 +1,4 @@ -from itertools import zip_longest from abc import ABC, abstractmethod -from enum import Enum from openai import OpenAI from ..messenger_clients.messenger_client import ClientMessage @@ -156,7 +154,7 @@ class OpenAIResponseAdapter(IMessageAdapter): class MessengerMessageAdapter(IMessageAdapter): - def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later + def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later self.message = message def to_message(self) -> ILLMMessage: diff --git a/reginaldCog/llm_clients/openai_data_models.py b/reginaldCog/llm_clients/openai_data_models.py index 171e7c4..0d138fd 100644 --- a/reginaldCog/llm_clients/openai_data_models.py +++ b/reginaldCog/llm_clients/openai_data_models.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field, asdict +from dataclasses import dataclass, field from abc import ABC diff --git a/reginaldCog/messenger_clients/discord_data_models.py b/reginaldCog/messenger_clients/discord_data_models.py index 8abb41e..56f621b 100644 --- a/reginaldCog/messenger_clients/discord_data_models.py +++ b/reginaldCog/messenger_clients/discord_data_models.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field, asdict +from dataclasses import dataclass, field from datetime import datetime From bd6a55cd85b539833f89cee45aa1a47c596b9acf Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Mon, 25 Aug 2025 12:32:35 +0500 Subject: [PATCH 08/12] Created DiscordMessageAdapter for creating ClientMessage instance instead of a builder's class method --- .../messenger_clients/messenger_client.py | 82 +++++++++---------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/reginaldCog/messenger_clients/messenger_client.py b/reginaldCog/messenger_clients/messenger_client.py index 685c898..4c49e85 100644 --- a/reginaldCog/messenger_clients/messenger_client.py +++ b/reginaldCog/messenger_clients/messenger_client.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from enum import Enum -from discord import Message +from discord import Message, Attachment class IClientMessage(ABC): @@ -18,68 +18,60 @@ class ClientMessage(IClientMessage): class IMessageBuilder(ABC): @abstractmethod - def __init__(self, raw_message: object): - self.raw_message = raw_message - self.message: IClientMessage = None - - def create_message(self) -> ClientMessage: - self.set_content() \ - .set_author_name() \ - .set_image_urls() - return self.message - - @abstractmethod - def set_content(self): + def __init__(self): pass @abstractmethod - def set_author_name(self): + def set_content(self, value: str): pass @abstractmethod - def set_image_urls(self): + def set_author_name(self, value: str): + pass + + @abstractmethod + def set_image_urls(self, value: list[str]): pass class DiscordMessageBuilder(IMessageBuilder): - def __init__(self, raw_message: Message): - self.raw_message = raw_message - self.message = ClientMessage() + def __init__(self, message: IClientMessage): + self.message = message - def create_message(self) -> ClientMessage: - return super().create_message() - - def set_author_name(self) -> 'DiscordMessageBuilder': - self.message.author_name = self.raw_message.author.name + def set_author_name(self, value: str): + self.message.author_name = value return self - def set_content(self) -> 'DiscordMessageBuilder': - self.message.content = self.raw_message.content + def set_content(self, value: str): + self.message.content = value return self - def set_image_urls(self) -> 'DiscordMessageBuilder': - self.message.image_urls = [ - i.url - for i in self.raw_message.attachments - if i.content_type in ('image/jpeg', 'image/png', 'image/webp', 'image/gif') - ] + def set_image_urls(self, value: list[str]): + self.message.image_urls = value return self -class MessengerTypes(Enum): - DISCORD = DiscordMessageBuilder +class IMessageAdapter(ABC): + @abstractmethod + def create_message(self, message: object) -> IClientMessage: + pass + + @staticmethod + def validate_image_urls(urls_list: list[Attachment]) -> list[str]: + supported_image_formats = ('image/jpeg', 'image/png', 'image/webp', 'image/gif') + return [i_attachment.url for i_attachment in urls_list if i_attachment.content_type in supported_image_formats] + + +class DiscordMessageAdapter(IMessageAdapter): + def create_message(self, message: Message) -> IClientMessage: + client_message = ClientMessage() + message_builder = DiscordMessageBuilder(client_message) + urls_list = self.validate_image_urls(message.attachments) + message_builder.set_content(message.content)\ + .set_author_name(message.author.name)\ + .set_image_urls(urls_list) + return client_message if __name__ == "__main__": - class TestAuthor: - name = 'John Doe' - - - class TestMessage: - content = 'Hello!' - author = TestAuthor() - attachments = [] - - - message = DiscordMessageBuilder(raw_message=TestMessage).create_message() - print(message.__dict__) + pass From a322fbae84ce46e6b04af876932015fea2a9a1a1 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Mon, 25 Aug 2025 19:09:44 +0500 Subject: [PATCH 09/12] Renamed adapter class to MessengerClientMessageAdapter for clarity --- reginaldCog/llm_clients/llm_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index c51f3d3..395fc30 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -153,7 +153,7 @@ class OpenAIResponseAdapter(IMessageAdapter): return message -class MessengerMessageAdapter(IMessageAdapter): +class MessengerClientMessageAdapter(IMessageAdapter): def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later self.message = message From 92f536d4077577e7bd2f76d6955dc77b55a6b530 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Mon, 25 Aug 2025 20:47:01 +0500 Subject: [PATCH 10/12] Fixed local import --- reginaldCog/llm_clients/llm_client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index 395fc30..403faf9 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from openai import OpenAI -from ..messenger_clients.messenger_client import ClientMessage +from reginaldCog.messenger_clients.messenger_client import ClientMessage class ILLMContent(ABC): @@ -176,7 +176,8 @@ class MessengerClientMessageAdapter(IMessageAdapter): if __name__ == "__main__": dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." - user_message_text = "Hi! Tell me about yourself!" + user_message_text = "Tell me what's on this picture" + pic_url = "https://media.discordapp.net/attachments/913835221814763550/1408534120421654669/image.png?ex=68ad62df&is=68ac115f&hm=bdfcf878f20e154a575a528b9cb274a8c2162f84ce12fc3876856e821ef9cc63&=&format=webp&quality=lossless&width=1342&height=755" # Create developer message content and message dev_content = OpenAIContent() @@ -186,7 +187,7 @@ if __name__ == "__main__": # Create user message content and message user_content = OpenAIContent() - OpenAIContentBuilder(user_content).add_input_text(user_message_text) + OpenAIContentBuilder(user_content).add_input_text(user_message_text).add_input_image(pic_url) user_message = OpenAIMessage() OpenAIMessageBuilder(user_message).set_role("user").set_content(user_content) From f195d77e671d80cec49b63bce91ceaf90f9430c4 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Wed, 27 Aug 2025 21:13:04 +0500 Subject: [PATCH 11/12] Made Discord bot service for getting llm response out of Discord message --- reginaldCog/llm_clients/llm_client.py | 61 ++++++++--------------- reginaldCog/messenger_clients/services.py | 39 +++++++++++++++ 2 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 reginaldCog/messenger_clients/services.py diff --git a/reginaldCog/llm_clients/llm_client.py b/reginaldCog/llm_clients/llm_client.py index 403faf9..e256bc8 100644 --- a/reginaldCog/llm_clients/llm_client.py +++ b/reginaldCog/llm_clients/llm_client.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod +from enum import Enum from openai import OpenAI -from reginaldCog.messenger_clients.messenger_client import ClientMessage +from discord import Message +from reginaldCog.messenger_clients.messenger_client import ClientMessage, DiscordMessageAdapter class ILLMContent(ABC): @@ -117,6 +119,13 @@ class ILLMClient(ABC): class OpenAIClient(ILLMClient): + content_class = OpenAIContent + content_builder_class = OpenAIContentBuilder + message_class = OpenAIMessage + message_builder_class = OpenAIMessageBuilder + prompt_class = OpenAIPrompt + prompt_builder_class = OpenAIPromptBuilder + def __init__(self): self.model = 'gpt-4.1-mini' self.client = OpenAI() @@ -153,15 +162,20 @@ class OpenAIResponseAdapter(IMessageAdapter): return message +class LMMClientType(Enum): + OPENAI = OpenAIClient + + class MessengerClientMessageAdapter(IMessageAdapter): - def __init__(self, message: ClientMessage): # Dependency Inversion violation, figure out later + def __init__(self, message: ClientMessage, llm_client: LMMClientType): self.message = message + self.llm_client = llm_client def to_message(self) -> ILLMMessage: - content = OpenAIContent() - content_builder = OpenAIContentBuilder(content) - message = OpenAIMessage() - message_builder = OpenAIMessageBuilder(message) + content = self.llm_client.value.content_class() + content_builder = self.llm_client.value.content_builder_class(content) + message = self.llm_client.value.message_class() + message_builder = self.llm_client.value.message_builder_class(message) message_builder.set_role("user")\ .set_content(content) @@ -175,37 +189,4 @@ class MessengerClientMessageAdapter(IMessageAdapter): if __name__ == "__main__": - dev_message_text = "You are a butler named Reginald. Give your answers in a refined manners of the English butler." - user_message_text = "Tell me what's on this picture" - pic_url = "https://media.discordapp.net/attachments/913835221814763550/1408534120421654669/image.png?ex=68ad62df&is=68ac115f&hm=bdfcf878f20e154a575a528b9cb274a8c2162f84ce12fc3876856e821ef9cc63&=&format=webp&quality=lossless&width=1342&height=755" - - # Create developer message content and message - dev_content = OpenAIContent() - dev_content_builder = OpenAIContentBuilder(dev_content).add_input_text(dev_message_text) - dev_message = OpenAIMessage() - OpenAIMessageBuilder(dev_message).set_role("developer").set_content(dev_content) - - # Create user message content and message - user_content = OpenAIContent() - OpenAIContentBuilder(user_content).add_input_text(user_message_text).add_input_image(pic_url) - user_message = OpenAIMessage() - OpenAIMessageBuilder(user_message).set_role("user").set_content(user_content) - - # Create prompt and add messages - test_prompt = OpenAIPrompt() - prompt_builder = OpenAIPromptBuilder(test_prompt) - prompt_builder.add_message(dev_message) - prompt_builder.add_message(user_message) - - # Create client and get response - client = OpenAIClient() - test_response = client.get_response(test_prompt) - - # Create response adapter and add response to prompt - response_adapter = OpenAIResponseAdapter(test_response) - response_message = response_adapter.to_message() - prompt_builder.add_message(response_message) - - # Print messages - print(test_prompt.to_list) - + pass diff --git a/reginaldCog/messenger_clients/services.py b/reginaldCog/messenger_clients/services.py new file mode 100644 index 0000000..fb6b9ab --- /dev/null +++ b/reginaldCog/messenger_clients/services.py @@ -0,0 +1,39 @@ +import asyncio +from discord import Message +from reginaldCog.messenger_clients.messenger_client import ClientMessage, DiscordMessageAdapter as MessengerDiscordAdapter +from reginaldCog.llm_clients.llm_client import LMMClientType, MessengerClientMessageAdapter + + +class MessageService: + def __init__(self, message: Message, llm_client: LMMClientType = LMMClientType.OPENAI): + self.message = message + self.llm_client = llm_client + + async def get_llm_response(self) -> str: + # Adapt discord.Message to ClientMessage domain object + client_message: ClientMessage = MessengerDiscordAdapter().create_message(self.message) + + # Create prompt and prompt builder for this LLM client + prompt = self.llm_client.value.prompt_class() + prompt_builder = self.llm_client.value.prompt_builder_class(prompt) + + # Adapt the messenger client message into LLM message and add it to prompt + llm_message = MessengerClientMessageAdapter(client_message, self.llm_client).to_message() + prompt_builder.add_message(llm_message) + + # Call the LLM client; run in executor if method is blocking sync + llm_client_instance = self.llm_client.value() + + loop = asyncio.get_running_loop() + # Assuming get_response is blocking - run in executor: + response = await loop.run_in_executor(None, llm_client_instance.get_response, prompt) + + # Extract plain text from the response (assuming OpenAIResponseAdapter is present) + from reginaldCog.llm_clients.llm_client import OpenAIResponseAdapter + + response_adapter = OpenAIResponseAdapter(response) + message_obj = response_adapter.to_message() + + # Concatenate all textual outputs for sending to Discord + texts = [item.get("text", "") for item in message_obj.content.content_items if item.get("type") == "output_text"] + return "\n".join(texts) if texts else "Sorry, no response generated." From 90d5dd973c6e196ea453d27f84fd8dfc6baf6119 Mon Sep 17 00:00:00 2001 From: T-BENZIN Date: Wed, 27 Aug 2025 21:14:17 +0500 Subject: [PATCH 12/12] Added demo bot for testing. Supports image input, but does not have dialog memory or tools usage yet. --- reginaldCog/demo_bot.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 reginaldCog/demo_bot.py diff --git a/reginaldCog/demo_bot.py b/reginaldCog/demo_bot.py new file mode 100644 index 0000000..8fd3756 --- /dev/null +++ b/reginaldCog/demo_bot.py @@ -0,0 +1,38 @@ +import os +import discord +from discord.ext import commands +from reginaldCog.messenger_clients.services import MessageService + +TOKEN = os.getenv('SCREAMING_OPOSSUM') # Your Discord bot token goes here +intents = discord.Intents.default() +intents.message_content = True +bot = commands.Bot(command_prefix='!', intents=intents) + + +@bot.event +async def on_ready(): + print(f'Logged in as {bot.user} (ID: {bot.user.id})') + print('------') + try: + synced = await bot.tree.sync() + print(f'Synced {len(synced)} command(s).') + except Exception as e: + print(f'Failed to sync commands: {e}') + + +@bot.event +async def on_message(message: discord.Message): + + if message.author == bot.user: + return + + async with message.channel.typing(): + message_service = MessageService(message) + response = await message_service.get_llm_response() + await message.channel.send(response) + print(response) + +if __name__ == '__main__': + if TOKEN is None: + raise RuntimeError('Discord token is not set') + bot.run(TOKEN)