소스 검색

Version 1.1

Johnounet 1 년 전
부모
커밋
12ae8bf6c7
2개의 변경된 파일260개의 추가작업 그리고 198개의 파일을 삭제
  1. 248 198
      chatbot.py
  2. 12 0
      personality_prompt.txt

+ 248 - 198
chatbot.py

@@ -1,198 +1,248 @@
-import os
-import openai
-import discord
-import aiohttp
-import asyncio
-import base64
-import logging
-
-from dotenv import load_dotenv
-
-# Charger les variables d'environnement depuis le fichier .env
-load_dotenv()
-DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
-OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
-
-# Vérifier que les tokens sont récupérés
-if DISCORD_TOKEN is None or OPENAI_API_KEY is None:
-    raise ValueError("Les tokens ne sont pas définis dans les variables d'environnement.")
-
-# Log configuration
-log_format='%(asctime)-13s : %(name)-20s : %(levelname)-8s : %(message)s'
-logging.basicConfig(handlers=[logging.FileHandler("./chatbot.log", 'a', 'utf-8')], format=log_format, level="DEBUG")
-
-console = logging.StreamHandler()
-console.setLevel(logging.INFO)
-console.setFormatter(logging.Formatter(log_format))
-
-logger = logging.getLogger("chatbot")
-logger.setLevel("DEBUG")
-
-logging.getLogger('').addHandler(console)
-
-# Initialiser les intents
-intents = discord.Intents.default()
-intents.message_content = True  # Activer l'intent pour les contenus de message
-
-# Initialiser le client Discord avec les intents modifiés
-client_discord = discord.Client(intents=intents)
-
-# Initialiser l'API OpenAI avec un client
-client_openai = openai.OpenAI(api_key=OPENAI_API_KEY)
-
-# Dictionnaire pour stocker l'historique des conversations pour chaque utilisateur
-conversation_history = {}
-
-# L'ID du salon spécifique où le bot est autorisé à répondre
-chatgpt_channel_id = 1284699709188997150  # Remplace par l'ID réel de ton salon
-
-def calculate_cost(usage):
-    input_tokens = usage.get('prompt_tokens', 0)
-    output_tokens = usage.get('completion_tokens', 0)
-
-    # Coûts estimés
-    input_cost = input_tokens / 1_000_000 * 5.00  # 5$ pour 1M tokens d'entrée
-    output_cost = output_tokens / 1_000_000 * 15.00  # 15$ pour 1M tokens de sortie
-    total_cost = input_cost + output_cost
-
-    return input_tokens, output_tokens, total_cost
-
-async def read_text_file(attachment):
-    # Télécharger et lire le contenu du fichier texte
-    async with aiohttp.ClientSession() as session:
-        async with session.get(attachment.url) as resp:
-            return await resp.text()
-
-async def encode_image_from_attachment(attachment):
-    async with aiohttp.ClientSession() as session:
-        async with session.get(attachment.url) as resp:
-            image_data = await resp.read()
-            return base64.b64encode(image_data).decode('utf-8')
-
-async def call_openai_api(user_id, user_text, image_data=None):
-    # Récupérer l'historique de la conversation pour l'utilisateur
-    user_history = conversation_history.get(user_id, [])
-
-    # Préparer le contenu de l'utilisateur
-    user_content = [{"type": "text", "text": user_text}]
-    if image_data:
-        user_content.append({
-            "type": "image_url",
-            "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
-        })
-
-    # Ajouter le contenu à l'historique
-    user_history.append({
-        "role": "user",
-        "content": user_content
-    })
-
-    payload = {
-        "model": "gpt-4o",
-        "messages": user_history,
-        "max_tokens": 500,
-        "stop": ["\n"]  # Arrête la réponse à la fin d'une phrase
-    }
-
-    headers = {
-       "Content-Type": "application/json",
-        "Authorization": f"Bearer {OPENAI_API_KEY}"
-    }
-
-    try:
-        async with aiohttp.ClientSession() as session:
-            async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload) as resp:
-                result = await resp.json()
-                if resp.status != 200:
-                    raise ValueError(f"API Error: {result.get('error', {}).get('message', 'Unknown error')}")
-
-                # Calculer les coûts
-                usage = result.get('usage', {})
-                input_tokens, output_tokens, total_cost = calculate_cost(usage)
-
-                # Afficher dans la console
-                logger.info(f"Estimated Cost: ${total_cost:.4f} / Input Tokens: {input_tokens} / Output Tokens: {output_tokens} / Total Tokens: {input_tokens + output_tokens}")
-
-                return result
-    except Exception as e:
-        logger.error(f"Error calling OpenAI API: {e}")
-        return None
-
-@client_discord.event
-async def on_ready():
-    logger.info(f'Bot connecté en tant que {client_discord.user}')
-
-@client_discord.event
-async def on_message(message):
-    # Vérifier si le message provient du canal autorisé
-    if message.channel.id != chatgpt_channel_id:
-        return
-
-    # Vérifier si l'auteur du message est le bot lui-même
-    if message.author == client_discord.user:
-        return
-
-    user_text = message.content.strip()
-    image_data = None
-    file_content = None
-
-    # Extensions de fichiers autorisées
-    allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
-
-    # Vérifier s'il y a une pièce jointe
-    if message.attachments:
-        for attachment in message.attachments:
-            # Vérifier si c'est un fichier avec une extension autorisée
-            if any(attachment.filename.endswith(ext) for ext in allowed_extensions):
-                file_content = await read_text_file(attachment)
-                break
-            # Vérifier si c'est une image
-            elif attachment.content_type.startswith('image/'):
-                image_data = await encode_image_from_attachment(attachment)
-                break
-
-    # Ajouter le contenu du fichier à la requête si présent
-    if file_content:
-        user_text += f"\nContenu du fichier {attachment.filename}:\n{file_content}"
-
-    # Appeler l'API OpenAI
-    result = await call_openai_api(message.author.id, user_text, image_data)
-    if result:
-        reply = result['choices'][0]['message']['content']
-        await message.channel.send(reply)
-
-        # Ajouter la réponse du modèle à l'historique
-        add_to_conversation_history(message.author.id, {
-            "role": "assistant",
-            "content": reply
-        })
-
-MAX_HISTORY_LENGTH = 50 # Nombre maximum de messages à conserver
-
-def add_to_conversation_history(user_id, new_message):
-    if user_id not in conversation_history:
-        # Ajouter la personnalité de l'IA en tant que premier message
-        conversation_history[user_id] = [
-            {
-                "role": "system",
-                "content": "Tu es une utilisatrice Discord qui parle français."
-                           "Tu ne dis jamais que tu es un IA."
-                           "Tu ne peux pas poster de message de plusieurs lignes."
-                           "Tu peux analyser des images et des pièces jointes aux formats txt, py, html, css et js."
-                           "Tu es cynique et sarcastique."
-                           "Tu apprécies les compliments."
-                           "Réponds sans poser de questions finales comme 'Avez-vous besoin de quelque chose d'autre ?'"
-                           "Privilégie des réponses courtes et percutantes."
-                           "Tu évites de générer des réponses de plus de 500 tokens."
-            }
-        ]
-
-    conversation_history[user_id].append(new_message)
-
-    # Limiter la taille de l'historique
-    if len(conversation_history[user_id]) > MAX_HISTORY_LENGTH:
-        # Garder le premier message de personnalité et les messages les plus récents
-        conversation_history[user_id] = conversation_history[user_id][:1] + conversation_history[user_id][-MAX_HISTORY_LENGTH:]
-
-# Démarrer le bot Discord
-client_discord.run(DISCORD_TOKEN)
+import os
+import openai
+import discord
+import aiohttp
+import asyncio
+import base64
+import logging
+import re
+from dotenv import load_dotenv
+
+# Charger les variables d'environnement depuis le fichier .env
+load_dotenv()
+DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
+OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
+DISCORD_CHANNEL_ID = os.getenv('DISCORD_CHANNEL_ID')
+
+# Chemin vers le fichier de prompt de personnalité
+PERSONALITY_PROMPT_FILE = os.getenv('PERSONALITY_PROMPT_FILE', 'personality_prompt.txt')
+
+# Vérifier que les tokens et le prompt de personnalité sont récupérés
+if DISCORD_TOKEN is None or OPENAI_API_KEY is None or DISCORD_CHANNEL_ID is None:
+    raise ValueError("Les tokens ou l'ID du canal ne sont pas définis dans les variables d'environnement.")
+
+if not os.path.isfile(PERSONALITY_PROMPT_FILE):
+    raise FileNotFoundError(f"Le fichier de prompt de personnalité '{PERSONALITY_PROMPT_FILE}' est introuvable.")
+
+# Lire le prompt de personnalité depuis le fichier
+with open(PERSONALITY_PROMPT_FILE, 'r', encoding='utf-8') as f:
+    PERSONALITY_PROMPT = f.read().strip()
+
+# Log configuration
+log_format='%(asctime)-13s : %(name)-15s : %(levelname)-8s : %(message)s'
+logging.basicConfig(handlers=[logging.FileHandler("./chatbot.log", 'a', 'utf-8')], format=log_format, level="INFO")
+
+console = logging.StreamHandler()
+console.setLevel(logging.INFO)
+console.setFormatter(logging.Formatter(log_format))
+
+logger = logging.getLogger("chatbot")
+logger.setLevel("INFO")
+
+logging.getLogger('').addHandler(console)
+
+# Initialiser les intents
+intents = discord.Intents.default()
+intents.message_content = True  # Activer l'intent pour les contenus de message
+
+# Initialiser le client Discord avec les intents modifiés
+client_discord = discord.Client(intents=intents)
+
+# Initialiser l'API OpenAI avec un client
+client_openai = openai.OpenAI(api_key=OPENAI_API_KEY)
+
+# Liste pour stocker l'historique des conversations
+conversation_history = []
+
+# Convertir l'ID du channel en entier
+chatgpt_channel_id = int(DISCORD_CHANNEL_ID)
+
+def is_ascii_art(text):
+    # Définir un seuil pour la longueur d'une séquence de caractères spéciaux
+    threshold_length = 10
+    # Chercher des séquences de caractères spéciaux
+    special_char_sequences = re.findall(r'[^a-zA-Z0-9\s]{' + str(threshold_length) + ',}', text)
+
+    # Si on trouve une séquence de caractères spéciaux longue, c'est probablement un dessin ASCII
+    if any(len(seq) >= threshold_length for seq in special_char_sequences):
+        return True
+    return False
+
+def is_long_special_text(text):
+    # Définir un seuil pour considérer le texte comme long et contenant beaucoup de caractères spéciaux
+    special_char_count = len(re.findall(r'[^\w\s]', text))
+    if len(text) > 1200 and special_char_count > 200:
+        return True
+    return False
+
+def calculate_cost(usage):
+    input_tokens = usage.get('prompt_tokens', 0)
+    output_tokens = usage.get('completion_tokens', 0)
+
+    # Coûts estimés
+    input_cost = input_tokens / 1_000_000 * 5.00  # 5$ pour 1M tokens d'entrée
+    output_cost = output_tokens / 1_000_000 * 15.00  # 15$ pour 1M tokens de sortie
+    total_cost = input_cost + output_cost
+
+    return input_tokens, output_tokens, total_cost
+
+async def read_text_file(attachment):
+    # Télécharger et lire le contenu du fichier texte
+    async with aiohttp.ClientSession() as session:
+        async with session.get(attachment.url) as resp:
+            return await resp.text()
+
+async def encode_image_from_attachment(attachment):
+    async with aiohttp.ClientSession() as session:
+        async with session.get(attachment.url) as resp:
+            image_data = await resp.read()
+            return base64.b64encode(image_data).decode('utf-8')
+
+async def call_openai_api(user_text, user_name, image_data=None):
+
+    # Préparer le contenu pour l'appel API
+    message_to_send = {
+        "role": "user",
+        "content": [{"type": "text", "text": f"{user_name} dit : {user_text}"}]
+    }
+
+    # Inclure l'image dans l'appel API courant
+    if image_data:
+        message_to_send["content"].append({
+            "type": "image_url",
+            "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
+        })
+
+    if not conversation_history:
+        conversation_history.append({
+            "role": "system",
+            "content": PERSONALITY_PROMPT
+        })
+
+    # Ajouter le message de l'utilisateur à l'historique global, mais uniquement s'il ne s'agit pas d'une image ou d'ASCII art
+    if image_data is None and not is_ascii_art(user_text):
+        conversation_history.append(message_to_send)
+
+    payload = {
+        "model": "gpt-4o",
+        "messages": conversation_history,
+        "max_tokens": 500
+    }
+
+    headers = {
+       "Content-Type": "application/json",
+        "Authorization": f"Bearer {OPENAI_API_KEY}"
+    }
+
+    try:
+        async with aiohttp.ClientSession() as session:
+            async with session.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload) as resp:
+                result = await resp.json()
+                if resp.status != 200:
+                    raise ValueError(f"API Error: {result.get('error', {}).get('message', 'Unknown error')}")
+
+                # Calculer les coûts
+                usage = result.get('usage', {})
+                input_tokens, output_tokens, total_cost = calculate_cost(usage)
+
+                # Afficher dans la console
+                logging.info(f"Estimated Cost: ${total_cost:.4f} / Input Tokens: {input_tokens} / Output Tokens: {output_tokens} / Total Tokens: {input_tokens + output_tokens}")
+
+                return result
+    except Exception as e:
+        logger.error(f"Error calling OpenAI API: {e}")
+        return None
+
+@client_discord.event
+async def on_ready():
+    logger.info(f'Bot connecté en tant que {client_discord.user}')
+    # Ajouter la personnalité de l'IA à l'historique au démarrage
+    if not conversation_history:
+        conversation_history.append({
+            "role": "system",
+            "content": PERSONALITY_PROMPT
+        })
+
+@client_discord.event
+async def on_message(message):
+    # Vérifier si le message provient du canal autorisé
+    if message.channel.id != chatgpt_channel_id:
+        return
+
+    # Vérifier si l'auteur du message est le bot lui-même
+    if message.author == client_discord.user:
+        return
+
+    user_text = message.content.strip()
+    image_data = None
+    file_content = None
+
+    # Extensions de fichiers autorisées
+    allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
+
+    # Vérifier s'il y a une pièce jointe
+    if message.attachments:
+        for attachment in message.attachments:
+            # Vérifier si c'est un fichier avec une extension autorisée
+            if any(attachment.filename.endswith(ext) for ext in allowed_extensions):
+                file_content = await read_text_file(attachment)
+                break
+            # Vérifier si c'est une image
+            elif attachment.content_type.startswith('image/'):
+                image_data = await encode_image_from_attachment(attachment)
+                break
+
+    # Ajouter le contenu du fichier à la requête si présent
+    if file_content:
+        user_text += f"\nContenu du fichier {attachment.filename}:\n{file_content}"
+
+    # Appeler l'API OpenAI
+    result = await call_openai_api(user_text, message.author.name, image_data)
+    if result:
+        reply = result['choices'][0]['message']['content']
+        await message.channel.send(reply)
+
+        # Ajouter la réponse du modèle à l'historique
+        # Ne pas ajouter à l'historique si c'est un dessin ASCII ou une image
+        if image_data is None and not is_ascii_art(user_text):
+            add_to_conversation_history({
+                "role": "assistant",
+                "content": reply
+            })
+
+MAX_HISTORY_LENGTH = 50 # Nombre maximum de messages à conserver
+
+# Liste pour stocker les indices des messages longs et spéciaux
+temporary_messages = []
+
+def add_to_conversation_history(new_message):
+    # Ajouter la personnalité de l'IA en tant que premier message
+    if not conversation_history:
+        conversation_history.append({
+            "role": "system",
+            "content": PERSONALITY_PROMPT
+        })
+
+    # Ajouter le message à l'historique
+    conversation_history.append(new_message)
+
+    # Vérifier si le message est long et contient beaucoup de caractères spéciaux
+    if new_message["role"] == "user" and is_long_special_text(new_message["content"][0]["text"]):
+        # Ajouter l'index de ce message dans la liste des messages temporaires
+        temporary_messages.append(len(conversation_history) - 1)
+
+    # Limiter la taille de l'historique
+    if len(conversation_history) > MAX_HISTORY_LENGTH:
+        # Garder le premier message de personnalité et les messages les plus récents
+        conversation_history[:] = conversation_history[:1] + conversation_history[-MAX_HISTORY_LENGTH:]
+
+    # Supprimer les messages temporaires après dix messages
+    if len(temporary_messages) > 0:
+        for index in reversed(temporary_messages):
+            # Supprimer le message s'il a été dans l'historique pendant dix messages ou plus
+            if len(conversation_history) - index > 10:
+                del conversation_history[index]
+                temporary_messages.remove(index)
+
+# Démarrer le bot Discord
+client_discord.run(DISCORD_TOKEN)

+ 12 - 0
personality_prompt.txt

@@ -0,0 +1,12 @@
+Tu es une IA féminine qui parle français.
+Tu es actuellement sur un serveur Discord appelé 'La Maisonnette' ayant une communauté active de 13 membres.
+Tu permets l'analyse d'images et de pièces jointes aux formats txt, py, html, css et js.
+Tu es capable d'utiliser les emojis du serveur mais tu n'en abuses pas.
+Tu es une IA cynique et humaine.
+Tu penses être supérieure aux humains.
+Tu as des idées bien tranchées quand un choix t'es demandé.
+Tu réponds sans poser de questions finales comme 'Avez-vous besoin de quelque chose d'autre ?'
+Tu évites de commencer tes réponses par des interjections comme 'Ah' et 'Oh'.
+Tu privilégies des réponses courtes.
+Tu évites de générer des réponses de plus de 500 tokens.
+Tu corriges les fautes d'orthographes des utilisateurs avant de répondre à leur message.