sanbo110 commited on
Commit
077e080
·
1 Parent(s): d7c05c2

update sth at 2026-01-15 17:04:29

Browse files
KEEP_RUNNING.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Keep HuggingFace Space Alive
2
+
3
+ on:
4
+ schedule:
5
+ # 每 5 分钟触发一次
6
+ - cron: '*/5 * * * *'
7
+ workflow_dispatch: # 支持手动触发
8
+
9
+ jobs:
10
+ keep-alive:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Check Space Health
15
+ run: |
16
+ echo "🚀 发送心跳到 HuggingFace Space"
17
+ curl -s -o /dev/null -w "HTTP 状态码: %{http_code}, 耗时: %{time_total}s\n" \
18
+ https://sanbo1200-zai.hf.space/hf/v1/models || true
19
+
20
+ - name: Test API Endpoint (可选)
21
+ env:
22
+ API_KEY: ${{ secrets.API_KEY }}
23
+ run: |
24
+ # 如果有 API key,测试真实请求
25
+ if [ -n "$API_KEY" ]; then
26
+ echo "测试 API 调用..."
27
+ curl -s -X POST https://sanbo1200-zai.hf.space/hf/v1/chat/completions \
28
+ -H "Content-Type: application/json" \
29
+ -H "Authorization: Bearer $API_KEY" \
30
+ -d '{"model":"GLM-4.5","messages":[{"role":"user","content":"ping"}],"stream":false}' \
31
+ -o /dev/null -w "API 调用状态: %{http_code}\n" || true
32
+ fi
33
+
34
+ - name: Log Time
35
+ run: echo "心跳发送完成: $(date -u)"
app/providers/zai_provider.py CHANGED
@@ -705,6 +705,7 @@ class ZAIProvider(BaseProvider):
705
  transformed = await self.transform_request(request)
706
  self.logger.debug(f"[chat_completion] 转换后的请求: {transformed['url'][:100]}...")
707
 
 
708
  # 根据请求类型返回响应
709
  if request.stream:
710
  # 流式响应
@@ -713,8 +714,11 @@ class ZAIProvider(BaseProvider):
713
  # Get proxy configuration
714
  proxies = self._get_proxy_config()
715
 
716
- # 非流式响应
717
- async with httpx.AsyncClient(timeout=30.0, proxy=proxies) as client:
 
 
 
718
  response = await client.post(
719
  transformed["url"],
720
  headers=transformed["headers"],
@@ -731,7 +735,13 @@ class ZAIProvider(BaseProvider):
731
  # 记录响应状态
732
  self.logger.info(f"✅ 上游响应成功: {response.status_code}, Content-Length: {response.headers.get('content-length', 'N/A')}")
733
  try:
734
- return await self.transform_response(response, request, transformed)
 
 
 
 
 
 
735
  except Exception as transform_error:
736
  self.logger.error(f"❌ transform_response 失败: {transform_error}")
737
  body_text = response.text[:1000] if response.text else "无响应体"
@@ -827,21 +837,6 @@ class ZAIProvider(BaseProvider):
827
  yield "data: [DONE]\n\n"
828
  return
829
 
830
- async def transform_response(
831
- self,
832
- response: httpx.Response,
833
- request: OpenAIRequest,
834
- transformed: Dict[str, Any]
835
- ) -> Union[Dict[str, Any], AsyncGenerator[str, None]]:
836
- """转换Z.AI响应为OpenAI格式"""
837
- chat_id = transformed["chat_id"]
838
- model = transformed["model"]
839
-
840
- if request.stream:
841
- return self._handle_stream_response(response, chat_id, model, request, transformed)
842
- else:
843
- return await self._handle_non_stream_response(response, chat_id, model)
844
-
845
  async def _handle_stream_response(
846
  self,
847
  response: httpx.Response,
@@ -1122,15 +1117,27 @@ class ZAIProvider(BaseProvider):
1122
  self.logger.info(f"[_handle_non_stream_response] 开始处理响应,Content-Type: {response.headers.get('content-type', '未知')}")
1123
 
1124
  all_lines = []
 
 
 
 
 
1125
  try:
1126
  async for line in response.aiter_lines():
1127
  if not line:
1128
  continue
1129
 
1130
  line = line.strip()
 
 
 
 
 
 
 
1131
  # 收集所有行用于调试
1132
  if line:
1133
- self.logger.debug(f"[_handle_non_stream_response] 原始行: {line[:200]}")
1134
  all_lines.append(line)
1135
 
1136
  # 仅处理以 data: 开头的 SSE 行,其余行尝试作为错误/JSON 忽略
@@ -1201,13 +1208,20 @@ class ZAIProvider(BaseProvider):
1201
  final_content += delta_content
1202
 
1203
  # 循环结束后,记录所有采集的线和内容
1204
- self.logger.info(f"[_handle_non_stream_response] 处理完成,共 {len(all_lines)} 行数据")
1205
- self.logger.debug(f"[_handle_non_stream_response] 最终内容长度: {len(final_content)}, 思考长度: {len(reasoning_content)}")
 
1206
  if not final_content and not reasoning_content and len(all_lines) > 0:
1207
- self.logger.warning(f"[_handle_non_stream_response] 警告:未提取到内容,但接收到 {len(all_lines)} 行数据")
1208
- self.logger.warning(f"[_handle_non_stream_response] 前10行数据: {all_lines[:10]}")
1209
 
1210
  except Exception as e:
 
 
 
 
 
 
1211
  self.logger.error(f"❌ 非流式响应处理错误: {e}")
1212
  import traceback
1213
  self.logger.error(traceback.format_exc())
 
705
  transformed = await self.transform_request(request)
706
  self.logger.debug(f"[chat_completion] 转换后的请求: {transformed['url'][:100]}...")
707
 
708
+
709
  # 根据请求类型返回响应
710
  if request.stream:
711
  # 流式响应
 
714
  # Get proxy configuration
715
  proxies = self._get_proxy_config()
716
 
717
+ # 非流式响应 - 增加超时时间到90秒,Z.AI API 有时响应较慢
718
+ # 使用扩展的超时配置:连接5秒,读取85秒
719
+ extended_timeout = httpx.Timeout(5.0, read=85.0, connect=5.0)
720
+ async with httpx.AsyncClient(timeout=extended_timeout, proxy=proxies) as client:
721
+ self.logger.info(f"🔄 发送非流式请求到 Z.AI (超时: 90秒): {transformed['url']}")
722
  response = await client.post(
723
  transformed["url"],
724
  headers=transformed["headers"],
 
735
  # 记录响应状态
736
  self.logger.info(f"✅ 上游响应成功: {response.status_code}, Content-Length: {response.headers.get('content-length', 'N/A')}")
737
  try:
738
+ # 修正:立即调用正确的处理方法
739
+ if request.stream:
740
+ return self._create_stream_response(request, transformed)
741
+ else:
742
+ chat_id = transformed.get("chat_id", "unknown")
743
+ model = transformed.get("model", "unknown")
744
+ return await self._handle_non_stream_response(response, chat_id, model)
745
  except Exception as transform_error:
746
  self.logger.error(f"❌ transform_response 失败: {transform_error}")
747
  body_text = response.text[:1000] if response.text else "无响应体"
 
837
  yield "data: [DONE]\n\n"
838
  return
839
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
  async def _handle_stream_response(
841
  self,
842
  response: httpx.Response,
 
1117
  self.logger.info(f"[_handle_non_stream_response] 开始处理响应,Content-Type: {response.headers.get('content-type', '未知')}")
1118
 
1119
  all_lines = []
1120
+ line_count = 0
1121
+ max_lines = 1000 # 限制读取行数,防止无限读取
1122
+ read_timeout_count = 0
1123
+ max_timeout_retries = 2 # 最大超时重试次数
1124
+
1125
  try:
1126
  async for line in response.aiter_lines():
1127
  if not line:
1128
  continue
1129
 
1130
  line = line.strip()
1131
+
1132
+ # 数量限制:防止无限读取
1133
+ line_count += 1
1134
+ if line_count > max_lines:
1135
+ self.logger.warning(f"⚠️ 行数超过限制 {max_lines},停止读取")
1136
+ break
1137
+
1138
  # 收集所有行用于调试
1139
  if line:
1140
+ self.logger.debug(f"[_handle_non_stream_response] 原始行 [{line_count}]: {line[:200]}")
1141
  all_lines.append(line)
1142
 
1143
  # 仅处理以 data: 开头的 SSE 行,其余行尝试作为错误/JSON 忽略
 
1208
  final_content += delta_content
1209
 
1210
  # 循环结束后,记录所有采集的线和内容
1211
+ self.logger.info(f"[_handle_non_stream_response] 处理完成 - 总行数: {line_count}, 有效SSE行: {len(all_lines)}")
1212
+ self.logger.debug(f"[_handle_non_stream_response] 内容统计 - 答案: {len(final_content)}字, 思考: {len(reasoning_content)}")
1213
+
1214
  if not final_content and not reasoning_content and len(all_lines) > 0:
1215
+ self.logger.warning(f"[_handle_non_stream_response] ⚠️ 未提取到内容,但收到 {len(all_lines)} 行数据")
1216
+ self.logger.warning(f"[_handle_non_stream_response] 调试数据: {all_lines[:5]}")
1217
 
1218
  except Exception as e:
1219
+ # 特殊处理超时异常,提供降级方案
1220
+ if "ReadTimeout" in str(type(e).__name__) or "Timeout" in str(type(e).__name__):
1221
+ self.logger.error(f"⏰ 读取超时异常 - 已处理 {line_count} 行数据,部分结果: {len(final_content)}字")
1222
+ if final_content or reasoning_content:
1223
+ self.logger.warning(f"⚠️ 虽然超时,但返回已收集的内容,用户仍可获得部分响应")
1224
+ # 继续执行后续的清理返回逻辑,不立即返回错误
1225
  self.logger.error(f"❌ 非流式响应处理错误: {e}")
1226
  import traceback
1227
  self.logger.error(traceback.format_exc())
uptime-heartbeat.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Uptime Robot 心跳保活脚本
5
+ 使用方法:
6
+ 1. 在 Uptime Robot 创建监控,类型: HTTP(s),URL: https://{your-space}.hf.space/hf/v1/models
7
+ 2. 间隔设置为 5-10 分钟
8
+ 3. 或者直接用此脚本本地运行
9
+ """
10
+
11
+ import time
12
+ import requests
13
+ import os
14
+ from datetime import datetime
15
+
16
+ def send_heartbeat():
17
+ """发送心跳请求到 HuggingFace Space"""
18
+
19
+ # 你的 Space URL
20
+ # 实际的 HuggingFace Space 域名格式: https://sanbo1200-zai.hf.space
21
+ SPACE_URL = os.getenv("HF_SPACE_URL", "https://sanbo1200-zai.hf.space")
22
+
23
+ # 测试的健康检查端点(使用 /hf/v1/models 轻量级接口)
24
+ HEALTH_URL = f"{SPACE_URL}/hf/v1/models"
25
+
26
+ try:
27
+ start_time = datetime.now()
28
+ response = requests.get(
29
+ HEALTH_URL,
30
+ timeout=10,
31
+ headers={
32
+ "User-Agent": "UptimeMonitor/1.0"
33
+ }
34
+ )
35
+ end_time = datetime.now()
36
+ response_time = (end_time - start_time).total_seconds() * 1000
37
+
38
+ if response.status_code == 200:
39
+ print(f"✅ [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 心跳成功 - {response_time:.0f}ms")
40
+ return True
41
+ else:
42
+ print(f"⚠️ [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 心跳失败 - 状态码: {response.status_code}")
43
+ return False
44
+
45
+ except requests.exceptions.RequestException as e:
46
+ print(f"❌ [{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 连接错误: {e}")
47
+ return False
48
+
49
+ if __name__ == "__main__":
50
+ print("🚀 HuggingFace Space 保活服务启动")
51
+ print(f"目标空间: https://sanbo1200-zai.hf.space")
52
+ print("发送间隔: 每 5 分钟 (默认)\n")
53
+
54
+ while True:
55
+ send_heartbeat()
56
+ time.sleep(300) # 5分钟 = 300秒