pikaball/ds
0
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 )