dacz123/ap1
0
1import logging2import json3import time4import asyncio5import os6import traceback7import sys8from contextlib import asynccontextmanager9import random10 11import uvicorn12from fastapi import FastAPI, Request, HTTPException13from fastapi.responses import StreamingResponse14from fastapi.middleware.cors import CORSMiddleware15from fastapi.responses import JSONResponse16from google import genai17from google.genai import types18from typing import Optional, List, Dict, Any19 20# 简化日志配置21logging.basicConfig(22 level=logging.INFO,23 format='%(asctime)s [%(levelname)s]: %(message)s',24 datefmt='%Y-%m-%d %H:%M:%S'25)26logger = logging.getLogger(__name__)27 28# 模型配置29GEMINI_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 "gemini-2.5-pro": "gemini-2.5-pro"35}36 37# 支持的模型列表38SUPPORTED_MODELS = [39 {40 "id": "gemini-2.5-flash-preview-05-20",41 "object": "model",42 "created": int(time.time()),43 "owned_by": "google",44 "permission": [],45 "root": "gemini-2.5-flash-preview-05-20",46 "parent": None,47 "description": "Gemini 2.5 Flash Preview - 最新实验性模型"48 },49 {50 "id": "gemini-2.5-flash-preview-04-17",51 "object": "model",52 "created": int(time.time()),53 "owned_by": "google",54 "permission": [],55 "root": "gemini-2.5-flash-preview-04-17",56 "parent": None,57 "description": "gemini-2.5-flash-preview-04-17- 经典专业模型"58 },59 {60 "id": "gemini-2.5-flash",61 "object": "model",62 "created": int(time.time()),63 "owned_by": "google",64 "permission": [],65 "root": "gemini-2.5-flash",66 "parent": None,67 "description": "gemini-2.5-flash稳定经典专业模型"68 },69 {70 "id": "gemini-2.5-pro",71 "object": "model",72 "created": int(time.time()),73 "owned_by": "google",74 "permission": [],75 "root": "gemini-2.5-pro",76 "parent": None,77 "description": "gemini-2.5-pro稳定经典专业模型"78 }79]80 81 82def load_api_keys():83 """从配置文件读取 API 密钥"""84 keys = []85 try:86 if os.path.exists('api_keys.txt'):87 with open('api_keys.txt', 'r', encoding='utf-8') as f:88 for line in f:89 line = line.strip()90 # 跳过空行和注释行91 if line and not line.startswith('#'):92 keys.append(line)93 94 if not keys:95 # 如果没有配置文件或文件为空,使用环境变量96 env_keys = os.environ.get('GEMINI_API_KEYS', '')97 if env_keys:98 keys = [key.strip() for key in env_keys.split(',') if key.strip()]99 100 if not keys:101 logger.error("没有找到有效的 API 密钥")102 raise ValueError("API_KEYS未设置")103 104 logger.info(f"成功加载 {len(keys)} 个 API 密钥")105 return keys106 107 except Exception as e:108 logger.error(f"读取 API 密钥失败: {e}")109 raise110 111# API密钥列表112API_KEYS = load_api_keys()113 114def get_random_api_key():115 return random.choice(API_KEYS)116 117def setup_gemini(api_key=None):118 if not api_key:119 api_key = get_random_api_key()120 121 if not API_KEYS:122 logger.error("请设置有效的API密钥列表")123 raise ValueError("API_KEYS未设置")124 125 client = genai.Client(api_key=api_key)126 return client, api_key127 128def get_model_name(requested_model: str) -> str:129 return GEMINI_MODELS.get(requested_model, "gemini-2.5-flash")130 131def convert_messages(messages):132 content_parts = []133 system_instruction = None134 135 for message in messages:136 role = message.get("role", "user")137 content = message.get("content", "")138 139 if role == "system":140 system_instruction = content141 elif role == "assistant":142 content_parts.append({143 "role": "model",144 "parts": [{"text": content}]145 })146 elif role == "user":147 content_parts.append({148 "role": "user",149 "parts": [{"text": content}]150 })151 152 return content_parts, system_instruction153 154def handle_error(error):155 error_str = str(error).lower()156 157 if "prompt_feedback" in error_str:158 if "other" in error_str:159 return "您的输入内容可能过长或触发了安全策略。请尝试缩短您的问题。", "length"160 elif "safety" in error_str:161 return "您的请求被安全策略阻止。请尝试修改您的问题。", "content_filter"162 elif "safety" in error_str:163 return "您的请求被安全策略过滤。请尝试修改您的问题。", "content_filter"164 165 return "生成内容时遇到错误。请稍后重试。", "stop"166 167@asynccontextmanager168async def lifespan(app: FastAPI):169 try:170 setup_gemini() # 测试API密钥是否有效171 logger.info("应用启动完成")172 yield173 except Exception as e:174 logger.error(f"应用启动失败: {str(e)}")175 raise176 finally:177 logger.info("应用关闭")178 179# 创建FastAPI应用实例180app = FastAPI(181 lifespan=lifespan,182 title="Gemini Official API",183 version="1.3.0"184)185 186# 添加CORS中间件187app.add_middleware(188 CORSMiddleware,189 allow_origins=["*"],190 allow_credentials=True,191 allow_methods=["*"],192 allow_headers=["*"],193)194 195# 配置安全设置196SAFETY_SETTINGS = [197 types.SafetySetting(198 category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,199 threshold=types.HarmBlockThreshold.BLOCK_NONE,200 ),201 types.SafetySetting(202 category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,203 threshold=types.HarmBlockThreshold.BLOCK_NONE,204 ),205 types.SafetySetting(206 category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,207 threshold=types.HarmBlockThreshold.BLOCK_NONE,208 ),209 types.SafetySetting(210 category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,211 threshold=types.HarmBlockThreshold.BLOCK_NONE,212 ),213 types.SafetySetting(214 category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,215 threshold=types.HarmBlockThreshold.BLOCK_NONE,216 ),217]218 219async def try_generate_content(model_name, content_parts, config, max_retries=3):220 last_error = None221 used_keys = set()222 223 for attempt in range(max_retries):224 try:225 # 获取新的API密钥(避免重复使用失败的密钥)226 available_keys = [key for key in API_KEYS if key not in used_keys]227 if not available_keys:228 # 如果所有密钥都试过了,重置使用记录229 used_keys.clear()230 available_keys = API_KEYS231 232 api_key = random.choice(available_keys)233 used_keys.add(api_key)234 235 client, current_key = setup_gemini(api_key)236 logger.info(f"尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")237 238 response = client.models.generate_content(239 model=model_name,240 contents=content_parts,241 config=config242 )243 244 return response, current_key245 246 except Exception as e:247 last_error = e248 error_str = str(e).lower()249 250 # 检查是否是需要重试的错误251 if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):252 logger.warning(f"第 {attempt + 1} 次尝试失败: {str(e)}")253 if attempt < max_retries - 1:254 await asyncio.sleep(1) # 等待1秒后重试255 continue256 else:257 # 非网络错误,直接抛出258 raise e259 260 # 所有重试都失败了261 raise last_error262 263@app.post("/v1/chat/completions")264async def chat_completions(request: Request):265 try:266 body = await request.json()267 268 messages = body.get('messages', [])269 stream = body.get('stream', False)270 max_tokens = body.get('max_tokens', 65536)271 temperature = body.get('temperature', 1.2)272 top_p = body.get('top_p', 0.0)273 requested_model = body.get('model', 'gemini-2.5-flash')274 model_name = get_model_name(requested_model)275 content_parts, system_instruction = convert_messages(messages)276 277 config = types.GenerateContentConfig(278 max_output_tokens=max_tokens,279 temperature=temperature,280 top_p=top_p,281 system_instruction=system_instruction,282 safety_settings=SAFETY_SETTINGS,283 )284 285 if stream:286 # 流式响应也需要重试机制287 client, api_key = setup_gemini()288 return StreamingResponse(289 stream_response_with_retry(client, model_name, content_parts, config),290 media_type='text/event-stream'291 )292 else:293 response, used_key = await try_generate_content(model_name, content_parts, config)294 295 response_text = response.text if response else ""296 finish_reason = "stop"297 298 if not response_text:299 response_text = "无法生成回复。请尝试修改您的问题。"300 301 logger.info(f"成功生成回复,使用密钥: {used_key[:20]}...")302 303 return {304 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',305 'object': 'chat.completion',306 'created': int(time.time()),307 'model': requested_model,308 'choices': [{309 'index': 0,310 'message': {311 'role': 'assistant',312 'content': response_text313 },314 'finish_reason': finish_reason315 }],316 'usage': {317 'prompt_tokens': len(content_parts),318 'completion_tokens': len(response_text.split()),319 'total_tokens': len(content_parts) + len(response_text.split())320 }321 }322 323 except Exception as e:324 logger.error(f"处理聊天请求出错: {str(e)}")325 error_message, finish_reason = handle_error(e)326 raise HTTPException(status_code=500, detail=str(e))327 328async def stream_response_with_retry(client, model_name, content_parts, config, max_retries=3):329 last_error = None330 used_keys = set()331 332 for attempt in range(max_retries):333 try:334 # 获取新的API密钥(避免重复使用失败的密钥)335 available_keys = [key for key in API_KEYS if key not in used_keys]336 if not available_keys:337 used_keys.clear()338 available_keys = API_KEYS339 340 api_key = random.choice(available_keys)341 used_keys.add(api_key)342 343 current_client, current_key = setup_gemini(api_key)344 logger.info(f"流式响应尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")345 346 for chunk in current_client.models.generate_content_stream(347 model=model_name,348 contents=content_parts,349 config=config350 ):351 # 确保chunk存在且有文本内容352 if chunk and hasattr(chunk, 'text') and chunk.text:353 data = {354 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',355 'object': 'chat.completion.chunk',356 'created': int(time.time()),357 'model': model_name,358 'choices': [{359 'index': 0,360 'delta': {361 'role': 'assistant',362 'content': chunk.text363 },364 'finish_reason': None365 }]366 }367 yield f'data: {json.dumps(data, ensure_ascii=False)}\n\n'368 await asyncio.sleep(0.01)369 370 # 发送结束标记371 final_data = {372 'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',373 'object': 'chat.completion.chunk',374 'created': int(time.time()),375 'model': model_name,376 'choices': [{377 'index': 0,378 'delta': {},379 'finish_reason': 'stop'380 }]381 }382 yield f'data: {json.dumps(final_data, ensure_ascii=False)}\n\n'383 yield 'data: [DONE]\n\n'384 385 logger.info(f"流式响应成功,使用密钥: {current_key[:20]}...")386 return # 成功完成,退出重试循环387 388 except Exception as e:389 last_error = e390 error_str = str(e).lower()391 392 # 检查是否是需要重试的错误393 if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):394 logger.warning(f"流式响应第 {attempt + 1} 次尝试失败: {str(e)}")395 if attempt < max_retries - 1:396 await asyncio.sleep(1)397 continue398 else:399 # 非网络错误,直接处理400 break401 402 # 所有重试都失败了,返回错误信息403 logger.error(f"流式响应所有重试失败: {str(last_error)}")404 error_message, finish_reason = handle_error(last_error)405 406 error_data = {407 'id': f'chatcmpl-{int(time.time())}-error',408 'object': 'chat.completion.chunk',409 'created': int(time.time()),410 'model': model_name,411 'choices': [{412 'index': 0,413 'delta': {414 'role': 'assistant',415 'content': error_message416 },417 'finish_reason': finish_reason418 }]419 }420 yield f'data: {json.dumps(error_data, ensure_ascii=False)}\n\n'421 yield 'data: [DONE]\n\n'422 423@app.get("/v1/models")424async def list_models():425 """返回符合 new-api 期望格式的模型列表"""426 try:427 # 返回 new-api 期望的格式,包含 success 字段428 return {429 "success": True,430 "data": SUPPORTED_MODELS431 }432 except Exception as e:433 logger.error(f"获取模型列表出错: {str(e)}")434 return {435 "success": False,436 "message": str(e),437 "data": []438 }439 440@app.get("/v1/models/{model_id}")441async def get_model_info(model_id: str):442 try:443 for model in SUPPORTED_MODELS:444 if model["id"] == model_id:445 return model446 raise HTTPException(status_code=404, detail=f"模型 {model_id} 未找到")447 except HTTPException:448 raise449 except Exception as e:450 logger.error(f"获取模型信息出错: {str(e)}")451 raise HTTPException(status_code=500, detail=str(e))452 453@app.get("/v1/chat/completions/v1/models")454async def list_models_alternative():455 """备用的模型列表端点,返回标准 OpenAI 格式"""456 try:457 return {458 "object": "list",459 "data": SUPPORTED_MODELS460 }461 except Exception as e:462 logger.error(f"获取模型列表出错: {str(e)}")463 raise HTTPException(status_code=500, detail=str(e))464 465@app.get("/health")466async def health_check():467 try:468 return {469 "status": "healthy",470 "timestamp": int(time.time()),471 "api": "gemini-official",472 "available_models": [model["id"] for model in SUPPORTED_MODELS],473 "version": "1.3.0"474 }475 except Exception as e:476 logger.error(f"健康检查失败: {str(e)}")477 return {478 "status": "unhealthy",479 "timestamp": int(time.time()),480 "error": str(e)481 }482 483@app.get("/")484async def root():485 return {486 "name": "Gemini Official API",487 "version": "1.3.0",488 "description": "Google Gemini官方API接口服务",489 "endpoints": {490 "models": "/v1/models",491 "models_alt": "/v1/chat/completions/v1/models",492 "chat": "/v1/chat/completions",493 "health": "/health"494 }495 }496 497@app.exception_handler(404)498async def not_found_handler(request: Request, exc: HTTPException):499 return JSONResponse(500 status_code=404,501 content={502 "error": "未找到",503 "requested_path": str(request.url.path),504 "message": "请求的路径不存在",505 "available_endpoints": {506 "models": "/v1/models",507 "models_alt": "/v1/chat/completions/v1/models",508 "chat": "/v1/chat/completions",509 "health": "/health",510 "info": "/"511 }512 }513 )514 515if __name__ == "__main__":516 print("🚀 启动Gemini官方API服务器")517 print(f"📊 支持的模型: {[model['id'] for model in SUPPORTED_MODELS]}")518 print(f"🔑 已配置 {len(API_KEYS)} 个API密钥")519 print("🔄 支持自动重试和密钥轮换")520 print("✅ 已适配 new-api 格式要求")521 522 uvicorn.run(app, host="0.0.0.0", port=7860) 