CoolFace
Apppublic

dacz123/ap1

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
main.py522 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 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)