konodioda6669 commited on
Commit
9da2e88
·
verified ·
1 Parent(s): 483c209

Upload 4 files

Browse files
Files changed (4) hide show
  1. .gitattributes +35 -0
  2. Dockerfile +12 -0
  3. README.md +11 -0
  4. app.py +1064 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN pip install --no-cache-dir flask requests curl_cffi werkzeug loguru
6
+
7
+ COPY . .
8
+
9
+ ENV PORT=5200
10
+ EXPOSE 5200
11
+
12
+ CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: GrokPythonTo2
3
+ emoji: 🏢
4
+ colorFrom: indigo
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 5200
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,1064 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import time
5
+ import base64
6
+ import sys
7
+ import inspect
8
+ from loguru import logger
9
+
10
+ import requests
11
+ from flask import Flask, request, Response, jsonify, stream_with_context
12
+ from curl_cffi import requests as curl_requests
13
+ from werkzeug.middleware.proxy_fix import ProxyFix
14
+
15
+
16
+ class Logger:
17
+ def __init__(self, level="INFO", colorize=True, format=None):
18
+ logger.remove()
19
+
20
+ if format is None:
21
+ format = (
22
+ "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
23
+ "<level>{level: <8}</level> | "
24
+ "<cyan>{extra[filename]}</cyan>:<cyan>{extra[function]}</cyan>:<cyan>{extra[lineno]}</cyan> | "
25
+ "<level>{message}</level>"
26
+ )
27
+
28
+ logger.add(
29
+ sys.stderr,
30
+ level=level,
31
+ format=format,
32
+ colorize=colorize,
33
+ backtrace=True,
34
+ diagnose=True
35
+ )
36
+
37
+ self.logger = logger
38
+
39
+ def _get_caller_info(self):
40
+ frame = inspect.currentframe()
41
+ try:
42
+ caller_frame = frame.f_back.f_back
43
+ full_path = caller_frame.f_code.co_filename
44
+ function = caller_frame.f_code.co_name
45
+ lineno = caller_frame.f_lineno
46
+
47
+ filename = os.path.basename(full_path)
48
+
49
+ return {
50
+ 'filename': filename,
51
+ 'function': function,
52
+ 'lineno': lineno
53
+ }
54
+ finally:
55
+ del frame
56
+
57
+ def info(self, message, source="API"):
58
+ caller_info = self._get_caller_info()
59
+ self.logger.bind(**caller_info).info(f"[{source}] {message}")
60
+
61
+ def error(self, message, source="API"):
62
+ caller_info = self._get_caller_info()
63
+
64
+ if isinstance(message, Exception):
65
+ self.logger.bind(**caller_info).exception(f"[{source}] {str(message)}")
66
+ else:
67
+ self.logger.bind(**caller_info).error(f"[{source}] {message}")
68
+
69
+ def warning(self, message, source="API"):
70
+ caller_info = self._get_caller_info()
71
+ self.logger.bind(**caller_info).warning(f"[{source}] {message}")
72
+
73
+ def debug(self, message, source="API"):
74
+ caller_info = self._get_caller_info()
75
+ self.logger.bind(**caller_info).debug(f"[{source}] {message}")
76
+
77
+ async def request_logger(self, request):
78
+ caller_info = self._get_caller_info()
79
+ self.logger.bind(**caller_info).info(f"请求: {request.method} {request.path}", "Request")
80
+
81
+ logger = Logger(level="INFO")
82
+
83
+
84
+ CONFIG = {
85
+ "MODELS": {
86
+ 'grok-2': 'grok-latest',
87
+ 'grok-2-imageGen': 'grok-latest',
88
+ 'grok-2-search': 'grok-latest',
89
+ "grok-3": "grok-3",
90
+ "grok-3-search": "grok-3",
91
+ "grok-3-imageGen": "grok-3",
92
+ "grok-3-deepsearch": "grok-3",
93
+ "grok-3-reasoning": "grok-3"
94
+ },
95
+ "API": {
96
+ "IS_CUSTOM_SSO": os.environ.get("IS_CUSTOM_SSO", "false").lower() == "true",
97
+ "BASE_URL": "https://grok.com",
98
+ "API_KEY": os.environ.get("API_KEY", "sk-123456"),
99
+ "SIGNATURE_COOKIE": None,
100
+ "PICGO_KEY": os.environ.get("PICGO_KEY") or None,
101
+ "TUMY_KEY": os.environ.get("TUMY_KEY") or None,
102
+ "RETRY_TIME": 1000,
103
+ "PROXY": os.environ.get("PROXY") or None
104
+ },
105
+ "SERVER": {
106
+ "PORT": int(os.environ.get("PORT", 5200))
107
+ },
108
+ "RETRY": {
109
+ "MAX_ATTEMPTS": 2
110
+ },
111
+ "SHOW_THINKING": os.environ.get("SHOW_THINKING") == "true",
112
+ "IS_THINKING": False,
113
+ "IS_IMG_GEN": False,
114
+ "IS_IMG_GEN2": False,
115
+ "ISSHOW_SEARCH_RESULTS": os.environ.get("ISSHOW_SEARCH_RESULTS", "true").lower() == "true"
116
+ }
117
+
118
+
119
+ DEFAULT_HEADERS = {
120
+ 'Accept': '*/*',
121
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
122
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
123
+ 'Content-Type': 'text/plain;charset=UTF-8',
124
+ 'Connection': 'keep-alive',
125
+ 'Origin': 'https://grok.com',
126
+ 'Priority': 'u=1, i',
127
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
128
+ 'Sec-Ch-Ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
129
+ 'Sec-Ch-Ua-Mobile': '?0',
130
+ 'Sec-Ch-Ua-Platform': '"macOS"',
131
+ 'Sec-Fetch-Dest': 'empty',
132
+ 'Sec-Fetch-Mode': 'cors',
133
+ 'Sec-Fetch-Site': 'same-origin',
134
+ 'Baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
135
+ }
136
+
137
+ class AuthTokenManager:
138
+ def __init__(self):
139
+ self.token_model_map = {}
140
+ self.expired_tokens = set()
141
+ self.token_status_map = {}
142
+
143
+ self.model_config = {
144
+ "grok-2": {
145
+ "RequestFrequency": 30,
146
+ "ExpirationTime": 1 * 60 * 60 * 1000 # 1小时
147
+ },
148
+ "grok-3": {
149
+ "RequestFrequency": 20,
150
+ "ExpirationTime": 2 * 60 * 60 * 1000 # 2小时
151
+ },
152
+ "grok-3-deepsearch": {
153
+ "RequestFrequency": 10,
154
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
155
+ },
156
+ "grok-3-reasoning": {
157
+ "RequestFrequency": 10,
158
+ "ExpirationTime": 24 * 60 * 60 * 1000 # 24小时
159
+ }
160
+ }
161
+ self.token_reset_switch = False
162
+ self.token_reset_timer = None
163
+
164
+ def add_token(self, token):
165
+ sso = token.split("sso=")[1].split(";")[0]
166
+ for model in self.model_config.keys():
167
+ if model not in self.token_model_map:
168
+ self.token_model_map[model] = []
169
+ if sso not in self.token_status_map:
170
+ self.token_status_map[sso] = {}
171
+
172
+ existing_token_entry = next((entry for entry in self.token_model_map[model] if entry["token"] == token), None)
173
+
174
+ if not existing_token_entry:
175
+ self.token_model_map[model].append({
176
+ "token": token,
177
+ "RequestCount": 0,
178
+ "AddedTime": int(time.time() * 1000),
179
+ "StartCallTime": None
180
+ })
181
+
182
+ if model not in self.token_status_map[sso]:
183
+ self.token_status_map[sso][model] = {
184
+ "isValid": True,
185
+ "invalidatedTime": None,
186
+ "totalRequestCount": 0
187
+ }
188
+
189
+ def set_token(self, token):
190
+ models = list(self.model_config.keys())
191
+ self.token_model_map = {model: [{
192
+ "token": token,
193
+ "RequestCount": 0,
194
+ "AddedTime": int(time.time() * 1000),
195
+ "StartCallTime": None
196
+ }] for model in models}
197
+
198
+ sso = token.split("sso=")[1].split(";")[0]
199
+ self.token_status_map[sso] = {model: {
200
+ "isValid": True,
201
+ "invalidatedTime": None,
202
+ "totalRequestCount": 0
203
+ } for model in models}
204
+
205
+ def delete_token(self, token):
206
+ try:
207
+ sso = token.split("sso=")[1].split(";")[0]
208
+ for model in self.token_model_map:
209
+ self.token_model_map[model] = [entry for entry in self.token_model_map[model] if entry["token"] != token]
210
+
211
+ if sso in self.token_status_map:
212
+ del self.token_status_map[sso]
213
+
214
+ logger.info(f"令牌已成功移除: {token}", "TokenManager")
215
+ return True
216
+ except Exception as error:
217
+ logger.error(f"令牌删除失败: {str(error)}")
218
+ return False
219
+
220
+ def get_next_token_for_model(self, model_id):
221
+ normalized_model = self.normalize_model_name(model_id)
222
+
223
+ if normalized_model not in self.token_model_map or not self.token_model_map[normalized_model]:
224
+ return None
225
+
226
+ token_entry = self.token_model_map[normalized_model][0]
227
+
228
+ if token_entry:
229
+ if token_entry["StartCallTime"] is None:
230
+ token_entry["StartCallTime"] = int(time.time() * 1000)
231
+
232
+ if not self.token_reset_switch:
233
+ self.start_token_reset_process()
234
+ self.token_reset_switch = True
235
+
236
+ token_entry["RequestCount"] += 1
237
+
238
+ if token_entry["RequestCount"] > self.model_config[normalized_model]["RequestFrequency"]:
239
+ self.remove_token_from_model(normalized_model, token_entry["token"])
240
+ next_token_entry = self.token_model_map[normalized_model][0] if self.token_model_map[normalized_model] else None
241
+ return next_token_entry["token"] if next_token_entry else None
242
+
243
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
244
+ if sso in self.token_status_map and normalized_model in self.token_status_map[sso]:
245
+ if token_entry["RequestCount"] == self.model_config[normalized_model]["RequestFrequency"]:
246
+ self.token_status_map[sso][normalized_model]["isValid"] = False
247
+ self.token_status_map[sso][normalized_model]["invalidatedTime"] = int(time.time() * 1000)
248
+ self.token_status_map[sso][normalized_model]["totalRequestCount"] += 1
249
+
250
+ return token_entry["token"]
251
+
252
+ return None
253
+
254
+ def remove_token_from_model(self, model_id, token):
255
+ normalized_model = self.normalize_model_name(model_id)
256
+
257
+ if normalized_model not in self.token_model_map:
258
+ logger.error(f"模型 {normalized_model} 不存在", "TokenManager")
259
+ return False
260
+
261
+ model_tokens = self.token_model_map[normalized_model]
262
+ token_index = next((i for i, entry in enumerate(model_tokens) if entry["token"] == token), -1)
263
+
264
+ if token_index != -1:
265
+ removed_token_entry = model_tokens.pop(token_index)
266
+ self.expired_tokens.add((
267
+ removed_token_entry["token"],
268
+ normalized_model,
269
+ int(time.time() * 1000)
270
+ ))
271
+
272
+ if not self.token_reset_switch:
273
+ self.start_token_reset_process()
274
+ self.token_reset_switch = True
275
+
276
+ logger.info(f"模型{model_id}的令牌已失效,已成功移除���牌: {token}", "TokenManager")
277
+ return True
278
+
279
+ logger.error(f"在模型 {normalized_model} 中未找到 token: {token}", "TokenManager")
280
+ return False
281
+
282
+ def get_expired_tokens(self):
283
+ return list(self.expired_tokens)
284
+
285
+ def normalize_model_name(self, model):
286
+ if model.startswith('grok-') and 'deepsearch' not in model and 'reasoning' not in model:
287
+ return '-'.join(model.split('-')[:2])
288
+ return model
289
+
290
+ def get_token_count_for_model(self, model_id):
291
+ normalized_model = self.normalize_model_name(model_id)
292
+ return len(self.token_model_map.get(normalized_model, []))
293
+
294
+ def get_remaining_token_request_capacity(self):
295
+ remaining_capacity_map = {}
296
+
297
+ for model in self.model_config.keys():
298
+ model_tokens = self.token_model_map.get(model, [])
299
+ model_request_frequency = self.model_config[model]["RequestFrequency"]
300
+
301
+ total_used_requests = sum(token_entry.get("RequestCount", 0) for token_entry in model_tokens)
302
+
303
+ remaining_capacity = (len(model_tokens) * model_request_frequency) - total_used_requests
304
+ remaining_capacity_map[model] = max(0, remaining_capacity)
305
+
306
+ return remaining_capacity_map
307
+
308
+ def get_token_array_for_model(self, model_id):
309
+ normalized_model = self.normalize_model_name(model_id)
310
+ return self.token_model_map.get(normalized_model, [])
311
+
312
+ def start_token_reset_process(self):
313
+ def reset_expired_tokens():
314
+ now = int(time.time() * 1000)
315
+
316
+ tokens_to_remove = set()
317
+ for token_info in self.expired_tokens:
318
+ token, model, expired_time = token_info
319
+ expiration_time = self.model_config[model]["ExpirationTime"]
320
+
321
+ if now - expired_time >= expiration_time:
322
+ if not any(entry["token"] == token for entry in self.token_model_map.get(model, [])):
323
+ if model not in self.token_model_map:
324
+ self.token_model_map[model] = []
325
+
326
+ self.token_model_map[model].append({
327
+ "token": token,
328
+ "RequestCount": 0,
329
+ "AddedTime": now,
330
+ "StartCallTime": None
331
+ })
332
+
333
+ sso = token.split("sso=")[1].split(";")[0]
334
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
335
+ self.token_status_map[sso][model]["isValid"] = True
336
+ self.token_status_map[sso][model]["invalidatedTime"] = None
337
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
338
+
339
+ tokens_to_remove.add(token_info)
340
+
341
+ self.expired_tokens -= tokens_to_remove
342
+
343
+ for model in self.model_config.keys():
344
+ if model not in self.token_model_map:
345
+ continue
346
+
347
+ for token_entry in self.token_model_map[model]:
348
+ if not token_entry.get("StartCallTime"):
349
+ continue
350
+
351
+ expiration_time = self.model_config[model]["ExpirationTime"]
352
+ if now - token_entry["StartCallTime"] >= expiration_time:
353
+ sso = token_entry["token"].split("sso=")[1].split(";")[0]
354
+ if sso in self.token_status_map and model in self.token_status_map[sso]:
355
+ self.token_status_map[sso][model]["isValid"] = True
356
+ self.token_status_map[sso][model]["invalidatedTime"] = None
357
+ self.token_status_map[sso][model]["totalRequestCount"] = 0
358
+
359
+ token_entry["RequestCount"] = 0
360
+ token_entry["StartCallTime"] = None
361
+
362
+ import threading
363
+ # 启动一个线程执行定时任务,每小时执行一次
364
+ def run_timer():
365
+ while True:
366
+ reset_expired_tokens()
367
+ time.sleep(3600)
368
+
369
+ timer_thread = threading.Thread(target=run_timer)
370
+ timer_thread.daemon = True
371
+ timer_thread.start()
372
+
373
+ def get_all_tokens(self):
374
+ all_tokens = set()
375
+ for model_tokens in self.token_model_map.values():
376
+ for entry in model_tokens:
377
+ all_tokens.add(entry["token"])
378
+ return list(all_tokens)
379
+
380
+ def get_token_status_map(self):
381
+ return self.token_status_map
382
+
383
+ class Utils:
384
+ @staticmethod
385
+ def organize_search_results(search_results):
386
+ if not search_results or 'results' not in search_results:
387
+ return ''
388
+
389
+ results = search_results['results']
390
+ formatted_results = []
391
+
392
+ for index, result in enumerate(results):
393
+ title = result.get('title', '未知标题')
394
+ url = result.get('url', '#')
395
+ preview = result.get('preview', '无预览内容')
396
+
397
+ formatted_result = f"\r\n<details><summary>资料[{index}]: {title}</summary>\r\n{preview}\r\n\n[Link]({url})\r\n</details>"
398
+ formatted_results.append(formatted_result)
399
+
400
+ return '\n\n'.join(formatted_results)
401
+
402
+ @staticmethod
403
+ def create_auth_headers(model):
404
+ return token_manager.get_next_token_for_model(model)
405
+
406
+ @staticmethod
407
+ def get_proxy_options():
408
+ proxy = CONFIG["API"]["PROXY"]
409
+ proxy_options = {}
410
+
411
+ if proxy:
412
+ logger.info(f"使用代理: {proxy}", "Server")
413
+ proxy_options["proxies"] = {"https": proxy, "http": proxy}
414
+
415
+ if proxy.startswith("socks5://"):
416
+ proxy_options["proxies"] = {"https": proxy, "http": proxy}
417
+ proxy_options["proxy_type"] = "socks5"
418
+
419
+ return proxy_options
420
+
421
+ class GrokApiClient:
422
+ def __init__(self, model_id):
423
+ if model_id not in CONFIG["MODELS"]:
424
+ raise ValueError(f"不支持的模型: {model_id}")
425
+ self.model_id = CONFIG["MODELS"][model_id]
426
+
427
+ def process_message_content(self, content):
428
+ if isinstance(content, str):
429
+ return content
430
+ return None
431
+
432
+ def get_image_type(self, base64_string):
433
+ mime_type = 'image/jpeg'
434
+ if 'data:image' in base64_string:
435
+ import re
436
+ matches = re.search(r'data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,', base64_string)
437
+ if matches:
438
+ mime_type = matches.group(1)
439
+
440
+ extension = mime_type.split('/')[1]
441
+ file_name = f"image.{extension}"
442
+
443
+ return {
444
+ "mimeType": mime_type,
445
+ "fileName": file_name
446
+ }
447
+
448
+ def upload_base64_image(self, base64_data, url):
449
+ try:
450
+ if 'data:image' in base64_data:
451
+ image_buffer = base64_data.split(',')[1]
452
+ else:
453
+ image_buffer = base64_data
454
+
455
+ image_info = self.get_image_type(base64_data)
456
+ mime_type = image_info["mimeType"]
457
+ file_name = image_info["fileName"]
458
+
459
+ upload_data = {
460
+ "rpc": "uploadFile",
461
+ "req": {
462
+ "fileName": file_name,
463
+ "fileMimeType": mime_type,
464
+ "content": image_buffer
465
+ }
466
+ }
467
+
468
+ logger.info("发送图片请求", "Server")
469
+
470
+ proxy_options = Utils.get_proxy_options()
471
+ response = curl_requests.post(
472
+ url,
473
+ headers={
474
+ **DEFAULT_HEADERS,
475
+ "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]
476
+ },
477
+ json=upload_data,
478
+ impersonate="chrome133a",
479
+ **proxy_options
480
+ )
481
+
482
+ if response.status_code != 200:
483
+ logger.error(f"上传图片失败,状态码:{response.status_code}", "Server")
484
+ return ''
485
+
486
+ result = response.json()
487
+ logger.info(f"上传图片成功: {result}", "Server")
488
+ return result.get("fileMetadataId", "")
489
+
490
+ except Exception as error:
491
+ logger.error(str(error), "Server")
492
+ return ''
493
+
494
+ def prepare_chat_request(self, request):
495
+ if ((request["model"] == 'grok-2-imageGen' or request["model"] == 'grok-3-imageGen') and
496
+ not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"] and
497
+ request.get("stream", False)):
498
+ raise ValueError("该模型流式输出需要配置PICGO或者TUMY图床密钥!")
499
+
500
+ todo_messages = request["messages"]
501
+ if request["model"] in ['grok-2-imageGen', 'grok-3-imageGen', 'grok-3-deepsearch']:
502
+ last_message = todo_messages[-1]
503
+ if last_message["role"] != 'user':
504
+ raise ValueError('此模型最后一条消息必须是用户消息!')
505
+ todo_messages = [last_message]
506
+
507
+ file_attachments = []
508
+ messages = ''
509
+ last_role = None
510
+ last_content = ''
511
+ search = request["model"] in ['grok-2-search', 'grok-3-search']
512
+
513
+ # 移除<think>标签及其内容和base64图片
514
+ def remove_think_tags(text):
515
+ import re
516
+ text = re.sub(r'<think>[\s\S]*?<\/think>', '', text).strip()
517
+ text = re.sub(r'!\[image\]\(data:.*?base64,.*?\)', '[图片]', text)
518
+ return text
519
+
520
+ def process_content(content):
521
+ if isinstance(content, list):
522
+ text_content = ''
523
+ for item in content:
524
+ if item["type"] == 'image_url':
525
+ text_content += ("[图片]" if not text_content else '\n[图片]')
526
+ elif item["type"] == 'text':
527
+ text_content += (remove_think_tags(item["text"]) if not text_content else '\n' + remove_think_tags(item["text"]))
528
+ return text_content
529
+ elif isinstance(content, dict) and content is not None:
530
+ if content["type"] == 'image_url':
531
+ return "[图片]"
532
+ elif content["type"] == 'text':
533
+ return remove_think_tags(content["text"])
534
+ return remove_think_tags(self.process_message_content(content))
535
+
536
+ for current in todo_messages:
537
+ role = 'assistant' if current["role"] == 'assistant' else 'user'
538
+ is_last_message = current == todo_messages[-1]
539
+
540
+ if is_last_message and "content" in current:
541
+ if isinstance(current["content"], list):
542
+ for item in current["content"]:
543
+ if item["type"] == 'image_url':
544
+ processed_image = self.upload_base64_image(
545
+ item["image_url"]["url"],
546
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
547
+ )
548
+ if processed_image:
549
+ file_attachments.append(processed_image)
550
+ elif isinstance(current["content"], dict) and current["content"].get("type") == 'image_url':
551
+ processed_image = self.upload_base64_image(
552
+ current["content"]["image_url"]["url"],
553
+ f"{CONFIG['API']['BASE_URL']}/api/rpc"
554
+ )
555
+ if processed_image:
556
+ file_attachments.append(processed_image)
557
+
558
+
559
+ text_content = process_content(current.get("content", ""))
560
+
561
+ if text_content or (is_last_message and file_attachments):
562
+ if role == last_role and text_content:
563
+ last_content += '\n' + text_content
564
+ messages = messages[:messages.rindex(f"{role.upper()}: ")] + f"{role.upper()}: {last_content}\n"
565
+ else:
566
+ messages += f"{role.upper()}: {text_content or '[图片]'}\n"
567
+ last_content = text_content
568
+ last_role = role
569
+
570
+ return {
571
+ "temporary": CONFIG["API"].get("IS_TEMP_CONVERSATION", False),
572
+ "modelName": self.model_id,
573
+ "message": messages.strip(),
574
+ "fileAttachments": file_attachments[:4],
575
+ "imageAttachments": [],
576
+ "disableSearch": False,
577
+ "enableImageGeneration": True,
578
+ "returnImageBytes": False,
579
+ "returnRawGrokInXaiRequest": False,
580
+ "enableImageStreaming": False,
581
+ "imageGenerationCount": 1,
582
+ "forceConcise": False,
583
+ "toolOverrides": {
584
+ "imageGen": request["model"] in ['grok-2-imageGen', 'grok-3-imageGen'],
585
+ "webSearch": search,
586
+ "xSearch": search,
587
+ "xMediaSearch": search,
588
+ "trendsSearch": search,
589
+ "xPostAnalyze": search
590
+ },
591
+ "enableSideBySide": True,
592
+ "isPreset": False,
593
+ "sendFinalMetadata": True,
594
+ "customInstructions": "",
595
+ "deepsearchPreset": "default" if request["model"] == 'grok-3-deepsearch' else "",
596
+ "isReasoning": request["model"] == 'grok-3-reasoning'
597
+ }
598
+
599
+ class MessageProcessor:
600
+ @staticmethod
601
+ def create_chat_response(message, model, is_stream=False):
602
+ base_response = {
603
+ "id": f"chatcmpl-{uuid.uuid4()}",
604
+ "created": int(time.time()),
605
+ "model": model
606
+ }
607
+
608
+ if is_stream:
609
+ return {
610
+ **base_response,
611
+ "object": "chat.completion.chunk",
612
+ "choices": [{
613
+ "index": 0,
614
+ "delta": {
615
+ "content": message
616
+ }
617
+ }]
618
+ }
619
+
620
+ return {
621
+ **base_response,
622
+ "object": "chat.completion",
623
+ "choices": [{
624
+ "index": 0,
625
+ "message": {
626
+ "role": "assistant",
627
+ "content": message
628
+ },
629
+ "finish_reason": "stop"
630
+ }],
631
+ "usage": None
632
+ }
633
+
634
+ def process_model_response(response, model):
635
+ result = {"token": None, "imageUrl": None}
636
+
637
+ if CONFIG["IS_IMG_GEN"]:
638
+ if response.get("cachedImageGenerationResponse") and not CONFIG["IS_IMG_GEN2"]:
639
+ result["imageUrl"] = response["cachedImageGenerationResponse"]["imageUrl"]
640
+ return result
641
+
642
+ if model == 'grok-2':
643
+ result["token"] = response.get("token")
644
+ elif model in ['grok-2-search', 'grok-3-search']:
645
+ if response.get("webSearchResults") and CONFIG["ISSHOW_SEARCH_RESULTS"]:
646
+ result["token"] = f"\r\n<think>{Utils.organize_search_results(response['webSearchResults'])}</think>\r\n"
647
+ else:
648
+ result["token"] = response.get("token")
649
+ elif model == 'grok-3':
650
+ result["token"] = response.get("token")
651
+ elif model == 'grok-3-deepsearch':
652
+ if response.get("messageStepId") and not CONFIG["SHOW_THINKING"]:
653
+ return result
654
+ if response.get("messageStepId") and not CONFIG["IS_THINKING"]:
655
+ result["token"] = "<think>" + response.get("token", "")
656
+ CONFIG["IS_THINKING"] = True
657
+ elif not response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "final":
658
+ result["token"] = "</think>" + response.get("token", "")
659
+ CONFIG["IS_THINKING"] = False
660
+ elif (response.get("messageStepId") and CONFIG["IS_THINKING"] and response.get("messageTag") == "assistant") or response.get("messageTag") == "final":
661
+ result["token"] = response.get("token")
662
+ elif model == 'grok-3-reasoning':
663
+ if response.get("isThinking") and not CONFIG["SHOW_THINKING"]:
664
+ return result
665
+
666
+ if response.get("isThinking") and not CONFIG["IS_THINKING"]:
667
+ result["token"] = "<think>" + response.get("token", "")
668
+ CONFIG["IS_THINKING"] = True
669
+ elif not response.get("isThinking") and CONFIG["IS_THINKING"]:
670
+ result["token"] = "</think>" + response.get("token", "")
671
+ CONFIG["IS_THINKING"] = False
672
+ else:
673
+ result["token"] = response.get("token")
674
+
675
+ return result
676
+
677
+ def handle_image_response(image_url):
678
+ max_retries = 2
679
+ retry_count = 0
680
+ image_base64_response = None
681
+
682
+ while retry_count < max_retries:
683
+ try:
684
+ proxy_options = Utils.get_proxy_options()
685
+ image_base64_response = curl_requests.get(
686
+ f"https://assets.grok.com/{image_url}",
687
+ headers={
688
+ **DEFAULT_HEADERS,
689
+ "Cookie": CONFIG["API"]["SIGNATURE_COOKIE"]
690
+ },
691
+ impersonate="chrome120",
692
+ **proxy_options
693
+ )
694
+
695
+ if image_base64_response.status_code == 200:
696
+ break
697
+
698
+ retry_count += 1
699
+ if retry_count == max_retries:
700
+ raise Exception(f"上游服务请求失败! status: {image_base64_response.status_code}")
701
+
702
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
703
+
704
+ except Exception as error:
705
+ logger.error(str(error), "Server")
706
+ retry_count += 1
707
+ if retry_count == max_retries:
708
+ raise
709
+
710
+ time.sleep(CONFIG["API"]["RETRY_TIME"] / 1000 * retry_count)
711
+
712
+ image_buffer = image_base64_response.content
713
+
714
+ if not CONFIG["API"]["PICGO_KEY"] and not CONFIG["API"]["TUMY_KEY"]:
715
+ base64_image = base64.b64encode(image_buffer).decode('utf-8')
716
+ image_content_type = image_base64_response.headers.get('content-type', 'image/jpeg')
717
+ return f"![image](data:{image_content_type};base64,{base64_image})"
718
+
719
+ logger.info("开始上传图床", "Server")
720
+
721
+ if CONFIG["API"]["PICGO_KEY"]:
722
+ files = {'source': ('image.jpg', image_buffer, 'image/jpeg')}
723
+ headers = {
724
+ "X-API-Key": CONFIG["API"]["PICGO_KEY"]
725
+ }
726
+
727
+ response_url = requests.post(
728
+ "https://www.picgo.net/api/1/upload",
729
+ files=files,
730
+ headers=headers
731
+ )
732
+
733
+ if response_url.status_code != 200:
734
+ return "生图失败,请查看PICGO图床密钥是否设置正确"
735
+ else:
736
+ logger.info("生图成功", "Server")
737
+ result = response_url.json()
738
+ return f"![image]({result['image']['url']})"
739
+
740
+
741
+ elif CONFIG["API"]["TUMY_KEY"]:
742
+ files = {'file': ('image.jpg', image_buffer, 'image/jpeg')}
743
+ headers = {
744
+ "Accept": "application/json",
745
+ 'Authorization': f"Bearer {CONFIG['API']['TUMY_KEY']}"
746
+ }
747
+
748
+ response_url = requests.post(
749
+ "https://tu.my/api/v1/upload",
750
+ files=files,
751
+ headers=headers
752
+ )
753
+
754
+ if response_url.status_code != 200:
755
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
756
+ else:
757
+ try:
758
+ result = response_url.json()
759
+ logger.info("生图成功", "Server")
760
+ return f"![image]({result['data']['links']['url']})"
761
+ except Exception as error:
762
+ logger.error(str(error), "Server")
763
+ return "生图失败,请查看TUMY图床密钥是否设置正确"
764
+
765
+ def handle_non_stream_response(response, model):
766
+ try:
767
+ logger.info("开始处理非流式响应", "Server")
768
+
769
+ stream = response.iter_lines()
770
+ full_response = ""
771
+
772
+ CONFIG["IS_THINKING"] = False
773
+ CONFIG["IS_IMG_GEN"] = False
774
+ CONFIG["IS_IMG_GEN2"] = False
775
+
776
+ for chunk in stream:
777
+ if not chunk:
778
+ continue
779
+ try:
780
+ line_json = json.loads(chunk.decode("utf-8").strip())
781
+ if line_json.get("error"):
782
+ logger.error(json.dumps(line_json, indent=2), "Server")
783
+ return json.dumps({"error": "RateLimitError"}) + "\n\n"
784
+
785
+ response_data = line_json.get("result", {}).get("response")
786
+ if not response_data:
787
+ continue
788
+
789
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
790
+ CONFIG["IS_IMG_GEN"] = True
791
+
792
+ result = process_model_response(response_data, model)
793
+
794
+ if result["token"]:
795
+ full_response += result["token"]
796
+
797
+ if result["imageUrl"]:
798
+ CONFIG["IS_IMG_GEN2"] = True
799
+ return handle_image_response(result["imageUrl"])
800
+
801
+ except json.JSONDecodeError:
802
+ continue
803
+ except Exception as e:
804
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
805
+ continue
806
+
807
+ return full_response
808
+ except Exception as error:
809
+ logger.error(str(error), "Server")
810
+ raise
811
+ def handle_stream_response(response, model):
812
+ def generate():
813
+ logger.info("开始处理流式响应", "Server")
814
+
815
+ stream = response.iter_lines()
816
+ CONFIG["IS_THINKING"] = False
817
+ CONFIG["IS_IMG_GEN"] = False
818
+ CONFIG["IS_IMG_GEN2"] = False
819
+
820
+ for chunk in stream:
821
+ if not chunk:
822
+ continue
823
+ try:
824
+ line_json = json.loads(chunk.decode("utf-8").strip())
825
+ if line_json.get("error"):
826
+ logger.error(json.dumps(line_json, indent=2), "Server")
827
+ yield json.dumps({"error": "RateLimitError"}) + "\n\n"
828
+ return
829
+
830
+ response_data = line_json.get("result", {}).get("response")
831
+ if not response_data:
832
+ continue
833
+
834
+ if response_data.get("doImgGen") or response_data.get("imageAttachmentInfo"):
835
+ CONFIG["IS_IMG_GEN"] = True
836
+
837
+ result = process_model_response(response_data, model)
838
+
839
+ if result["token"]:
840
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(result['token'], model, True))}\n\n"
841
+
842
+ if result["imageUrl"]:
843
+ CONFIG["IS_IMG_GEN2"] = True
844
+ image_data = handle_image_response(result["imageUrl"])
845
+ yield f"data: {json.dumps(MessageProcessor.create_chat_response(image_data, model, True))}\n\n"
846
+
847
+ except json.JSONDecodeError:
848
+ continue
849
+ except Exception as e:
850
+ logger.error(f"处理流式响应行时出错: {str(e)}", "Server")
851
+ continue
852
+
853
+ yield "data: [DONE]\n\n"
854
+ return generate()
855
+
856
+ def initialization():
857
+ sso_array = os.environ.get("SSO", "").split(',')
858
+ logger.info("开始加载令牌", "Server")
859
+ for sso in sso_array:
860
+ if sso:
861
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
862
+
863
+ logger.info(f"成功加载令牌: {json.dumps(token_manager.get_all_tokens(), indent=2)}", "Server")
864
+ logger.info(f"令牌加载完成,共加载: {len(token_manager.get_all_tokens())}个令牌", "Server")
865
+
866
+ if CONFIG["API"]["PROXY"]:
867
+ logger.info(f"代理已设置: {CONFIG['API']['PROXY']}", "Server")
868
+
869
+ logger.info("初始化完成", "Server")
870
+
871
+
872
+ app = Flask(__name__)
873
+ app.wsgi_app = ProxyFix(app.wsgi_app)
874
+
875
+
876
+ @app.before_request
877
+ def log_request_info():
878
+ logger.info(f"{request.method} {request.path}", "Request")
879
+
880
+ @app.route('/get/tokens', methods=['GET'])
881
+ def get_tokens():
882
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
883
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
884
+ return jsonify({"error": '自定义的SSO令牌模式无法获取轮询sso令牌状态'}), 403
885
+ elif auth_token != CONFIG["API"]["API_KEY"]:
886
+ return jsonify({"error": 'Unauthorized'}), 401
887
+
888
+ return jsonify(token_manager.get_token_status_map())
889
+
890
+ @app.route('/add/token', methods=['POST'])
891
+ def add_token():
892
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
893
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
894
+ return jsonify({"error": '自定义的SSO令牌模式无法添加sso令牌'}), 403
895
+ elif auth_token != CONFIG["API"]["API_KEY"]:
896
+ return jsonify({"error": 'Unauthorized'}), 401
897
+
898
+ try:
899
+ sso = request.json.get('sso')
900
+ token_manager.add_token(f"sso-rw={sso};sso={sso}")
901
+ return jsonify(token_manager.get_token_status_map().get(sso, {})), 200
902
+ except Exception as error:
903
+ logger.error(str(error), "Server")
904
+ return jsonify({"error": '添加sso令牌失败'}), 500
905
+
906
+ @app.route('/delete/token', methods=['POST'])
907
+ def delete_token():
908
+ auth_token = request.headers.get('Authorization', '').replace('Bearer ', '')
909
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
910
+ return jsonify({"error": '自定义的SSO令牌模式无法删除sso令牌'}), 403
911
+ elif auth_token != CONFIG["API"]["API_KEY"]:
912
+ return jsonify({"error": 'Unauthorized'}), 401
913
+
914
+ try:
915
+ sso = request.json.get('sso')
916
+ token_manager.delete_token(f"sso-rw={sso};sso={sso}")
917
+ return jsonify({"message": '删除sso令牌成功'}), 200
918
+ except Exception as error:
919
+ logger.error(str(error), "Server")
920
+ return jsonify({"error": '删除sso令牌失败'}), 500
921
+
922
+ @app.route('/v1/models', methods=['GET'])
923
+ def get_models():
924
+ return jsonify({
925
+ "object": "list",
926
+ "data": [
927
+ {
928
+ "id": model,
929
+ "object": "model",
930
+ "created": int(time.time()),
931
+ "owned_by": "grok"
932
+ }
933
+ for model in CONFIG["MODELS"].keys()
934
+ ]
935
+ })
936
+
937
+ @app.route('/v1/chat/completions', methods=['POST'])
938
+ def chat_completions():
939
+ try:
940
+ auth_token = request.headers.get('Authorization',
941
+ '').replace('Bearer ', '')
942
+ if auth_token:
943
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
944
+ result = f"sso={auth_token};sso-rw={auth_token}"
945
+ token_manager.set_token(result)
946
+ elif auth_token != CONFIG["API"]["API_KEY"]:
947
+ return jsonify({"error": 'Unauthorized'}), 401
948
+ else:
949
+ return jsonify({"error": 'API_KEY缺失'}), 401
950
+
951
+ data = request.json
952
+ model = data.get("model")
953
+ stream = data.get("stream", False)
954
+
955
+ retry_count = 0
956
+ grok_client = GrokApiClient(model)
957
+ request_payload = grok_client.prepare_chat_request(data)
958
+
959
+ while retry_count < CONFIG["RETRY"]["MAX_ATTEMPTS"]:
960
+ retry_count += 1
961
+ CONFIG["API"]["SIGNATURE_COOKIE"] = Utils.create_auth_headers(
962
+ model)
963
+
964
+ if not CONFIG["API"]["SIGNATURE_COOKIE"]:
965
+ raise ValueError('该模型无可用令牌')
966
+
967
+ logger.info(
968
+ f"当前令牌: {json.dumps(CONFIG['API']['SIGNATURE_COOKIE'], indent=2)}",
969
+ "Server")
970
+ logger.info(
971
+ f"当前可用模型的全部可用数量: {json.dumps(token_manager.get_remaining_token_request_capacity(), indent=2)}",
972
+ "Server")
973
+
974
+ try:
975
+ proxy_options = Utils.get_proxy_options()
976
+ response = curl_requests.post(
977
+ f"{CONFIG['API']['BASE_URL']}/rest/app-chat/conversations/new",
978
+ headers={
979
+ **DEFAULT_HEADERS, "Cookie":
980
+ CONFIG["API"]["SIGNATURE_COOKIE"]
981
+ },
982
+ data=json.dumps(request_payload),
983
+ impersonate="chrome133a",
984
+ stream=True,
985
+ **proxy_options)
986
+ if response.status_code == 200:
987
+ logger.info("请求成功", "Server")
988
+ logger.info(
989
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
990
+ "Server")
991
+
992
+ try:
993
+ if stream:
994
+ return Response(stream_with_context(
995
+ handle_stream_response(response, model)),
996
+ content_type='text/event-stream')
997
+ else:
998
+ content = handle_non_stream_response(
999
+ response, model)
1000
+ return jsonify(
1001
+ MessageProcessor.create_chat_response(
1002
+ content, model))
1003
+
1004
+ except Exception as error:
1005
+ logger.error(str(error), "Server")
1006
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1007
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1008
+
1009
+ token_manager.remove_token_from_model(
1010
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1011
+ if token_manager.get_token_count_for_model(model) == 0:
1012
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1013
+
1014
+ elif response.status_code == 429:
1015
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1016
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1017
+
1018
+ token_manager.remove_token_from_model(
1019
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1020
+ if token_manager.get_token_count_for_model(model) == 0:
1021
+ raise ValueError(f"{model} 次数已达上限,请切换其他模型或者重新对话")
1022
+
1023
+ else:
1024
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1025
+ raise ValueError(f"自定义SSO令牌当前模型{model}的请求次数已失效")
1026
+
1027
+ logger.error(f"令牌异常错误状态!status: {response.status_code}",
1028
+ "Server")
1029
+ token_manager.remove_token_from_model(
1030
+ model, CONFIG["API"]["SIGNATURE_COOKIE"])
1031
+ logger.info(
1032
+ f"当前{model}剩余可用令牌数: {token_manager.get_token_count_for_model(model)}",
1033
+ "Server")
1034
+
1035
+ except Exception as e:
1036
+ logger.error(f"请求处理异常: {str(e)}", "Server")
1037
+ if CONFIG["API"]["IS_CUSTOM_SSO"]:
1038
+ raise
1039
+ continue
1040
+
1041
+ raise ValueError('当前模型所有令牌都已耗尽')
1042
+
1043
+ except Exception as error:
1044
+ logger.error(str(error), "ChatAPI")
1045
+ return jsonify(
1046
+ {"error": {
1047
+ "message": str(error),
1048
+ "type": "server_error"
1049
+ }}), 500
1050
+
1051
+ @app.route('/', defaults={'path': ''})
1052
+ @app.route('/<path:path>')
1053
+ def catch_all(path):
1054
+ return 'api运行正常', 200
1055
+
1056
+ if __name__ == '__main__':
1057
+ token_manager = AuthTokenManager()
1058
+ initialization()
1059
+
1060
+ app.run(
1061
+ host='0.0.0.0',
1062
+ port=CONFIG["SERVER"]["PORT"],
1063
+ debug=False
1064
+ )