|
from flask import Flask, request, jsonify, Response |
|
import requests |
|
import uuid |
|
import json |
|
import time |
|
import os |
|
import re |
|
import logging |
|
from itertools import cycle |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
_COOKIES = os.getenv("COOKIES", "") |
|
|
|
API_KEY = os.getenv("API_KEY", "linux.do") |
|
|
|
app = Flask(__name__) |
|
|
|
COOKIES = _COOKIES.split(',') |
|
iterator = cycle(COOKIES) |
|
|
|
cookie_index = 0 |
|
|
|
def get_cookie(): |
|
return next(iterator) |
|
|
|
@app.before_request |
|
def check_api_key(): |
|
key = request.headers.get("Authorization") |
|
if key != "Bearer " + API_KEY: |
|
logging.warning("Unauthorized access attempt with key: %s", key) |
|
return jsonify({"success": False, "message": "Unauthorized: Invalid API key"}), 403 |
|
|
|
@app.route('/v1/models', methods=['GET']) |
|
def get_models(): |
|
logging.info("Received /v1/models request") |
|
_cookie = get_cookie() |
|
logging.info(_cookie[:50]) |
|
headers = {"Content-Type": "application/json", "Cookie": _cookie} |
|
response = requests.get('https://chat.akash.network/api/models', headers=headers) |
|
models_data = response.json() |
|
print(models_data) |
|
current_timestamp = int(time.time()) |
|
converted_data = { |
|
"object": "list", |
|
"data": [ |
|
{ |
|
"id": model["id"], |
|
"object": "model", |
|
"created": current_timestamp, |
|
"owned_by": "openai" if "Meta" in model["id"] else "third_party", |
|
"permissions": [], |
|
"root": model["id"], |
|
"parent": None, |
|
"capabilities": { |
|
"temperature": model.get("temperature"), |
|
"top_p": model.get("top_p") |
|
}, |
|
"name": model.get("name"), |
|
"description": model.get("description"), |
|
"available": model.get("available") |
|
} |
|
for model in models_data |
|
] |
|
} |
|
logging.info("Response for /v1/models: %s", json.dumps(converted_data, ensure_ascii=False)) |
|
return jsonify(converted_data) |
|
|
|
def generate_stream(akash_response, chat_id, model): |
|
""" |
|
解析 Akash 接口的流式响应数据,并生成符合 OpenAI API 流返回格式的 chunk 数据。 |
|
""" |
|
for line in akash_response.iter_lines(): |
|
if not line: |
|
continue |
|
try: |
|
line_str = line.decode('utf-8').strip() |
|
msg_type, msg_data = line_str.split(':', 1) |
|
if msg_type == '0': |
|
token = msg_data.strip() |
|
|
|
if token.startswith('"') and token.endswith('"'): |
|
token = token[1:-1].replace('\\"', '"') |
|
token = token.replace("\\n", "\n") |
|
chunk = { |
|
"id": f"chatcmpl-{chat_id}", |
|
"object": "chat.completion.chunk", |
|
"created": int(time.time()), |
|
"model": model, |
|
"choices": [{ |
|
"delta": {"content": token}, |
|
"index": 0, |
|
"finish_reason": None |
|
}] |
|
} |
|
logging.debug("Streaming chunk: %s", json.dumps(chunk, ensure_ascii=False)) |
|
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n" |
|
elif msg_type in ['e', 'd']: |
|
chunk = { |
|
"id": f"chatcmpl-{chat_id}", |
|
"object": "chat.completion.chunk", |
|
"created": int(time.time()), |
|
"model": model, |
|
"choices": [{ |
|
"delta": {}, |
|
"index": 0, |
|
"finish_reason": "stop" |
|
}] |
|
} |
|
logging.debug("Streaming finish chunk: %s", json.dumps(chunk, ensure_ascii=False)) |
|
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n" |
|
yield "data: [DONE]\n\n" |
|
break |
|
except Exception as ex: |
|
logging.error("Error processing stream line: %s", ex) |
|
continue |
|
|
|
@app.route('/v1/chat/completions', methods=['POST']) |
|
def chat_completions(): |
|
try: |
|
data = request.get_json() |
|
logging.info("Received /v1/chat/completions request: %s", json.dumps(data, ensure_ascii=False)) |
|
chat_id = str(uuid.uuid4()).replace('-', '')[:16] |
|
model = data.get('model', "DeepSeek-R1") |
|
akash_data = { |
|
"id": chat_id, |
|
"messages": data.get('messages', []), |
|
"model": model, |
|
"system": data.get('system_message', "You are a helpful assistant."), |
|
"temperature": data.get('temperature', 0.6), |
|
"topP": data.get('top_p', 0.95) |
|
} |
|
_cookie = get_cookie() |
|
logging.info(_cookie[:50]) |
|
headers = {"Content-Type": "application/json", "Cookie": _cookie} |
|
|
|
stream_flag = True |
|
if model == "AkashGen": |
|
stream_flag = False |
|
else: |
|
stream_flag = data.get('stream',False) |
|
logging.info("streamflag: %s", stream_flag) |
|
akash_response = requests.post( |
|
'https://chat.akash.network/api/chat', |
|
json=akash_data, |
|
headers=headers, |
|
stream=stream_flag |
|
) |
|
logging.info("Akash API response status: %s", akash_response.status_code) |
|
|
|
if stream_flag: |
|
return Response( |
|
generate_stream(akash_response, chat_id, model), |
|
mimetype='text/event-stream', |
|
headers={ |
|
'Cache-Control': 'no-cache', |
|
'Connection': 'keep-alive' |
|
} |
|
) |
|
else: |
|
if model != "AkashGen": |
|
text_matches = re.findall(r'0:"(.*?)"', akash_response.text) |
|
parsed_text = "".join(text_matches) |
|
response_payload = { |
|
"object": "chat.completion", |
|
"created": int(time.time() * 1000), |
|
"model": model, |
|
"choices": [{ |
|
"index": 0, |
|
"message": {"role": "assistant", "content": parsed_text}, |
|
"finish_reason": "stop" |
|
}] |
|
} |
|
logging.info("Non-stream response payload: %s", json.dumps(response_payload, ensure_ascii=False)) |
|
return Response( |
|
json.dumps(response_payload, ensure_ascii=False), |
|
status=akash_response.status_code, |
|
mimetype='application/json' |
|
) |
|
else: |
|
match = re.search(r"jobId='([^']+)'", akash_response.text) |
|
if match: |
|
job_id = match.group(1) |
|
logging.info("AkashGen jobId: %s", job_id) |
|
while True: |
|
try: |
|
img_response = requests.get( |
|
f'https://chat.akash.network/api/image-status?ids={job_id}', |
|
headers=headers |
|
) |
|
img_data = img_response.json() |
|
if img_data[0]["status"] == "completed": |
|
response_payload = { |
|
"object": "chat.completion", |
|
"created": int(time.time() * 1000), |
|
"model": model, |
|
"choices": [{ |
|
"index": 0, |
|
"message": { |
|
"role": "assistant", |
|
"content": f"{img_data[0]['result']}" |
|
}, |
|
"finish_reason": "stop" |
|
}] |
|
} |
|
logging.info("AkashGen completed response payload: %s", json.dumps(response_payload, ensure_ascii=False)) |
|
return Response( |
|
json.dumps(response_payload, ensure_ascii=False), |
|
status=akash_response.status_code, |
|
mimetype='application/json' |
|
) |
|
else: |
|
logging.info("图片生成中,jobId: %s", job_id) |
|
except Exception as e: |
|
logging.error("请求图片状态异常: %s", e) |
|
time.sleep(5) |
|
else: |
|
logging.error("未能解析到 jobId") |
|
return jsonify({"error": "当前官方服务异常"}), 500 |
|
|
|
except Exception as e: |
|
logging.exception("chat_completions error:") |
|
return jsonify({"error": str(e)}), 500 |
|
|
|
if __name__ == '__main__': |
|
app.run(host='0.0.0.0', port=5200) |
|
|