Clemylia commited on
Commit
805398b
·
verified ·
1 Parent(s): 5ae422f

Create core_logic.py

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