polarbearblue commited on
Commit
a05d855
·
verified ·
1 Parent(s): 18d3814

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +607 -0
app.py ADDED
@@ -0,0 +1,607 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import time
4
+ import uuid
5
+ import threading
6
+ from typing import Any, Dict, List, Optional, TypedDict, Union
7
+
8
+ import requests
9
+ from fastapi import FastAPI, HTTPException, Depends, Query
10
+ from fastapi.responses import StreamingResponse
11
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ # CodeGeeX Token Management
16
+ class CodeGeeXToken(TypedDict):
17
+ token: str
18
+ is_valid: bool
19
+ last_used: float
20
+ error_count: int
21
+
22
+
23
+ # Global variables
24
+ VALID_CLIENT_KEYS: set = set()
25
+ CODEGEEX_TOKENS: List[CodeGeeXToken] = []
26
+ CODEGEEX_MODELS: List[str] = ["claude-3-7-sonnet", "claude-sonnet-4"]
27
+ token_rotation_lock = threading.Lock()
28
+ MAX_ERROR_COUNT = 3
29
+ ERROR_COOLDOWN = 300 # 5 minutes cooldown for tokens with errors
30
+ DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
31
+
32
+
33
+ # Pydantic Models
34
+ class ChatMessage(BaseModel):
35
+ role: str
36
+ content: Union[str, List[Dict[str, Any]]]
37
+ reasoning_content: Optional[str] = None
38
+
39
+
40
+ class ChatCompletionRequest(BaseModel):
41
+ model: str
42
+ messages: List[ChatMessage]
43
+ stream: bool = True
44
+ temperature: Optional[float] = None
45
+ max_tokens: Optional[int] = None
46
+ top_p: Optional[float] = None
47
+
48
+
49
+ class ModelInfo(BaseModel):
50
+ id: str
51
+ object: str = "model"
52
+ created: int
53
+ owned_by: str
54
+
55
+
56
+ class ModelList(BaseModel):
57
+ object: str = "list"
58
+ data: List[ModelInfo]
59
+
60
+
61
+ class ChatCompletionChoice(BaseModel):
62
+ message: ChatMessage
63
+ index: int = 0
64
+ finish_reason: str = "stop"
65
+
66
+
67
+ class ChatCompletionResponse(BaseModel):
68
+ id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}")
69
+ object: str = "chat.completion"
70
+ created: int = Field(default_factory=lambda: int(time.time()))
71
+ model: str
72
+ choices: List[ChatCompletionChoice]
73
+ usage: Dict[str, int] = Field(
74
+ default_factory=lambda: {
75
+ "prompt_tokens": 0,
76
+ "completion_tokens": 0,
77
+ "total_tokens": 0,
78
+ }
79
+ )
80
+
81
+
82
+ class StreamChoice(BaseModel):
83
+ delta: Dict[str, Any] = Field(default_factory=dict)
84
+ index: int = 0
85
+ finish_reason: Optional[str] = None
86
+
87
+
88
+ class StreamResponse(BaseModel):
89
+ id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}")
90
+ object: str = "chat.completion.chunk"
91
+ created: int = Field(default_factory=lambda: int(time.time()))
92
+ model: str
93
+ choices: List[StreamChoice]
94
+
95
+
96
+ # FastAPI App
97
+ app = FastAPI(title="CodeGeeX OpenAI API Adapter")
98
+ security = HTTPBearer(auto_error=False)
99
+
100
+
101
+ def log_debug(message: str):
102
+ """Debug日志函数"""
103
+ if DEBUG_MODE:
104
+ print(f"[DEBUG] {message}")
105
+
106
+
107
+ def load_client_api_keys():
108
+ """Load client API keys from client_api_keys.json"""
109
+ global VALID_CLIENT_KEYS
110
+ try:
111
+ with open("client_api_keys.json", "r", encoding="utf-8") as f:
112
+ keys = json.load(f)
113
+ VALID_CLIENT_KEYS = set(keys) if isinstance(keys, list) else set()
114
+ print(f"Successfully loaded {len(VALID_CLIENT_KEYS)} client API keys.")
115
+ except FileNotFoundError:
116
+ print("Error: client_api_keys.json not found. Client authentication will fail.")
117
+ VALID_CLIENT_KEYS = set()
118
+ except Exception as e:
119
+ print(f"Error loading client_api_keys.json: {e}")
120
+ VALID_CLIENT_KEYS = set()
121
+
122
+
123
+ def load_codegeex_tokens():
124
+ """Load CodeGeeX tokens from codegeex.txt"""
125
+ global CODEGEEX_TOKENS
126
+ CODEGEEX_TOKENS = []
127
+ try:
128
+ with open("codegeex.txt", "r", encoding="utf-8") as f:
129
+ for line in f:
130
+ token = line.strip()
131
+ if token:
132
+ CODEGEEX_TOKENS.append({
133
+ "token": token,
134
+ "is_valid": True,
135
+ "last_used": 0,
136
+ "error_count": 0
137
+ })
138
+ print(f"Successfully loaded {len(CODEGEEX_TOKENS)} CodeGeeX tokens.")
139
+ except FileNotFoundError:
140
+ print("Error: codegeex.txt not found. API calls will fail.")
141
+ except Exception as e:
142
+ print(f"Error loading codegeex.txt: {e}")
143
+
144
+
145
+ def get_best_codegeex_token() -> Optional[CodeGeeXToken]:
146
+ """Get the best available CodeGeeX token using a smart selection algorithm."""
147
+ with token_rotation_lock:
148
+ now = time.time()
149
+ valid_tokens = [
150
+ token for token in CODEGEEX_TOKENS
151
+ if token["is_valid"] and (
152
+ token["error_count"] < MAX_ERROR_COUNT or
153
+ now - token["last_used"] > ERROR_COOLDOWN
154
+ )
155
+ ]
156
+
157
+ if not valid_tokens:
158
+ return None
159
+
160
+ # Reset error count for tokens that have been in cooldown
161
+ for token in valid_tokens:
162
+ if token["error_count"] >= MAX_ERROR_COUNT and now - token["last_used"] > ERROR_COOLDOWN:
163
+ token["error_count"] = 0
164
+
165
+ # Sort by last used (oldest first) and error count (lowest first)
166
+ valid_tokens.sort(key=lambda x: (x["last_used"], x["error_count"]))
167
+ token = valid_tokens[0]
168
+ token["last_used"] = now
169
+ return token
170
+
171
+
172
+ def _convert_messages_to_codegeex_format(messages: List[ChatMessage]):
173
+ """Convert OpenAI messages format to CodeGeeX prompt and history format."""
174
+ if not messages:
175
+ return "", []
176
+
177
+ # Extract the last user message as prompt
178
+ last_user_msg = None
179
+ for msg in reversed(messages):
180
+ if msg.role == "user":
181
+ last_user_msg = msg
182
+ break
183
+
184
+ if not last_user_msg:
185
+ raise HTTPException(status_code=400, detail="No user message found in the conversation.")
186
+
187
+ prompt = last_user_msg.content if isinstance(last_user_msg.content, str) else ""
188
+
189
+ # Build history from previous messages (excluding the last user message)
190
+ history = []
191
+ user_content = ""
192
+ assistant_content = ""
193
+
194
+ for i, msg in enumerate(messages[:-1] if messages[-1].role == "user" else messages):
195
+ if msg == last_user_msg:
196
+ continue
197
+
198
+ if msg.role == "user":
199
+ # If we have a complete pair, add it to history
200
+ if user_content and assistant_content:
201
+ history.append({
202
+ "query": user_content,
203
+ "answer": assistant_content,
204
+ "id": f"{uuid.uuid4()}"
205
+ })
206
+ user_content = ""
207
+ assistant_content = ""
208
+
209
+ # Start a new pair with this user message
210
+ content = msg.content if isinstance(msg.content, str) else ""
211
+ user_content = content
212
+
213
+ elif msg.role == "assistant":
214
+ content = msg.content if isinstance(msg.content, str) else ""
215
+ assistant_content = content
216
+
217
+ # If we have a complete pair, add it to history
218
+ if user_content:
219
+ history.append({
220
+ "query": user_content,
221
+ "answer": assistant_content,
222
+ "id": f"{uuid.uuid4()}"
223
+ })
224
+ user_content = ""
225
+ assistant_content = ""
226
+
227
+ # Handle any remaining unpaired messages
228
+ if user_content and not assistant_content:
229
+ # Unpaired user message - treat as part of the prompt
230
+ prompt = user_content + "\n" + prompt
231
+
232
+ return prompt, history
233
+
234
+
235
+ async def authenticate_client(
236
+ auth: Optional[HTTPAuthorizationCredentials] = Depends(security),
237
+ ):
238
+ """Authenticate client based on API key in Authorization header"""
239
+ if not VALID_CLIENT_KEYS:
240
+ raise HTTPException(
241
+ status_code=503,
242
+ detail="Service unavailable: Client API keys not configured on server.",
243
+ )
244
+
245
+ if not auth or not auth.credentials:
246
+ raise HTTPException(
247
+ status_code=401,
248
+ detail="API key required in Authorization header.",
249
+ headers={"WWW-Authenticate": "Bearer"},
250
+ )
251
+
252
+ if auth.credentials not in VALID_CLIENT_KEYS:
253
+ raise HTTPException(status_code=403, detail="Invalid client API key.")
254
+
255
+
256
+ @app.on_event("startup")
257
+ async def startup():
258
+ """应用启动时初始化配置"""
259
+ print("Starting CodeGeeX OpenAI API Adapter server...")
260
+ load_client_api_keys()
261
+ load_codegeex_tokens()
262
+ print("Server initialization completed.")
263
+
264
+
265
+ def get_models_list_response() -> ModelList:
266
+ """Helper to construct ModelList response from cached models."""
267
+ model_infos = [
268
+ ModelInfo(
269
+ id=model,
270
+ created=int(time.time()),
271
+ owned_by="anthropic"
272
+ )
273
+ for model in CODEGEEX_MODELS
274
+ ]
275
+ return ModelList(data=model_infos)
276
+
277
+
278
+ @app.get("/v1/models", response_model=ModelList)
279
+ async def list_v1_models(_: None = Depends(authenticate_client)):
280
+ """List available models - authenticated"""
281
+ return get_models_list_response()
282
+
283
+
284
+ @app.get("/models", response_model=ModelList)
285
+ async def list_models_no_auth():
286
+ """List available models without authentication - for client compatibility"""
287
+ return get_models_list_response()
288
+
289
+
290
+ @app.get("/debug")
291
+ async def toggle_debug(enable: bool = Query(None)):
292
+ """切换调试模式"""
293
+ global DEBUG_MODE
294
+ if enable is not None:
295
+ DEBUG_MODE = enable
296
+ return {"debug_mode": DEBUG_MODE}
297
+
298
+
299
+ def _codegeex_stream_generator(response, model: str):
300
+ """Real-time streaming with format conversion - CodeGeeX to OpenAI"""
301
+ stream_id = f"chatcmpl-{uuid.uuid4().hex}"
302
+ created_time = int(time.time())
303
+
304
+ # 发送初始角色增量
305
+ yield f"data: {StreamResponse(id=stream_id, created=created_time, model=model, choices=[StreamChoice(delta={'role': 'assistant'})]).json()}\n\n"
306
+
307
+ buffer = ""
308
+
309
+ try:
310
+ for chunk in response.iter_content(chunk_size=1024):
311
+ if not chunk:
312
+ continue
313
+
314
+ chunk_text = chunk.decode("utf-8")
315
+ log_debug(f"Received chunk: {chunk_text[:100]}..." if len(chunk_text) > 100 else chunk_text)
316
+ buffer += chunk_text
317
+
318
+ # 处理缓冲区中的完整事件块
319
+ while "\n\n" in buffer:
320
+ event_data, buffer = buffer.split("\n\n", 1)
321
+ event_data = event_data.strip()
322
+
323
+ if not event_data:
324
+ continue
325
+
326
+ # 解析事件
327
+ event_type = None
328
+ data_json = None
329
+
330
+ for line in event_data.split("\n"):
331
+ line = line.strip()
332
+ if line.startswith("event:"):
333
+ event_type = line[6:].strip()
334
+ elif line.startswith("data:"):
335
+ try:
336
+ data_json = json.loads(line[5:].strip())
337
+ except json.JSONDecodeError:
338
+ log_debug(f"Failed to parse JSON: {line[5:].strip()}")
339
+
340
+ if not event_type or not data_json:
341
+ continue
342
+
343
+ if event_type == "add":
344
+ # 'text' 字段本身就是增量内容
345
+ delta = data_json.get("text", "")
346
+ if delta:
347
+ openai_response = StreamResponse(
348
+ id=stream_id,
349
+ created=created_time,
350
+ model=model,
351
+ choices=[StreamChoice(delta={"content": delta})],
352
+ )
353
+ yield f"data: {openai_response.json()}\n\n"
354
+
355
+ elif event_type == "finish":
356
+ # 'finish' 事件标志着流的结束
357
+ log_debug("Received finish event.")
358
+ openai_response = StreamResponse(
359
+ id=stream_id,
360
+ created=created_time,
361
+ model=model,
362
+ choices=[StreamChoice(delta={}, finish_reason="stop")],
363
+ )
364
+ yield f"data: {openai_response.json()}\n\n"
365
+ yield "data: [DONE]\n\n"
366
+ return # 终止生成器
367
+
368
+ except Exception as e:
369
+ log_debug(f"Stream processing error: {e}")
370
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
371
+
372
+ # 如果流意外中断,也发送终止信号
373
+ log_debug("Stream finished unexpectedly, sending completion signal.")
374
+ yield f"data: {StreamResponse(id=stream_id, created=created_time, model=model, choices=[StreamChoice(delta={}, finish_reason='stop')]).json()}\n\n"
375
+ yield "data: [DONE]\n\n"
376
+
377
+
378
+ def _build_codegeex_non_stream_response(response, model: str) -> ChatCompletionResponse:
379
+ """Build non-streaming response by accumulating stream data."""
380
+ full_content = ""
381
+ buffer = ""
382
+
383
+ for chunk in response.iter_content(chunk_size=1024):
384
+ if not chunk:
385
+ continue
386
+
387
+ buffer += chunk.decode("utf-8")
388
+
389
+ # 处理缓冲区中的完整事件块
390
+ while "\n\n" in buffer:
391
+ event_data, buffer = buffer.split("\n\n", 1)
392
+ event_data = event_data.strip()
393
+
394
+ if not event_data:
395
+ continue
396
+
397
+ # 解析事件
398
+ event_type = None
399
+ data_json = None
400
+
401
+ for line in event_data.split("\n"):
402
+ line = line.strip()
403
+ if line.startswith("event:"):
404
+ event_type = line[6:].strip()
405
+ elif line.startswith("data:"):
406
+ try:
407
+ data_json = json.loads(line[5:].strip())
408
+ except json.JSONDecodeError:
409
+ continue
410
+
411
+ if not event_type or not data_json:
412
+ continue
413
+
414
+ if event_type == "add":
415
+ # 正确地累积增量文本
416
+ full_content += data_json.get("text", "")
417
+
418
+ elif event_type == "finish":
419
+ # finish事件中的text是最终的完整文本,以此为准
420
+ finish_text = data_json.get("text", "")
421
+ if finish_text:
422
+ full_content = finish_text
423
+ # 收到finish事件,可以提前结束解析
424
+ return ChatCompletionResponse(
425
+ model=model,
426
+ choices=[
427
+ ChatCompletionChoice(
428
+ message=ChatMessage(
429
+ role="assistant",
430
+ content=full_content
431
+ )
432
+ )
433
+ ],
434
+ )
435
+
436
+ # 如果循环结束仍未返回(例如没有finish事件),则使用累积的内容
437
+ return ChatCompletionResponse(
438
+ model=model,
439
+ choices=[
440
+ ChatCompletionChoice(
441
+ message=ChatMessage(
442
+ role="assistant",
443
+ content=full_content
444
+ )
445
+ )
446
+ ],
447
+ )
448
+
449
+
450
+ @app.post("/v1/chat/completions")
451
+ async def chat_completions(
452
+ request: ChatCompletionRequest, _: None = Depends(authenticate_client)
453
+ ):
454
+ """Create chat completion using CodeGeeX backend"""
455
+ if request.model not in CODEGEEX_MODELS:
456
+ raise HTTPException(status_code=404, detail=f"Model '{request.model}' not found.")
457
+
458
+ if not request.messages:
459
+ raise HTTPException(status_code=400, detail="No messages provided in the request.")
460
+
461
+ log_debug(f"Processing request for model: {request.model}")
462
+
463
+ # 转换消息格式
464
+ try:
465
+ prompt, history = _convert_messages_to_codegeex_format(request.messages)
466
+ except Exception as e:
467
+ raise HTTPException(status_code=400, detail=f"Failed to process messages: {str(e)}")
468
+
469
+ # 尝试所有令牌
470
+ for attempt in range(len(CODEGEEX_TOKENS) + 1): # +1 to handle the case of no tokens
471
+ if attempt == len(CODEGEEX_TOKENS):
472
+ raise HTTPException(
473
+ status_code=503,
474
+ detail="All attempts to contact CodeGeeX API failed."
475
+ )
476
+
477
+ token = get_best_codegeex_token()
478
+ if not token:
479
+ raise HTTPException(
480
+ status_code=503,
481
+ detail="No valid CodeGeeX tokens available."
482
+ )
483
+
484
+ try:
485
+ # 构建请求
486
+ payload = {
487
+ "user_role": 0,
488
+ "ide": "VSCode",
489
+ "ide_version": "",
490
+ "plugin_version": "",
491
+ "prompt": prompt,
492
+ "machineId": "",
493
+ "talkId": f"{uuid.uuid4()}",
494
+ "locale": "",
495
+ "model": request.model,
496
+ "agent": None,
497
+ "candidates": {
498
+ "candidate_msg_id": "",
499
+ "candidate_type": "",
500
+ "selected_candidate": "",
501
+ },
502
+ "history": history,
503
+ }
504
+
505
+ headers = {
506
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36",
507
+ "Accept": "text/event-stream",
508
+ "Accept-Encoding": "gzip, deflate, br, zstd",
509
+ "Content-Type": "application/json",
510
+ "code-token": token["token"],
511
+ }
512
+
513
+ log_debug(f"Sending request to CodeGeeX API with token ending in ...{token['token'][-4:]}")
514
+
515
+ response = requests.post(
516
+ "https://codegeex.cn/prod/code/chatCodeSseV3/chat",
517
+ data=json.dumps(payload),
518
+ headers=headers,
519
+ stream=True,
520
+ timeout=300.0,
521
+ )
522
+ response.raise_for_status()
523
+
524
+ if request.stream:
525
+ log_debug("Returning stream response")
526
+ return StreamingResponse(
527
+ _codegeex_stream_generator(response, request.model),
528
+ media_type="text/event-stream",
529
+ headers={
530
+ "Cache-Control": "no-cache",
531
+ "Connection": "keep-alive",
532
+ "X-Accel-Buffering": "no",
533
+ },
534
+ )
535
+ else:
536
+ log_debug("Building non-stream response")
537
+ return _build_codegeex_non_stream_response(response, request.model)
538
+
539
+ except requests.HTTPError as e:
540
+ status_code = getattr(e.response, "status_code", 500)
541
+ error_detail = getattr(e.response, "text", str(e))
542
+ log_debug(f"CodeGeeX API error ({status_code}): {error_detail}")
543
+
544
+ with token_rotation_lock:
545
+ if status_code in [401, 403]:
546
+ # 标记令牌为无效
547
+ token["is_valid"] = False
548
+ print(f"Token ...{token['token'][-4:]} marked as invalid due to auth error.")
549
+ elif status_code in [429, 500, 502, 503, 504]:
550
+ # 增加错误计数
551
+ token["error_count"] += 1
552
+ print(f"Token ...{token['token'][-4:]} error count: {token['error_count']}")
553
+
554
+ except Exception as e:
555
+ log_debug(f"Request error: {e}")
556
+ with token_rotation_lock:
557
+ token["error_count"] += 1
558
+
559
+
560
+ async def error_stream_generator(error_detail: str, status_code: int):
561
+ """Generate error stream response"""
562
+ yield f'data: {json.dumps({"error": {"message": error_detail, "type": "codegeex_api_error", "code": status_code}})}\n\n'
563
+ yield "data: [DONE]\n\n"
564
+
565
+
566
+ if __name__ == "__main__":
567
+ import uvicorn
568
+
569
+ # 设置环境变量以启用调试模式
570
+ if os.environ.get("DEBUG_MODE", "").lower() == "true":
571
+ DEBUG_MODE = True
572
+ print("Debug mode enabled via environment variable")
573
+
574
+ if not os.path.exists("codegeex.txt"):
575
+ print("Warning: codegeex.txt not found. Creating a dummy file.")
576
+ with open("codegeex.txt", "w", encoding="utf-8") as f:
577
+ f.write(f"your-codegeex-token-here\n")
578
+ print("Created dummy codegeex.txt. Please replace with valid CodeGeeX token.")
579
+
580
+ if not os.path.exists("client_api_keys.json"):
581
+ print("Warning: client_api_keys.json not found. Creating a dummy file.")
582
+ dummy_key = f"sk-dummy-{uuid.uuid4().hex}"
583
+ with open("client_api_keys.json", "w", encoding="utf-8") as f:
584
+ json.dump([dummy_key], f, indent=2)
585
+ print(f"Created dummy client_api_keys.json with key: {dummy_key}")
586
+
587
+ load_client_api_keys()
588
+ load_codegeex_tokens()
589
+
590
+ print("\n--- CodeGeeX OpenAI API Adapter ---")
591
+ print(f"Debug Mode: {DEBUG_MODE}")
592
+ print("Endpoints:")
593
+ print(" GET /v1/models (Client API Key Auth)")
594
+ print(" GET /models (No Auth)")
595
+ print(" POST /v1/chat/completions (Client API Key Auth)")
596
+ print(" GET /debug?enable=[true|false] (Toggle Debug Mode)")
597
+
598
+ print(f"\nClient API Keys: {len(VALID_CLIENT_KEYS)}")
599
+ if CODEGEEX_TOKENS:
600
+ print(f"CodeGeeX Tokens: {len(CODEGEEX_TOKENS)}")
601
+ else:
602
+ print("CodeGeeX Tokens: None loaded. Check codegeex.txt.")
603
+
604
+ print(f"Available models: {', '.join(CODEGEEX_MODELS)}")
605
+ print("------------------------------------")
606
+
607
+ uvicorn.run(app, host="0.0.0.0", port=8000)