Charlotte-APY / src /core_logic.py
Clemylia's picture
Upload core_logic.py
a374fc3 verified
# Fichier : core_logic.py
import sqlite3
import datetime
import re
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import uuid # <-- AJOUT NÉCESSAIRE pour générer des IDs uniques
# --- CONFIGURATION GLOBALE ---
DB_NAME = "charlotte_apy.db"
MODEL_NAME = "Clemylia/Tiny-charlotte"
MAX_QUOTA = 1200
MAX_TOKENS_PER_RESPONSE = 100
# ----------------------------------------------------
# A. LOGIQUE DE BASE DE DONNÉES SQLite (Fonctions réutilisables)
# ----------------------------------------------------
def init_db():
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS api_keys (
key_id TEXT PRIMARY KEY,
quota_remaining INTEGER,
max_quota INTEGER,
date_last_use TEXT
)
""")
conn.commit()
conn.close()
def get_all_keys():
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT key_id, quota_remaining, max_quota, date_last_use FROM api_keys")
rows = cursor.fetchall()
conn.close()
keys = {}
for row in rows:
keys[row[0]] = {'quota_remaining': row[1], 'max_quota': row[2], 'date_last_use': row[3]}
return keys
def get_key_data(key_id):
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
cursor.execute("SELECT quota_remaining, max_quota, date_last_use FROM api_keys WHERE key_id = ?", (key_id,))
row = cursor.fetchone()
conn.close()
if row:
return {'quota_remaining': row[0], 'max_quota': row[1], 'date_last_use': row[2]}
return None
def add_key_to_db(key_id, max_quota=MAX_QUOTA):
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
today = datetime.date.today().isoformat()
try:
cursor.execute("INSERT INTO api_keys (key_id, quota_remaining, max_quota, date_last_use) VALUES (?, ?, ?, ?)", (key_id, max_quota, max_quota, today))
conn.commit()
conn.close()
return True
except sqlite3.IntegrityError:
conn.close()
return False
# 🔑 NOUVELLE FONCTION AJOUTÉE POUR CRÉER ET ENREGISTRER UNE CLÉ UNIQUE
def create_api_key(max_quota=MAX_QUOTA):
"""
Génère une clé d'API unique (Tn-charlotte-...) et l'enregistre immédiatement
dans la base de données via add_key_to_db.
"""
# Générer une clé unique au format souhaité
unique_id = uuid.uuid4().hex[:20].upper()
api_key = f"Tn-charlotte-{unique_id}"
# Validation minimale (vérification du format)
is_valid, _ = validate_key(api_key)
# Enregistrement dans la DB
if is_valid and add_key_to_db(api_key, max_quota):
return api_key
return None # Retourne None en cas d'échec d'insertion ou de validation
def delete_key_from_db(key_id):
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
cursor.execute("DELETE FROM api_keys WHERE key_id = ?", (key_id,))
conn.commit()
conn.close()
def update_key_quota_in_db(key_id, new_remaining_quota, new_date_last_use):
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
cursor.execute("UPDATE api_keys SET quota_remaining = ?, date_last_use = ? WHERE key_id = ?", (new_remaining_quota, new_date_last_use, key_id))
conn.commit()
conn.close()
def reset_key_quota_in_db(key_id):
conn = sqlite3.connect(DB_NAME)
cursor = conn.cursor()
today = datetime.date.today().isoformat()
cursor.execute("UPDATE api_keys SET quota_remaining = max_quota, date_last_use = ? WHERE key_id = ?", (today, key_id))
conn.commit()
conn.close()
def validate_key(key_str):
# (Logique de validation inchangée)
if not key_str.startswith("Tn-charlotte"):
return False, "La clé doit commencer par Tn-charlotte."
num_digits = len(re.findall(r'\d', key_str))
if num_digits < 5:
return False, f"La clé doit contenir au moins 5 chiffres (actuel : {num_digits})."
num_letters = len(re.findall(r'[a-zA-Z]', key_str))
if num_letters < 7:
return False, f"La clé doit contenir au moins 7 lettres (actuel : {num_letters})."
return True, "Clé valide !"
# Appel initial
init_db()
# ----------------------------------------------------
# B. CHARGEMENT DU MODÈLE (Chargement unique pour le serveur)
# ----------------------------------------------------
# Ces variables seront utilisées par le serveur FastAPI
MODEL, TOKENIZER, DEVICE = None, None, None
def load_tiny_charlotte_server():
"""Charge le modèle une seule fois pour le serveur API."""
global MODEL, TOKENIZER, DEVICE
if MODEL is None:
try:
print("INFO: Chargement du modèle Tiny-Charlotte...")
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
TOKENIZER = AutoTokenizer.from_pretrained(MODEL_NAME)
MODEL = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(DEVICE)
print(f"INFO: Modèle chargé avec succès sur {DEVICE}.")
except Exception as e:
print(f"ERREUR: Échec du chargement du modèle: {e}")
# Le serveur FastAPI appellera cette fonction au démarrage.
# ----------------------------------------------------
# C. LOGIQUE D'INFÉRENCE POUR L'API (Retourne données et code statut)
# ----------------------------------------------------
def run_inference_api(api_key, prompt):
"""
Exécute l'inférence et gère le quota.
Retourne (données JSON, code statut HTTP).
"""
if MODEL is None or TOKENIZER is None:
return {"error": "Internal Server Error: Model not loaded."}, 500
today = datetime.date.today().isoformat()
key_data = get_key_data(api_key)
if key_data is None:
# 401 Unauthorized: Clé invalide
return {"error": "Unauthorized: Invalid API Key."}, 401
# Réinitialisation automatique du Quota
if key_data['date_last_use'] != today:
key_data['quota_remaining'] = key_data['max_quota']
key_data['date_last_use'] = today
update_key_quota_in_db(api_key, key_data['quota_remaining'], today)
# Vérification du Quota
if key_data['quota_remaining'] < MAX_TOKENS_PER_RESPONSE:
# 429 Too Many Requests: Quota atteint
return {
"error": "Quota Exceeded: Daily token limit reached.",
"usage": {"tokens_remaining": key_data['quota_remaining'], "limit": MAX_QUOTA}
}, 429
try:
# Encodage et Génération
input_ids = TOKENIZER.encode(prompt, return_tensors='pt').to(DEVICE)
output = MODEL.generate(
input_ids,
max_length=input_ids.shape[1] + MAX_TOKENS_PER_RESPONSE,
do_sample=True, top_k=50, top_p=0.95, num_return_sequences=1,
pad_token_id=TOKENIZER.eos_token_id
)
response_text = TOKENIZER.decode(output[0], skip_special_tokens=True)
tokens_generated = output.shape[1] - input_ids.shape[1]
except Exception as e:
print(f"ERREUR d'inférence: {e}")
return {"error": "Internal Server Error: Inference failed."}, 500
# Mise à Jour du Quota DANS LA BASE DE DONNÉES
new_remaining = key_data['quota_remaining'] - tokens_generated
update_key_quota_in_db(api_key, new_remaining, today)
# Retour de la réponse (200 OK)
return {
"generated_text": response_text,
"model": MODEL_NAME,
"usage": {
"tokens_used": tokens_generated,
"tokens_remaining": new_remaining,
"limit": MAX_QUOTA
}
}, 200
# ----------------------------------------------------
# D. LOGIQUE D'INFÉRENCE POUR STREAMLIT (Nécessite les outils Streamlit)
# ----------------------------------------------------
# (Ceci est un placeholder, la logique Streamlit sera dans charlotte_apy.py)