Fonctionnalités
-
Exécute les commandes utilisateur
-
Filtre le spam
-
Exécute les tâches en arrière-plan
Bibliothèques
-
Application : Il s'agit de votre système ou service principal qui doit interagir avec le LLM.
-
Appeleur de fonction : Agit comme un intermédiaire qui envoie des entrées au LLM et reçoit la sortie brute. Ce composant encapsule la logique nécessaire pour communiquer avec le LLM.
-
LLM (Large Language Model) : Le modèle d'IA qui traite l'entrée et génère une sortie basée sur l'appel de fonction. Les exemples incluent GPT-4, Claude3, etc.
-
Gestionnaire de réponse (Response Handler) : Prend la sortie brute du LLM et commence le processus de structuration. Cela peut impliquer la vérification des erreurs, le filtrage et la préparation des données pour la conversion en un format structuré.
-
Modèles Pydantic (Pydantic Models) : Ceux-ci sont utilisés pour définir explicitement la structure des données de sortie. Les modèles Pydantic appliquent la vérification de type et la validation des données, ce qui aide à garantir que les données sont conformes à un schéma spécifié.
-
Sortie structurée (Structured Output) : La sortie finale qui est bien structurée et prête à être utilisée par l'application. Cette sortie est prévisible et plus facile à intégrer dans les processus ou systèmes en aval.
-
Application Client : Il s'agit de votre application Python qui doit interagir avec les grands modèles linguistiques. Elle utilise la bibliothèque Instructor pour faciliter ces interactions.
-
Bibliothèque Instructor : Agit comme un middleware qui enveloppe les grands modèles linguistiques. Elle est responsable de l'envoi des requêtes à ces modèles et du traitement de leurs sorties.
-
Modèles de langage étendus : Inclut GPT-4, LLAMA3 et Claude3. Chacun de ces modèles peut générer des sorties de texte complexes basées sur l'entrée qu'il reçoit.
-
Modèles Pydantic : Ceux-ci sont utilisés au sein de la bibliothèque Instructor pour structurer la sortie brute des modèles de langage dans un format plus gérable et défini, rendant le développement d'applications plus propre et plus prévisible.
-
Sorties structurées : Les sorties structurées finales sont ensuite renvoyées à l'application cliente, où elles peuvent être utilisées ou affichées.
Implémentation
Implémentation de l'API
#!/usr/bin/env python3
from litestar import Litestar, get, post
from litestar.openapi import OpenAPIConfig
from litestar.openapi.plugins import (
ScalarRenderPlugin,
RapidocRenderPlugin,
RedocRenderPlugin,
SwaggerRenderPlugin,
)
from enhanced_discord_bot_llms.llm_svc import (
LLMModel,
gen_async_client,
UserInfo,
streaming_usine_de_gaou_creation,
)
@get("/", sync_to_thread=False)
def read_root() -> dict:
return {"Hello": "World"}
@post("/gaou/{parametre:str}")
async def creer_gaou(parametre: str) -> UserInfo:
model = LLMModel.LLAMA3
client = gen_async_client(model=model)
gaou = await streaming_usine_de_gaou_creation(client, parametre, model=model)
print(f"Nouveau gaou créé: {gaou},\n selon le paramètre {parametre}\n\n")
return gaou
app = Litestar(
route_handlers= [read_root, creer_gaou],
openapi_config=OpenAPIConfig(
title="Gaou API",
description="API pour créer des Gaous",
version="0.1.0",
path="/docs",
render_plugins= [
RapidocRenderPlugin(),
# RedocRenderPlugin(),
# ScalarRenderPlugin(),
# SwaggerRenderPlugin(),
],
),
debug=True,
)
if name == "main":
import uvicorn
application = "gaouapp:app"
uvicorn.run(application, host="0.0.0.0", port=8000, reload=True)
## Services de Bot
#!/usr/bin/env python3
import os
import instructor
from instructor import Instructor, AsyncInstructor
from anthropic import Anthropic, AsyncAnthropic
from groq import Groq, AsyncGroq
from openai import OpenAI, AsyncOpenAI
from pydantic import BaseModel, Field
from enum import Enum, auto
class LLMModel(str, Enum):
Claude3 = "claude-3-opus-20240229"
GPT4_Omni = "gpt-4o"
LLAMA3 = "llama3-70b-8192"
def gen_client(model=LLMModel.GPT4_Omni) -> Instructor:
match model:
case LLMModel.Claude3:
client = instructor.from_anthropic(Anthropic())
case LLMModel.GPT4_Omni:
client = instructor.patch(OpenAI())
case LLMModel.LLAMA3:
client = instructor.patch(Groq())
return client
def gen_async_client(model=LLMModel.GPT4_Omni) -> AsyncInstructor:
match model:
case LLMModel.Claude3:
client = instructor.from_anthropic(AsyncAnthropic())
case LLMModel.GPT4_Omni:
client = instructor.patch(AsyncOpenAI())
case LLMModel.LLAMA3:
client = instructor.patch(AsyncGroq())
return client
Domaine Gaou
class UserInfo(BaseModel):
name: str
age: int
is_teenager: bool
is_intelligent: bool
def usine_de_gaou_creation(
ai_client: Instructor, parametre: str, model=LLMModel.GPT4_Omni
) -> UserInfo:
gaou = ai_client.chat.completions.create(
model=model,
response_model=UserInfo,
messages= [{"role": "user", "content": parametre}],
)
return gaou
async def streaming_usine_de_gaou_creation(
ai_client: AsyncInstructor, parametre: str, model=LLMModel.GPT4_Omni
) -> UserInfo:
gaou = await ai_client.chat.completions.create(
model=model,
response_model=UserInfo,
messages= [
{
"role": "system",
"content": "L'utilisateur peut fournir une invite dans la langue de son choix (telle que l'anglais, le français, le créole, l'espagnol, etc.), veuillez donc en tenir compte.",
},
{"role": "user", "content": parametre},
],
)
return gaou
class Language(str, Enum):
nouchi = "Nouchi"
moore = "Mooré"
lingala = "Lingala"
english = "Anglais"
french = "Français"
creole = "Créole"
spanish = "Espagnol"
class GaouJoke(BaseModel):
friend_gaou_joke: str = Field(
...,
description="La blague qui qualifie l'ami de Gaou. La blague doit être légère et humoristique et alterner entre le Nouchi, le Mooré, le Lingala, l'anglais, le français, le créole et l'espagnol.",
)
language: Language
async def streaming_gaou_formula(
ai_client: AsyncInstructor, gaou_name: str, model=LLMModel.GPT4_Omni
) -> GaouJoke:
gaou = await ai_client.chat.completions.create(
model=model,
temperature=1, # Go wild with the temperature!!!!
max_tokens=1024,
response_model=GaouJoke,
messages= [
{
"role": "system",
"content": f"""
Le terme 'Gaou' est un terme amusant, utilisé uniquement entre amis. Par exemple, {gaou_name} est tellement Gaou !.
Vous aiderez à qualifier un ami de Gaou, en vous basant sur les critères suivants :
- Le nom de l'ami
- Inventez une blague légère qui finit toujours par qualifier l'ami de Gaou
- Mélangez de l'humour et du sarcasme
- D'une certaine manière, Gaou désigne quelqu'un de naïf, crédule ou facilement trompé, mais de manière amicale.
- Utilisez différentes langues parmi les suivantes : Mooré, anglais, français, créole, espagnol, etc.
""",
},
{"role": "user", "content": gaou_name},
],
)
return gaou
## Implémentation du bot
#!/usr/bin/env python3
import os
import random
import discord
from asyncio import sleep
from discord.ext import commands, tasks
from enhanced_discord_bot_llms.constants import (
WORDS_THE_BOT_DONT_LIKE,
FROWNING_FACE_EMOJI,
)
from enhanced_discord_bot_llms.llm_svc import (
gen_client,
usine_de_gaou_creation,
gen_async_client,
streaming_usine_de_gaou_creation,
LLMModel,
streaming_gaou_formula,
)
intents = discord.Intents.default()
intents.typing = False
intents.messages = True
intents.message_content = True
intents.reactions = True
intents.members = True
bot = commands.Bot(command_prefix="?", intents=intents)
@bot.event
async def on_message(message):
# nous ne voulons pas que le bot se réponde à lui-même
if message.author == bot.user:
return
try:
content = message.content.lower()
for word in WORDS_THE_BOT_DONT_LIKE:
if word in content:
await sleep(10)
await message.channel.send(
f"{message.author.mention} Hey! Do not use that word again {FROWNING_FACE_EMOJI}"
)
await message.channel.send(
f"{message.author.mention} You called me: {word} and I don't like it. I've deleted your message."
)
await sleep(10)
await message.delete()
except Exception as e:
print(f"Error: {e}")
if message.content == "pingGG":
await message.channel.send("pongGG")
return
await bot.process_commands(message)
@bot.event
async def on_message_edit(before, after):
if before.author == bot.user:
return
if after.content == "ping":
await after.channel.send("pong")
return
await bot.process_commands(after)
@bot.command()
@commands.guild_only()
async def ping(ctx: commands.Context):
"""
ctx: Context (discord.ext.commands.Context, informations sur la commande)
?ping
"""
await ctx.reply("pong")
@bot.command()
@commands.guild_only()
async def new_gaou(ctx: commands.Context, parametre: str):
"""
ctx: Context (discord.ext.commands.Context, informations sur la commande)
parametre: str (message à envoyer au modèle)
?new_gaou "Je ne suis pas un Gaou nommé Lambert qui a 15 ans et est intelligent."
"""
model = LLMModel.GPT4_Omni
# model = LLMModel.LLAMA3
try:
client = gen_async_client(model=model)
gueou = await streaming_usine_de_gaou_creation(client, parametre, model=model)
await ctx.reply(f"""
```json
{gueou.model_dump_json(
indent=4
)}
""")
except Exception as e:
print(f"Error: {e}")
await ctx.reply(f"An error occurred: {e}")
@bot.command()
@commands.has_permissions(administrator=True)
@commands.bot_has_permissions(manage_messages=True)
async def cleanup(ctx: commands.Context, limit: int):
"""
ctx: Contexte (discord.ext.commands.Context, informations sur la commande)
limit: int (nombre de messages à supprimer)
?cleanup 10
"""
await delete_messages(ctx, limit)
@bot.command()
@commands.dm_only()
async def dm_cleanup(ctx: commands.Context, limit: int):
"""
ctx: Contexte (discord.ext.commands.Context, informations sur la commande)
limit: int (nombre de messages à supprimer)
?dm_cleanup 10
"""
await delete_messages(ctx, limit)
async def delete_messages(ctx: commands.Context, limit: int):
print(f"Cleaning up: {limit} messages...")
async for msg in ctx.channel.history(limit=limit):
try:
print(f"Deleting message: {msg.content}")
await sleep(1)
await msg.delete()
except Exception as e:
print(f"Error: {e}")
await ctx.reply(f"You may not have permission to delete messages.")
continue
@tasks.loop(minutes=16)
async def my_background_gaou_tasks():
await bot.change_presence(activity=discord.Game(name="With Gaous"))
# members = [ [member for member in guild.members] for guild in bot.guilds]
# members = bot.get_all_members()
channels = bot.get_all_channels()
for chnl in channels:
if isinstance(chnl, discord.TextChannel) and chnl.name == "botexperiments":
await chnl.send(
f"Qui est Gaou de toute façon ? Moi Gaou ? Réfléchis encore... {chnl.mention}"
)
chnl_members = chnl.members
for chnl_m in chnl_members:
if chnl_m.bot:
continue
elif (
"african" in chnl_m.name.lower()
or "dog" in chnl_m.name.lower()
or "lle" in chnl_m.name.lower()
or "bru" in chnl_m.name.lower()
):
await sleep(8)
# model = random.choice(
# [model.value for model in LLMModel]
# ) # Choose a model at random
model = LLMModel.Claude3
client = gen_async_client(model=model)
gueou_joke = await streaming_gaou_formula(
client, chnl_m.display_name, model=model
)
# message_to_gueou = f"{gueou_joke.friend_gaou_joke} ({gueou_joke.language.name} => {gueou_joke.language.value}) {chnl_m.mention}"
message_to_gueou = f"{gueou_joke.friend_gaou_joke} ({gueou_joke.language.value}) {chnl_m.mention}"
await chnl.send(message_to_gueou)
@my_background_gaou_tasks.before_loop
async def before_gueou():
await bot.wait_until_ready()
print("Ready for Gaous!")
@bot.event
async def on_ready():
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
my_background_gaou_tasks.start()
if __name__ == "__main__":
token = os.environ ["DISCORD_BOT_TOKEN"]
bot.run(token)
Déploiement
Conteneurs Docker
Dockeriser l'API Discord
FROM python:3.12.3-alpine3.19
COPY . .
RUN apk add --no-cache libffi-dev openssl-dev gcc musl-dev make
RUN pip install -r requirements.lock
WORKDIR /src/enhanced_discord_bot_llms
CMD ["python", "gaouapp.py"]
Dockeriser le Bot Discord
FROM python:3.12.3-alpine3.19
COPY . .
RUN apk add --no-cache libffi-dev openssl-dev gcc musl-dev make
RUN pip install -r requirements.lock
WORKDIR /src/enhanced_discord_bot_llms
CMD ["python", "gaoubot.py"]
Script d'aide
#!/usr/bin/env bash
set -x #echo on
BASEDIR=(dirname "0")
DOCKERDIR=$BASEDIR/docker
PLATFORM=linux/amd64
REGISTRY=ttl.sh
echo "BASEDIR: $BASEDIR"
echo "DOCKERDIR: $DOCKERDIR"
case "$1" in
"dockerize:api")
echo "Building Docker image for API..."
docker buildx build --platform $PLATFORM -t $2 -f $DOCKERDIR/Dockerfile.api $BASEDIR
;;
"dockerize:bot")
echo "Building Docker image for Bot..."
docker buildx build --platform $PLATFORM -t $2 -f $DOCKERDIR/Dockerfile.bot $BASEDIR
;;
"docker:publish")
echo "Publishing Docker image..."
docker push $2
;;
*)
echo "Usage: $0 {dockerize:api|dockerize:bot|docker:publish}"
exit 1
;;
esac
exit 0
Infrastructure
graph TB
DockerEngine(Moteur Docker)
DockerEngine -- Exécute --> DockerContainer
DockerEngine -- Construit --> DockerImage
DockerFile(Dockerfile : Recette pour les images) -- Définit --> DockerImage
DockerHub(Docker Hub : Dépôt public) -- Stocke et partage --> DockerImage
DockerContainer(Conteneur Docker : Petit paquet autonome et exécutable)
DockerImage(Image Docker : Plans pour les conteneurs) -- Crée --> DockerContainer
subgraph "Analogie : Construction"
DockerFile -- "Plan de l'architecte" --> DockerImage
DockerImage -- "Pièces de maison préfabriquées" --> DockerContainer
end
#### Installer Docker sur Ubuntu
Mettre à jour votre liste de paquets existante
sudo apt update
Installer quelques paquets prérequis qui permettent à apt d'utiliser des paquets via HTTPS
sudo apt install apt-transport-https ca-certificates curl software-properties-common
Ajouter la clé GPG du dépôt officiel Docker à votre système
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Ajouter le dépôt Docker aux sources APT
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Mettre à jour votre liste de paquets existante pour que l'ajout soit reconnu
sudo apt update
Assurez-vous que vous êtes sur le point d'installer depuis le dépôt Docker plutôt que depuis le dépôt Ubuntu par défaut
apt-cache policy docker-ce
Installer Docker
sudo apt install docker-ce
Vérifier qu'il est en cours d'exécution
sudo systemctl status docker
#### Configurer Docker
Docker sans`sudo`
Ajouter votre nom d'utilisateur au groupe docker
sudo usermod -aG docker ${USER}
Appliquez la nouvelle appartenance au groupe, déconnectez-vous du serveur et reconnectez-vous (Facultatif ?)
su - ${USER}
groups
#### Gestion des variables d'environnement
Nous utiliserons [direnv](https://direnv.net/) et le [configurerons](https://direnv.net/docs/hook.html) pour bash à l'intérieur de notre machine virtuelle.
sudo apt install direnv
eval "$(direnv hook bash)"
