MHD011 commited on
Commit
7a3df70
·
verified ·
1 Parent(s): 2b295d5

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +17 -0
  2. app.py +133 -110
  3. requirements.txt +3 -4
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the current directory contents into the container
8
+ COPY . .
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Make port 7860 available to the world outside this container
14
+ EXPOSE 7860
15
+
16
+ # Run app.py when the container launches
17
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -1,47 +1,22 @@
1
  import os
2
  import re
3
- import logging
4
- from flask import Flask, request, jsonify, Response
5
- from flask_cors import CORS
6
- from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
7
- import torch
8
  import psycopg2
 
 
 
9
  import json
10
 
 
11
  app = Flask(__name__)
12
- CORS(app)
13
 
14
- # --- إعداد السجل ---
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
 
17
 
18
- # --- إعداد النموذج ---
19
- MODEL_NAME = "tscholak/cxmefzzi" # نموذج Text-to-SQL بديل
20
  SUPABASE_DB_URL = "postgresql://postgres.mougnkvoyyhcuxeeqvmh:Xf5E0DhUvKEHEAqq@aws-0-eu-central-1.pooler.supabase.com:6543/postgres"
21
 
22
- tokenizer = None
23
- model = None
24
-
25
- def initialize():
26
- global tokenizer, model
27
-
28
- # تحديد مسار ذاكرة مؤقتة ضمن المساحة المسموح بها
29
- cache_dir = os.path.join(os.getcwd(), "model_cache")
30
- os.makedirs(cache_dir, exist_ok=True)
31
-
32
- device = "cuda" if torch.cuda.is_available() else "cpu"
33
- logger.info(f"تحميل النموذج على الجهاز: {device}")
34
-
35
- try:
36
- tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, cache_dir=cache_dir)
37
- model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME, cache_dir=cache_dir).to(device)
38
- logger.info("تم تحميل النموذج بنجاح")
39
- except Exception as e:
40
- logger.error(f"فشل في تحميل النموذج: {str(e)}")
41
- raise
42
-
43
- initialize()
44
-
45
  # --- سكيمة قاعدة البيانات ---
46
  DB_SCHEMA = """
47
  CREATE TABLE public.profiles (
@@ -127,14 +102,15 @@ CREATE TABLE public.flag (
127
  CONSTRAINT flag_pkey PRIMARY KEY (id),
128
  CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
129
  );
130
- """.strip()
 
131
 
132
  # --- الاتصال بقاعدة البيانات ---
133
  def get_db_connection():
134
  try:
135
  return psycopg2.connect(SUPABASE_DB_URL)
136
  except Exception as err:
137
- logger.error(f"Database connection error: {err}")
138
  return None
139
 
140
  # --- التحقق من صحة cam_mac ---
@@ -148,50 +124,76 @@ def validate_cam_mac(cam_mac):
148
  cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
149
  return cursor.fetchone() is not None
150
  except Exception as e:
151
- logger.error(f"Validation error: {e}")
152
  return False
153
  finally:
154
  if conn:
155
  conn.close()
156
 
157
- @app.route('/api/query', methods=['POST'])
158
- def handle_query():
159
- if tokenizer is None or model is None:
160
- return jsonify({"error": "النموذج غير محمل، يرجى المحاولة لاحقاً"}), 503
161
-
162
- try:
163
- data = request.get_json()
164
- if not data or 'text' not in data or 'cam_mac' not in data:
165
- return jsonify({"error": "يرجى إرسال 'text' و 'cam_mac'"}), 400
166
 
167
- natural_query = data['text']
168
- cam_mac = data['cam_mac']
169
- logger.info(f"استعلام من {cam_mac}: {natural_query}")
170
-
171
- # التحقق من صحة cam_mac
172
- if not validate_cam_mac(cam_mac):
173
- return jsonify({"error": "عنوان MAC غير صالح"}), 403
 
 
 
 
 
 
 
 
 
174
 
175
- prompt = f"""
176
- ### Postgres SQL table definitions
177
- {DB_SCHEMA}
178
 
179
- ### Rules:
180
- - Always filter by cam_mac = '{cam_mac}'
181
- - Use only SELECT statements
182
- - Use proper JOINs
183
- - Use table aliases when helpful
184
- - The output must contain only the SQL query
185
 
186
- ### User question: {natural_query}
 
 
187
 
188
- ### SQL query:
189
- SELECT
190
- """.strip()
191
 
192
- inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
193
- outputs = model.generate(**inputs, max_length=256)
194
- sql = tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
  # تنظيف الناتج
197
  sql = re.sub(r"^```sql\s*", "", sql, flags=re.IGNORECASE)
@@ -204,54 +206,75 @@ def handle_query():
204
  if not sql.endswith(";"):
205
  sql += ";"
206
 
207
- logger.info(f"استعلام SQL المولد: {sql}")
208
-
209
- if not sql.upper().strip().startswith("SELECT"):
210
- return jsonify({"error": "يُسمح فقط باستعلامات SELECT"}), 403
211
-
212
- conn = get_db_connection()
213
- if not conn:
214
- return jsonify({"error": "فشل الاتصال بقاعدة البيانات"}), 500
215
-
216
- cursor = None
217
- try:
218
- cursor = conn.cursor()
219
- cursor.execute(sql)
220
- columns = [desc[0] for desc in cursor.description]
221
- rows = cursor.fetchall()
222
- data = [dict(zip(columns, row)) for row in rows]
223
-
224
- response_data = {
225
- "data": data,
226
- }
227
-
228
- response_json = json.dumps(response_data, ensure_ascii=False)
229
-
230
- return Response(
231
- response_json,
232
- status=200,
233
- mimetype='application/json; charset=utf-8'
234
- )
235
-
236
- except Exception as e:
237
- logger.error(f"خطأ في تنفيذ SQL: {e}")
238
- return jsonify({"error": str(e), "generated_sql": sql}), 500
239
- finally:
240
- if cursor:
241
- cursor.close()
242
- if conn:
243
- conn.close()
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  except Exception as e:
246
- logger.error(f"خطأ في التوليد: {str(e)}")
247
- return jsonify({"error": "فشل في توليد الاستعلام"}), 500
 
 
 
 
 
248
 
249
  @app.route('/')
250
  def home():
251
  return """
252
- <h1>Text2SQL API</h1>
253
  <p>Use <code>/api/query</code> with POST {"text": "your question", "cam_mac": "device_mac_address"}.</p>
254
  """
255
 
256
  if __name__ == '__main__':
257
- app.run(host='0.0.0.0', port=7860)
 
1
  import os
2
  import re
 
 
 
 
 
3
  import psycopg2
4
+ from flask import Flask, request, jsonify
5
+ import google.generativeai as genai
6
+ from flask import Response
7
  import json
8
 
9
+ # --- إعدادات Flask ---
10
  app = Flask(__name__)
 
11
 
12
+ # --- إعدادات Gemini ---
13
+ GEMINI_API_KEY = "AIzaSyCWukRy76nPgkrMflCTWh_s4gEU--wSVr8" # يفضل استخدام متغيرات البيئة
14
+ genai.configure(api_key=GEMINI_API_KEY)
15
+ model = genai.GenerativeModel('gemini-2.0-flash')
16
 
17
+ # --- إعدادات Supabase ---
 
18
  SUPABASE_DB_URL = "postgresql://postgres.mougnkvoyyhcuxeeqvmh:Xf5E0DhUvKEHEAqq@aws-0-eu-central-1.pooler.supabase.com:6543/postgres"
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # --- سكيمة قاعدة البيانات ---
21
  DB_SCHEMA = """
22
  CREATE TABLE public.profiles (
 
102
  CONSTRAINT flag_pkey PRIMARY KEY (id),
103
  CONSTRAINT flag_user_mac_address_fkey FOREIGN KEY (user_mac_address) REFERENCES public.profiles(cam_mac)
104
  );
105
+
106
+ """
107
 
108
  # --- الاتصال بقاعدة البيانات ---
109
  def get_db_connection():
110
  try:
111
  return psycopg2.connect(SUPABASE_DB_URL)
112
  except Exception as err:
113
+ print(f"Database connection error: {err}")
114
  return None
115
 
116
  # --- التحقق من صحة cam_mac ---
 
124
  cursor.execute("SELECT 1 FROM profiles WHERE cam_mac = %s;", (cam_mac,))
125
  return cursor.fetchone() is not None
126
  except Exception as e:
127
+ print(f"Validation error: {e}")
128
  return False
129
  finally:
130
  if conn:
131
  conn.close()
132
 
133
+ # --- توليد SQL باستخدام Gemini مع تخصيص حسب cam_mac ---
134
+ def generate_sql_gemini(natural_language_query, cam_mac):
135
+ prompt = f"""YYou are a PostgreSQL expert.
136
+ Your job is to convert a natural language query into a SQL SELECT statement, based on the following database schema.
 
 
 
 
 
137
 
138
+ The query **must always be filtered by the camera MAC address: '{cam_mac}'**, using the appropriate field.
139
+
140
+ Schema:
141
+ {DB_SCHEMA}
142
+
143
+ Schema Description:
144
+
145
+ 1. **profiles**
146
+ - Represents users/devices.
147
+ - cam_mac (TEXT, UNIQUE) is the MAC address of the camera device.
148
+ - Linked to most tables using cam_mac.
149
+
150
+ 2. **data**
151
+ - Stores captured image info (image_url, caption, created_at, etc.).
152
+ - Linked via cam_mac and user_place_id.
153
+ - To find places, JOIN with `user_place` → `place`.
154
 
155
+ 3. **biodata**
156
+ - Contains sensor readings (acceleration, gyro, temp).
157
+ - Linked via mac_address to profiles.cam_mac.
158
 
159
+ 4. **notification**
160
+ - Stores alerts/messages for the user.
161
+ - Linked via user_cam_mac to profiles.cam_mac.
 
 
 
162
 
163
+ 5. **flag**
164
+ - Represents boolean flags (e.g. status).
165
+ - Linked via user_mac_address to profiles.cam_mac.
166
 
167
+ 6. **user_place**
168
+ - Connects a user_cam_mac to a place_id.
169
+ - JOIN with `place` to get the name.
170
 
171
+ 7. **place**
172
+ - List of place names.
173
+
174
+ Rules:
175
+ - If the question is about number of visits, frequency, or attendance to a specific place, use the `data` table.
176
+ - Use **only SELECT** statements.
177
+ - Use only the provided schema.
178
+ - Use **camel_mac** filter in WHERE clause.
179
+ - Use proper JOINs (no subqueries unless necessary).
180
+ - Always match table relationships correctly:
181
+ data.user_place_id = user_place.id
182
+ user_place.place_id = place.id
183
+ user_place.user_cam_mac = profiles.cam_mac
184
+ - Use table aliases (like d, p, up, pl) when helpful.
185
+ - The output must contain only the SQL query, no comments or explanations.
186
+ - Add a semicolon at the end.
187
+
188
+
189
+ Question: "{natural_language_query}"
190
+
191
+
192
+ SQL:"""
193
+
194
+ try:
195
+ response = model.generate_content(prompt)
196
+ sql = response.text.strip()
197
 
198
  # تنظيف الناتج
199
  sql = re.sub(r"^```sql\s*", "", sql, flags=re.IGNORECASE)
 
206
  if not sql.endswith(";"):
207
  sql += ";"
208
 
209
+ return sql
210
+ except Exception as e:
211
+ print(f"Gemini error: {e}")
212
+ return None
213
+
214
+ # --- نقطة النهاية الرئيسية ---
215
+ @app.route('/api/query', methods=['POST'])
216
+ def handle_query():
217
+ data = request.get_json()
218
+ if not data or 'text' not in data or 'cam_mac' not in data:
219
+ return jsonify({"error": "Please send 'text' and 'cam_mac' in the request body"}), 400
220
+
221
+ natural_query = data['text']
222
+ cam_mac = data['cam_mac']
223
+ print(f"Natural query from {cam_mac}: {natural_query}")
224
+
225
+ # التحقق من صحة cam_mac
226
+ if not validate_cam_mac(cam_mac):
227
+ return jsonify({"error": "Invalid cam_mac address"}), 403
228
+
229
+ sql_query = generate_sql_gemini(natural_query, cam_mac)
230
+
231
+ if not sql_query:
232
+ return jsonify({"error": "Failed to generate SQL query"}), 500
233
+
234
+ print(f"Generated SQL: {sql_query}")
235
+
236
+ if not sql_query.upper().strip().startswith("SELECT"):
237
+ return jsonify({"error": "Only SELECT queries are allowed"}), 403
 
 
 
 
 
 
 
 
238
 
239
+ conn = get_db_connection()
240
+ if not conn:
241
+ return jsonify({"error": "Database connection failed"}), 500
242
+
243
+ cursor = None
244
+ try:
245
+ cursor = conn.cursor()
246
+ cursor.execute(sql_query)
247
+ columns = [desc[0] for desc in cursor.description]
248
+ rows = cursor.fetchall()
249
+ data = [dict(zip(columns, row)) for row in rows]
250
+
251
+ response_data = {
252
+ "data": data,
253
+ }
254
+
255
+ response_json = json.dumps(response_data, ensure_ascii=False)
256
+
257
+ return Response(
258
+ response_json,
259
+ status=200,
260
+ mimetype='application/json; charset=utf-8'
261
+ )
262
+
263
  except Exception as e:
264
+ print(f"SQL execution error: {e}")
265
+ return jsonify({"error": str(e), "generated_sql": sql_query}), 500
266
+ finally:
267
+ if cursor:
268
+ cursor.close()
269
+ if conn:
270
+ conn.close()
271
 
272
  @app.route('/')
273
  def home():
274
  return """
275
+ <h1>Natural Language to SQL API (Gemini)</h1>
276
  <p>Use <code>/api/query</code> with POST {"text": "your question", "cam_mac": "device_mac_address"}.</p>
277
  """
278
 
279
  if __name__ == '__main__':
280
+ app.run(host='0.0.0.0', port=7860)
requirements.txt CHANGED
@@ -1,5 +1,4 @@
1
  flask==2.3.2
2
- flask-cors==3.0.10
3
- torch==2.0.1
4
- transformers==4.30.2
5
- psycopg2-binary==2.9.7
 
1
  flask==2.3.2
2
+ psycopg2-binary==2.9.7
3
+ google-generativeai==0.3.2
4
+ python-dotenv==1.0.0