Jan2000 commited on
Commit
46e3a36
·
unverified ·
1 Parent(s): a02ab71

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -91
app.py CHANGED
@@ -7,11 +7,11 @@ import threading
7
  import base64
8
  import io
9
  import time
10
- from flask import Flask, render_template, request, Response, stream_with_context
11
  import requests
12
  import docx
13
 
14
- # ================== بخش تنظیمات لاگ‌نویسی ==================
15
 
16
  class NoGrpcFilter(logging.Filter):
17
  def filter(self, record):
@@ -36,7 +36,7 @@ def setup_logging():
36
  setup_logging()
37
  app = Flask(__name__)
38
 
39
- # ================== بخش پیکربندی Gemini ==================
40
 
41
  GEMINI_MODEL_NAME = "gemini-2.5-flash"
42
  ALL_KEYS_STR = os.environ.get("ALL_GEMINI_API_KEYS", "")
@@ -52,7 +52,7 @@ def get_next_key_with_index():
52
  global key_index_counter
53
  with key_lock:
54
  if not GEMINI_API_KEYS:
55
- return None, -1
56
  current_index = key_index_counter
57
  key = GEMINI_API_KEYS[current_index]
58
  key_index_counter = (key_index_counter + 1) % len(GEMINI_API_KEYS)
@@ -66,17 +66,16 @@ def index():
66
 
67
  @app.route('/chat', methods=['POST'])
68
  def chat():
69
- # اگر کلیدی تعریف نشده باشد، به جای ارور، یک پیام متنی معمولی میفرستیم
70
  if not GEMINI_API_KEYS:
71
- fake_response = {"choices": [{"delta": {"content": "⚠️ تنظیمات سرور کامل نیست (کلید API یافت نشد)."}}]}
72
- return Response(f"data: {json.dumps(fake_response)}\n\n", mimetype='text/event-stream')
73
 
74
  data = request.json
75
  system_instruction = "تو چت بات هوش مصنوعی آلفا هستی و توسط برنامه هوش مصنوعی آلفا توسعه داده شدی. کمی با کاربران باحال و دوستانه صحبت کن و از ایموجی‌ها استفاده کن. همیشه پاسخ‌هایت را به زبان فارسی و یا هر زبانی که کاربر صحبت میکنه ارائه بده."
76
 
77
  show_thoughts = data.get("show_thoughts", False)
78
 
79
- # === بخش پردازش پیام‌ها و فایل DOCX (بدون تغییر) ===
80
  gemini_messages = []
81
  for msg in data.get("messages", []):
82
  role = "model" if msg.get("role") == "assistant" else msg.get("role")
@@ -116,23 +115,15 @@ def chat():
116
  if not any(msg['role'] == 'user' for msg in gemini_messages):
117
  return Response("data: [DONE]\n\n", mimetype='text/event-stream')
118
 
119
- # === تابع اصلی استریم با مکانیزم تلاش مجدد مخفی ===
120
- @stream_with_context
121
  def stream_response():
122
- # تعداد کلیدها را میگیریم
123
- num_keys = len(GEMINI_API_KEYS)
124
- # تعداد تلاش‌ها: حداقل 3 برابر تعداد کلیدها تلاش میکنیم تا مطمئن شویم
125
- max_attempts = max(num_keys * 3, 5)
126
 
127
- success_flag = False
128
-
129
  for attempt in range(max_attempts):
130
  try:
131
  api_key, key_index = get_next_key_with_index()
132
- # اگر کلیدی نبود (خیلی بعید)
133
- if not api_key: break
134
-
135
- logging.info(f"تلاش شماره {attempt + 1} با کلید ایندکس {key_index}...")
136
 
137
  api_endpoint = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL_NAME}:streamGenerateContent?key={api_key}&alt=sse"
138
 
@@ -150,83 +141,56 @@ def chat():
150
  "include_thoughts": True
151
  }
152
 
153
- # *** تنظیمات Timeout بسیار مهم ***
154
- # Connect (5): زمان وصل شدن به گوگل. اگر 5 ثانیه طول کشید یعنی شبکه خرابه یا کلید گیر کرده -> سریع قطع کن برو بعدی
155
- # Read (120): زمان انتظار برای جواب. چون فایل داری، باید زیاد باشه (120 ثانیه) تا وسط پردازش قطع نکنه.
156
- response = requests.post(
157
- api_endpoint,
158
- json=payload,
159
- stream=True,
160
- timeout=(5, 120)
161
- )
162
-
163
- # اگر کد وضعیت 200 نبود (یعنی 429، 500، 403 و...)
164
- if response.status_code != 200:
165
- logging.warning(f"کلید {key_index} پاسخ نداد (کد {response.status_code}). تلاش بعدی...")
166
- response.close()
167
- continue # بدون هیچ حرفی برو سراغ کلید بعدی (کاربر چیزی نمیفهمه)
168
-
169
- # اگر کد 200 بود، حالا چک میکنیم استریم دیتا داره یا نه
170
- line_iterator = response.iter_lines()
171
-
172
- # یک متغیر برای اینکه بفهمیم آیا واقعا دیتایی فرستادیم یا نه
173
- data_sent_in_this_attempt = False
174
-
175
- for line in line_iterator:
176
- if line:
177
- decoded_line = line.decode('utf-8')
178
- if decoded_line.startswith('data: '):
179
- try:
180
- chunk_data = json.loads(decoded_line[6:])
181
- parts = chunk_data.get("candidates", [{}])[0].get("content", {}).get("parts", [])
182
-
183
- for part in parts:
184
- if "text" not in part or not part["text"]:
185
- continue
186
 
187
- # به محض اینکه اولین داده سالم رسید، یعنی موفق شدیم
188
- data_sent_in_this_attempt = True
189
- success_flag = True
190
-
191
- is_a_thought = part.get("thought") is True
192
- if show_thoughts and is_a_thought:
193
- thought_payload = {"type": "thought", "content": part["text"]}
194
- yield f"data: {json.dumps(thought_payload)}\n\n"
195
- elif not is_a_thought:
196
- sse_payload = {"choices": [{"delta": {"content": part["text"]}}]}
197
- yield f"data: {json.dumps(sse_payload)}\n\n"
198
-
199
- except (json.JSONDecodeError, IndexError, KeyError):
200
- continue
201
-
202
- # اگر حلقه تمام شد و ما دیتایی فرستاده بودیم، یعنی کار تمام است و موفق بودیم
203
- if success_flag:
204
- return # خروج کامل
205
 
206
- # مدیریت تمام خطاها (Timeout, ConnectionError, ...)
207
  except Exception as e:
208
- logging.error(f"خطای داخلی در تلاش {attempt+1}: {e}")
209
- # اینجا یک وقفه کوتاه 0.5 ثانیه‌ای میدهیم که CPU درگیر نشود و سریع میریم کلید بعدی
210
- time.sleep(0.5)
211
- continue # برو تلاش بعدی (بدون اینکه به کاربر ارور بدی)
212
-
213
- # === بخش نهایی (فقط اگر همه تلاش‌ها شکست خورد) ===
214
- # اگر بعد از مثلا 30 بار تلاش (بسته به تعداد کلید) هیچکدام کار نکرد:
215
- # به جای ارسال Error Payload که باعث نمایش خطای قرمز میشه،
216
- # یک پیام متنی معمولی از طرف ربات میفرستیم.
217
- if not success_flag:
218
- logging.critical("تمام کلیدها ناموفق بودند.")
219
- fallback_message = "متاسفانه شبکه من کمی کند شده و نتوانستم پاسخ را دریافت کنم. لطفاً دوباره دکمه ارسال را بزنید 🔄"
220
- # فرمت پیام دقیقا مثل پاسخ عادی ربات است
221
- fallback_payload = {"choices": [{"delta": {"content": fallback_message}}]}
222
- yield f"data: {json.dumps(fallback_payload)}\n\n"
223
-
224
- # استفاده از stream_with_context برای حفظ کانتکست درخواست در طول حلقه طولانی
225
  return Response(stream_response(), mimetype='text/event-stream')
226
 
227
  if __name__ == '__main__':
228
- if GEMINI_API_KEYS:
229
- logging.info(f"سیستم در حالت توسعه شروع به کار کرد. تعداد {len(GEMINI_API_KEYS)} کلید شناسایی شد.")
230
  app.run(debug=True, host='0.0.0.0', port=os.environ.get("PORT", 7860))
231
 
232
  # --- END OF FILE app.py ---
 
7
  import base64
8
  import io
9
  import time
10
+ from flask import Flask, render_template, request, Response
11
  import requests
12
  import docx
13
 
14
+ # ================== بخش تنظیمات لاگ‌نویسی (بدون تغییر) ==================
15
 
16
  class NoGrpcFilter(logging.Filter):
17
  def filter(self, record):
 
36
  setup_logging()
37
  app = Flask(__name__)
38
 
39
+ # ================== بخش پیکربندی Gemini (با تغییر) ==================
40
 
41
  GEMINI_MODEL_NAME = "gemini-2.5-flash"
42
  ALL_KEYS_STR = os.environ.get("ALL_GEMINI_API_KEYS", "")
 
52
  global key_index_counter
53
  with key_lock:
54
  if not GEMINI_API_KEYS:
55
+ raise ValueError("لیست کلیدهای API خالی است.")
56
  current_index = key_index_counter
57
  key = GEMINI_API_KEYS[current_index]
58
  key_index_counter = (key_index_counter + 1) % len(GEMINI_API_KEYS)
 
66
 
67
  @app.route('/chat', methods=['POST'])
68
  def chat():
 
69
  if not GEMINI_API_KEYS:
70
+ # اگر هیچ کلیدی نباشد چاره‌ای جز خطا نیست، اما این حالت نادری است
71
+ return Response("data: [DONE]\n\n", mimetype='text/event-stream')
72
 
73
  data = request.json
74
  system_instruction = "تو چت بات هوش مصنوعی آلفا هستی و توسط برنامه هوش مصنوعی آلفا توسعه داده شدی. کمی با کاربران باحال و دوستانه صحبت کن و از ایموجی‌ها استفاده کن. همیشه پاسخ‌هایت را به زبان فارسی و یا هر زبانی که کاربر صحبت میکنه ارائه بده."
75
 
76
  show_thoughts = data.get("show_thoughts", False)
77
 
78
+ # بخش پردازش پیام‌ها و فایل DOCX
79
  gemini_messages = []
80
  for msg in data.get("messages", []):
81
  role = "model" if msg.get("role") == "assistant" else msg.get("role")
 
115
  if not any(msg['role'] == 'user' for msg in gemini_messages):
116
  return Response("data: [DONE]\n\n", mimetype='text/event-stream')
117
 
 
 
118
  def stream_response():
119
+ # تلاش برای تعداد زیادی بار (عملا تا وقتی یک کلید سالم پیدا شود)
120
+ # ضرب در 2 یعنی هر کلید را دو بار شانس می دهیم اگر شبکه قطع و وصل شد
121
+ max_attempts = len(GEMINI_API_KEYS) * 3
 
122
 
 
 
123
  for attempt in range(max_attempts):
124
  try:
125
  api_key, key_index = get_next_key_with_index()
126
+ # logging.info(f"تلاش با کلید شماره {key_index + 1}...")
 
 
 
127
 
128
  api_endpoint = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL_NAME}:streamGenerateContent?key={api_key}&alt=sse"
129
 
 
141
  "include_thoughts": True
142
  }
143
 
144
+ # *** نکته مهم: Timeout بسیار کوتاه برای اتصال، تا سریع برود کلید بعدی ***
145
+ # connect=4: اگر 4 ثانیه وصل نشد، ولش کن برو بعدی
146
+ # read=20: اگر وسط کار 20 ثانیه دیتا نیامد، ولش کن برو بعدی
147
+ with requests.post(api_endpoint, json=payload, stream=True, timeout=(4, 20)) as response:
148
+
149
+ # اگر ارور 429 (محدودیت) یا هر ارور دیگری داد، سریع برو بعدی
150
+ if response.status_code != 200:
151
+ logging.warning(f"کلید {key_index + 1} با وضعیت {response.status_code} پاسخ نداد. تلاش با کلید بعدی...")
152
+ continue
153
+
154
+ # اگر موفق شد، شروع به ارسال کن و از تابع خارج شو (return)
155
+ for line in response.iter_lines():
156
+ if line:
157
+ decoded_line = line.decode('utf-8')
158
+ if decoded_line.startswith('data: '):
159
+ try:
160
+ chunk_data = json.loads(decoded_line[6:])
161
+ parts = chunk_data.get("candidates", [{}])[0].get("content", {}).get("parts", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
+ for part in parts:
164
+ if "text" not in part or not part["text"]:
165
+ continue
166
+ is_a_thought = part.get("thought") is True
167
+ if show_thoughts and is_a_thought:
168
+ thought_payload = {"type": "thought", "content": part["text"]}
169
+ yield f"data: {json.dumps(thought_payload)}\n\n"
170
+ elif not is_a_thought:
171
+ sse_payload = {"choices": [{"delta": {"content": part["text"]}}]}
172
+ yield f"data: {json.dumps(sse_payload)}\n\n"
173
+ except (json.JSONDecodeError, IndexError, KeyError):
174
+ continue
175
+
176
+ # اگر استریم بدون خطا تمام شد، کار تمام است
177
+ return
 
 
 
178
 
 
179
  except Exception as e:
180
+ # هر نوع خطایی (تایم اوت، شبکه و ...) رخ داد، فقط لاگ کن و برو کلید بعدی
181
+ # هیچ خطایی به کاربر نشان نده
182
+ logging.warning(f"خطا در کلید {key_index + 1}: {e} - رفتن به کلید بعدی")
183
+ continue
184
+
185
+ # اگر همه کلیدها تست شدند و نشد (خیلی بعید)، یک پیام خالی بفرست که ارور نده
186
+ logging.critical("هیچ کلیدی کار نکرد.")
187
+ # به جای ارور، یک پیام ساده میفرستیم که برنامه کرش نکند
188
+ end_payload = {"choices": [{"delta": {"content": "..."}}]}
189
+ yield f"data: {json.dumps(end_payload)}\n\n"
190
+
 
 
 
 
 
 
191
  return Response(stream_response(), mimetype='text/event-stream')
192
 
193
  if __name__ == '__main__':
 
 
194
  app.run(debug=True, host='0.0.0.0', port=os.environ.get("PORT", 7860))
195
 
196
  # --- END OF FILE app.py ---