ZyphrZero commited on
Commit
9b0b6dd
·
1 Parent(s): f0cacfe

Initial commit

Browse files
Files changed (1) hide show
  1. main.py +187 -173
main.py CHANGED
@@ -1,29 +1,4 @@
1
- """
2
- Go到Python代码转换说明
3
- =====================
4
 
5
- 这是一个将Go语言实现的OpenAI兼容API代理服务器转换为Python版本的代码。
6
- 使用FastAPI作为Web框架,httpx用于HTTP请求,uvicorn作为ASGI服务器。
7
-
8
- 主要功能对应关系:
9
- 1. 配置常量:使用Python模块级常量替代Go的const
10
- 2. 数据结构:使用Pydantic模型替代Go的struct
11
- 3. HTTP处理:使用FastAPI路由替代Go的http.HandleFunc
12
- 4. 流式响应:使用FastAPI的StreamingResponse替代Go的http.Flusher
13
- 5. SSE处理:使用生成器函数和字符串格式化替代Go的fmt.Fprintf
14
-
15
- 关键实现思路:
16
- - 保持了原有的API认证逻辑
17
- - 维持了上游API调用的头部伪装
18
- - 实现了相同的思考内容处理策略
19
- - 保持了流式和非流式响应的处理逻辑
20
-
21
- 依赖安装:
22
- pip install fastapi uvicorn httpx pydantic
23
-
24
- 运行方式:
25
- uvicorn main:app --host 0.0.0.0 --port 8080 --reload
26
- """
27
 
28
  import json
29
  import re
@@ -217,7 +192,8 @@ async def add_cors_headers(request: Request, call_next):
217
 
218
  # OPTIONS处理器
219
  @app.options("/")
220
- async def handle_options():
 
221
  return Response(status_code=200)
222
 
223
 
@@ -243,19 +219,19 @@ async def handle_models():
243
  ),
244
  ]
245
  )
246
- return response
247
 
248
 
249
  # 聊天完成接口
250
  @app.post("/v1/chat/completions")
251
  async def handle_chat_completions(
252
  request: OpenAIRequest,
253
- authorization: str = Header(...)
254
  ):
255
  debug_log("收到chat completions请求")
256
 
257
  # 验证API Key
258
- if not authorization.startswith("Bearer "):
259
  debug_log("缺少或无效的Authorization头")
260
  raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
261
 
@@ -312,8 +288,11 @@ async def handle_chat_completions(
312
  if ANON_TOKEN_ENABLED:
313
  try:
314
  token = await get_anonymous_token()
315
- auth_token = token
316
- debug_log(f"匿名token获取成功: {token[:10]}...")
 
 
 
317
  except Exception as e:
318
  debug_log(f"匿名token获取失败,回退固定token: {e}")
319
 
@@ -348,7 +327,7 @@ async def call_upstream_with_headers(upstream_req: UpstreamRequest, referer_chat
348
  }
349
 
350
  debug_log(f"调用上游API: {UPSTREAM_URL}")
351
- debug_log(f"上游请求体: {upstream_req.model_dump_json()}")
352
 
353
  async with httpx.AsyncClient(timeout=60.0) as client:
354
  response = await client.post(
@@ -390,14 +369,14 @@ async def handle_stream_response(upstream_req: UpstreamRequest, chat_id: str, au
390
  response = await call_upstream_with_headers(upstream_req, chat_id, auth_token)
391
  except Exception as e:
392
  debug_log(f"调用上游失败: {e}")
393
- yield "data: {\"error\": \"Failed to call upstream\"}\n\n"
394
  return
395
 
396
  if response.status_code != 200:
397
  debug_log(f"上游返回错误状态: {response.status_code}")
398
  if DEBUG_MODE:
399
  debug_log(f"上游错误响应: {response.text}")
400
- yield "data: {\"error\": \"Upstream error\"}\n\n"
401
  return
402
 
403
  # 发送第一个chunk(role)
@@ -418,66 +397,90 @@ async def handle_stream_response(upstream_req: UpstreamRequest, chat_id: str, au
418
  line_count = 0
419
  sent_initial_answer = False
420
 
421
- async for line in response.aiter_lines():
422
- line_count += 1
423
-
424
- if not line.startswith("data: "):
425
- continue
426
-
427
- data_str = line[6:] # 去掉 "data: "
428
- if not data_str:
429
- continue
430
-
431
- debug_log(f"收到SSE数据 (第{line_count}行): {data_str}")
432
-
433
- try:
434
- upstream_data = UpstreamData.model_validate_json(data_str)
435
- except Exception as e:
436
- debug_log(f"SSE数据解析失败: {e}")
437
- continue
438
-
439
- # 错误检测
440
- if (upstream_data.error or
441
- upstream_data.data.error or
442
- (upstream_data.data.inner and upstream_data.data.inner.error)):
443
 
444
- err_obj = upstream_data.error or upstream_data.data.error
445
- if not err_obj and upstream_data.data.inner:
446
- err_obj = upstream_data.data.inner.error
447
 
448
- debug_log(f"上游错误: code={err_obj.code}, detail={err_obj.detail}")
 
 
449
 
450
- # 结束下游流
451
- end_chunk = OpenAIResponse(
452
- id=f"chatcmpl-{int(time.time())}",
453
- object="chat.completion.chunk",
454
- created=int(time.time()),
455
- model=DEFAULT_MODEL_NAME,
456
- choices=[Choice(
457
- index=0,
458
- delta=Delta(),
459
- finish_reason="stop"
460
- )]
461
- )
462
- yield f"data: {end_chunk.model_dump_json()}\n\n"
463
- yield "data: [DONE]\n\n"
464
- break
465
-
466
- debug_log(f"解析成功 - 类型: {upstream_data.type}, 阶段: {upstream_data.data.phase}, "
467
- f"内容长度: {len(upstream_data.data.delta_content)}, 完成: {upstream_data.data.done}")
468
-
469
- # 处理EditContent在最初的answer信息(只发送一次)
470
- if (not sent_initial_answer and
471
- upstream_data.data.edit_content and
472
- upstream_data.data.phase == "answer"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
- out = upstream_data.data.edit_content
475
- if out:
476
- parts = out.split("</details>")
477
- if len(parts) > 1:
478
- content = parts[1]
479
- if content:
480
- debug_log(f"发送普通内容: {content}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  chunk = OpenAIResponse(
482
  id=f"chatcmpl-{int(time.time())}",
483
  object="chat.completion.chunk",
@@ -485,68 +488,49 @@ async def handle_stream_response(upstream_req: UpstreamRequest, chat_id: str, au
485
  model=DEFAULT_MODEL_NAME,
486
  choices=[Choice(
487
  index=0,
488
- delta=Delta(content=content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  )]
490
  )
491
  yield f"data: {chunk.model_dump_json()}\n\n"
492
- sent_initial_answer = True
493
-
494
- # 处理DeltaContent
495
- if upstream_data.data.delta_content:
496
- out = upstream_data.data.delta_content
497
-
498
- if upstream_data.data.phase == "thinking":
499
- out = transform_thinking(out)
500
- # 思考内容使用 reasoning_content 字段
501
- if out:
502
- debug_log(f"发送思考内容: {out}")
503
- chunk = OpenAIResponse(
504
- id=f"chatcmpl-{int(time.time())}",
505
- object="chat.completion.chunk",
506
- created=int(time.time()),
507
- model=DEFAULT_MODEL_NAME,
508
- choices=[Choice(
509
- index=0,
510
- delta=Delta(reasoning_content=out)
511
- )]
512
- )
513
- yield f"data: {chunk.model_dump_json()}\n\n"
514
- else:
515
- # 普通内容使用 content 字段
516
- if out:
517
- debug_log(f"发送普通内容: {out}")
518
- chunk = OpenAIResponse(
519
- id=f"chatcmpl-{int(time.time())}",
520
- object="chat.completion.chunk",
521
- created=int(time.time()),
522
- model=DEFAULT_MODEL_NAME,
523
- choices=[Choice(
524
- index=0,
525
- delta=Delta(content=out)
526
- )]
527
- )
528
- yield f"data: {chunk.model_dump_json()}\n\n"
529
-
530
- # 检查是否结束
531
- if upstream_data.data.done or upstream_data.data.phase == "done":
532
- debug_log("检测到流结束信号")
533
 
534
- # 发送结束chunk
535
- end_chunk = OpenAIResponse(
536
- id=f"chatcmpl-{int(time.time())}",
537
- object="chat.completion.chunk",
538
- created=int(time.time()),
539
- model=DEFAULT_MODEL_NAME,
540
- choices=[Choice(
541
- index=0,
542
- delta=Delta(),
543
- finish_reason="stop"
544
- )]
545
- )
546
- yield f"data: {end_chunk.model_dump_json()}\n\n"
547
- yield "data: [DONE]\n\n"
548
- debug_log(f"流式响应完成,共处理{line_count}行")
549
- break
 
 
 
 
 
 
 
550
 
551
 
552
  async def handle_non_stream_response(upstream_req: UpstreamRequest, chat_id: str, auth_token: str) -> JSONResponse:
@@ -569,31 +553,35 @@ async def handle_non_stream_response(upstream_req: UpstreamRequest, chat_id: str
569
  full_content = []
570
  debug_log("开始收集完整响应内容")
571
 
572
- async for line in response.aiter_lines():
573
- if not line.startswith("data: "):
574
- continue
575
-
576
- data_str = line[6:]
577
- if not data_str:
578
- continue
579
-
580
- try:
581
- upstream_data = UpstreamData.model_validate_json(data_str)
582
- except Exception:
583
- continue
584
-
585
- if upstream_data.data.delta_content:
586
- out = upstream_data.data.delta_content
587
 
588
- if upstream_data.data.phase == "thinking":
589
- out = transform_thinking(out)
 
590
 
591
- if out:
592
- full_content.append(out)
593
-
594
- if upstream_data.data.done or upstream_data.data.phase == "done":
595
- debug_log("检测到完成信号,停止收集")
596
- break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
  final_content = "".join(full_content)
599
  debug_log(f"内容收集完成,最终长度: {len(final_content)}")
@@ -616,7 +604,17 @@ async def handle_non_stream_response(upstream_req: UpstreamRequest, chat_id: str
616
  )
617
 
618
  debug_log("非流式响应发送完成")
619
- return JSONResponse(content=response_data.model_dump(exclude_none=True))
 
 
 
 
 
 
 
 
 
 
620
 
621
 
622
  # 根路径处理器
@@ -625,6 +623,22 @@ async def root():
625
  return {"message": "OpenAI Compatible API Server"}
626
 
627
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  if __name__ == "__main__":
629
  import uvicorn
630
- uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=True)
 
 
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import json
4
  import re
 
192
 
193
  # OPTIONS处理器
194
  @app.options("/")
195
+ @app.options("/v1/{path:path}")
196
+ async def handle_options(path: str = ""):
197
  return Response(status_code=200)
198
 
199
 
 
219
  ),
220
  ]
221
  )
222
+ return JSONResponse(content=response.model_dump(exclude_none=True))
223
 
224
 
225
  # 聊天完成接口
226
  @app.post("/v1/chat/completions")
227
  async def handle_chat_completions(
228
  request: OpenAIRequest,
229
+ authorization: Optional[str] = Header(None)
230
  ):
231
  debug_log("收到chat completions请求")
232
 
233
  # 验证API Key
234
+ if not authorization or not authorization.startswith("Bearer "):
235
  debug_log("缺少或无效的Authorization头")
236
  raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
237
 
 
288
  if ANON_TOKEN_ENABLED:
289
  try:
290
  token = await get_anonymous_token()
291
+ if token:
292
+ auth_token = token
293
+ debug_log(f"匿名token获取成功: {token[:10] if len(token) > 10 else token}...")
294
+ else:
295
+ debug_log("获取到的匿名token为空,使用固定token")
296
  except Exception as e:
297
  debug_log(f"匿名token获取失败,回退固定token: {e}")
298
 
 
327
  }
328
 
329
  debug_log(f"调用上游API: {UPSTREAM_URL}")
330
+ debug_log(f"上游请求体: {upstream_req.model_dump_json(exclude_none=True)}")
331
 
332
  async with httpx.AsyncClient(timeout=60.0) as client:
333
  response = await client.post(
 
369
  response = await call_upstream_with_headers(upstream_req, chat_id, auth_token)
370
  except Exception as e:
371
  debug_log(f"调用上游失败: {e}")
372
+ yield f"data: {{\"error\": \"Failed to call upstream\", \"type\": \"server_error\"}}\n\n"
373
  return
374
 
375
  if response.status_code != 200:
376
  debug_log(f"上游返回错误状态: {response.status_code}")
377
  if DEBUG_MODE:
378
  debug_log(f"上游错误响应: {response.text}")
379
+ yield f"data: {{\"error\": \"Upstream error\", \"type\": \"upstream_error\"}}\n\n"
380
  return
381
 
382
  # 发送第一个chunk(role)
 
397
  line_count = 0
398
  sent_initial_answer = False
399
 
400
+ try:
401
+ async for line in response.aiter_lines():
402
+ line_count += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
+ if not line.startswith("data: "):
405
+ continue
 
406
 
407
+ data_str = line[6:] # 去掉 "data: "
408
+ if not data_str:
409
+ continue
410
 
411
+ debug_log(f"收到SSE数据 (第{line_count}行): {data_str}")
412
+
413
+ try:
414
+ upstream_data = UpstreamData.model_validate_json(data_str)
415
+ except Exception as e:
416
+ debug_log(f"SSE数据解析失败: {e}")
417
+ continue
418
+
419
+ # 错误检测
420
+ if (upstream_data.error or
421
+ upstream_data.data.error or
422
+ (upstream_data.data.inner and upstream_data.data.inner.error)):
423
+
424
+ err_obj = upstream_data.error or upstream_data.data.error
425
+ if not err_obj and upstream_data.data.inner:
426
+ err_obj = upstream_data.data.inner.error
427
+
428
+ debug_log(f"上游错误: code={err_obj.code}, detail={err_obj.detail}")
429
+
430
+ # 结束下游流
431
+ end_chunk = OpenAIResponse(
432
+ id=f"chatcmpl-{int(time.time())}",
433
+ object="chat.completion.chunk",
434
+ created=int(time.time()),
435
+ model=DEFAULT_MODEL_NAME,
436
+ choices=[Choice(
437
+ index=0,
438
+ delta=Delta(),
439
+ finish_reason="stop"
440
+ )]
441
+ )
442
+ yield f"data: {end_chunk.model_dump_json()}\n\n"
443
+ yield "data: [DONE]\n\n"
444
+ break
445
+
446
+ debug_log(f"解析成功 - 类型: {upstream_data.type}, 阶段: {upstream_data.data.phase}, "
447
+ f"内容长度: {len(upstream_data.data.delta_content)}, 完成: {upstream_data.data.done}")
448
 
449
+ # 处理EditContent在最初的answer信息(只发送一次)
450
+ if (not sent_initial_answer and
451
+ upstream_data.data.edit_content and
452
+ upstream_data.data.phase == "answer"):
453
+
454
+ out = upstream_data.data.edit_content
455
+ if out:
456
+ # 使用正则表达式分割,支持多行
457
+ parts = re.split(r'</details>', out)
458
+ if len(parts) > 1:
459
+ content = parts[1]
460
+ if content:
461
+ debug_log(f"发送普通内容: {content}")
462
+ chunk = OpenAIResponse(
463
+ id=f"chatcmpl-{int(time.time())}",
464
+ object="chat.completion.chunk",
465
+ created=int(time.time()),
466
+ model=DEFAULT_MODEL_NAME,
467
+ choices=[Choice(
468
+ index=0,
469
+ delta=Delta(content=content)
470
+ )]
471
+ )
472
+ yield f"data: {chunk.model_dump_json()}\n\n"
473
+ sent_initial_answer = True
474
+
475
+ # 处理DeltaContent
476
+ if upstream_data.data.delta_content:
477
+ out = upstream_data.data.delta_content
478
+
479
+ if upstream_data.data.phase == "thinking":
480
+ out = transform_thinking(out)
481
+ # 思考内容使用 reasoning_content 字段
482
+ if out:
483
+ debug_log(f"发送思考内容: {out}")
484
  chunk = OpenAIResponse(
485
  id=f"chatcmpl-{int(time.time())}",
486
  object="chat.completion.chunk",
 
488
  model=DEFAULT_MODEL_NAME,
489
  choices=[Choice(
490
  index=0,
491
+ delta=Delta(reasoning_content=out)
492
+ )]
493
+ )
494
+ yield f"data: {chunk.model_dump_json()}\n\n"
495
+ else:
496
+ # 普通内容使用 content 字段
497
+ if out:
498
+ debug_log(f"发送普通内容: {out}")
499
+ chunk = OpenAIResponse(
500
+ id=f"chatcmpl-{int(time.time())}",
501
+ object="chat.completion.chunk",
502
+ created=int(time.time()),
503
+ model=DEFAULT_MODEL_NAME,
504
+ choices=[Choice(
505
+ index=0,
506
+ delta=Delta(content=out)
507
  )]
508
  )
509
  yield f"data: {chunk.model_dump_json()}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
 
511
+ # 检查是否结束
512
+ if upstream_data.data.done or upstream_data.data.phase == "done":
513
+ debug_log("检测到流结束信号")
514
+
515
+ # 发送结束chunk
516
+ end_chunk = OpenAIResponse(
517
+ id=f"chatcmpl-{int(time.time())}",
518
+ object="chat.completion.chunk",
519
+ created=int(time.time()),
520
+ model=DEFAULT_MODEL_NAME,
521
+ choices=[Choice(
522
+ index=0,
523
+ delta=Delta(),
524
+ finish_reason="stop"
525
+ )]
526
+ )
527
+ yield f"data: {end_chunk.model_dump_json()}\n\n"
528
+ yield "data: [DONE]\n\n"
529
+ debug_log(f"流式响应完成,共处理{line_count}行")
530
+ break
531
+ except Exception as e:
532
+ debug_log(f"读取SSE流时发生错误: {e}")
533
+ yield f"data: {{\"error\": \"Stream reading error\", \"type\": \"stream_error\"}}\n\n"
534
 
535
 
536
  async def handle_non_stream_response(upstream_req: UpstreamRequest, chat_id: str, auth_token: str) -> JSONResponse:
 
553
  full_content = []
554
  debug_log("开始收集完整响应内容")
555
 
556
+ try:
557
+ async for line in response.aiter_lines():
558
+ if not line.startswith("data: "):
559
+ continue
 
 
 
 
 
 
 
 
 
 
 
560
 
561
+ data_str = line[6:]
562
+ if not data_str:
563
+ continue
564
 
565
+ try:
566
+ upstream_data = UpstreamData.model_validate_json(data_str)
567
+ except Exception:
568
+ continue
569
+
570
+ if upstream_data.data.delta_content:
571
+ out = upstream_data.data.delta_content
572
+
573
+ if upstream_data.data.phase == "thinking":
574
+ out = transform_thinking(out)
575
+
576
+ if out:
577
+ full_content.append(out)
578
+
579
+ if upstream_data.data.done or upstream_data.data.phase == "done":
580
+ debug_log("检测到完成信号,停止收集")
581
+ break
582
+ except Exception as e:
583
+ debug_log(f"读取响应流时发生错误: {e}")
584
+ raise HTTPException(status_code=502, detail="Failed to read upstream response")
585
 
586
  final_content = "".join(full_content)
587
  debug_log(f"内容收集完成,最终长度: {len(final_content)}")
 
604
  )
605
 
606
  debug_log("非流式响应发送完成")
607
+ # 添加CORS头
608
+ headers = {
609
+ "Access-Control-Allow-Origin": "*",
610
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
611
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
612
+ "Access-Control-Allow-Credentials": "true"
613
+ }
614
+ return JSONResponse(
615
+ content=response_data.model_dump(exclude_none=True),
616
+ headers=headers
617
+ )
618
 
619
 
620
  # 根路径处理器
 
623
  return {"message": "OpenAI Compatible API Server"}
624
 
625
 
626
+ # 根路径处理器
627
+ @app.get("/")
628
+ async def root():
629
+ return {"message": "OpenAI Compatible API Server"}
630
+
631
+
632
+ # 健康检查接口
633
+ @app.get("/health")
634
+ async def health_check():
635
+ return {"status": "ok", "timestamp": int(time.time())}
636
+
637
+
638
  if __name__ == "__main__":
639
  import uvicorn
640
+ print(f"OpenAI兼容API服务器启动在端口 {PORT}")
641
+ print(f"模型: {DEFAULT_MODEL_NAME}")
642
+ print(f"上游: {UPSTREAM_URL}")
643
+ print(f"Debug模式: {DEBUG_MODE}")
644
+ uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=DEBUG_MODE)