xidu commited on
Commit
41cd9eb
·
1 Parent(s): 5734f91

deploy: Launch ap3 service on port 7862

Browse files
Files changed (4) hide show
  1. Dockerfile +14 -0
  2. README.md +10 -6
  3. app.py +368 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY ./requirements.txt /app/requirements.txt
6
+ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
7
+
8
+ COPY ./app.py /app/
9
+
10
+ # Hugging Face 通过下面的 README.md 中的 app_port 来映射端口
11
+ EXPOSE 7862
12
+
13
+ # 直接运行 uvicorn,监听 7862 端口
14
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7862"]
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Ap3
3
- emoji: 📚
4
- colorFrom: purple
5
- colorTo: blue
6
  sdk: docker
7
- pinned: false
8
  ---
 
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
  ---
2
+ title: GenAI API Service (ap3)
3
+ emoji: 🛰️
4
+ colorFrom: red
5
+ colorTo: yellow
6
  sdk: docker
7
+ app_port: 7862
8
  ---
9
+ This is a professional-grade FastAPI application that proxies requests to the Google GenAI API on port 7862.
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 ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import json
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, JSONResponse
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ # 正确的导入方式
16
+ import google.generativeai as genai
17
+ from typing import Optional, List, Dict, Any
18
+
19
+ # 简化日志配置
20
+ logging.basicConfig(
21
+ level=logging.INFO,
22
+ format='%(asctime)s [%(levelname)s]: %(message)s',
23
+ datefmt='%Y-%m-%d %H:%M:%S'
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # 模型配置
28
+ GEMINI_MODELS = {
29
+ "gemini-2.0-flash-exp": "gemini-2.0-flash-exp",
30
+ "gemini-2.5-flash-preview-05-20": "gemini-2.5-flash-preview-05-20",
31
+ "gemini-2.5-flash": "gemini-2.5-flash",
32
+ "gemini-2.5-flash-preview-04-17": "gemini-2.5-flash-preview-04-17"
33
+ }
34
+
35
+ # 支持的模型列表
36
+ SUPPORTED_MODELS = [
37
+ {
38
+ "id": "gemini-2.5-flash-preview-05-20",
39
+ "object": "model",
40
+ "created": int(time.time()),
41
+ "owned_by": "google",
42
+ "permission": [],
43
+ "root": "gemini-2.5-flash-preview-05-20",
44
+ "parent": None,
45
+ "description": "Gemini 2.5 Flash Preview - 最新实验性模型"
46
+ },
47
+ {
48
+ "id": "gemini-2.5-flash-preview-04-17",
49
+ "object": "model",
50
+ "created": int(time.time()),
51
+ "owned_by": "google",
52
+ "permission": [],
53
+ "root": "gemini-2.5-flash-preview-04-17",
54
+ "parent": None,
55
+ "description": "gemini-2.5-flash-preview-04-17- 经典专业模型"
56
+ },
57
+ {
58
+ "id": "gemini-2.5-flash",
59
+ "object": "model",
60
+ "created": int(time.time()),
61
+ "owned_by": "google",
62
+ "permission": [],
63
+ "root": "gemini-2.5-flash",
64
+ "parent": None,
65
+ "description": "gemini-2.5-flash稳定经典专业模型"
66
+ }
67
+ ]
68
+
69
+
70
+ def get_model_name(requested_model: str) -> str:
71
+ """获取实际的Gemini模型名称"""
72
+ return GEMINI_MODELS.get(requested_model, "gemini-2.5-flash")
73
+
74
+
75
+ def convert_messages(messages):
76
+ content_parts = []
77
+ system_instruction = None
78
+
79
+ for message in messages:
80
+ role = message.get("role", "user")
81
+ content = message.get("content", "")
82
+
83
+ if role == "system":
84
+ system_instruction = content
85
+ elif role == "assistant":
86
+ content_parts.append({
87
+ "role": "model",
88
+ "parts": [{"text": content}]
89
+ })
90
+ elif role == "user":
91
+ content_parts.append({
92
+ "role": "user",
93
+ "parts": [{"text": content}]
94
+ })
95
+
96
+ return content_parts, system_instruction
97
+
98
+
99
+ def handle_error(error):
100
+ """简化的错误处理"""
101
+ error_str = str(error).lower()
102
+
103
+ if "prompt_feedback" in error_str:
104
+ if "other" in error_str:
105
+ return "您的输入内容可能过长或触发了安全策略。请尝试缩短您的问题。", "length"
106
+ elif "safety" in error_str:
107
+ return "您的请求被安全策略阻止。请尝试修改您的问题。", "content_filter"
108
+ elif "safety" in error_str:
109
+ return "您的请求被安全策略过滤。请尝试修改您的问题。", "content_filter"
110
+
111
+ return "生成内容时遇到错误。请稍后重试。", "stop"
112
+
113
+ def get_random_api_key():
114
+ """获取随机API密钥"""
115
+ return random.choice(API_KEYS)
116
+
117
+
118
+ def setup_gemini(api_key=None):
119
+ """配置Gemini API"""
120
+ if not api_key:
121
+ api_key = get_random_api_key()
122
+
123
+ if not API_KEYS:
124
+ logger.error("请设置有效的API密钥列表")
125
+ raise ValueError("API_KEYS未设置")
126
+ genai.configure(api_key=api_key)
127
+ return api_key
128
+
129
+ @asynccontextmanager
130
+ async def lifespan(app: FastAPI):
131
+ try:
132
+ setup_gemini()
133
+ logger.info("应用启动完成")
134
+ yield
135
+ except Exception as e:
136
+ logger.error(f"应用启动失败: {str(e)}")
137
+ finally:
138
+ logger.info("应用关闭")
139
+
140
+
141
+ # 创建FastAPI应用实例
142
+ app = FastAPI(
143
+ lifespan=lifespan,
144
+ title="Gemini Official API (ap3)",
145
+ version="1.3.0"
146
+ )
147
+
148
+ # 添加CORS中间件
149
+ app.add_middleware(
150
+ CORSMiddleware,
151
+ allow_origins=["*"],
152
+ allow_credentials=True,
153
+ allow_methods=["*"],
154
+ allow_headers=["*"],
155
+ )
156
+
157
+ # API密钥列表 (已更新为您提供的新密钥)
158
+ API_KEYS = [
159
+ 'AIzaSyDglvTu5EbXfcBMsv1gBjSH7TOtZxNsQuo',
160
+ 'AIzaSyBOT0mNoemVm-VEwL3YNOAQu_tirwYJDyE'
161
+ ]
162
+
163
+
164
+ # 配置安全设置
165
+ SAFETY_SETTINGS = [
166
+ genai.types.SafetySetting(
167
+ category=genai.types.HarmCategory.HARM_CATEGORY_HARASSMENT,
168
+ threshold=genai.types.HarmBlockThreshold.BLOCK_NONE,
169
+ ),
170
+ genai.types.SafetySetting(
171
+ category=genai.types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
172
+ threshold=genai.types.HarmBlockThreshold.BLOCK_NONE,
173
+ ),
174
+ genai.types.SafetySetting(
175
+ category=genai.types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
176
+ threshold=genai.types.HarmBlockThreshold.BLOCK_NONE,
177
+ ),
178
+ genai.types.SafetySetting(
179
+ category=genai.types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
180
+ threshold=genai.types.HarmBlockThreshold.BLOCK_NONE,
181
+ ),
182
+ genai.types.SafetySetting(
183
+ category=genai.types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
184
+ threshold=genai.types.HarmBlockThreshold.BLOCK_NONE,
185
+ ),
186
+ ]
187
+
188
+
189
+ async def try_generate_content(model_name, content_parts, system_instruction, config, max_retries=3):
190
+ """带重试机制的内容生成"""
191
+ last_error = None
192
+ used_keys = set()
193
+ model = None
194
+
195
+ for attempt in range(max_retries):
196
+ try:
197
+ available_keys = [key for key in API_KEYS if key not in used_keys]
198
+ if not available_keys:
199
+ used_keys.clear()
200
+ available_keys = API_KEYS
201
+
202
+ api_key = random.choice(available_keys)
203
+ used_keys.add(api_key)
204
+
205
+ setup_gemini(api_key)
206
+ model = genai.GenerativeModel(model_name, system_instruction=system_instruction)
207
+ logger.info(f"尝试第 {attempt + 1} 次,使用密钥: {api_key[:10]}...")
208
+
209
+ response = await model.generate_content_async(
210
+ contents=content_parts,
211
+ generation_config=config,
212
+ safety_settings=SAFETY_SETTINGS,
213
+ )
214
+
215
+ return response, api_key
216
+
217
+ except Exception as e:
218
+ last_error = e
219
+ error_str = str(e).lower()
220
+
221
+ if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):
222
+ logger.warning(f"第 {attempt + 1} 次尝试失败: {str(e)}")
223
+ if attempt < max_retries - 1:
224
+ await asyncio.sleep(1)
225
+ continue
226
+ else:
227
+ raise e
228
+
229
+ raise last_error
230
+
231
+
232
+ @app.post("/v1/chat/completions")
233
+ async def chat_completions(request: Request):
234
+ """聊天对话接口"""
235
+ try:
236
+ body = await request.json()
237
+ messages = body.get('messages', [])
238
+ stream = body.get('stream', False)
239
+
240
+ requested_model = body.get('model', 'gemini-2.5-flash')
241
+ model_name = get_model_name(requested_model)
242
+
243
+ content_parts, system_instruction = convert_messages(messages)
244
+
245
+ config = genai.types.GenerationConfig(
246
+ max_output_tokens=body.get('max_tokens', 65536),
247
+ temperature=body.get('temperature', 1.0),
248
+ top_p=body.get('top_p', 1.0),
249
+ )
250
+
251
+ if stream:
252
+ return StreamingResponse(
253
+ stream_response_with_retry(model_name, content_parts, system_instruction, config),
254
+ media_type='text/event-stream'
255
+ )
256
+ else:
257
+ response, used_key = await try_generate_content(model_name, content_parts, system_instruction, config)
258
+ response_text = response.text if response else ""
259
+ finish_reason = "stop"
260
+
261
+ if not response_text:
262
+ response_text = "无法生成回复。请尝试修改您的问题。"
263
+
264
+ logger.info(f"成功生成回复,使用密钥: {used_key[:10]}...")
265
+ return {
266
+ 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',
267
+ 'object': 'chat.completion', 'created': int(time.time()), 'model': requested_model,
268
+ 'choices': [{'index': 0, 'message': {'role': 'assistant', 'content': response_text}, 'finish_reason': finish_reason}],
269
+ 'usage': {'prompt_tokens': 0, 'completion_tokens': 0, 'total_tokens': 0}
270
+ }
271
+ except Exception as e:
272
+ logger.error(f"处理聊天请求出错: {traceback.format_exc()}")
273
+ error_message, finish_reason = handle_error(e)
274
+ raise HTTPException(status_code=500, detail=error_message)
275
+
276
+
277
+ async def stream_response_with_retry(model_name, content_parts, system_instruction, config, max_retries=3):
278
+ """带重试机制的流式响应生成器"""
279
+ last_error = None
280
+ used_keys = set()
281
+ for attempt in range(max_retries):
282
+ try:
283
+ available_keys = [key for key in API_KEYS if key not in used_keys]
284
+ if not available_keys:
285
+ used_keys.clear()
286
+ available_keys = API_KEYS
287
+ api_key = random.choice(available_keys)
288
+ used_keys.add(api_key)
289
+
290
+ setup_gemini(api_key)
291
+ model = genai.GenerativeModel(model_name, system_instruction=system_instruction)
292
+ logger.info(f"流式响应尝试第 {attempt + 1} 次,使用密钥: {api_key[:10]}...")
293
+
294
+ async for chunk in await model.generate_content_async(contents=content_parts, generation_config=config, safety_settings=SAFETY_SETTINGS, stream=True):
295
+ if chunk and hasattr(chunk, 'text') and chunk.text:
296
+ data = {'id': f'chatcmpl-{int(time.time())}', 'object': 'chat.completion.chunk', 'created': int(time.time()), 'model': model_name, 'choices': [{'index': 0, 'delta': {'role': 'assistant', 'content': chunk.text}, 'finish_reason': None}]}
297
+ yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
298
+
299
+ final_data = {'id': f'chatcmpl-{int(time.time())}', 'object': 'chat.completion.chunk', 'created': int(time.time()), 'model': model_name, 'choices': [{'index': 0, 'delta': {}, 'finish_reason': 'stop'}]}
300
+ yield f'data: {json.dumps(final_data, ensure_ascii=False)}\n\n'
301
+ yield 'data: [DONE]\n\n'
302
+
303
+ logger.info(f"流式响应成功,使用密钥: {api_key[:10]}...")
304
+ return
305
+ except Exception as e:
306
+ last_error = e
307
+ error_str = str(e).lower()
308
+ if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):
309
+ logger.warning(f"流式响应第 {attempt + 1} 次尝试失败: {str(e)}")
310
+ if attempt < max_retries - 1:
311
+ await asyncio.sleep(1)
312
+ continue
313
+ else:
314
+ break
315
+ logger.error(f"流式响应所有重试失败: {traceback.format_exc()}")
316
+ error_message, finish_reason = handle_error(last_error)
317
+ error_data = {'id': f'chatcmpl-{int(time.time())}-error', 'object': 'chat.completion.chunk', 'created': int(time.time()), 'model': model_name, 'choices': [{'index': 0, 'delta': {'role': 'assistant', 'content': error_message}, 'finish_reason': finish_reason}]}
318
+ yield f'data: {json.dumps(error_data, ensure_ascii=False)}\n\n'
319
+ yield 'data: [DONE]\n\n'
320
+
321
+
322
+ @app.get("/v1/models")
323
+ async def list_models():
324
+ return {"object": "list", "data": SUPPORTED_MODELS}
325
+
326
+ @app.get("/v1/models/{model_id}")
327
+ async def get_model_info(model_id: str):
328
+ for model in SUPPORTED_MODELS:
329
+ if model["id"] == model_id:
330
+ return model
331
+ raise HTTPException(status_code=404, detail=f"模型 {model_id} 未找到")
332
+
333
+ @app.get("/v1/chat/completions/v1/models")
334
+ async def list_models_alternative():
335
+ return {"object": "list", "data": SUPPORTED_MODELS}
336
+
337
+ @app.get("/health")
338
+ async def health_check():
339
+ return {"status": "healthy", "timestamp": int(time.time()), "api": "gemini-official", "available_models": [model["id"] for model in SUPPORTED_MODELS], "version": "1.3.0"}
340
+
341
+ @app.get("/")
342
+ async def root():
343
+ return {"name": "Gemini Official API (ap3)", "version": "1.3.0", "description": "Google Gemini官方API接口服务", "endpoints": {"models": "/v1/models", "models_alt": "/v1/chat/completions/v1/models", "chat": "/v1/chat/completions", "health": "/health"}}
344
+
345
+ @app.exception_handler(404)
346
+ async def not_found_handler(request: Request, exc: HTTPException):
347
+ """处理404错误"""
348
+ content = {
349
+ "error": "未找到",
350
+ "requested_path": str(request.url.path),
351
+ "message": "请求的路径不存在",
352
+ "available_endpoints": {
353
+ "models": "/v1/models",
354
+ "models_alt": "/v1/chat/completions/v1/models",
355
+ "chat": "/v1/chat/completions",
356
+ "health": "/health",
357
+ "info": "/"
358
+ }
359
+ }
360
+ return JSONResponse(status_code=404, content=content)
361
+
362
+ if __name__ == "__main__":
363
+ port = int(os.environ.get("PORT", 7862))
364
+ print(f"🚀 启动Gemini官方API服务器于端口 {port}")
365
+ print(f"📊 支持的模型: {[model['id'] for model in SUPPORTED_MODELS]}")
366
+ print(f"🔑 已配置 {len(API_KEYS)} 个API密钥")
367
+ print("🔄 支持自动重试和密钥轮换")
368
+ uvicorn.run("app:app", host="0.0.0.0", port=port, reload=True)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ google-genai
4
+ loguru
5
+ httpx