xidu commited on
Commit
58763e7
·
1 Parent(s): 0806587

deploy: Final attempt with user-provided code and google-genai

Browse files
Files changed (3) hide show
  1. README.md +2 -1
  2. app.py +438 -134
  3. requirements.txt +1 -1
README.md CHANGED
@@ -10,4 +10,5 @@ This is a professional-grade FastAPI application that proxies requests to the Go
10
 
11
  - **`POST /v1/chat/completions`**: Main endpoint for chat, supports streaming.
12
  - **`GET /v1/models`**: Lists available models.
13
- - **`GET /`**: Health check.
 
 
10
 
11
  - **`POST /v1/chat/completions`**: Main endpoint for chat, supports streaming.
12
  - **`GET /v1/models`**: Lists available models.
13
+ - **`GET /health`**: Health check.
14
+ - **`GET /`**: API Info.
app.py CHANGED
@@ -3,18 +3,21 @@ import json
3
  import time
4
  import asyncio
5
  import os
6
- import random
 
7
  from contextlib import asynccontextmanager
8
- from typing import List
9
 
10
  import uvicorn
11
  from fastapi import FastAPI, Request, HTTPException
12
- from fastapi.responses import StreamingResponse, JSONResponse
13
  from fastapi.middleware.cors import CORSMiddleware
14
- # 正确的导入方式
15
- import google.generativeai as genai
 
 
16
 
17
- # 配置日志
18
  logging.basicConfig(
19
  level=logging.INFO,
20
  format='%(asctime)s [%(levelname)s]: %(message)s',
@@ -22,79 +25,114 @@ logging.basicConfig(
22
  )
23
  logger = logging.getLogger(__name__)
24
 
25
-
26
- # !!! 重要:管理您的 API 密钥 !!!
27
- # 为了安全,强烈建议您通过 Hugging Face Space 的 "Settings" -> "Secrets"
28
- # 来设置您的 API 密钥。例如,创建一个名为 "GOOGLE_API_KEYS" 的 Secret,
29
- # 它的值是 "key1,key2,key3"
30
- # 然后使用下面这行代码来读取它们:
31
- # api_keys_str = os.environ.get("GOOGLE_API_KEYS", "")
32
- # API_KEYS = [key.strip() for key in api_keys_str.split(',') if key.strip()]
33
-
34
- # 为了方便首次测试,您可以先临时使用下面的列表:
35
- API_KEYS = [
36
- "AIzaSyCJGYHjn3m41mYpft9j3G9-RXWkDAcAsPs", # 请替换成您的第一个有效密钥
37
- "AIzaSyCoDA9F9bsCXJx9p1CQqdKpeSO4n31DPt0", # 请替换成您的第二个有效密钥
38
- ]
39
-
40
 
41
  # 支持的模型列表
42
  SUPPORTED_MODELS = [
43
- {"id": "gemini-1.5-flash-latest", "object": "model", "owned_by": "google"},
44
- {"id": "gemini-pro", "object": "model", "owned_by": "google"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  ]
46
 
 
47
  def get_model_name(requested_model: str) -> str:
48
- if any(model['id'] == requested_model for model in SUPPORTED_MODELS):
49
- return requested_model
50
- return "gemini-1.5-flash-latest"
51
 
52
- def convert_messages_to_gemini_format(messages: List[dict]):
53
- gemini_messages = []
54
  system_instruction = None
 
55
  for message in messages:
56
- role = message.get("role")
57
- content = message.get("content")
 
58
  if role == "system":
59
  system_instruction = content
60
- elif role == "user":
61
- gemini_messages.append({'role': 'user', 'parts': [{'text': content}]})
62
  elif role == "assistant":
63
- gemini_messages.append({'role': 'model', 'parts': [{'text': content}]})
64
- return gemini_messages, system_instruction
 
 
 
 
 
 
 
65
 
 
66
 
67
- def handle_error_response(error):
 
 
68
  error_str = str(error).lower()
69
- if "api_key_invalid" in error_str:
70
- return "提供的 API 密钥无效。", "invalid_request_error"
71
  if "prompt_feedback" in error_str:
72
- return "请求因安全或内容策略被拒绝。", "content_filter"
73
- return f"发生未知错误: {error}", "stop"
 
 
 
 
 
 
74
 
75
- def get_api_key():
76
- if not API_KEYS or all("YOUR_GOOGLE_API_KEY" in key for key in API_KEYS):
77
- raise ValueError("API 密钥列表为空或未配置。请在 app.py 中设置它们或使用 Space Secrets。")
78
- return random.choice(API_KEYS)
79
 
80
  @asynccontextmanager
81
  async def lifespan(app: FastAPI):
82
- logger.info("应用启动...")
83
  try:
84
- if not API_KEYS or all("YOUR_GOOGLE_API_KEY" in key for key in API_KEYS):
85
- logger.warning("警告:未检测到有效的 Google API 密钥。应用可能会在处理请求时失败。")
86
- else:
87
- # 启动时用一个密钥测试配置是否正确
88
- genai.configure(api_key=API_KEYS[0])
89
- logger.info(f"已加载 {len(API_KEYS)} 个 API 密钥,并成功配置。")
90
  except Exception as e:
91
- logger.error(f"Gemini 配置失败: {e}")
92
- yield
93
- logger.info("应用关闭。")
 
94
 
95
 
96
- app = FastAPI(lifespan=lifespan, title="Gemini API Proxy", version="1.5.0")
 
 
 
 
 
97
 
 
98
  app.add_middleware(
99
  CORSMiddleware,
100
  allow_origins=["*"],
@@ -103,102 +141,368 @@ app.add_middleware(
103
  allow_headers=["*"],
104
  )
105
 
106
- # 使用 genai.types 来访问类型定义
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  SAFETY_SETTINGS = [
108
- genai.types.SafetySetting(category=cat, threshold=genai.types.HarmBlockThreshold.BLOCK_NONE)
109
- for cat in [
110
- genai.types.HarmCategory.HARM_CATEGORY_HARASSMENT,
111
- genai.types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
112
- genai.types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
113
- genai.types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
114
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  ]
116
 
117
- async def stream_generator(model_name: str, gemini_messages, generation_config):
118
- try:
119
- genai.configure(api_key=get_api_key())
120
- model = genai.GenerativeModel(model_name)
121
-
122
- async for chunk in await model.generate_content_async(
123
- contents=gemini_messages,
124
- generation_config=generation_config,
125
- stream=True
126
- ):
127
- if chunk.text:
128
- data = {
129
- "id": f"chatcmpl-{int(time.time())}", "object": "chat.completion.chunk",
130
- "created": int(time.time()), "model": model_name,
131
- "choices": [{"delta": {"content": chunk.text}}],
132
- }
133
- yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
134
 
135
- except Exception as e:
136
- logger.error(f"流式响应生成时出错: {e}")
137
- error_message, finish_reason = handle_error_response(e)
138
- error_data = {
139
- "id": f"chatcmpl-{int(time.time())}", "object": "chat.completion.chunk",
140
- "created": int(time.time()), "model": model_name,
141
- "choices": [{"delta": {"content": error_message}, "finish_reason": finish_reason}],
142
- }
143
- yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n"
144
-
145
- finally:
146
- final_data = {
147
- "id": f"chatcmpl-{int(time.time())}", "object": "chat.completion.chunk",
148
- "created": int(time.time()), "model": model_name,
149
- "choices": [{"delta": {}, "finish_reason": "stop"}],
150
- }
151
- yield f"data: {json.dumps(final_data, ensure_ascii=False)}\n\n"
152
- yield "data: [DONE]\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
 
155
  @app.post("/v1/chat/completions")
156
  async def chat_completions(request: Request):
157
- body = await request.json()
158
- model_name = get_model_name(body.get("model", "gemini-1.5-flash-latest"))
159
- messages = body.get("messages", [])
160
- gemini_messages, system_instruction = convert_messages_to_gemini_format(messages)
161
-
162
- generation_config = genai.types.GenerationConfig(
163
- temperature=body.get("temperature", 0.7),
164
- top_p=body.get("top_p", 1.0),
165
- max_output_tokens=body.get("max_tokens", 8192),
166
- )
167
-
168
- if system_instruction:
169
- generation_config.system_instruction = system_instruction
170
-
171
- generation_config.safety_settings = SAFETY_SETTINGS
172
-
173
- if body.get("stream", False):
174
- return StreamingResponse(
175
- stream_generator(model_name, gemini_messages, generation_config),
176
- media_type="text/event-stream",
177
  )
178
- else:
179
- try:
180
- genai.configure(api_key=get_api_key())
181
- model = genai.GenerativeModel(model_name)
182
- response = await model.generate_content_async(
183
- contents=gemini_messages,
184
- generation_config=generation_config
185
  )
 
 
 
 
 
 
 
 
 
 
 
186
  return {
187
- "id": f"chatcmpl-{int(time.time())}", "object": "chat.completion",
188
- "created": int(time.time()), "model": model_name,
189
- "choices": [{"message": {"role": "assistant", "content": response.text}, "finish_reason": "stop"}],
190
- "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  }
 
 
 
 
 
 
192
  except Exception as e:
193
- logger.error(f"非流式响应生成时出错: {e}")
194
- error_message, _ = handle_error_response(e)
195
- return JSONResponse(status_code=500, content={"error": {"message": error_message}})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
 
198
  @app.get("/v1/models")
199
  async def list_models():
200
- return {"object": "list", "data": SUPPORTED_MODELS}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  @app.get("/")
203
- def read_root():
204
- return {"status": "ok", "message": "Gemini API Proxy is running."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import time
4
  import asyncio
5
  import os
6
+ import traceback
7
+ import sys
8
  from contextlib import asynccontextmanager
9
+ import random
10
 
11
  import uvicorn
12
  from fastapi import FastAPI, Request, HTTPException
13
+ from fastapi.responses import StreamingResponse
14
  from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import JSONResponse
16
+ from google import genai
17
+ from google.genai import types
18
+ from typing import Optional, List, Dict, Any
19
 
20
+ # 简化日志配置
21
  logging.basicConfig(
22
  level=logging.INFO,
23
  format='%(asctime)s [%(levelname)s]: %(message)s',
 
25
  )
26
  logger = logging.getLogger(__name__)
27
 
28
+ # 模型配置
29
+ GEMINI_MODELS = {
30
+ "gemini-2.0-flash-exp": "gemini-2.0-flash-exp",
31
+ "gemini-2.5-flash-preview-05-20": "gemini-2.5-flash-preview-05-20",
32
+ "gemini-2.5-flash": "gemini-2.5-flash",
33
+ "gemini-2.5-flash-preview-04-17": "gemini-2.5-flash-preview-04-17"
34
+ }
 
 
 
 
 
 
 
 
35
 
36
  # 支持的模型列表
37
  SUPPORTED_MODELS = [
38
+ {
39
+ "id": "gemini-2.5-flash-preview-05-20",
40
+ "object": "model",
41
+ "created": int(time.time()),
42
+ "owned_by": "google",
43
+ "permission": [],
44
+ "root": "gemini-2.5-flash-preview-05-20",
45
+ "parent": None,
46
+ "description": "Gemini 2.5 Flash Preview - 最新实验性模型"
47
+ },
48
+ {
49
+ "id": "gemini-2.5-flash-preview-04-17",
50
+ "object": "model",
51
+ "created": int(time.time()),
52
+ "owned_by": "google",
53
+ "permission": [],
54
+ "root": "gemini-2.5-flash-preview-04-17",
55
+ "parent": None,
56
+ "description": "gemini-2.5-flash-preview-04-17- 经典专业模型"
57
+ },
58
+ {
59
+ "id": "gemini-2.5-flash",
60
+ "object": "model",
61
+ "created": int(time.time()),
62
+ "owned_by": "google",
63
+ "permission": [],
64
+ "root": "gemini-2.5-flash",
65
+ "parent": None,
66
+ "description": "gemini-2.5-flash稳定经典专业模型"
67
+ }
68
  ]
69
 
70
+
71
  def get_model_name(requested_model: str) -> str:
72
+ """获取实际的Gemini模型名称"""
73
+ return GEMINI_MODELS.get(requested_model, "gemini-2.5-flash")
74
+
75
 
76
+ def convert_messages(messages):
77
+ content_parts = []
78
  system_instruction = None
79
+
80
  for message in messages:
81
+ role = message.get("role", "user")
82
+ content = message.get("content", "")
83
+
84
  if role == "system":
85
  system_instruction = content
 
 
86
  elif role == "assistant":
87
+ content_parts.append({
88
+ "role": "model",
89
+ "parts": [{"text": content}]
90
+ })
91
+ elif role == "user":
92
+ content_parts.append({
93
+ "role": "user",
94
+ "parts": [{"text": content}]
95
+ })
96
 
97
+ return content_parts, system_instruction
98
 
99
+
100
+ def handle_error(error):
101
+ """简化的错误处理"""
102
  error_str = str(error).lower()
103
+
 
104
  if "prompt_feedback" in error_str:
105
+ if "other" in error_str:
106
+ return "您的输入内容可能过长或触了安全策略。请尝试缩短您的问题。", "length"
107
+ elif "safety" in error_str:
108
+ return "您的请求被安全策略阻止。请尝��修改您的问题。", "content_filter"
109
+ elif "safety" in error_str:
110
+ return "您的请求被安全策略过滤。请尝试修改您的问题。", "content_filter"
111
+
112
+ return "生成内容时遇到错误。请稍后重试。", "stop"
113
 
 
 
 
 
114
 
115
  @asynccontextmanager
116
  async def lifespan(app: FastAPI):
 
117
  try:
118
+ setup_gemini() # 测试API密钥是否有效
119
+ logger.info("应用启动完成")
120
+ yield
 
 
 
121
  except Exception as e:
122
+ logger.error(f"应用启动失败: {str(e)}")
123
+ raise
124
+ finally:
125
+ logger.info("应用关闭")
126
 
127
 
128
+ # 创建FastAPI应用实例
129
+ app = FastAPI(
130
+ lifespan=lifespan,
131
+ title="Gemini Official API",
132
+ version="1.3.0"
133
+ )
134
 
135
+ # 添加CORS中间件
136
  app.add_middleware(
137
  CORSMiddleware,
138
  allow_origins=["*"],
 
141
  allow_headers=["*"],
142
  )
143
 
144
+ # API密钥列表
145
+ API_KEYS = [
146
+ 'AIzaSyCJGYHjn3m41mYpft9j3G9-RXWkDAcAsPs',
147
+ 'AIzaSyCoDA9F9bsCXJx9p1CQqdKpeSO4n31DPt0'
148
+ ]
149
+
150
+ # 当前使用的API密钥索引
151
+ current_key_index = 0
152
+
153
+
154
+ def get_random_api_key():
155
+ """获取随机API密钥"""
156
+ return random.choice(API_KEYS)
157
+
158
+
159
+ def setup_gemini(api_key=None):
160
+ """配置Gemini API"""
161
+ if not api_key:
162
+ api_key = get_random_api_key()
163
+
164
+ if not API_KEYS:
165
+ logger.error("请设置有效的API密钥列表")
166
+ raise ValueError("API_KEYS未设置")
167
+
168
+ client = genai.Client(api_key=api_key)
169
+ return client, api_key
170
+
171
+
172
+ # 配置安全设置
173
  SAFETY_SETTINGS = [
174
+ types.SafetySetting(
175
+ category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
176
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
177
+ ),
178
+ types.SafetySetting(
179
+ category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
180
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
181
+ ),
182
+ types.SafetySetting(
183
+ category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
184
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
185
+ ),
186
+ types.SafetySetting(
187
+ category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
188
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
189
+ ),
190
+ types.SafetySetting(
191
+ category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
192
+ threshold=types.HarmBlockThreshold.BLOCK_NONE,
193
+ ),
194
  ]
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ async def try_generate_content(model_name, content_parts, config, max_retries=3):
198
+ """带重试机制的内容生成"""
199
+ last_error = None
200
+ used_keys = set()
201
+
202
+ for attempt in range(max_retries):
203
+ try:
204
+ # 获取新的API密钥(避免重复使用失败的密钥)
205
+ available_keys = [key for key in API_KEYS if key not in used_keys]
206
+ if not available_keys:
207
+ # 如果所有密钥都试过了,重置使用记录
208
+ used_keys.clear()
209
+ available_keys = API_KEYS
210
+
211
+ api_key = random.choice(available_keys)
212
+ used_keys.add(api_key)
213
+
214
+ client, current_key = setup_gemini(api_key)
215
+ logger.info(f"尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")
216
+
217
+ response = client.models.generate_content(
218
+ model=model_name,
219
+ contents=content_parts,
220
+ config=config
221
+ )
222
+
223
+ return response, current_key
224
+
225
+ except Exception as e:
226
+ last_error = e
227
+ error_str = str(e).lower()
228
+
229
+ # 检查是否是需要重试的错误
230
+ if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):
231
+ logger.warning(f"第 {attempt + 1} 次尝试失败: {str(e)}")
232
+ if attempt < max_retries - 1:
233
+ await asyncio.sleep(1) # 等待1秒后重试
234
+ continue
235
+ else:
236
+ # 非网络错误,直接抛出
237
+ raise e
238
+
239
+ # 所有重试都失败了
240
+ raise last_error
241
 
242
 
243
  @app.post("/v1/chat/completions")
244
  async def chat_completions(request: Request):
245
+ """聊天对话接口"""
246
+ try:
247
+ body = await request.json()
248
+
249
+ messages = body.get('messages', [])
250
+ stream = body.get('stream', False)
251
+ max_tokens = body.get('max_tokens', 65536)
252
+ temperature = body.get('temperature', 1.2)
253
+ top_p = body.get('top_p', 0.0)
254
+ requested_model = body.get('model', 'gemini-2.5-flash')
255
+
256
+ model_name = get_model_name(requested_model)
257
+ content_parts, system_instruction = convert_messages(messages)
258
+
259
+ config = types.GenerateContentConfig(
260
+ max_output_tokens=max_tokens,
261
+ temperature=temperature,
262
+ top_p=top_p,
263
+ system_instruction=system_instruction,
264
+ safety_settings=SAFETY_SETTINGS,
265
  )
266
+
267
+ if stream:
268
+ # 流式响应也需要重试机制
269
+ client, api_key = setup_gemini()
270
+ return StreamingResponse(
271
+ stream_response_with_retry(client, model_name, content_parts, config),
272
+ media_type='text/event-stream'
273
  )
274
+ else:
275
+ response, used_key = await try_generate_content(model_name, content_parts, config)
276
+
277
+ response_text = response.text if response else ""
278
+ finish_reason = "stop"
279
+
280
+ if not response_text:
281
+ response_text = "无法生成回复。请尝试修改您的问题。"
282
+
283
+ logger.info(f"成功生成回复,使用密钥: {used_key[:20]}...")
284
+
285
  return {
286
+ 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',
287
+ 'object': 'chat.completion',
288
+ 'created': int(time.time()),
289
+ 'model': requested_model,
290
+ 'choices': [{
291
+ 'index': 0,
292
+ 'message': {
293
+ 'role': 'assistant',
294
+ 'content': response_text
295
+ },
296
+ 'finish_reason': finish_reason
297
+ }],
298
+ 'usage': {
299
+ 'prompt_tokens': len(content_parts),
300
+ 'completion_tokens': len(response_text.split()),
301
+ 'total_tokens': len(content_parts) + len(response_text.split())
302
+ }
303
+ }
304
+
305
+ except Exception as e:
306
+ logger.error(f"处理聊天请求出错: {str(e)}")
307
+ error_message, finish_reason = handle_error(e)
308
+ raise HTTPException(status_code=500, detail=str(e))
309
+
310
+
311
+ async def stream_response_with_retry(client, model_name, content_parts, config, max_retries=3):
312
+ """带重试机制的流式响应生成器"""
313
+ last_error = None
314
+ used_keys = set()
315
+
316
+ for attempt in range(max_retries):
317
+ try:
318
+ # 获取新的API密钥(避免重复使用失败的密钥)
319
+ available_keys = [key for key in API_KEYS if key not in used_keys]
320
+ if not available_keys:
321
+ used_keys.clear()
322
+ available_keys = API_KEYS
323
+
324
+ api_key = random.choice(available_keys)
325
+ used_keys.add(api_key)
326
+
327
+ current_client, current_key = setup_gemini(api_key)
328
+ logger.info(f"流式响应尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")
329
+
330
+ for chunk in current_client.models.generate_content_stream(
331
+ model=model_name,
332
+ contents=content_parts,
333
+ config=config
334
+ ):
335
+ # 确保chunk存在且有文本内容
336
+ if chunk and hasattr(chunk, 'text') and chunk.text:
337
+ data = {
338
+ 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',
339
+ 'object': 'chat.completion.chunk',
340
+ 'created': int(time.time()),
341
+ 'model': model_name,
342
+ 'choices': [{
343
+ 'index': 0,
344
+ 'delta': {
345
+ 'role': 'assistant',
346
+ 'content': chunk.text
347
+ },
348
+ 'finish_reason': None
349
+ }]
350
+ }
351
+ yield f'data: {json.dumps(data, ensure_ascii=False)}\n\n'
352
+ await asyncio.sleep(0.01)
353
+
354
+ # 发送结束标记
355
+ final_data = {
356
+ 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',
357
+ 'object': 'chat.completion.chunk',
358
+ 'created': int(time.time()),
359
+ 'model': model_name,
360
+ 'choices': [{
361
+ 'index': 0,
362
+ 'delta': {},
363
+ 'finish_reason': 'stop'
364
+ }]
365
  }
366
+ yield f'data: {json.dumps(final_data, ensure_ascii=False)}\n\n'
367
+ yield 'data: [DONE]\n\n'
368
+
369
+ logger.info(f"流式响应成功,使用密钥: {current_key[:20]}...")
370
+ return # 成功完成,退出重试循环
371
+
372
  except Exception as e:
373
+ last_error = e
374
+ error_str = str(e).lower()
375
+
376
+ # 检查是否是需要重试的错误
377
+ if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):
378
+ logger.warning(f"流式响应第 {attempt + 1} 次尝试失败: {str(e)}")
379
+ if attempt < max_retries - 1:
380
+ await asyncio.sleep(1)
381
+ continue
382
+ else:
383
+ # 非网络错误,直接处理
384
+ break
385
+
386
+ # 所有重试都失败了,返回错误信息
387
+ logger.error(f"流式响应所有重试失败: {str(last_error)}")
388
+ error_message, finish_reason = handle_error(last_error)
389
+
390
+ error_data = {
391
+ 'id': f'chatcmpl-{int(time.time())}-error',
392
+ 'object': 'chat.completion.chunk',
393
+ 'created': int(time.time()),
394
+ 'model': model_name,
395
+ 'choices': [{
396
+ 'index': 0,
397
+ 'delta': {
398
+ 'role': 'assistant',
399
+ 'content': error_message
400
+ },
401
+ 'finish_reason': finish_reason
402
+ }]
403
+ }
404
+ yield f'data: {json.dumps(error_data, ensure_ascii=False)}\n\n'
405
+ yield 'data: [DONE]\n\n'
406
 
407
 
408
  @app.get("/v1/models")
409
  async def list_models():
410
+ """获取可用模型列表"""
411
+ try:
412
+ return {
413
+ "object": "list",
414
+ "data": SUPPORTED_MODELS
415
+ }
416
+ except Exception as e:
417
+ logger.error(f"获取模型列表出错: {str(e)}")
418
+ raise HTTPException(status_code=500, detail=str(e))
419
+
420
+
421
+ @app.get("/v1/models/{model_id}")
422
+ async def get_model_info(model_id: str):
423
+ """获取特定模型信息"""
424
+ try:
425
+ for model in SUPPORTED_MODELS:
426
+ if model["id"] == model_id:
427
+ return model
428
+ raise HTTPException(status_code=404, detail=f"模型 {model_id} 未找到")
429
+ except HTTPException:
430
+ raise
431
+ except Exception as e:
432
+ logger.error(f"获取模型信息出错: {str(e)}")
433
+ raise HTTPException(status_code=500, detail=str(e))
434
+
435
+
436
+ @app.get("/v1/chat/completions/v1/models")
437
+ async def list_models_alternative():
438
+ """获取可用模型列表 - 兼容路径"""
439
+ try:
440
+ return {
441
+ "object": "list",
442
+ "data": SUPPORTED_MODELS
443
+ }
444
+ except Exception as e:
445
+ logger.error(f"获取模型列表出错: {str(e)}")
446
+ raise HTTPException(status_code=500, detail=str(e))
447
+
448
+
449
+ @app.get("/health")
450
+ async def health_check():
451
+ """健康检查端点"""
452
+ try:
453
+ return {
454
+ "status": "healthy",
455
+ "timestamp": int(time.time()),
456
+ "api": "gemini-official",
457
+ "available_models": [model["id"] for model in SUPPORTED_MODELS],
458
+ "version": "1.3.0"
459
+ }
460
+ except Exception as e:
461
+ logger.error(f"健康检查失败: {str(e)}")
462
+ return {
463
+ "status": "unhealthy",
464
+ "timestamp": int(time.time()),
465
+ "error": str(e)
466
+ }
467
+
468
 
469
  @app.get("/")
470
+ async def root():
471
+ """根路径信息"""
472
+ return {
473
+ "name": "Gemini Official API",
474
+ "version": "1.3.0",
475
+ "description": "Google Gemini官方API接口服务",
476
+ "endpoints": {
477
+ "models": "/v1/models",
478
+ "models_alt": "/v1/chat/completions/v1/models",
479
+ "chat": "/v1/chat/completions",
480
+ "health": "/health"
481
+ }
482
+ }
483
+
484
+
485
+ @app.exception_handler(404)
486
+ async def not_found_handler(request: Request, exc: HTTPException):
487
+ """处理404错误"""
488
+ return {
489
+ "error": "未找到",
490
+ "requested_path": str(request.url.path),
491
+ "message": "请求的路径不存在",
492
+ "available_endpoints": {
493
+ "models": "/v1/models",
494
+ "models_alt": "/v1/chat/completions/v1/models",
495
+ "chat": "/v1/chat/completions",
496
+ "health": "/health",
497
+ "info": "/"
498
+ }
499
+ }
500
+
501
+
502
+ if __name__ == "__main__":
503
+ print("🚀 启动Gemini官方API服务器")
504
+ print(f"📊 支持的模型: {[model['id'] for model in SUPPORTED_MODELS]}")
505
+ print(f"🔑 已配置 {len(API_KEYS)} 个API密钥")
506
+ print("🔄 支持自动重试和密钥轮换")
507
+
508
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
  fastapi
2
  uvicorn
3
- google-genai==1.20.0
4
  loguru
5
  httpx
 
1
  fastapi
2
  uvicorn
3
+ google-genai
4
  loguru
5
  httpx