MHD011 commited on
Commit
ca1d327
·
verified ·
1 Parent(s): e1cc896

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -252
app.py CHANGED
@@ -19,92 +19,18 @@ logger = logging.getLogger(__name__)
19
  MODEL_NAME = "tscholak/3vnuv1vf" # نموذج متخصص لـ PostgreSQL
20
  SUPABASE_DB_URL = os.getenv('SUPABASE_DB_URL')
21
 
22
- # تهيئة مجلد الذاكرة المؤقتة
23
- os.makedirs("model_cache", exist_ok=True)
24
- os.environ["TRANSFORMERS_CACHE"] = "model_cache"
25
- os.environ["TORCH_CACHE"] = "model_cache"
26
 
27
- tokenizer = None
28
- model = None
 
 
29
 
30
- def initialize():
31
- global tokenizer, model
32
- try:
33
- logger.info("🚀 جاري تحميل النموذج...")
34
- tokenizer = AutoTokenizer.from_pretrained(
35
- MODEL_NAME,
36
- cache_dir="model_cache"
37
- )
38
-
39
- model = AutoModelForSeq2SeqLM.from_pretrained(
40
- MODEL_NAME,
41
- cache_dir="model_cache",
42
- device_map="auto",
43
- torch_dtype=torch.float32, # تغيير من float16 إلى float32 للإصلاح
44
- low_cpu_mem_usage=True
45
- )
46
-
47
- if torch.cuda.is_available():
48
- model.to('cuda')
49
- logger.info("✅ تم تحميل النموذج على GPU")
50
- else:
51
- model.float() # تأكد من تحويل النموذج إلى float32 على CPU
52
- logger.info("✅ تم تحميل النموذج على CPU (باستخدام float32)")
53
-
54
- model.eval()
55
- except Exception as e:
56
- logger.error(f"❌ فشل في تحميل النموذج: {str(e)}")
57
- raise
58
-
59
- initialize()
60
-
61
- # سكيما قاعدة البيانات (نفسها كما في الكود الأصلي)
62
  DB_SCHEMA = """
63
- profiles (
64
- id uuid NOT NULL,
65
- updated_at timestamp with time zone,
66
- username text UNIQUE CHECK (char_length(username) >= 3),
67
- full_name text,
68
- avatar_url text,
69
- website text,
70
- cam_mac text UNIQUE,
71
- fcm_token text,
72
- notification_enabled boolean DEFAULT true,
73
- CONSTRAINT profiles_pkey PRIMARY KEY (id),
74
- CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
75
- );
76
-
77
- place (
78
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
79
- created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
80
- name text,
81
- CONSTRAINT place_pkey PRIMARY KEY (id)
82
- );
83
-
84
- user_place (
85
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
86
- created_at timestamp with time zone NOT NULL DEFAULT now(),
87
- place_id bigint,
88
- user_cam_mac text,
89
- CONSTRAINT user_place_pkey PRIMARY KEY (id),
90
- CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id),
91
- CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
92
- );
93
-
94
- data (
95
- id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
96
- created_at timestamp without time zone,
97
- caption text,
98
- image_url text,
99
- latitude double precision DEFAULT '36.1833854'::double precision,
100
- longitude double precision DEFAULT '37.1309255'::double precision,
101
- user_place_id bigint,
102
- cam_mac text,
103
- CONSTRAINT data_pkey PRIMARY KEY (id),
104
- CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
105
- );
106
-
107
- biodata (
108
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
109
  created_at timestamp with time zone NOT NULL DEFAULT now(),
110
  mac_address text,
@@ -118,8 +44,27 @@ biodata (
118
  CONSTRAINT biodata_pkey PRIMARY KEY (id),
119
  CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
120
  );
121
-
122
- notification (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
124
  created_at timestamp without time zone NOT NULL DEFAULT now(),
125
  user_cam_mac text,
@@ -135,191 +80,97 @@ notification (
135
  CONSTRAINT notification_pkey PRIMARY KEY (id),
136
  CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
137
  );
138
-
139
- flag (
 
140
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
141
- flag smallint,
142
- user_mac_address text,
143
- CONSTRAINT flag_pkey PRIMARY KEY (id),
144
- CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  );
146
  """.strip()
147
 
148
- def get_db_connection():
149
- try:
150
- conn = psycopg2.connect(SUPABASE_DB_URL)
151
- logger.info("✅ تم الاتصال بقاعدة البيانات")
152
- return conn
153
- except Exception as err:
154
- logger.error(f"❌ خطأ في الاتصال بقاعدة البيانات: {err}")
155
- return None
156
-
157
- def validate_cam_mac(cam_mac):
158
- conn = get_db_connection()
159
- if not conn:
160
- return False
161
-
162
- try:
163
- cursor = conn.cursor()
164
- cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
165
- return cursor.fetchone() is not None
166
- except Exception as e:
167
- logger.error(f"❌ خطأ في التحقق من cam_mac: {e}")
168
- return False
169
- finally:
170
- if conn:
171
- conn.close()
172
-
173
- def is_safe_sql(sql):
174
- """تحقق مما إذا كان الاستعلام SELECT فقط وآمن للتنفيذ"""
175
- sql_upper = sql.upper()
176
-
177
- # قائمة بالأوامر المحظورة
178
- forbidden_commands = [
179
- "INSERT", "UPDATE", "DELETE", "CREATE",
180
- "DROP", "ALTER", "TRUNCATE", "GRANT",
181
- "REVOKE", "COMMIT", "ROLLBACK"
182
- ]
183
-
184
- # التأكد من أن الاستعلام يبدأ بـ SELECT
185
- if not sql_upper.strip().startswith("SELECT"):
186
- return False
187
-
188
- # التأكد من عدم وجود أي أوامر محظورة
189
- for cmd in forbidden_commands:
190
- if cmd in sql_upper:
191
- return False
192
-
193
- return True
194
-
195
- def clean_sql(sql, cam_mac):
196
- """تنظيف استعلام SQL لضمان أنه SELECT فقط"""
197
- # إزالة أي أوامر غير SELECT
198
- sql = re.sub(r'CREATE\s+TABLE', '', sql, flags=re.IGNORECASE)
199
- sql = re.sub(r'INSERT\s+INTO', '', sql, flags=re.IGNORECASE)
200
- sql = re.sub(r'UPDATE\s+', '', sql, flags=re.IGNORECASE)
201
- sql = re.sub(r'DELETE\s+FROM', '', sql, flags=re.IGNORECASE)
202
-
203
- # إزالة أي شيء قبل SELECT
204
- sql = re.sub(r'^[^S]*(SELECT)', 'SELECT', sql, flags=re.IGNORECASE)
205
-
206
- # أخذ أول عبارة SQL فقط
207
- sql = sql.split(';')[0] + ';'
208
-
209
- # إزالة المسافات الزائدة
210
- sql = ' '.join(sql.split()).strip()
211
 
212
- # إذا لم يعد يحتوي على SELECT بعد التنظيف
213
- if not sql.upper().startswith('SELECT'):
214
- return f"SELECT id, created_at FROM data WHERE cam_mac = '{cam_mac}' ORDER BY created_at DESC LIMIT 1;"
 
 
 
 
 
215
 
216
- return sql
 
217
 
218
  @app.route('/api/query', methods=['POST'])
219
  def handle_query():
220
  try:
221
  data = request.get_json()
222
- logger.info(f"📩 بيانات الطلب: {data}")
223
 
224
- if not data or 'text' not in data or 'cam_mac' not in data:
225
- return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
226
-
227
- if not validate_cam_mac(data['cam_mac']):
228
- return jsonify({"error": "عنوان MAC غير صالح"}), 403
229
 
 
230
  prompt = f"""
231
- ### التعليمات الصارمة:
232
- 1. قم بتحويل السؤال إلى استعلام SELECT فقط لـ PostgreSQL.
233
- 2. يجب أن يتضمن الشرط: WHERE cam_mac = '{data['cam_mac']}'.
234
- 3. ممنوع تمامًا استخدام أي أوامر غير SELECT.
235
- 4. استخدم أسماء الجداول الموجودة فقط: profiles, place, user_place, data, biodata, notification, flag.
236
-
237
- ### هيكل قاعدة البيانات:
238
- {DB_SCHEMA}
239
-
240
- ### أمثلة صحيحة:
241
- - السؤال: "ما عدد زياراتي للمكتب؟"
242
- SQL: SELECT COUNT(*) FROM data WHERE cam_mac = '{data['cam_mac']}';
243
-
244
- - السؤال: "ما هي آخر زيارة؟"
245
- SQL: SELECT id, created_at FROM data WHERE cam_mac = '{data['cam_mac']}' ORDER BY created_at DESC LIMIT 1;
246
 
247
- ### السؤال:
248
- {data['text']}
 
 
249
 
250
- ### استعلام SQL (SELECT فقط):
 
 
251
  """
252
-
253
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512).to('cpu')
254
-
255
- with torch.no_grad():
256
- outputs = model.generate(
257
- **inputs,
258
- max_length=256,
259
- num_beams=4,
260
- early_stopping=True,
261
- temperature=0.7
262
- )
263
-
264
- sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
265
- logger.info(f"⚡ الاستعلام المولد (قبل التنظيف): {sql}")
266
-
267
- sql = clean_sql(sql, data['cam_mac'])
268
- logger.info(f"🔧 الاستعلام بعد التنظيف: {sql}")
269
 
 
270
  if not is_safe_sql(sql):
271
- logger.warning(f"استعلام غير آمن بعد التنظيف، استخدام استعلام افتراضي")
272
- sql = f"SELECT id, created_at FROM data WHERE cam_mac = '{data['cam_mac']}' ORDER BY created_at DESC LIMIT 10;"
273
-
274
- conn = get_db_connection()
275
- if not conn:
276
- return jsonify({"error": "فشل الاتصال بقاعدة البيانات"}), 500
277
-
278
- try:
279
- cursor = conn.cursor()
280
- cursor.execute(sql)
281
-
282
- columns = [desc[0] for desc in cursor.description] if cursor.description else []
283
- rows = cursor.fetchall()
284
-
285
- return jsonify({
286
- "data": [dict(zip(columns, row)) for row in rows],
287
- "sql": sql,
288
- "timestamp": datetime.now().isoformat()
289
- })
290
-
291
- except Exception as e:
292
- return jsonify({
293
- "error": "خطأ في تنفيذ الاستعلام",
294
- "sql": sql,
295
- "details": str(e)
296
- }), 500
297
-
298
- finally:
299
- if conn:
300
- conn.close()
301
 
302
  except Exception as e:
303
- logger.error(f" خطأ غير متوقع: {str(e)}", exc_info=True)
304
- return jsonify({"error": "فشل في معالجة الطلب"}), 500
305
-
306
- @app.route('/health')
307
- def health_check():
308
- return jsonify({
309
- "status": "healthy",
310
- "model_loaded": model is not None,
311
- "device": str(model.device) if model else "none",
312
- "db_connection": get_db_connection() is not None,
313
- "timestamp": datetime.now().isoformat()
314
- })
315
-
316
- @app.route('/')
317
- def home():
318
- return """
319
- <h1>Text2SQL API</h1>
320
- <p>استخدم <code>/api/query</code> مع POST</p>
321
- <p>تحقق من الحالة: <a href="/health">/health</a></p>
322
- """
323
-
324
- if __name__ == '__main__':
325
- app.run(host='0.0.0.0', port=7860)
 
19
  MODEL_NAME = "tscholak/3vnuv1vf" # نموذج متخصص لـ PostgreSQL
20
  SUPABASE_DB_URL = os.getenv('SUPABASE_DB_URL')
21
 
22
+ # التهيئة المسبقة للنموذج
23
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
24
+ model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME)
 
25
 
26
+ # نقل النموذج إلى الجهاز المناسب
27
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
28
+ model.to(device)
29
+ model.eval()
30
 
31
+ # سكيما قاعدة البيانات
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  DB_SCHEMA = """
33
+ CREATE TABLE public.biodata (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
35
  created_at timestamp with time zone NOT NULL DEFAULT now(),
36
  mac_address text,
 
44
  CONSTRAINT biodata_pkey PRIMARY KEY (id),
45
  CONSTRAINT biodata_mac_address_fkey FOREIGN KEY (mac_address) REFERENCES public.profiles(cam_mac)
46
  );
47
+ CREATE TABLE public.data (
48
+ user_place_id bigint,
49
+ cam_mac text,
50
+ does_loaction_update boolean DEFAULT false,
51
+ caption text,
52
+ image_url text,
53
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
54
+ longitude double precision DEFAULT '37.1309255'::double precision,
55
+ created_at timestamp without time zone,
56
+ latitude double precision DEFAULT '36.1833854'::double precision,
57
+ CONSTRAINT data_pkey PRIMARY KEY (id),
58
+ CONSTRAINT data_user_place_id_fkey FOREIGN KEY (user_place_id) REFERENCES public.user_place(id)
59
+ );
60
+ CREATE TABLE public.flag (
61
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
62
+ flag smallint,
63
+ user_mac_address text,
64
+ CONSTRAINT flag_pkey PRIMARY KEY (id),
65
+ CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
66
+ );
67
+ CREATE TABLE public.notification (
68
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
69
  created_at timestamp without time zone NOT NULL DEFAULT now(),
70
  user_cam_mac text,
 
80
  CONSTRAINT notification_pkey PRIMARY KEY (id),
81
  CONSTRAINT notification_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac)
82
  );
83
+ CREATE TABLE public.place (
84
+ name text,
85
+ created_at timestamp with time zone DEFAULT (now() AT TIME ZONE 'utc'::text),
86
  id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
87
+ CONSTRAINT place_pkey PRIMARY KEY (id)
88
+ );
89
+ CREATE TABLE public.profiles (
90
+ fcm_token text,
91
+ notification_enabled boolean DEFAULT true,
92
+ id uuid NOT NULL,
93
+ updated_at timestamp with time zone,
94
+ username text UNIQUE CHECK (char_length(username) >= 3),
95
+ full_name text,
96
+ website text,
97
+ cam_mac text UNIQUE,
98
+ avatar_url text DEFAULT 'https://mougnkvoyyhcuxeeqvmh.supabase.co/storage/v1/object/public/profiles//download.jpeg'::text,
99
+ CONSTRAINT profiles_pkey PRIMARY KEY (id),
100
+ CONSTRAINT profiles_id_fkey FOREIGN KEY (id) REFERENCES auth.users(id)
101
+ );
102
+ CREATE TABLE public.user_place (
103
+ place_id bigint,
104
+ user_cam_mac text,
105
+ id bigint GENERATED ALWAYS AS IDENTITY NOT NULL,
106
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
107
+ CONSTRAINT user_place_pkey PRIMARY KEY (id),
108
+ CONSTRAINT user_place_user_cam_mac_fkey FOREIGN KEY (user_cam_mac) REFERENCES public.profiles(cam_mac),
109
+ CONSTRAINT user_place_place_id_fkey FOREIGN KEY (place_id) REFERENCES public.place(id)
110
  );
111
  """.strip()
112
 
113
+ def generate_sql(prompt: str, cam_mac: str) -> str:
114
+ """
115
+ توليد استعلام SQL من النص باستخدام النموذج
116
+ """
117
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512).to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ with torch.no_grad():
120
+ outputs = model.generate(
121
+ **inputs,
122
+ max_length=256,
123
+ num_beams=4,
124
+ early_stopping=True,
125
+ temperature=0.7
126
+ )
127
 
128
+ sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
129
+ return clean_sql(sql, cam_mac)
130
 
131
  @app.route('/api/query', methods=['POST'])
132
  def handle_query():
133
  try:
134
  data = request.get_json()
 
135
 
136
+ # التحقق من البيانات المدخلة
137
+ if not all(k in data for k in ['text', 'cam_mac']):
138
+ return jsonify({"error": "المعطيات ناقصة"}), 400
 
 
139
 
140
+ # إنشاء الـ prompt المحسن
141
  prompt = f"""
142
+ قم بتحويل السؤال التالي إلى استعلام PostgreSQL SELECT صالح:
143
+ السؤال: {data['text']}
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ الشروط:
146
+ 1. يجب تضمين شرط WHERE: cam_mac = '{data['cam_mac']}'
147
+ 2. مسموح فقط باستخدام SELECT
148
+ 3. الجداول المتاحة: profiles, data, place
149
 
150
+ المثال:
151
+ السؤال: "ما عدد زياراتي؟"
152
+ SQL: SELECT COUNT(*) FROM data WHERE cam_mac = '{data['cam_mac']}';
153
  """
154
+ # توليد الاستعلام
155
+ sql = generate_sql(prompt, data['cam_mac'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ # التحقق من الأمان
158
  if not is_safe_sql(sql):
159
+ sql = f"SELECT * FROM data WHERE cam_mac = '{data['cam_mac']}' LIMIT 10;"
160
+
161
+ # تنفيذ الاستعلام
162
+ with psycopg2.connect(SUPABASE_DB_URL) as conn:
163
+ with conn.cursor() as cursor:
164
+ cursor.execute(sql)
165
+ columns = [desc[0] for desc in cursor.description]
166
+ rows = cursor.fetchall()
167
+
168
+ return jsonify({
169
+ "data": [dict(zip(columns, row)) for row in rows],
170
+ "sql": sql,
171
+ "timestamp": datetime.now().isoformat()
172
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  except Exception as e:
175
+ logger.error(f"Error: {str(e)}")
176
+ return jsonify({"error": "حدث خطأ في المعالجة"}), 500