Team Ai
Apppublic

codex86/api1

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
main.py524 linesDownload Raw Back to root
1 2import logging3import json4import time5import asyncio6import os7import traceback8import sys9from contextlib import asynccontextmanager10import random11 12import uvicorn13from fastapi import FastAPI, Request, HTTPException14from fastapi.responses import StreamingResponse15from fastapi.middleware.cors import CORSMiddleware16from fastapi.responses import JSONResponse17from google import genai18from google.genai import types19from typing import Optional, List, Dict, Any20 21# 简化日志配置22logging.basicConfig(23    level=logging.INFO,24    format='%(asctime)s [%(levelname)s]: %(message)s',25    datefmt='%Y-%m-%d %H:%M:%S'26)27logger = logging.getLogger(__name__)28 29# 模型配置30GEMINI_MODELS = {31    "gemini-2.0-flash-exp": "gemini-2.0-flash-exp",32    "gemini-2.5-flash-preview-05-20": "gemini-2.5-flash-preview-05-20",33    "gemini-2.5-flash": "gemini-2.5-flash",34    "gemini-2.5-flash-preview-04-17": "gemini-2.5-flash-preview-04-17",35    "gemini-2.5-pro": "gemini-2.5-pro"36}37 38# 支持的模型列表39SUPPORTED_MODELS = [40    {41        "id": "gemini-2.5-flash-preview-05-20",42        "object": "model",43        "created": int(time.time()),44        "owned_by": "google",45        "permission": [],46        "root": "gemini-2.5-flash-preview-05-20",47        "parent": None,48        "description": "Gemini 2.5 Flash Preview - 最新实验性模型"49    },50    {51        "id": "gemini-2.5-flash-preview-04-17",52        "object": "model",53        "created": int(time.time()),54        "owned_by": "google",55        "permission": [],56        "root": "gemini-2.5-flash-preview-04-17",57        "parent": None,58        "description": "gemini-2.5-flash-preview-04-17- 经典专业模型"59    },60    {61        "id": "gemini-2.5-flash",62        "object": "model",63        "created": int(time.time()),64        "owned_by": "google",65        "permission": [],66        "root": "gemini-2.5-flash",67        "parent": None,68        "description": "gemini-2.5-flash稳定经典专业模型"69    },70       {71        "id": "gemini-2.5-pro",72        "object": "model",73        "created": int(time.time()),74        "owned_by": "google",75        "permission": [],76        "root": "gemini-2.5-pro",77        "parent": None,78        "description": "gemini-2.5-pro稳定经典专业模型"79    }80]81 82 83def get_model_name(requested_model: str) -> str:84    print(f"实际模型名称:{GEMINI_MODELS.get(requested_model)}")85    return GEMINI_MODELS.get(requested_model, "gemini-2.5-flash")86 87 88def convert_messages(messages):89    content_parts = []90    system_instruction = None91 92    for message in messages:93        role = message.get("role", "user")94        content = message.get("content", "")95 96        if role == "system":97            system_instruction = content98        elif role == "assistant":99            content_parts.append({100                "role": "model",101                "parts": [{"text": content}]102            })103        elif role == "user":104            content_parts.append({105                "role": "user",106                "parts": [{"text": content}]107            })108 109    return content_parts, system_instruction110 111 112def handle_error(error):113    error_str = str(error).lower()114 115    if "prompt_feedback" in error_str:116        if "other" in error_str:117            return "您的输入内容可能过长或触发了安全策略。请尝试缩短您的问题。", "length"118        elif "safety" in error_str:119            return "您的请求被安全策略阻止。请尝试修改您的问题。", "content_filter"120    elif "safety" in error_str:121        return "您的请求被安全策略过滤。请尝试修改您的问题。", "content_filter"122 123    return "生成内容时遇到错误。请稍后重试。", "stop"124 125 126@asynccontextmanager127async def lifespan(app: FastAPI):128    try:129        setup_gemini()  # 测试API密钥是否有效130        logger.info("应用启动完成")131        yield132    except Exception as e:133        logger.error(f"应用启动失败: {str(e)}")134        raise135    finally:136        logger.info("应用关闭")137 138 139# 创建FastAPI应用实例140app = FastAPI(141    lifespan=lifespan,142    title="Gemini Official API",143    version="1.3.0"144)145 146# 添加CORS中间件147app.add_middleware(148    CORSMiddleware,149    allow_origins=["*"],150    allow_credentials=True,151    allow_methods=["*"],152    allow_headers=["*"],153)154 155# API密钥列表156API_KEYS = [157    'AIzaSyAainmVAjjiQoUFqF1X3-5AI4_qUg1UkJw',158     'AIzaSyC68ywBZ0u-BYV1PmRbwldyymHBhRAqcpM'159]160 161# 当前使用的API密钥索引162current_key_index = 0163 164 165def get_random_api_key():166    return random.choice(API_KEYS)167 168 169def setup_gemini(api_key=None):170    if not api_key:171        api_key = get_random_api_key()172 173    if not API_KEYS:174        logger.error("请设置有效的API密钥列表")175        raise ValueError("API_KEYS未设置")176 177    client = genai.Client(api_key=api_key)178    return client, api_key179 180 181# 配置安全设置182SAFETY_SETTINGS = [183    types.SafetySetting(184        category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,185        threshold=types.HarmBlockThreshold.BLOCK_NONE,186    ),187    types.SafetySetting(188        category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,189        threshold=types.HarmBlockThreshold.BLOCK_NONE,190    ),191    types.SafetySetting(192        category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,193        threshold=types.HarmBlockThreshold.BLOCK_NONE,194    ),195    types.SafetySetting(196        category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,197        threshold=types.HarmBlockThreshold.BLOCK_NONE,198    ),199    types.SafetySetting(200        category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,201        threshold=types.HarmBlockThreshold.BLOCK_NONE,202    ),203]204 205 206async def try_generate_content(model_name, content_parts, config, max_retries=3):207    last_error = None208    used_keys = set()209 210    for attempt in range(max_retries):211        try:212            # 获取新的API密钥(避免重复使用失败的密钥)213            available_keys = [key for key in API_KEYS if key not in used_keys]214            if not available_keys:215                # 如果所有密钥都试过了,重置使用记录216                used_keys.clear()217                available_keys = API_KEYS218 219            api_key = random.choice(available_keys)220            used_keys.add(api_key)221 222            client, current_key = setup_gemini(api_key)223            logger.info(f"尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")224 225            response = client.models.generate_content(226                model=model_name,227                contents=content_parts,228                config=config229            )230 231            return response, current_key232 233        except Exception as e:234            last_error = e235            error_str = str(e).lower()236 237            # 检查是否是需要重试的错误238            if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):239                logger.warning(f"第 {attempt + 1} 次尝试失败: {str(e)}")240                if attempt < max_retries - 1:241                    await asyncio.sleep(1)  # 等待1秒后重试242                    continue243            else:244                # 非网络错误,直接抛出245                raise e246 247    # 所有重试都失败了248    raise last_error249 250 251@app.post("/v1/chat/completions")252async def chat_completions(request: Request):253    try:254        body = await request.json()255 256        messages = body.get('messages', [])257        stream = body.get('stream', False)258        max_tokens = body.get('max_tokens', 65536)259        temperature = body.get('temperature', 1.2)260        top_p = body.get('top_p', 0.0)261        requested_model = body.get('model', 'gemini-2.5-flash')262        model_name = get_model_name(requested_model)263        content_parts, system_instruction = convert_messages(messages)264 265        config = types.GenerateContentConfig(266            max_output_tokens=max_tokens,267            temperature=temperature,268            top_p=top_p,269            system_instruction=system_instruction,270            safety_settings=SAFETY_SETTINGS,271        )272 273        if stream:274            # 流式响应也需要重试机制275            client, api_key = setup_gemini()276            return StreamingResponse(277                stream_response_with_retry(client, model_name, content_parts, config),278                media_type='text/event-stream'279            )280        else:281            response, used_key = await try_generate_content(model_name, content_parts, config)282 283            response_text = response.text if response else ""284            finish_reason = "stop"285 286            if not response_text:287                response_text = "无法生成回复。请尝试修改您的问题。"288 289            logger.info(f"成功生成回复,使用密钥: {used_key[:20]}...")290 291            return {292                'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',293                'object': 'chat.completion',294                'created': int(time.time()),295                'model': requested_model,296                'choices': [{297                    'index': 0,298                    'message': {299                        'role': 'assistant',300                        'content': response_text301                    },302                    'finish_reason': finish_reason303                }],304                'usage': {305                    'prompt_tokens': len(content_parts),306                    'completion_tokens': len(response_text.split()),307                    'total_tokens': len(content_parts) + len(response_text.split())308                }309            }310 311    except Exception as e:312        logger.error(f"处理聊天请求出错: {str(e)}")313        error_message, finish_reason = handle_error(e)314        raise HTTPException(status_code=500, detail=str(e))315 316 317async def stream_response_with_retry(client, model_name, content_parts, config, max_retries=3):318    last_error = None319    used_keys = set()320 321    for attempt in range(max_retries):322        try:323            # 获取新的API密钥(避免重复使用失败的密钥)324            available_keys = [key for key in API_KEYS if key not in used_keys]325            if not available_keys:326                used_keys.clear()327                available_keys = API_KEYS328 329            api_key = random.choice(available_keys)330            used_keys.add(api_key)331 332            current_client, current_key = setup_gemini(api_key)333            logger.info(f"流式响应尝试第 {attempt + 1} 次,使用密钥: {current_key[:20]}...")334 335            for chunk in current_client.models.generate_content_stream(336                    model=model_name,337                    contents=content_parts,338                    config=config339            ):340                # 确保chunk存在且有文本内容341                if chunk and hasattr(chunk, 'text') and chunk.text:342                    data = {343                        'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',344                        'object': 'chat.completion.chunk',345                        'created': int(time.time()),346                        'model': model_name,347                        'choices': [{348                            'index': 0,349                            'delta': {350                                'role': 'assistant',351                                'content': chunk.text352                            },353                            'finish_reason': None354                        }]355                    }356                    yield f'data: {json.dumps(data, ensure_ascii=False)}357 358'359                    await asyncio.sleep(0.01)360 361            # 发送结束标记362            final_data = {363                'id': f'chatcmpl-{int(time.time())}-{random.randint(1000, 9999)}',364                'object': 'chat.completion.chunk',365                'created': int(time.time()),366                'model': model_name,367                'choices': [{368                    'index': 0,369                    'delta': {},370                    'finish_reason': 'stop'371                }]372            }373            yield f'data: {json.dumps(final_data, ensure_ascii=False)}374 375'376            yield 'data: [DONE]377 378'379 380            logger.info(f"流式响应成功,使用密钥: {current_key[:20]}...")381            return  # 成功完成,退出重试循环382 383        except Exception as e:384            last_error = e385            error_str = str(e).lower()386 387            # 检查是否是需要重试的错误388            if any(code in error_str for code in ['400', '401', '403', '429', '500', '502', '503', '504']):389                logger.warning(f"流式响应第 {attempt + 1} 次尝试失败: {str(e)}")390                if attempt < max_retries - 1:391                    await asyncio.sleep(1)392                    continue393            else:394                # 非网络错误,直接处理395                break396 397    # 所有重试都失败了,返回错误信息398    logger.error(f"流式响应所有重试失败: {str(last_error)}")399    error_message, finish_reason = handle_error(last_error)400 401    error_data = {402        'id': f'chatcmpl-{int(time.time())}-error',403        'object': 'chat.completion.chunk',404        'created': int(time.time()),405        'model': model_name,406        'choices': [{407            'index': 0,408            'delta': {409                'role': 'assistant',410                'content': error_message411            },412            'finish_reason': finish_reason413        }]414    }415    yield f'data: {json.dumps(error_data, ensure_ascii=False)}416 417'418    yield 'data: [DONE]419 420'421 422 423@app.get("/v1/models")424async def list_models():425    try:426        # 返回 new-api 期望的格式,包含 success 字段427        return {428            "success": True,429            "data": SUPPORTED_MODELS430        }431    except Exception as e:432        logger.error(f"获取模型列表出错: {str(e)}")433        return {434            "success": False,435            "message": str(e),436            "data": []437        }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 454@app.get("/v1/chat/completions/v1/models")455async def list_models_alternative():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 466@app.get("/health")467async def health_check():468    try:469        return {470            "status": "healthy",471            "timestamp": int(time.time()),472            "api": "gemini-official",473            "available_models": [model["id"] for model in SUPPORTED_MODELS],474            "version": "1.3.0"475        }476    except Exception as e:477        logger.error(f"健康检查失败: {str(e)}")478        return {479            "status": "unhealthy",480            "timestamp": int(time.time()),481            "error": str(e)482        }483 484 485@app.get("/")486async def root():487    return {488        "name": "Gemini Official API",489        "version": "1.3.0",490        "description": "Google Gemini官方API接口服务",491        "endpoints": {492            "models": "/v1/models",493            "models_alt": "/v1/chat/completions/v1/models",494            "chat": "/v1/chat/completions",495            "health": "/health"496        }497    }498 499 500@app.exception_handler(404)501async def not_found_handler(request: Request, exc: HTTPException):502    return {503        "error": "未找到",504        "requested_path": str(request.url.path),505        "message": "请求的路径不存在",506        "available_endpoints": {507            "models": "/v1/models",508            "models_alt": "/v1/chat/completions/v1/models",509            "chat": "/v1/chat/completions",510            "health": "/health",511            "info": "/"512        }513    }514 515 516if __name__ == "__main__":517    print("🚀 启动Gemini官方API服务器")518    print(f"📊 支持的模型: {[model['id'] for model in SUPPORTED_MODELS]}")519    print(f"🔑 已配置 {len(API_KEYS)} 个API密钥")520    print("🔄 支持自动重试和密钥轮换")521    print("✅ 已适配 new-api 格式要求")522 523    uvicorn.run(app, host="0.0.0.0", port=7860)524