astron-eval 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/bin/astron-eval.mjs +111 -0
- package/package.json +24 -0
- package/skills/astron-eval/SKILL.md +60 -0
- package/skills/model-evaluation/SKILL.md +180 -0
- package/skills/model-evaluation/assets/dimensions//345/206/205/345/256/271/347/233/270/345/205/263/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/206/205/345/256/271/347/262/276/347/241/256/347/273/264/345/272/246.json +19 -0
- package/skills/model-evaluation/assets/dimensions//345/207/206/347/241/256/346/200/247/347/273/264/345/272/246-/344/270/252/346/200/247/345/214/226/350/247/204/345/210/222.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/207/206/347/241/256/346/200/247/347/273/264/345/272/246-/344/277/241/346/201/257/345/210/206/346/236/220.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/207/206/347/241/256/346/200/247/347/273/264/345/272/246-/346/227/205/346/270/270/345/207/272/350/241/214.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/207/206/347/241/256/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/210/233/346/204/217/346/200/247-/345/220/270/345/274/225/346/200/247/347/273/264/345/272/246.json +21 -0
- package/skills/model-evaluation/assets/dimensions//345/210/233/346/226/260/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/256/214/346/225/264/346/200/247/347/273/264/345/272/246-/344/277/241/346/201/257/345/210/206/346/236/220.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/256/214/346/225/264/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/275/242/345/274/217/347/233/270/345/205/263/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//345/277/240/350/257/232/345/272/246/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/214/207/344/273/244/351/201/265/345/276/252/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/226/207/346/234/254/345/267/256/345/274/202/345/272/246-TER/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/234/211/346/225/210/346/200/247/347/273/264/345/272/246-/344/270/252/346/200/247/345/214/226/350/247/204/345/210/222.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/234/211/346/225/210/346/200/247/347/273/264/345/272/246-/344/277/241/346/201/257/345/210/206/346/236/220.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/234/211/346/225/210/346/200/247/347/273/264/345/272/246-/346/265/201/347/250/213/350/207/252/345/212/250/345/214/226.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/234/211/346/225/210/346/200/247/347/273/264/345/272/246.json +21 -0
- package/skills/model-evaluation/assets/dimensions//346/240/270/345/277/203/345/205/203/347/264/240/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//346/240/274/345/274/217/351/201/265/345/276/252/347/273/264/345/272/246.json +19 -0
- package/skills/model-evaluation/assets/dimensions//347/211/271/350/211/262/344/272/256/347/202/271/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/224/250/344/276/213/347/272/247/350/257/204/346/265/213/347/273/264/345/272/246/346/250/241/346/235/277.json +25 -0
- package/skills/model-evaluation/assets/dimensions//347/233/270/344/274/274/345/272/246-BERTScore/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/233/270/344/274/274/345/272/246-Cosine/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/233/270/344/274/274/345/272/246-ROUGE/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/233/270/345/205/263/346/200/247/347/273/264/345/272/246-/344/270/252/346/200/247/345/214/226/350/247/204/345/210/222.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/233/270/345/205/263/346/200/247/347/273/264/345/272/246.json +21 -0
- package/skills/model-evaluation/assets/dimensions//347/262/276/347/241/256/346/200/247-BLUE/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//347/262/276/347/241/256/346/200/247-COMET/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//351/200/273/350/276/221/345/220/210/347/220/206/346/200/247/347/273/264/345/272/246.json +20 -0
- package/skills/model-evaluation/assets/dimensions//351/200/273/350/276/221/350/277/236/350/264/257/346/200/247/347/273/264/345/272/246-/344/270/252/346/200/247/345/214/226/350/247/204/345/210/222.json +20 -0
- package/skills/model-evaluation/assets/dimensions//351/200/273/350/276/221/350/277/236/350/264/257/346/200/247/347/273/264/345/272/246-/344/277/241/346/201/257/345/210/206/346/236/220.json +20 -0
- package/skills/model-evaluation/assets/dimensions//351/200/273/350/276/221/350/277/236/350/264/257/346/200/247/347/273/264/345/272/246-/346/265/201/347/250/213/350/207/252/345/212/250/345/214/226.json +20 -0
- package/skills/model-evaluation/assets/dimensions//351/200/273/350/276/221/350/277/236/350/264/257/346/200/247/347/273/264/345/272/246.json +21 -0
- package/skills/model-evaluation/assets/eval-judge.json +11 -0
- package/skills/model-evaluation/assets/experts/business-process-automation.json +71 -0
- package/skills/model-evaluation/assets/experts/content-generation.json +75 -0
- package/skills/model-evaluation/assets/experts/content-match.json +37 -0
- package/skills/model-evaluation/assets/experts/information-analysis.json +87 -0
- package/skills/model-evaluation/assets/experts/marketing-digital-human.json +27 -0
- package/skills/model-evaluation/assets/experts/personalized-planning.json +87 -0
- package/skills/model-evaluation/assets/experts/text-translation.json +103 -0
- package/skills/model-evaluation/assets/experts/tourism-travel.json +119 -0
- package/skills/model-evaluation/assets/templates/custom-dimension.template.json +30 -0
- package/skills/model-evaluation/eval-build.md +281 -0
- package/skills/model-evaluation/eval-execute.md +196 -0
- package/skills/model-evaluation/eval-init.md +237 -0
- package/skills/model-evaluation/processes/dimension-process.md +207 -0
- package/skills/model-evaluation/processes/evalset-create-process.md +184 -0
- package/skills/model-evaluation/processes/evalset-parse-process.md +171 -0
- package/skills/model-evaluation/processes/evalset-supplement-process.md +136 -0
- package/skills/model-evaluation/processes/keypoint-process.md +148 -0
- package/skills/model-evaluation/processes/python-env-process.md +113 -0
- package/skills/model-evaluation/references//344/270/255/351/227/264/344/272/247/347/211/251/350/257/264/346/230/216.md +340 -0
- package/skills/model-evaluation/references//345/206/205/347/275/256/346/250/241/346/235/277/350/257/264/346/230/216.md +149 -0
- package/skills/model-evaluation/references//350/204/232/346/234/254/345/256/232/344/271/211.md +274 -0
- package/skills/model-evaluation/references//350/256/244/350/257/201/346/234/215/345/212/241/346/216/245/345/217/243/350/257/264/346/230/216.md +271 -0
- package/skills/model-evaluation/references//350/257/204/346/265/213/346/234/215/345/212/241/346/216/245/345/217/243/350/257/264/346/230/216.md +455 -0
- package/skills/model-evaluation/references//350/257/204/346/265/213/347/273/264/345/272/246/350/257/264/346/230/216.md +171 -0
- package/skills/model-evaluation/scripts/cfg/eval-auth.cfg +16 -0
- package/skills/model-evaluation/scripts/cfg/eval-server.cfg +1 -0
- package/skills/model-evaluation/scripts/clients/__init__.py +33 -0
- package/skills/model-evaluation/scripts/clients/api_client.py +97 -0
- package/skills/model-evaluation/scripts/clients/auth_client.py +96 -0
- package/skills/model-evaluation/scripts/clients/http_client.py +199 -0
- package/skills/model-evaluation/scripts/clients/oauth_callback.py +397 -0
- package/skills/model-evaluation/scripts/clients/token_manager.py +53 -0
- package/skills/model-evaluation/scripts/eval_auth.py +588 -0
- package/skills/model-evaluation/scripts/eval_dimension.py +240 -0
- package/skills/model-evaluation/scripts/eval_set.py +410 -0
- package/skills/model-evaluation/scripts/eval_task.py +324 -0
- package/skills/model-evaluation/scripts/files/__init__.py +38 -0
- package/skills/model-evaluation/scripts/files/file_utils.py +330 -0
- package/skills/model-evaluation/scripts/files/streaming.py +245 -0
- package/skills/model-evaluation/scripts/utils/__init__.py +128 -0
- package/skills/model-evaluation/scripts/utils/constants.py +101 -0
- package/skills/model-evaluation/scripts/utils/datetime_utils.py +60 -0
- package/skills/model-evaluation/scripts/utils/errors.py +244 -0
- package/skills/model-evaluation/scripts/utils/keypoint_prompts.py +73 -0
- package/skills/skill-driven-eval/SKILL.md +456 -0
- package/skills/skill-driven-eval/agents/grader.md +144 -0
- package/skills/skill-driven-eval/eval-viewer/__init__.py +1 -0
- package/skills/skill-driven-eval/eval-viewer/generate_report.py +485 -0
- package/skills/skill-driven-eval/eval-viewer/viewer.html +767 -0
- package/skills/skill-driven-eval/references/schemas.md +282 -0
- package/skills/skill-driven-eval/scripts/__init__.py +1 -0
- package/skills/skill-driven-eval/scripts/__main__.py +70 -0
- package/skills/skill-driven-eval/scripts/aggregate_results.py +681 -0
- package/skills/skill-driven-eval/scripts/extract_transcript.py +294 -0
- package/skills/skill-driven-eval/scripts/test_aggregate.py +244 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
评测服务 API 客户端模块
|
|
4
|
+
|
|
5
|
+
继承 BaseHttpClient,添加 Bearer Token 注入
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Any
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from .http_client import BaseHttpClient
|
|
13
|
+
from utils.constants import DEFAULT_TIMEOUT
|
|
14
|
+
from utils.errors import (
|
|
15
|
+
AuthExpiredError,
|
|
16
|
+
ApiError,
|
|
17
|
+
)
|
|
18
|
+
from .token_manager import TokenManager
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
# 评测服务 API 客户端
|
|
25
|
+
# ============================================================================
|
|
26
|
+
|
|
27
|
+
class ApiClient(BaseHttpClient):
|
|
28
|
+
"""
|
|
29
|
+
评测服务 API 客户端
|
|
30
|
+
|
|
31
|
+
继承 BaseHttpClient,添加:
|
|
32
|
+
- Bearer Token 认证注入
|
|
33
|
+
- 评测服务特定的响应处理
|
|
34
|
+
- Token 过期检测
|
|
35
|
+
|
|
36
|
+
D-05: RequestBuilder 负责构建请求、注入 header、处理响应
|
|
37
|
+
D-08: 只重试瞬态故障: 502, 503, 504
|
|
38
|
+
D-09: 3 次重试 + 指数退避 (1s, 2s, 4s)
|
|
39
|
+
D-10: 透传远程服务错误,保留原 code
|
|
40
|
+
D-12: 简洁日志 - URL + 方法 + 状态码 + 耗时
|
|
41
|
+
D-13: 分级日志 - INFO/WARNING/ERROR
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
token_manager: TokenManager,
|
|
47
|
+
base_url: str,
|
|
48
|
+
timeout: int = DEFAULT_TIMEOUT
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
初始化评测服务客户端
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
token_manager: Token 管理器
|
|
55
|
+
base_url: 评测服务基础 URL
|
|
56
|
+
timeout: 请求超时时间(秒)
|
|
57
|
+
"""
|
|
58
|
+
# 保存 token_manager
|
|
59
|
+
self.token_manager = token_manager
|
|
60
|
+
|
|
61
|
+
# 调用父类初始化
|
|
62
|
+
super().__init__(
|
|
63
|
+
base_url=base_url,
|
|
64
|
+
timeout=timeout,
|
|
65
|
+
verify_ssl=False # 跳过SSL验证(部分服务器证书异常)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _inject_auth(self, headers: Dict[str, str]) -> Dict[str, str]:
|
|
69
|
+
"""注入 Bearer Token 认证"""
|
|
70
|
+
token = self.token_manager.get_token()
|
|
71
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
72
|
+
return headers
|
|
73
|
+
|
|
74
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
处理评测服务响应
|
|
77
|
+
|
|
78
|
+
D-10: 透传远程错误码和消息
|
|
79
|
+
"""
|
|
80
|
+
# 检查认证过期 (code=10002)
|
|
81
|
+
if response.status_code == 401:
|
|
82
|
+
try:
|
|
83
|
+
if response.json().get('code') == 10002:
|
|
84
|
+
raise AuthExpiredError("Token 已过期,请重新授权")
|
|
85
|
+
except ValueError:
|
|
86
|
+
pass
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
|
|
89
|
+
result = response.json()
|
|
90
|
+
if result.get('code') != 0:
|
|
91
|
+
# D-10: 透传远程错误码和消息
|
|
92
|
+
raise ApiError(
|
|
93
|
+
message=result.get('message', 'Unknown error'),
|
|
94
|
+
code=result.get('code'),
|
|
95
|
+
data=result.get('data')
|
|
96
|
+
)
|
|
97
|
+
return result.get('data', {})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
认证服务客户端模块
|
|
4
|
+
|
|
5
|
+
用于与 OAuth2 认证服务交互:
|
|
6
|
+
- 无 Bearer Token 注入(认证服务本身不需要)
|
|
7
|
+
- 处理认证服务特定的响应格式
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from .http_client import BaseHttpClient
|
|
15
|
+
from utils.constants import DEFAULT_TIMEOUT, MAX_RETRIES, RETRY_BACKOFF_FACTOR
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthClient(BaseHttpClient):
|
|
21
|
+
"""
|
|
22
|
+
认证服务客户端
|
|
23
|
+
|
|
24
|
+
用于与 OAuth2 认证服务交互:
|
|
25
|
+
- 无 Bearer Token 注入(认证服务本身不需要)
|
|
26
|
+
- 处理认证服务特定的响应格式
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
base_url: str = "",
|
|
32
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
33
|
+
max_retries: int = MAX_RETRIES,
|
|
34
|
+
retry_backoff_factor: float = RETRY_BACKOFF_FACTOR,
|
|
35
|
+
verify_ssl: bool = False
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
初始化认证客户端
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
base_url: 认证服务基础 URL(可选,可使用完整 URL 请求)
|
|
42
|
+
timeout: 请求超时时间(秒)
|
|
43
|
+
max_retries: 最大重试次数
|
|
44
|
+
retry_backoff_factor: 重试退避因子
|
|
45
|
+
verify_ssl: 是否验证 SSL 证书
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(
|
|
48
|
+
base_url=base_url,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
max_retries=max_retries,
|
|
51
|
+
retry_backoff_factor=retry_backoff_factor,
|
|
52
|
+
verify_ssl=verify_ssl
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
56
|
+
"""
|
|
57
|
+
处理认证服务响应
|
|
58
|
+
|
|
59
|
+
认证服务响应格式:
|
|
60
|
+
{
|
|
61
|
+
"code": 0,
|
|
62
|
+
"message": "success",
|
|
63
|
+
"data": {...}
|
|
64
|
+
}
|
|
65
|
+
或直接返回数据
|
|
66
|
+
"""
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
result = response.json()
|
|
69
|
+
|
|
70
|
+
# 兼容两种响应格式
|
|
71
|
+
if "code" in result:
|
|
72
|
+
if result.get("code") != 0:
|
|
73
|
+
return result # 返回完整错误信息
|
|
74
|
+
return result.get("data", result)
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def request_full_url(
|
|
78
|
+
self,
|
|
79
|
+
method: str,
|
|
80
|
+
url: str,
|
|
81
|
+
**kwargs
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
使用完整 URL 发送请求(不拼接 base_url)
|
|
85
|
+
|
|
86
|
+
适用于认证服务的端点 URL 是完整路径的场景
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
method: HTTP 方法
|
|
90
|
+
url: 完整 URL
|
|
91
|
+
**kwargs: 其他参数
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
响应数据
|
|
95
|
+
"""
|
|
96
|
+
return self.request(method, url, full_url=True, **kwargs)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
基础 HTTP 客户端模块
|
|
4
|
+
|
|
5
|
+
提供统一的 HTTP 请求处理:
|
|
6
|
+
- 自动重试(连接超时、读取超时、瞬态故障)
|
|
7
|
+
- 超时控制
|
|
8
|
+
- 连接池管理
|
|
9
|
+
- SSL 证书验证控制
|
|
10
|
+
- 分级日志
|
|
11
|
+
|
|
12
|
+
子类可覆写:
|
|
13
|
+
- _inject_auth(): 注入认证信息
|
|
14
|
+
- _handle_response(): 处理响应
|
|
15
|
+
"""
|
|
16
|
+
import time
|
|
17
|
+
import logging
|
|
18
|
+
import requests
|
|
19
|
+
from typing import Optional, Dict, Any
|
|
20
|
+
from requests.adapters import HTTPAdapter
|
|
21
|
+
from urllib3.util.retry import Retry
|
|
22
|
+
import urllib3
|
|
23
|
+
|
|
24
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
25
|
+
|
|
26
|
+
from utils.constants import (
|
|
27
|
+
DEFAULT_TIMEOUT,
|
|
28
|
+
MAX_RETRIES,
|
|
29
|
+
RETRY_BACKOFF_FACTOR,
|
|
30
|
+
ERR_NETWORK_RETRY_EXHAUSTED,
|
|
31
|
+
)
|
|
32
|
+
from utils.errors import (
|
|
33
|
+
NetworkError,
|
|
34
|
+
NetworkTimeoutError,
|
|
35
|
+
NetworkConnectionError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseHttpClient:
|
|
42
|
+
"""
|
|
43
|
+
基础 HTTP 客户端
|
|
44
|
+
|
|
45
|
+
提供统一的 HTTP 请求处理:
|
|
46
|
+
- 自动重试(连接超时、读取超时、瞬态故障)
|
|
47
|
+
- 超时控制
|
|
48
|
+
- 连接池管理
|
|
49
|
+
- SSL 证书验证控制
|
|
50
|
+
- 分级日志
|
|
51
|
+
|
|
52
|
+
子类可覆写:
|
|
53
|
+
- _inject_auth(): 注入认证信息
|
|
54
|
+
- _handle_response(): 处理响应
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
base_url: str = "",
|
|
60
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
61
|
+
max_retries: int = MAX_RETRIES,
|
|
62
|
+
retry_backoff_factor: float = RETRY_BACKOFF_FACTOR,
|
|
63
|
+
verify_ssl: bool = False
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
初始化基础 HTTP 客户端
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
base_url: 服务基础 URL(可选,可使用完整 URL 请求)
|
|
70
|
+
timeout: 请求超时时间(秒)
|
|
71
|
+
max_retries: 最大重试次数
|
|
72
|
+
retry_backoff_factor: 重试退避因子
|
|
73
|
+
verify_ssl: 是否验证 SSL 证书
|
|
74
|
+
"""
|
|
75
|
+
self.base_url = base_url.rstrip('/') if base_url else ""
|
|
76
|
+
self.timeout = timeout
|
|
77
|
+
self.max_retries = max_retries
|
|
78
|
+
self.retry_backoff_factor = retry_backoff_factor
|
|
79
|
+
self.verify_ssl = verify_ssl
|
|
80
|
+
self.session = self._create_session()
|
|
81
|
+
|
|
82
|
+
def _create_session(self) -> requests.Session:
|
|
83
|
+
"""创建带重试配置的 Session"""
|
|
84
|
+
session = requests.Session()
|
|
85
|
+
|
|
86
|
+
# 配置重试策略
|
|
87
|
+
# 重试条件:连接超时、读取超时、连接错误、瞬态 HTTP 错误
|
|
88
|
+
retry = Retry(
|
|
89
|
+
total=self.max_retries,
|
|
90
|
+
backoff_factor=self.retry_backoff_factor,
|
|
91
|
+
status_forcelist=[429, 500, 502, 503, 504], # 瞬态故障
|
|
92
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE", "PATCH"],
|
|
93
|
+
raise_on_status=False,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
adapter = HTTPAdapter(max_retries=retry)
|
|
97
|
+
session.mount("https://", adapter)
|
|
98
|
+
session.mount("http://", adapter)
|
|
99
|
+
|
|
100
|
+
return session
|
|
101
|
+
|
|
102
|
+
def _inject_auth(self, headers: Dict[str, str]) -> Dict[str, str]:
|
|
103
|
+
"""
|
|
104
|
+
注入认证信息(子类覆写)
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
headers: 现有请求头
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
注入认证信息后的请求头
|
|
111
|
+
"""
|
|
112
|
+
return headers
|
|
113
|
+
|
|
114
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
115
|
+
"""
|
|
116
|
+
处理响应(子类可覆写)
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
response: HTTP 响应对象
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
解析后的响应数据
|
|
123
|
+
"""
|
|
124
|
+
response.raise_for_status()
|
|
125
|
+
return response.json()
|
|
126
|
+
|
|
127
|
+
def request(
|
|
128
|
+
self,
|
|
129
|
+
method: str,
|
|
130
|
+
url_or_endpoint: str,
|
|
131
|
+
full_url: bool = False,
|
|
132
|
+
**kwargs
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""
|
|
135
|
+
发送 HTTP 请求
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
method: HTTP 方法(GET, POST, etc.)
|
|
139
|
+
url_or_endpoint: URL 或端点路径
|
|
140
|
+
full_url: 是否为完整 URL(True 则不拼接 base_url)
|
|
141
|
+
**kwargs: 传递给 requests 的其他参数
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
解析后的响应数据
|
|
145
|
+
"""
|
|
146
|
+
# 构建 URL
|
|
147
|
+
if full_url:
|
|
148
|
+
url = url_or_endpoint
|
|
149
|
+
else:
|
|
150
|
+
url = f"{self.base_url}{url_or_endpoint}"
|
|
151
|
+
|
|
152
|
+
# 设置默认参数
|
|
153
|
+
kwargs.setdefault("timeout", self.timeout)
|
|
154
|
+
kwargs.setdefault("verify", self.verify_ssl)
|
|
155
|
+
|
|
156
|
+
# 处理请求头
|
|
157
|
+
headers = kwargs.pop("headers", {})
|
|
158
|
+
headers.setdefault("Content-Type", "application/json")
|
|
159
|
+
headers = self._inject_auth(headers)
|
|
160
|
+
kwargs["headers"] = headers
|
|
161
|
+
|
|
162
|
+
start = time.time()
|
|
163
|
+
try:
|
|
164
|
+
resp = self.session.request(method, url, **kwargs)
|
|
165
|
+
elapsed = time.time() - start
|
|
166
|
+
logger.info(f"{method} {url} -> {resp.status_code} ({elapsed:.2f}s)")
|
|
167
|
+
return self._handle_response(resp)
|
|
168
|
+
|
|
169
|
+
except requests.Timeout as e:
|
|
170
|
+
logger.error(f"{method} {url} -> TIMEOUT")
|
|
171
|
+
raise NetworkTimeoutError(f"请求超时: {url}", original_error=e)
|
|
172
|
+
|
|
173
|
+
except requests.ConnectionError as e:
|
|
174
|
+
logger.error(f"{method} {url} -> CONNECTION_ERROR")
|
|
175
|
+
raise NetworkConnectionError(f"连接失败: {url}", original_error=e)
|
|
176
|
+
|
|
177
|
+
except requests.exceptions.RetryError as e:
|
|
178
|
+
logger.error(f"{method} {url} -> RETRY_EXHAUSTED")
|
|
179
|
+
raise NetworkError(
|
|
180
|
+
f"重试耗尽: {url}",
|
|
181
|
+
original_error=e,
|
|
182
|
+
code=ERR_NETWORK_RETRY_EXHAUSTED
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def get(self, url_or_endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
186
|
+
"""发送 GET 请求"""
|
|
187
|
+
return self.request("GET", url_or_endpoint, **kwargs)
|
|
188
|
+
|
|
189
|
+
def post(self, url_or_endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
190
|
+
"""发送 POST 请求"""
|
|
191
|
+
return self.request("POST", url_or_endpoint, **kwargs)
|
|
192
|
+
|
|
193
|
+
def put(self, url_or_endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
194
|
+
"""发送 PUT 请求"""
|
|
195
|
+
return self.request("PUT", url_or_endpoint, **kwargs)
|
|
196
|
+
|
|
197
|
+
def delete(self, url_or_endpoint: str, **kwargs) -> Dict[str, Any]:
|
|
198
|
+
"""发送 DELETE 请求"""
|
|
199
|
+
return self.request("DELETE", url_or_endpoint, **kwargs)
|