CoolFace
Apppublic

pikaball/ds

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
app.py607 linesDownload Raw Back to root
1import os2import time3import logging4import requests5import json6import concurrent.futures7import threading8from datetime import datetime, timedelta9from apscheduler.schedulers.background import BackgroundScheduler10from flask import Flask, request, jsonify, Response, stream_with_context11 12os.environ['TZ'] = 'Asia/Shanghai'13time.tzset()14 15logging.basicConfig(level=logging.INFO,16                    format='%(asctime)s - %(levelname)s - %(message)s')17 18API_ENDPOINT = "https://api.deepseek.com/user/balance"19TEST_MODEL_ENDPOINT = "https://api.deepseek.com/v1/chat/completions"20MODELS_ENDPOINT = "https://api.deepseek.com/models"21 22app = Flask(__name__)23 24text_models = []25 26invalid_keys_global = []27valid_keys_global = []28 29executor = concurrent.futures.ThreadPoolExecutor(max_workers=10000)30model_key_indices = {}31 32request_timestamps = []33token_counts = []34data_lock = threading.Lock()35 36def get_credit_summary(api_key):37    headers = {38        "Authorization": f"Bearer {api_key}",39        "Content-Type": "application/json"40    }41    try:42        response = requests.get(API_ENDPOINT, headers=headers)43        response.raise_for_status()44        data = response.json()45        if not data.get("is_available", False):46            logging.warning(f"API Key: {api_key} is not available.")47            return None48      49        balance_infos = data.get("balance_infos", [])50        total_balance_cny = 0.051        usd_balance = 0.052        for balance_info in balance_infos:53            currency = balance_info.get("currency")54            total_balance = float(balance_info.get("total_balance", 0))55 56            if currency == "CNY":57                total_balance_cny += total_balance58            elif currency == "USD":59                usd_balance = total_balance60 61        try:62            exchange_rate = get_usd_to_cny_rate()63            if exchange_rate is not None:64                total_balance_cny += usd_balance * exchange_rate65                logging.info(f"获取美元兑人民币汇率成功,API Key:{api_key},当前总额度(CNY): {total_balance_cny}")66            else:67                logging.warning(f"获取美元兑人民币汇率失败,无法转换美元余额,API Key:{api_key}")68                total_balance_cny += usd_balance * 7.269        except Exception as e:70            logging.error(f"获取美元兑人民币汇率失败,API Key:{api_key},错误信息:{e}")71            total_balance_cny += usd_balance * 7.2 72 73        return {"total_balance": float(total_balance_cny)}74    except requests.exceptions.RequestException as e:75        logging.error(f"获取额度信息失败,API Key:{api_key},错误信息:{e}")76        return None77    except Exception as e:78        logging.error(f"处理额度信息失败,API Key:{api_key},错误信息:{e}")79        return None80 81def get_usd_to_cny_rate():82    try:83        response = requests.get("https://api.exchangerate-api.com/v4/latest/USD")84        response.raise_for_status()85        data = response.json()86        return data.get("rates", {}).get("CNY")87    except requests.exceptions.RequestException as e:88        logging.error(f"获取美元兑人民币汇率失败,错误信息:{e}")89        return None90 91def refresh_models():92    text_models = ["deepseek-chat", "deepseek-reasoner"]93    logging.info(f"所有文本模型列表:{text_models}")94 95def load_keys():96    keys_str = os.environ.get("KEYS")97    keys = [key.strip() for key in keys_str.split(',')]98    unique_keys = list(set(keys))99    keys_str = ','.join(unique_keys) 100    os.environ["KEYS"] = keys_str101 102    logging.info(f"加载的 keys:{unique_keys}")103 104    with concurrent.futures.ThreadPoolExecutor(105        max_workers=10000106    ) as executor:107        future_to_key = {108            executor.submit(109                process_key, key110            ): key for key in unique_keys111        }112 113        invalid_keys = []114        valid_keys = []115 116        for future in concurrent.futures.as_completed(117            future_to_key118        ):119            key = future_to_key[future]120            try:121                key_type = future.result()122                if key_type == "invalid":123                    invalid_keys.append(key)124                elif key_type == "valid":125                    valid_keys.append(key)126            except Exception as exc:127                logging.error(f"处理 KEY {key} 生成异常: {exc}")128 129    logging.info(f"无效 KEY:{invalid_keys}")130    logging.info(f"有效 KEY:{valid_keys}")131 132    global invalid_keys_global, valid_keys_global133    invalid_keys_global = invalid_keys134    valid_keys_global = valid_keys135 136def process_key(key):137    credit_summary = get_credit_summary(key)138    if credit_summary is None:139        return "invalid"140    else:141        total_balance = credit_summary.get("total_balance", 0)142        if total_balance <= 0:143            return "invalid"144        else:145            return "valid"146 147def select_key(model_name):148    available_keys = valid_keys_global149 150    current_index = model_key_indices.get(model_name, 0)151 152    for _ in range(len(available_keys)):153        key = available_keys[current_index % len(available_keys)]154        current_index += 1155        model_key_indices[model_name] = current_index156        return key157 158    model_key_indices[model_name] = 0159    return None160 161def check_authorization(request):162    authorization_key = os.environ.get("AUTHORIZATION_KEY")163    if not authorization_key:164        logging.warning("环境变量 AUTHORIZATION_KEY 未设置,请设置后重试。")165        return False166 167    auth_header = request.headers.get('Authorization')168    if not auth_header:169        logging.warning("请求头中缺少 Authorization 字段。")170        return False171 172    if auth_header != f"Bearer {authorization_key}":173        logging.warning(f"无效的 Authorization 密钥:{auth_header}")174        return False175 176    return True177 178scheduler = BackgroundScheduler()179scheduler.add_job(load_keys, 'interval', hours=1)180scheduler.remove_all_jobs()181 182@app.route('/')183def index():184    current_time = time.time()185    one_minute_ago = current_time - 60186 187    with data_lock:188        while request_timestamps and request_timestamps[0] < one_minute_ago:189            request_timestamps.pop(0)190            token_counts.pop(0)191 192        rpm = len(request_timestamps)193        tpm = sum(token_counts)194 195    return jsonify({"rpm": rpm, "tpm": tpm})196          197@app.route('/handsome/v1/models', methods=['GET'])198def list_models():199    if not check_authorization(request):200        return jsonify({"error": "Unauthorized"}), 401201  202    detailed_models = [203        {204            "id": "deepseek-chat",205            "object": "model",206            "created": 1678888888,207            "owned_by": "openai",208            "root": "deepseek-chat",209            "parent": None210        },211        {212            "id": "deepseek-reasoner",213            "object": "model",214            "created": 1678888889,215            "owned_by": "openai",216            "root": "deepseek-reasoner",217            "parent": None218        }219    ]220 221    return jsonify({222        "success": True,223        "data": detailed_models224    })225 226def get_billing_info():227    keys = valid_keys_global228    total_balance = 0229 230    with concurrent.futures.ThreadPoolExecutor(231        max_workers=10000232    ) as executor:233        futures = [234            executor.submit(get_credit_summary, key) for key in keys235        ]236 237        for future in concurrent.futures.as_completed(futures):238            try:239                credit_summary = future.result()240                if credit_summary:241                    total_balance += credit_summary.get(242                        "total_balance",243                        0244                    )245            except Exception as exc:246                logging.error(f"获取额度信息生成异常: {exc}")247 248    return total_balance249 250@app.route('/handsome/v1/dashboard/billing/usage', methods=['GET'])251def billing_usage():252    if not check_authorization(request):253        return jsonify({"error": "Unauthorized"}), 401254 255    end_date = datetime.now()256    start_date = end_date - timedelta(days=30)257 258    daily_usage = []259    current_date = start_date260    while current_date <= end_date:261        daily_usage.append({262            "timestamp": int(current_date.timestamp()),263            "daily_usage": 0264        })265        current_date += timedelta(days=1)266 267    return jsonify({268        "object": "list",269        "data": daily_usage,270        "total_usage": 0271    })272 273@app.route('/handsome/v1/dashboard/billing/subscription', methods=['GET'])274def billing_subscription():275    if not check_authorization(request):276        return jsonify({"error": "Unauthorized"}), 401277 278    total_balance = get_billing_info()279 280    return jsonify({281        "object": "billing_subscription",282        "has_payment_method": False,283        "canceled": False,284        "canceled_at": None,285        "delinquent": None,286        "access_until": int(datetime(9999, 12, 31).timestamp()),287        "soft_limit": 0,288        "hard_limit": total_balance,289        "system_hard_limit": total_balance,290        "soft_limit_usd": 0,291        "hard_limit_usd": total_balance,292        "system_hard_limit_usd": total_balance,293        "plan": {294            "name": "SiliconFlow API",295            "id": "siliconflow-api"296        },297        "account_name": "SiliconFlow User",298        "po_number": None,299        "billing_email": None,300        "tax_ids": [],301        "billing_address": None,302        "business_address": None303    })304 305@app.route('/handsome/v1/chat/completions', methods=['POST'])306def handsome_chat_completions():307    if not check_authorization(request):308        return jsonify({"error": "Unauthorized"}), 401309 310    data = request.get_json()311    if not data or 'model' not in data:312        return jsonify({"error": "Invalid request data"}), 400313 314    model_name = data['model']315 316    api_key = select_key(model_name)317 318    if not api_key:319        return jsonify(320            {321                "error": (322                    "No available API key for this "323                    "request type or all keys have "324                    "reached their limits"325                )326            }327        ), 429328 329    if model_name == "deepseek-reasoner":330        for param in ["temperature", "top_p", "presence_penalty", "frequency_penalty", "logprobs", "top_logprobs"]:331            if param in data:332                del data[param]333 334    headers = {335        "Authorization": f"Bearer {api_key}",336        "Content-Type": "application/json"337    }338 339    try:340        start_time = time.time()341        response = requests.post(342            TEST_MODEL_ENDPOINT,343            headers=headers,344            json=data,345            stream=data.get("stream", False),346            timeout=60347        )348 349        if response.status_code == 429:350            return jsonify(response.json()), 429351 352        if data.get("stream", False):353            def generate():354                first_chunk_time = None355                full_response_content = ""356                reasoning_content_accumulated = ""357                content_accumulated = ""358                first_reasoning_chunk = True359                360                for chunk in response.iter_content(chunk_size=10000000000):361                    if chunk:362                        if first_chunk_time is None:363                            first_chunk_time = time.time()364                        full_response_content += chunk.decode("utf-8")365                        366                        for line in chunk.decode("utf-8").splitlines():367                            # print(line)368 369                            if line.startswith("data:"):370                                try:371                                    chunk_json = json.loads(line.lstrip("data: ").strip())372                                    if "choices" in chunk_json and len(chunk_json["choices"]) > 0:373                                        delta = chunk_json["choices"][0].get("delta", {})374                                        375                                        if delta.get("reasoning_content") is not None:376                                            reasoning_chunk = delta["reasoning_content"]377                                            reasoning_chunk = reasoning_chunk.replace('\n', '\n> ')378                                            if first_reasoning_chunk:379                                                reasoning_chunk = "> " + reasoning_chunk380                                                first_reasoning_chunk = False381                                            yield f"data: {json.dumps({'choices': [{'delta': {'content': reasoning_chunk}, 'index': 0}]})}\n\n"382                                            383                                        if delta.get("content") is not None:384                                            if not first_reasoning_chunk:385                                                # yield f"data: {json.dumps({'choices': [{'delta': {'content': '\n```\n'}, 'index': 0}]})}\n\n"386                                                # yield f"data: {json.dumps({'choices': [{'delta': {'content': '\n\n---\n\n### 结果输出\n'}, 'index': 0}]})}\n\n"387                                                yield f"data: {json.dumps({'choices': [{'delta': {'content': '\n\n'}, 'index': 0}]})}\n\n"388                                                first_reasoning_chunk = True389                                            yield f"data: {json.dumps({'choices': [{'delta': {'content': delta["content"]}, 'index': 0}]})}\n\n"390 391                                except (KeyError, ValueError, json.JSONDecodeError) as e:392                                    logging.error(f"解析流式响应单行 JSON 失败: {e}, 行内容: {line}")393                                    continue394 395                end_time = time.time()396                first_token_time = (397                    first_chunk_time - start_time398                    if first_chunk_time else 0399                )400                total_time = end_time - start_time401 402                prompt_tokens = 0403                completion_tokens = 0404                for line in full_response_content.splitlines():405                    if line.startswith("data:"):406                        line = line[5:].strip()407                        if line == "[DONE]":408                            continue409                        try:410                            response_json = json.loads(line)411 412                            if (413                                "usage" in response_json and414                                "completion_tokens" in response_json["usage"]415                            ):416                                completion_tokens += response_json[417                                    "usage"418                                ]["completion_tokens"]419                            if (420                                "usage" in response_json and421                                "prompt_tokens" in response_json["usage"]422                            ):423                                prompt_tokens = response_json[424                                    "usage"425                                ]["prompt_tokens"]426 427                        except (428                            KeyError,429                            ValueError,430                            IndexError431                        ) as e:432                            logging.error(433                                f"解析流式响应单行 JSON 失败: {e}, "434                                f"行内容: {line}"435                            )436 437                user_content = ""438                messages = data.get("messages", [])439                for message in messages:440                    if message["role"] == "user":441                        if isinstance(message["content"], str):442                            user_content += message["content"] + " "443                        elif isinstance(message["content"], list):444                            for item in message["content"]:445                                if (446                                    isinstance(item, dict) and447                                    item.get("type") == "text"448                                ):449                                    user_content += (450                                        item.get("text", "") +451                                        " "452                                    )453 454                user_content = user_content.strip()455 456                user_content_replaced = user_content.replace(457                    '\n', '\\n'458                ).replace('\r', '\\n')459                response_content_replaced = (f"```Thinking\n{reasoning_content_accumulated}\n```\n" if reasoning_content_accumulated else "") + content_accumulated460                response_content_replaced = response_content_replaced.replace(461                    '\n', '\\n'462                ).replace('\r', '\\n')463 464                logging.info(465                    f"使用的key: {api_key}, "466                    f"提示token: {prompt_tokens}, "467                    f"输出token: {completion_tokens}, "468                    f"首字用时: {first_token_time:.4f}秒, "469                    f"总共用时: {total_time:.4f}秒, "470                    f"使用的模型: {model_name}, "471                    f"用户的内容: {user_content_replaced}, "472                    f"输出的内容: {response_content_replaced}"473                )474 475                with data_lock:476                    request_timestamps.append(time.time())477                    token_counts.append(prompt_tokens + completion_tokens)478 479                yield "data: [DONE]\n\n"480 481            return Response(482                stream_with_context(generate()),483                content_type="text/event-stream"484            )485        else:486            response.raise_for_status()487            end_time = time.time()488            response_json = response.json()489            total_time = end_time - start_time490 491            try:492                prompt_tokens = response_json["usage"]["prompt_tokens"]493                completion_tokens = response_json["usage"]["completion_tokens"]494                response_content = ""495 496                if model_name == "deepseek-reasoner" and "choices" in response_json and len(response_json["choices"]) > 0:497                    choice = response_json["choices"][0]498                    if "message" in choice:499                        if "reasoning_content" in choice["message"]:500                            reasoning_content = choice["message"]["reasoning_content"]501                            reasoning_content = reasoning_content.replace('\n', '\n> ')502                            reasoning_content = '> ' + reasoning_content503                            formatted_reasoning = f"{reasoning_content}\n"504                            response_content += formatted_reasoning + "\n"505                        if "content" in choice["message"]:506                            response_content += choice["message"]["content"]507                elif "choices" in response_json and len(response_json["choices"]) > 0:508                    response_content = response_json["choices"][0]["message"]["content"]509 510            except (KeyError, ValueError, IndexError) as e:511                logging.error(512                    f"解析非流式响应 JSON 失败: {e}, "513                    f"完整内容: {response_json}"514                )515                prompt_tokens = 0516                completion_tokens = 0517                response_content = ""518 519            user_content = ""520            messages = data.get("messages", [])521            for message in messages:522                if message["role"] == "user":523                    if isinstance(message["content"], str):524                        user_content += message["content"] + " "525                    elif isinstance(message["content"], list):526                        for item in message["content"]:527                            if (528                                isinstance(item, dict) and529                                item.get("type") == "text"530                            ):531                                user_content += (532                                    item.get("text", "") +533                                    " "534                                )535 536            user_content = user_content.strip()537 538            user_content_replaced = user_content.replace(539                '\n', '\\n'540            ).replace('\r', '\\n')541            response_content_replaced = response_content.replace(542                '\n', '\\n'543            ).replace('\r', '\\n')544 545            logging.info(546                f"使用的key: {api_key}, "547                f"提示token: {prompt_tokens}, "548                f"输出token: {completion_tokens}, "549                f"首字用时: 0, "550                f"总共用时: {total_time:.4f}秒, "551                f"使用的模型: {model_name}, "552                f"用户的内容: {user_content_replaced}, "553                f"输出的内容: {response_content_replaced}"554            )555            with data_lock:556                request_timestamps.append(time.time())557                token_counts.append(prompt_tokens + completion_tokens)558 559            formatted_response = {560                "id": response_json.get("id", ""),561                "object": "chat.completion",562                "created": response_json.get("created", int(time.time())),563                "model": model_name,564                "choices": [565                    {566                        "index": 0,567                        "message": {568                            "role": "assistant",569                            "content": response_content570                        },571                        "finish_reason": "stop"572                    }573                ],574                "usage": {575                    "prompt_tokens": prompt_tokens,576                    "completion_tokens": completion_tokens,577                    "total_tokens": prompt_tokens + completion_tokens578                }579            }580 581            return jsonify(formatted_response)582 583    except requests.exceptions.RequestException as e:584        logging.error(f"请求转发异常: {e}")585        return jsonify({"error": str(e)}), 500586 587if __name__ == '__main__':588    logging.info(f"环境变量:{os.environ}")589 590    invalid_keys_global = []591    valid_keys_global = []592 593    load_keys()594    logging.info("程序启动时首次加载 keys 已执行")595 596    scheduler.start()597 598    logging.info("首次加载 keys 已手动触发执行")599 600    refresh_models()601    logging.info("首次刷新模型列表已手动触发执行")602 603    app.run(604        debug=False,605        host='0.0.0.0',606        port=int(os.environ.get('PORT', 7860))607    )