Clemylia commited on
Commit
a374fc3
·
verified ·
1 Parent(s): 960ffeb

Upload core_logic.py

Browse files
Files changed (1) hide show
  1. src/core_logic.py +215 -0
src/core_logic.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fichier : core_logic.py
2
+ import sqlite3
3
+ import datetime
4
+ import re
5
+ import torch
6
+ from transformers import AutoModelForCausalLM, AutoTokenizer
7
+ import uuid # <-- AJOUT NÉCESSAIRE pour générer des IDs uniques
8
+
9
+ # --- CONFIGURATION GLOBALE ---
10
+ DB_NAME = "charlotte_apy.db"
11
+ MODEL_NAME = "Clemylia/Tiny-charlotte"
12
+ MAX_QUOTA = 1200
13
+ MAX_TOKENS_PER_RESPONSE = 100
14
+
15
+ # ----------------------------------------------------
16
+ # A. LOGIQUE DE BASE DE DONNÉES SQLite (Fonctions réutilisables)
17
+ # ----------------------------------------------------
18
+
19
+ def init_db():
20
+ conn = sqlite3.connect(DB_NAME)
21
+ cursor = conn.cursor()
22
+ cursor.execute("""
23
+ CREATE TABLE IF NOT EXISTS api_keys (
24
+ key_id TEXT PRIMARY KEY,
25
+ quota_remaining INTEGER,
26
+ max_quota INTEGER,
27
+ date_last_use TEXT
28
+ )
29
+ """)
30
+ conn.commit()
31
+ conn.close()
32
+
33
+ def get_all_keys():
34
+ conn = sqlite3.connect(DB_NAME)
35
+ cursor = conn.cursor()
36
+ cursor.execute("SELECT key_id, quota_remaining, max_quota, date_last_use FROM api_keys")
37
+ rows = cursor.fetchall()
38
+ conn.close()
39
+ keys = {}
40
+ for row in rows:
41
+ keys[row[0]] = {'quota_remaining': row[1], 'max_quota': row[2], 'date_last_use': row[3]}
42
+ return keys
43
+
44
+ def get_key_data(key_id):
45
+ conn = sqlite3.connect(DB_NAME)
46
+ cursor = conn.cursor()
47
+ cursor.execute("SELECT quota_remaining, max_quota, date_last_use FROM api_keys WHERE key_id = ?", (key_id,))
48
+ row = cursor.fetchone()
49
+ conn.close()
50
+ if row:
51
+ return {'quota_remaining': row[0], 'max_quota': row[1], 'date_last_use': row[2]}
52
+ return None
53
+
54
+ def add_key_to_db(key_id, max_quota=MAX_QUOTA):
55
+ conn = sqlite3.connect(DB_NAME)
56
+ cursor = conn.cursor()
57
+ today = datetime.date.today().isoformat()
58
+ try:
59
+ cursor.execute("INSERT INTO api_keys (key_id, quota_remaining, max_quota, date_last_use) VALUES (?, ?, ?, ?)", (key_id, max_quota, max_quota, today))
60
+ conn.commit()
61
+ conn.close()
62
+ return True
63
+ except sqlite3.IntegrityError:
64
+ conn.close()
65
+ return False
66
+
67
+ # 🔑 NOUVELLE FONCTION AJOUTÉE POUR CRÉER ET ENREGISTRER UNE CLÉ UNIQUE
68
+ def create_api_key(max_quota=MAX_QUOTA):
69
+ """
70
+ Génère une clé d'API unique (Tn-charlotte-...) et l'enregistre immédiatement
71
+ dans la base de données via add_key_to_db.
72
+ """
73
+ # Générer une clé unique au format souhaité
74
+ unique_id = uuid.uuid4().hex[:20].upper()
75
+ api_key = f"Tn-charlotte-{unique_id}"
76
+
77
+ # Validation minimale (vérification du format)
78
+ is_valid, _ = validate_key(api_key)
79
+
80
+ # Enregistrement dans la DB
81
+ if is_valid and add_key_to_db(api_key, max_quota):
82
+ return api_key
83
+
84
+ return None # Retourne None en cas d'échec d'insertion ou de validation
85
+
86
+
87
+ def delete_key_from_db(key_id):
88
+ conn = sqlite3.connect(DB_NAME)
89
+ cursor = conn.cursor()
90
+ cursor.execute("DELETE FROM api_keys WHERE key_id = ?", (key_id,))
91
+ conn.commit()
92
+ conn.close()
93
+
94
+ def update_key_quota_in_db(key_id, new_remaining_quota, new_date_last_use):
95
+ conn = sqlite3.connect(DB_NAME)
96
+ cursor = conn.cursor()
97
+ cursor.execute("UPDATE api_keys SET quota_remaining = ?, date_last_use = ? WHERE key_id = ?", (new_remaining_quota, new_date_last_use, key_id))
98
+ conn.commit()
99
+ conn.close()
100
+
101
+ def reset_key_quota_in_db(key_id):
102
+ conn = sqlite3.connect(DB_NAME)
103
+ cursor = conn.cursor()
104
+ today = datetime.date.today().isoformat()
105
+ cursor.execute("UPDATE api_keys SET quota_remaining = max_quota, date_last_use = ? WHERE key_id = ?", (today, key_id))
106
+ conn.commit()
107
+ conn.close()
108
+
109
+ def validate_key(key_str):
110
+ # (Logique de validation inchangée)
111
+ if not key_str.startswith("Tn-charlotte"):
112
+ return False, "La clé doit commencer par Tn-charlotte."
113
+ num_digits = len(re.findall(r'\d', key_str))
114
+ if num_digits < 5:
115
+ return False, f"La clé doit contenir au moins 5 chiffres (actuel : {num_digits})."
116
+ num_letters = len(re.findall(r'[a-zA-Z]', key_str))
117
+ if num_letters < 7:
118
+ return False, f"La clé doit contenir au moins 7 lettres (actuel : {num_letters})."
119
+ return True, "Clé valide !"
120
+
121
+ # Appel initial
122
+ init_db()
123
+
124
+ # ----------------------------------------------------
125
+ # B. CHARGEMENT DU MODÈLE (Chargement unique pour le serveur)
126
+ # ----------------------------------------------------
127
+
128
+ # Ces variables seront utilisées par le serveur FastAPI
129
+ MODEL, TOKENIZER, DEVICE = None, None, None
130
+
131
+ def load_tiny_charlotte_server():
132
+ """Charge le modèle une seule fois pour le serveur API."""
133
+ global MODEL, TOKENIZER, DEVICE
134
+ if MODEL is None:
135
+ try:
136
+ print("INFO: Chargement du modèle Tiny-Charlotte...")
137
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
138
+ TOKENIZER = AutoTokenizer.from_pretrained(MODEL_NAME)
139
+ MODEL = AutoModelForCausalLM.from_pretrained(MODEL_NAME).to(DEVICE)
140
+ print(f"INFO: Modèle chargé avec succès sur {DEVICE}.")
141
+ except Exception as e:
142
+ print(f"ERREUR: Échec du chargement du modèle: {e}")
143
+
144
+ # Le serveur FastAPI appellera cette fonction au démarrage.
145
+
146
+ # ----------------------------------------------------
147
+ # C. LOGIQUE D'INFÉRENCE POUR L'API (Retourne données et code statut)
148
+ # ----------------------------------------------------
149
+
150
+ def run_inference_api(api_key, prompt):
151
+ """
152
+ Exécute l'inférence et gère le quota.
153
+ Retourne (données JSON, code statut HTTP).
154
+ """
155
+ if MODEL is None or TOKENIZER is None:
156
+ return {"error": "Internal Server Error: Model not loaded."}, 500
157
+
158
+ today = datetime.date.today().isoformat()
159
+ key_data = get_key_data(api_key)
160
+
161
+ if key_data is None:
162
+ # 401 Unauthorized: Clé invalide
163
+ return {"error": "Unauthorized: Invalid API Key."}, 401
164
+
165
+ # Réinitialisation automatique du Quota
166
+ if key_data['date_last_use'] != today:
167
+ key_data['quota_remaining'] = key_data['max_quota']
168
+ key_data['date_last_use'] = today
169
+ update_key_quota_in_db(api_key, key_data['quota_remaining'], today)
170
+
171
+ # Vérification du Quota
172
+ if key_data['quota_remaining'] < MAX_TOKENS_PER_RESPONSE:
173
+ # 429 Too Many Requests: Quota atteint
174
+ return {
175
+ "error": "Quota Exceeded: Daily token limit reached.",
176
+ "usage": {"tokens_remaining": key_data['quota_remaining'], "limit": MAX_QUOTA}
177
+ }, 429
178
+
179
+ try:
180
+ # Encodage et Génération
181
+ input_ids = TOKENIZER.encode(prompt, return_tensors='pt').to(DEVICE)
182
+
183
+ output = MODEL.generate(
184
+ input_ids,
185
+ max_length=input_ids.shape[1] + MAX_TOKENS_PER_RESPONSE,
186
+ do_sample=True, top_k=50, top_p=0.95, num_return_sequences=1,
187
+ pad_token_id=TOKENIZER.eos_token_id
188
+ )
189
+
190
+ response_text = TOKENIZER.decode(output[0], skip_special_tokens=True)
191
+ tokens_generated = output.shape[1] - input_ids.shape[1]
192
+
193
+ except Exception as e:
194
+ print(f"ERREUR d'inférence: {e}")
195
+ return {"error": "Internal Server Error: Inference failed."}, 500
196
+
197
+ # Mise à Jour du Quota DANS LA BASE DE DONNÉES
198
+ new_remaining = key_data['quota_remaining'] - tokens_generated
199
+ update_key_quota_in_db(api_key, new_remaining, today)
200
+
201
+ # Retour de la réponse (200 OK)
202
+ return {
203
+ "generated_text": response_text,
204
+ "model": MODEL_NAME,
205
+ "usage": {
206
+ "tokens_used": tokens_generated,
207
+ "tokens_remaining": new_remaining,
208
+ "limit": MAX_QUOTA
209
+ }
210
+ }, 200
211
+
212
+ # ----------------------------------------------------
213
+ # D. LOGIQUE D'INFÉRENCE POUR STREAMLIT (Nécessite les outils Streamlit)
214
+ # ----------------------------------------------------
215
+ # (Ceci est un placeholder, la logique Streamlit sera dans charlotte_apy.py)