Upload 4 files
Browse files
api.py
CHANGED
|
@@ -47,12 +47,12 @@ def check_private_key():
|
|
| 47 |
break
|
| 48 |
|
| 49 |
if not PRIVATE_KEY:
|
| 50 |
-
logging.warning("PRIVATE_KEY 未设置
|
| 51 |
return None
|
| 52 |
|
| 53 |
if not key_from_header or key_from_header != PRIVATE_KEY:
|
| 54 |
-
logging.warning(f"未授权访问:
|
| 55 |
-
return jsonify({"error": "
|
| 56 |
return None
|
| 57 |
|
| 58 |
# 密钥管理
|
|
@@ -69,7 +69,7 @@ class KeyManager:
|
|
| 69 |
def get(self):
|
| 70 |
with self.lock:
|
| 71 |
if not self.key_list:
|
| 72 |
-
raise ValueError("API
|
| 73 |
|
| 74 |
now = time.time()
|
| 75 |
for _ in range(len(self.key_list)):
|
|
@@ -119,7 +119,7 @@ def create_session(apikey, external_user_id=None):
|
|
| 119 |
resp.raise_for_status()
|
| 120 |
return resp.json()["data"]["id"]
|
| 121 |
except Exception as e:
|
| 122 |
-
logging.error(f"创建会话失败
|
| 123 |
raise
|
| 124 |
|
| 125 |
# 处理流式请求
|
|
@@ -141,6 +141,7 @@ def handle_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
|
| 141 |
with requests.post(url, json=payload, headers=headers, stream=True, timeout=180) as resp:
|
| 142 |
resp.raise_for_status()
|
| 143 |
first_chunk = True
|
|
|
|
| 144 |
|
| 145 |
for line in resp.iter_lines():
|
| 146 |
if not line:
|
|
@@ -152,6 +153,9 @@ def handle_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
|
| 152 |
|
| 153 |
data = line[5:].strip()
|
| 154 |
if data == "[DONE]":
|
|
|
|
|
|
|
|
|
|
| 155 |
yield "data: [DONE]\n\n"
|
| 156 |
break
|
| 157 |
|
|
@@ -161,6 +165,10 @@ def handle_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
|
| 161 |
content = event_data.get("answer", "")
|
| 162 |
if content is None:
|
| 163 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
delta = {}
|
| 166 |
if first_chunk:
|
|
@@ -177,7 +185,7 @@ def handle_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
|
| 177 |
}
|
| 178 |
yield format_openai_sse_delta(chunk)
|
| 179 |
except Exception as e:
|
| 180 |
-
logging.warning(f"处理流数据出错
|
| 181 |
continue
|
| 182 |
except Exception as e:
|
| 183 |
error = {
|
|
@@ -189,8 +197,10 @@ def handle_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
|
| 189 |
}
|
| 190 |
yield format_openai_sse_delta(error)
|
| 191 |
yield "data: [DONE]\n\n"
|
|
|
|
|
|
|
| 192 |
|
| 193 |
-
# 处理非流式请求
|
| 194 |
def handle_non_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
| 195 |
url = f"{ONDEMAND_API_BASE}/sessions/{session_id}/query"
|
| 196 |
payload = {
|
|
@@ -207,6 +217,10 @@ def handle_non_stream_request(apikey, session_id, query, endpoint_id, model_name
|
|
| 207 |
response_data = resp.json()
|
| 208 |
content = response_data["data"]["answer"]
|
| 209 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
return jsonify({
|
| 211 |
"id": f"chatcmpl-{str(uuid.uuid4())[:12]}",
|
| 212 |
"object": "chat.completion",
|
|
@@ -220,7 +234,9 @@ def handle_non_stream_request(apikey, session_id, query, endpoint_id, model_name
|
|
| 220 |
"usage": {}
|
| 221 |
})
|
| 222 |
except Exception as e:
|
| 223 |
-
|
|
|
|
|
|
|
| 224 |
|
| 225 |
# 路��处理
|
| 226 |
@app.route("/v1/chat/completions", methods=["POST"])
|
|
@@ -228,11 +244,11 @@ def chat_completions():
|
|
| 228 |
try:
|
| 229 |
data = request.json
|
| 230 |
if not data or "messages" not in data:
|
| 231 |
-
return jsonify({"error": "
|
| 232 |
|
| 233 |
messages = data["messages"]
|
| 234 |
if not isinstance(messages, list) or not messages:
|
| 235 |
-
return jsonify({"error": "
|
| 236 |
|
| 237 |
model = data.get("model", "gpt-4o")
|
| 238 |
endpoint_id = get_endpoint_id(model)
|
|
@@ -259,7 +275,7 @@ def chat_completions():
|
|
| 259 |
formatted_messages.append(f"<|{role}|>: {content}")
|
| 260 |
|
| 261 |
if not formatted_messages:
|
| 262 |
-
return jsonify({"error": "
|
| 263 |
|
| 264 |
# 添加系统提示词
|
| 265 |
system_prompt = f"<|system|>: {CLAUDE_SYSTEM_PROMPT}\n"
|
|
@@ -269,29 +285,47 @@ def chat_completions():
|
|
| 269 |
max_retries = 5
|
| 270 |
retry_count = 0
|
| 271 |
last_error = None
|
|
|
|
|
|
|
| 272 |
|
| 273 |
while retry_count < max_retries:
|
| 274 |
try:
|
| 275 |
apikey = keymgr.get()
|
| 276 |
if not apikey:
|
| 277 |
-
return jsonify({"error": "
|
| 278 |
|
| 279 |
session_id = create_session(apikey)
|
| 280 |
|
| 281 |
if is_stream:
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
else:
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
except Exception as e:
|
| 290 |
last_error = str(e)
|
| 291 |
if isinstance(e, requests.exceptions.RequestException):
|
| 292 |
keymgr.mark_bad(apikey)
|
| 293 |
|
| 294 |
-
logging.warning(f"请求失败 (尝试 {retry_count+1}/{max_retries})
|
| 295 |
retry_count += 1
|
| 296 |
|
| 297 |
# 如果还有重试次数,继续尝试
|
|
@@ -299,10 +333,10 @@ def chat_completions():
|
|
| 299 |
continue
|
| 300 |
|
| 301 |
# 超过最大重试次数,返回400错误
|
| 302 |
-
return jsonify({"error": "超过重试次数,请
|
| 303 |
|
| 304 |
except Exception as e:
|
| 305 |
-
return jsonify({"error": str(e)}), 500
|
| 306 |
|
| 307 |
@app.route("/v1/models", methods=["GET"])
|
| 308 |
def list_models():
|
|
@@ -458,7 +492,7 @@ if __name__ == "__main__":
|
|
| 458 |
)
|
| 459 |
|
| 460 |
if not ONDEMAND_APIKEYS:
|
| 461 |
-
logging.warning("未设置ONDEMAND_APIKEYS环境变量
|
| 462 |
|
| 463 |
port = int(os.environ.get("PORT", 7860))
|
| 464 |
app.run(host="0.0.0.0", port=port, debug=False)
|
|
|
|
| 47 |
break
|
| 48 |
|
| 49 |
if not PRIVATE_KEY:
|
| 50 |
+
logging.warning("安全警告:PRIVATE_KEY 未设置,服务将不进行鉴权!这可能导致未授权访问!")
|
| 51 |
return None
|
| 52 |
|
| 53 |
if not key_from_header or key_from_header != PRIVATE_KEY:
|
| 54 |
+
logging.warning(f"未授权访问: 路径={request.path}, IP地址={request.remote_addr}")
|
| 55 |
+
return jsonify({"error": "未授权访问。请提供正确的'Authorization: Bearer <PRIVATE_KEY>'或'X-API-KEY: <PRIVATE_KEY>'请求头。"}), 401
|
| 56 |
return None
|
| 57 |
|
| 58 |
# 密钥管理
|
|
|
|
| 69 |
def get(self):
|
| 70 |
with self.lock:
|
| 71 |
if not self.key_list:
|
| 72 |
+
raise ValueError("API密钥池为空,无法提供服务。请确保已配置有效的API密钥。")
|
| 73 |
|
| 74 |
now = time.time()
|
| 75 |
for _ in range(len(self.key_list)):
|
|
|
|
| 119 |
resp.raise_for_status()
|
| 120 |
return resp.json()["data"]["id"]
|
| 121 |
except Exception as e:
|
| 122 |
+
logging.error(f"创建会话失败:无法与API服务建立连接,错误详情:{e}")
|
| 123 |
raise
|
| 124 |
|
| 125 |
# 处理流式请求
|
|
|
|
| 141 |
with requests.post(url, json=payload, headers=headers, stream=True, timeout=180) as resp:
|
| 142 |
resp.raise_for_status()
|
| 143 |
first_chunk = True
|
| 144 |
+
has_content = False # 标记是否接收到内容
|
| 145 |
|
| 146 |
for line in resp.iter_lines():
|
| 147 |
if not line:
|
|
|
|
| 153 |
|
| 154 |
data = line[5:].strip()
|
| 155 |
if data == "[DONE]":
|
| 156 |
+
# 如果没有接收到任何内容,抛出异常
|
| 157 |
+
if not has_content:
|
| 158 |
+
raise ValueError("空回复:未从API接收到任何有效内容,请稍后重试或联系管理员")
|
| 159 |
yield "data: [DONE]\n\n"
|
| 160 |
break
|
| 161 |
|
|
|
|
| 165 |
content = event_data.get("answer", "")
|
| 166 |
if content is None:
|
| 167 |
continue
|
| 168 |
+
|
| 169 |
+
# 如果内容不为空,标记为已接收到内容
|
| 170 |
+
if content.strip():
|
| 171 |
+
has_content = True
|
| 172 |
|
| 173 |
delta = {}
|
| 174 |
if first_chunk:
|
|
|
|
| 185 |
}
|
| 186 |
yield format_openai_sse_delta(chunk)
|
| 187 |
except Exception as e:
|
| 188 |
+
logging.warning(f"处理流式响应数据出错:解析或处理数据时发生异常,详情:{e}")
|
| 189 |
continue
|
| 190 |
except Exception as e:
|
| 191 |
error = {
|
|
|
|
| 197 |
}
|
| 198 |
yield format_openai_sse_delta(error)
|
| 199 |
yield "data: [DONE]\n\n"
|
| 200 |
+
# 重新抛出异常,以便上层函数可以捕获并重试
|
| 201 |
+
raise
|
| 202 |
|
| 203 |
+
# 处理非流式请求
|
| 204 |
def handle_non_stream_request(apikey, session_id, query, endpoint_id, model_name):
|
| 205 |
url = f"{ONDEMAND_API_BASE}/sessions/{session_id}/query"
|
| 206 |
payload = {
|
|
|
|
| 217 |
response_data = resp.json()
|
| 218 |
content = response_data["data"]["answer"]
|
| 219 |
|
| 220 |
+
# 检查回复是否为空
|
| 221 |
+
if not content or not content.strip():
|
| 222 |
+
raise ValueError("空回复:API返回了空内容,无法提供有效回答,请稍后重试")
|
| 223 |
+
|
| 224 |
return jsonify({
|
| 225 |
"id": f"chatcmpl-{str(uuid.uuid4())[:12]}",
|
| 226 |
"object": "chat.completion",
|
|
|
|
| 234 |
"usage": {}
|
| 235 |
})
|
| 236 |
except Exception as e:
|
| 237 |
+
# 不在这里处理错误,而是将异常抛给上层函数处理
|
| 238 |
+
logging.warning(f"非流式请求失败:无法获取完整响应,错误详情:{e}")
|
| 239 |
+
raise
|
| 240 |
|
| 241 |
# 路��处理
|
| 242 |
@app.route("/v1/chat/completions", methods=["POST"])
|
|
|
|
| 244 |
try:
|
| 245 |
data = request.json
|
| 246 |
if not data or "messages" not in data:
|
| 247 |
+
return jsonify({"error": "无效的请求格式:请求体必须包含messages字段"}), 400
|
| 248 |
|
| 249 |
messages = data["messages"]
|
| 250 |
if not isinstance(messages, list) or not messages:
|
| 251 |
+
return jsonify({"error": "消息格式错误:messages必须是非空列表,且至少包含一条消息"}), 400
|
| 252 |
|
| 253 |
model = data.get("model", "gpt-4o")
|
| 254 |
endpoint_id = get_endpoint_id(model)
|
|
|
|
| 275 |
formatted_messages.append(f"<|{role}|>: {content}")
|
| 276 |
|
| 277 |
if not formatted_messages:
|
| 278 |
+
return jsonify({"error": "消息内容为空:所有消息均不包含有效内容,请检查消息格式"}), 400
|
| 279 |
|
| 280 |
# 添加系统提示词
|
| 281 |
system_prompt = f"<|system|>: {CLAUDE_SYSTEM_PROMPT}\n"
|
|
|
|
| 285 |
max_retries = 5
|
| 286 |
retry_count = 0
|
| 287 |
last_error = None
|
| 288 |
+
empty_response_retries = 0 # 空回复重试计数
|
| 289 |
+
max_empty_retries = 5 # 最大空回复重试次数
|
| 290 |
|
| 291 |
while retry_count < max_retries:
|
| 292 |
try:
|
| 293 |
apikey = keymgr.get()
|
| 294 |
if not apikey:
|
| 295 |
+
return jsonify({"error": "服务暂时不可用:没有可用的API密钥,请稍后重试或联系管理员"}), 503
|
| 296 |
|
| 297 |
session_id = create_session(apikey)
|
| 298 |
|
| 299 |
if is_stream:
|
| 300 |
+
try:
|
| 301 |
+
return Response(
|
| 302 |
+
handle_stream_request(apikey, session_id, query, endpoint_id, model),
|
| 303 |
+
content_type='text/event-stream'
|
| 304 |
+
)
|
| 305 |
+
except ValueError as ve:
|
| 306 |
+
# 捕获空回复异常
|
| 307 |
+
if "空回复" in str(ve) and empty_response_retries < max_empty_retries:
|
| 308 |
+
empty_response_retries += 1
|
| 309 |
+
logging.warning(f"检测到空回复:API未返回有效内容,正在使用新密钥重试 ({empty_response_retries}/{max_empty_retries})")
|
| 310 |
+
continue # 使用新密钥重试
|
| 311 |
+
raise # 其他ValueError或超过重试次数,重新抛出
|
| 312 |
else:
|
| 313 |
+
try:
|
| 314 |
+
return handle_non_stream_request(apikey, session_id, query, endpoint_id, model)
|
| 315 |
+
except ValueError as ve:
|
| 316 |
+
# 捕获空回复异常
|
| 317 |
+
if "空回复" in str(ve) and empty_response_retries < max_empty_retries:
|
| 318 |
+
empty_response_retries += 1
|
| 319 |
+
logging.warning(f"检测到空回复:API未返回有效内容,正在使用新密钥重试 ({empty_response_retries}/{max_empty_retries})")
|
| 320 |
+
continue # 使用新密钥重试
|
| 321 |
+
raise # 其他ValueError或超过重试次数,重新抛出
|
| 322 |
|
| 323 |
except Exception as e:
|
| 324 |
last_error = str(e)
|
| 325 |
if isinstance(e, requests.exceptions.RequestException):
|
| 326 |
keymgr.mark_bad(apikey)
|
| 327 |
|
| 328 |
+
logging.warning(f"请求处理失败 (尝试 {retry_count+1}/{max_retries}):可能是网络问题或API服务不稳定,错误详情:{last_error}")
|
| 329 |
retry_count += 1
|
| 330 |
|
| 331 |
# 如果还有重试次数,继续尝试
|
|
|
|
| 333 |
continue
|
| 334 |
|
| 335 |
# 超过最大重试次数,返回400错误
|
| 336 |
+
return jsonify({"error": "请求失败:已超过最大重试次数,请稍后再试", "details": last_error}), 400
|
| 337 |
|
| 338 |
except Exception as e:
|
| 339 |
+
return jsonify({"error": f"服务器内部错误:{str(e)},请联系管理员"}), 500
|
| 340 |
|
| 341 |
@app.route("/v1/models", methods=["GET"])
|
| 342 |
def list_models():
|
|
|
|
| 492 |
)
|
| 493 |
|
| 494 |
if not ONDEMAND_APIKEYS:
|
| 495 |
+
logging.warning("配置错误:未设置ONDEMAND_APIKEYS环境变量,服务将无法连接到API提供商,请配置至少一个有效的API密钥")
|
| 496 |
|
| 497 |
port = int(os.environ.get("PORT", 7860))
|
| 498 |
app.run(host="0.0.0.0", port=port, debug=False)
|