Team Ai
Apppublic

codex86/api5

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
main.py513 linesDownload Raw Back to root
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 get_model_name(requested_model: str) -> str:83    print(f"实际模型名称:{GEMINI_MODELS.get(requested_model)}")84    return GEMINI_MODELS.get(requested_model, "gemini-2.5-flash")85 86 87def convert_messages(messages):88    content_parts = []89    system_instruction = None90 91    for message in messages:92        role = message.get("role", "user")93        content = message.get("content", "")94 95        if role == "system":96            system_instruction = content97        elif role == "assistant":98            content_parts.append({99                "role": "model",100                "parts": [{"text": content}]101            })102        elif role == "user":103            content_parts.append({104                "role": "user",105                "parts": [{"text": content}]106            })107 108    return content_parts, system_instruction109 110 111def handle_error(error):112    error_str = str(error).lower()113 114    if "prompt_feedback" in error_str:115        if "other" in error_str:116            return "您的输入内容可能过长或触发了安全策略。请尝试缩短您的问题。", "length"117        elif "safety" in error_str:118            return "您的请求被安全策略阻止。请尝试修改您的问题。", "content_filter"119    elif "safety" in error_str:120        return "您的请求被安全策略过滤。请尝试修改您的问题。", "content_filter"121 122    return "生成内容时遇到错误。请稍后重试。", "stop"123 124 125@asynccontextmanager126async def lifespan(app: FastAPI):127    try:128        setup_gemini()  # 测试API密钥是否有效129        logger.info("应用启动完成")130        yield131    except Exception as e:132        logger.error(f"应用启动失败: {str(e)}")133        raise134    finally:135        logger.info("应用关闭")136 137 138# 创建FastAPI应用实例139app = FastAPI(140    lifespan=lifespan,141    title="Gemini Official API",142    version="1.3.0"143)144 145# 添加CORS中间件146app.add_middleware(147    CORSMiddleware,148    allow_origins=["*"],149    allow_credentials=True,150    allow_methods=["*"],151    allow_headers=["*"],152)153 154# API密钥列表155API_KEYS = [156    'AIzaSyAainmVAjjiQoUFqF1X3-5AI4_qUg1UkJw',157     'AIzaSyC68ywBZ0u-BYV1PmRbwldyymHBhRAqcpM'158]159 160# 当前使用的API密钥索引161current_key_index = 0162 163 164def get_random_api_key():165    return random.choice(API_KEYS)166 167 168def setup_gemini(api_key=None):169    if not api_key:170        api_key = get_random_api_key()171 172    if not API_KEYS:173        logger.error("请设置有效的API密钥列表")174        raise ValueError("API_KEYS未设置")175 176    client = genai.Client(api_key=api_key)177    return client, api_key178 179 180# 配置安全设置181SAFETY_SETTINGS = [182    types.SafetySetting(183        category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,184        threshold=types.HarmBlockThreshold.BLOCK_NONE,185    ),186    types.SafetySetting(187        category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,188        threshold=types.HarmBlockThreshold.BLOCK_NONE,189    ),190    types.SafetySetting(191        category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,192        threshold=types.HarmBlockThreshold.BLOCK_NONE,193    ),194    types.SafetySetting(195        category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,196        threshold=types.HarmBlockThreshold.BLOCK_NONE,197    ),198    types.SafetySetting(199        category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,200        threshold=types.HarmBlockThreshold.BLOCK_NONE,201    ),202]203 204 205async def try_generate_content(model_name, content_parts, config, max_retries=3):206    last_error = None207    used_keys = set()208 209    for attempt in range(max_retries):210        try:211            # 获取新的API密钥(避免重复使用失败的密钥)212            available_keys = [key for key in API_KEYS if key not in used_keys]213            if not available_keys:214                # 如果所有密钥都试过了,重置使用记录215                used_keys.clear()216                available_keys = API_KEYS217 218            api_key = random.choice(available_keys)219            used_keys.add(api_key)220 221            client, current_key = setup_gemini(api_key)222            logger.info(f"尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")223 224            response = client.models.generate_content(225                model=model_name,226                contents=content_parts,227                config=config228            )229 230            return response, current_key231 232        except Exception as e:233            last_error = e234            error_str = str(e).lower()235 236            # 检查是否是需要重试的错误237            if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):238                logger.warning(f"第 {attempt + 1} 次尝试失败: {str(e)}")239                if attempt < max_retries - 1:240                    await asyncio.sleep(1)  # 等待1秒后重试241                    continue242            else:243                # 非网络错误,直接抛出244                raise e245 246    # 所有重试都失败了247    raise last_error248 249 250@app.post("/v1/chat/completions")251async def chat_completions(request: Request):252    try:253        body = await request.json()254 255        messages = body.get('messages', [])256        stream = body.get('stream', False)257        max_tokens = body.get('max_tokens', 65536)258        temperature = body.get('temperature', 1.2)259        top_p = body.get('top_p', 0.0)260        requested_model = body.get('model', 'gemini-2.5-flash')261        model_name = get_model_name(requested_model)262        content_parts, system_instruction = convert_messages(messages)263 264        config = types.GenerateContentConfig(265            max_output_tokens=max_tokens,266            temperature=temperature,267            top_p=top_p,268            system_instruction=system_instruction,269            safety_settings=SAFETY_SETTINGS,270        )271 272        if stream:273            # 流式响应也需要重试机制274            client, api_key = setup_gemini()275            return StreamingResponse(276                stream_response_with_retry(client, model_name, content_parts, config),277                media_type='text/event-stream'278            )279        else:280            response, used_key = await try_generate_content(model_name, content_parts, config)281 282            response_text = response.text if response else ""283            finish_reason = "stop"284 285            if not response_text:286                response_text = "无法生成回复。请尝试修改您的问题。"287 288            logger.info(f"成功生成回复,使用密钥: {used_key[:20]}...")289 290            return {291                'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',292                'object': 'chat.completion',293                'created': int(time.time()),294                'model': requested_model,295                'choices': [{296                    'index': 0,297                    'message': {298                        'role': 'assistant',299                        'content': response_text300                    },301                    'finish_reason': finish_reason302                }],303                'usage': {304                    'prompt_tokens': len(content_parts),305                    'completion_tokens': len(response_text.split()),306                    'total_tokens': len(content_parts) + len(response_text.split())307                }308            }309 310    except Exception as e:311        logger.error(f"处理聊天请求出错: {str(e)}")312        error_message, finish_reason = handle_error(e)313        raise HTTPException(status_code=500, detail=str(e))314 315 316async def stream_response_with_retry(client, model_name, content_parts, config, max_retries=3):317    last_error = None318    used_keys = set()319 320    for attempt in range(max_retries):321        try:322            # 获取新的API密钥(避免重复使用失败的密钥)323            available_keys = [key for key in API_KEYS if key not in used_keys]324            if not available_keys:325                used_keys.clear()326                available_keys = API_KEYS327 328            api_key = random.choice(available_keys)329            used_keys.add(api_key)330 331            current_client, current_key = setup_gemini(api_key)332            logger.info(f"流式响应尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")333 334            for chunk in current_client.models.generate_content_stream(335                    model=model_name,336                    contents=content_parts,337                    config=config338            ):339                # 确保chunk存在且有文本内容340                if chunk and hasattr(chunk, 'text') and chunk.text:341                    data = {342                        'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',343                        'object': 'chat.completion.chunk',344                        'created': int(time.time()),345                        'model': model_name,346                        'choices': [{347                            'index': 0,348                            'delta': {349                                'role': 'assistant',350                                'content': chunk.text351                            },352                            'finish_reason': None353                        }]354                    }355                    yield f'data: {json.dumps(data, ensure_ascii=False)}\n\n'356                    await asyncio.sleep(0.01)357 358            # 发送结束标记359            final_data = {360                'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',361                'object': 'chat.completion.chunk',362                'created': int(time.time()),363                'model': model_name,364                'choices': [{365                    'index': 0,366                    'delta': {},367                    'finish_reason': 'stop'368                }]369            }370            yield f'data: {json.dumps(final_data, ensure_ascii=False)}\n\n'371            yield 'data: [DONE]\n\n'372 373            logger.info(f"流式响应成功,使用密钥: {current_key[:20]}...")374            return  # 成功完成,退出重试循环375 376        except Exception as e:377            last_error = e378            error_str = str(e).lower()379 380            # 检查是否是需要重试的错误381            if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):382                logger.warning(f"流式响应第 {attempt + 1} 次尝试失败: {str(e)}")383                if attempt < max_retries - 1:384                    await asyncio.sleep(1)385                    continue386            else:387                # 非网络错误,直接处理388                break389 390    # 所有重试都失败了,返回错误信息391    logger.error(f"流式响应所有重试失败: {str(last_error)}")392    error_message, finish_reason = handle_error(last_error)393 394    error_data = {395        'id': f'chatcmpl-{int(time.time())}-error',396        'object': 'chat.completion.chunk',397        'created': int(time.time()),398        'model': model_name,399        'choices': [{400            'index': 0,401            'delta': {402                'role': 'assistant',403                'content': error_message404            },405            'finish_reason': finish_reason406        }]407    }408    yield f'data: {json.dumps(error_data, ensure_ascii=False)}\n\n'409    yield 'data: [DONE]\n\n'410 411 412@app.get("/v1/models")413async def list_models():414    try:415        # 返回 new-api 期望的格式,包含 success 字段416        return {417            "success": True,418            "data": SUPPORTED_MODELS419        }420    except Exception as e:421        logger.error(f"获取模型列表出错: {str(e)}")422        return {423            "success": False,424            "message": str(e),425            "data": []426        }427 428 429@app.get("/v1/models/{model_id}")430async def get_model_info(model_id: str):431    try:432        for model in SUPPORTED_MODELS:433            if model["id"] == model_id:434                return model435        raise HTTPException(status_code=404, detail=f"模型 {model_id} 未找到")436    except HTTPException:437        raise438    except Exception as e:439        logger.error(f"获取模型信息出错: {str(e)}")440        raise HTTPException(status_code=500, detail=str(e))441 442 443@app.get("/v1/chat/completions/v1/models")444async def list_models_alternative():445    try:446        return {447            "object": "list",448            "data": SUPPORTED_MODELS449        }450    except Exception as e:451        logger.error(f"获取模型列表出错: {str(e)}")452        raise HTTPException(status_code=500, detail=str(e))453 454 455@app.get("/health")456async def health_check():457    try:458        return {459            "status": "healthy",460            "timestamp": int(time.time()),461            "api": "gemini-official",462            "available_models": [model["id"] for model in SUPPORTED_MODELS],463            "version": "1.3.0"464        }465    except Exception as e:466        logger.error(f"健康检查失败: {str(e)}")467        return {468            "status": "unhealthy",469            "timestamp": int(time.time()),470            "error": str(e)471        }472 473 474@app.get("/")475async def root():476    return {477        "name": "Gemini Official API",478        "version": "1.3.0",479        "description": "Google Gemini官方API接口服务",480        "endpoints": {481            "models": "/v1/models",482            "models_alt": "/v1/chat/completions/v1/models",483            "chat": "/v1/chat/completions",484            "health": "/health"485        }486    }487 488 489@app.exception_handler(404)490async def not_found_handler(request: Request, exc: HTTPException):491    return {492        "error": "未找到",493        "requested_path": str(request.url.path),494        "message": "请求的路径不存在",495        "available_endpoints": {496            "models": "/v1/models",497            "models_alt": "/v1/chat/completions/v1/models",498            "chat": "/v1/chat/completions",499            "health": "/health",500            "info": "/"501        }502    }503 504 505if __name__ == "__main__":506    print("🚀 启动Gemini官方API服务器")507    print(f"📊 支持的模型: {[model['id'] for model in SUPPORTED_MODELS]}")508    print(f"🔑 已配置 {len(API_KEYS)} 个API密钥")509    print("🔄 支持自动重试和密钥轮换")510    print("✅ 已适配 new-api 格式要求")511 512    uvicorn.run(app, host="0.0.0.0", port=7860)513