sdpy-kit 0.1.4__tar.gz → 0.1.6__tar.gz
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.
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/PKG-INFO +53 -10
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/README.md +34 -1
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/__init__.py +1 -1
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/base_service.py +11 -3
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/chat_service.py +20 -4
- sdpy_kit-0.1.6/kit/ai/exceptions.py +99 -0
- sdpy_kit-0.1.6/kit/ai/failover.py +350 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/image_service.py +1 -3
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/model_client.py +69 -13
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/pool.py +27 -17
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/profiles.py +18 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/runner.py +149 -39
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/crud.py +8 -14
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/engine.py +1 -3
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/sqlite_kit.py +6 -1
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/doc_kit.py +9 -6
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/mineru_kit.py +6 -1
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/pyproject.toml +35 -15
- sdpy_kit-0.1.4/kit/ai/exceptions.py +0 -57
- sdpy_kit-0.1.4/kit/ai/failover.py +0 -244
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/.gitignore +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/types.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/common_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/query.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/session.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/file_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/zvec_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,32 +17,42 @@ Classifier: Topic :: Database
|
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.13
|
|
20
|
-
Requires-Dist: firecrawl-anydoc>=0.2.4
|
|
21
|
-
Requires-Dist: httpx>=0.28.1
|
|
22
|
-
Requires-Dist: markdown-it-py>=3.0
|
|
23
|
-
Requires-Dist: numpy>=2.0
|
|
24
|
-
Requires-Dist: pimd[pdfa]>=2.2.5
|
|
25
20
|
Requires-Dist: pydantic-settings>=2.0
|
|
26
21
|
Requires-Dist: pydantic>=2.0
|
|
27
|
-
Requires-Dist: python-dotenv>=1.2.3
|
|
28
|
-
Requires-Dist: requests>=2.34.0
|
|
29
22
|
Requires-Dist: sqlalchemy~=2.0.51
|
|
30
|
-
Requires-Dist: sqliteai-vector>=1.0.0
|
|
31
23
|
Requires-Dist: sqlmodel>=0.0.39
|
|
24
|
+
Provides-Extra: ai
|
|
25
|
+
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
32
26
|
Provides-Extra: all
|
|
33
27
|
Requires-Dist: fastmcp>=4.0.3; extra == 'all'
|
|
28
|
+
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
29
|
+
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
30
|
+
Requires-Dist: markdown-it-py>=3.0; extra == 'all'
|
|
31
|
+
Requires-Dist: numpy>=2.0; extra == 'all'
|
|
32
|
+
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
|
|
34
33
|
Requires-Dist: psycopg2-binary>=2.9.12; extra == 'all'
|
|
35
34
|
Requires-Dist: pymysql>=1.2.0; extra == 'all'
|
|
36
35
|
Requires-Dist: redis>=5.0.0; extra == 'all'
|
|
36
|
+
Requires-Dist: requests>=2.34.0; extra == 'all'
|
|
37
|
+
Requires-Dist: sqliteai-vector>=1.1.0; extra == 'all'
|
|
37
38
|
Requires-Dist: zvec>=0.6.0; extra == 'all'
|
|
39
|
+
Provides-Extra: doc
|
|
40
|
+
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'doc'
|
|
41
|
+
Requires-Dist: markdown-it-py>=3.0; extra == 'doc'
|
|
42
|
+
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'doc'
|
|
38
43
|
Provides-Extra: mcp
|
|
39
44
|
Requires-Dist: fastmcp>=4.0.3; extra == 'mcp'
|
|
45
|
+
Provides-Extra: mineru
|
|
46
|
+
Requires-Dist: requests>=2.34.0; extra == 'mineru'
|
|
40
47
|
Provides-Extra: mysql
|
|
41
48
|
Requires-Dist: pymysql>=1.2.0; extra == 'mysql'
|
|
42
49
|
Provides-Extra: pg
|
|
43
50
|
Requires-Dist: psycopg2-binary>=2.9.12; extra == 'pg'
|
|
44
51
|
Provides-Extra: redis
|
|
45
52
|
Requires-Dist: redis>=5.0.0; extra == 'redis'
|
|
53
|
+
Provides-Extra: sqliteai-vector
|
|
54
|
+
Requires-Dist: numpy>=2.0; extra == 'sqliteai-vector'
|
|
55
|
+
Requires-Dist: sqliteai-vector>=1.1.0; extra == 'sqliteai-vector'
|
|
46
56
|
Provides-Extra: zvec
|
|
47
57
|
Requires-Dist: zvec>=0.6.0; extra == 'zvec'
|
|
48
58
|
Description-Content-Type: text/markdown
|
|
@@ -56,11 +66,26 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:统一数
|
|
|
56
66
|
## 安装
|
|
57
67
|
|
|
58
68
|
```bash
|
|
59
|
-
pip install sdpy-kit
|
|
69
|
+
pip install sdpy-kit # 基础版:config/log + kit.db 核心(SQLite 常规操作零额外依赖)
|
|
60
70
|
# 或
|
|
61
71
|
uv add sdpy-kit
|
|
62
72
|
```
|
|
63
73
|
|
|
74
|
+
可选能力按 extras 按需安装(均为延迟导入,不装不影响基础使用,缺依赖时报错会提示安装命令):
|
|
75
|
+
|
|
76
|
+
| extra | 能力 | 安装 |
|
|
77
|
+
|-------|------|------|
|
|
78
|
+
| `pg` | PgKit(PostgreSQL 驱动) | `pip install "sdpy-kit[pg]"` |
|
|
79
|
+
| `mysql` | MySqlKit(MySQL 驱动) | `pip install "sdpy-kit[mysql]"` |
|
|
80
|
+
| `vec` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[vec]"` |
|
|
81
|
+
| `zvec` | ZvecKit(zvec,阿里独立向量库,与 `vec` 完全独立) | `pip install "sdpy-kit[zvec]"` |
|
|
82
|
+
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
83
|
+
| `mcp` | McpKit(fastmcp) | `pip install "sdpy-kit[mcp]"` |
|
|
84
|
+
| `ai` | AI 服务池(httpx) | `pip install "sdpy-kit[ai]"` |
|
|
85
|
+
| `doc` | DocKit 文档转换 | `pip install "sdpy-kit[doc]"` |
|
|
86
|
+
| `mineru` | MineruKit 解析 API | `pip install "sdpy-kit[mineru]"` |
|
|
87
|
+
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
88
|
+
|
|
64
89
|
## 配置(.env)
|
|
65
90
|
|
|
66
91
|
kit 通过 pydantic-settings 读取项目根目录 `.env`(默认值见下,均可省略):
|
|
@@ -98,6 +123,24 @@ AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
|
|
|
98
123
|
文件缺失时,首次使用相应功能会报错,**报错信息中附带可直接复制的最小模板**;
|
|
99
124
|
其中 `api_key` / `token_env` 字段填写环境变量名,真实密钥配置在 `.env`。
|
|
100
125
|
|
|
126
|
+
### ai_models.json 可选字段
|
|
127
|
+
|
|
128
|
+
| 层级 | 字段 | 说明 |
|
|
129
|
+
|------|------|------|
|
|
130
|
+
| 档案级 | `timeout` | 请求超时秒数(默认 60) |
|
|
131
|
+
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
132
|
+
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
133
|
+
| 类型级 | `chain_timeout` | 整链预算秒数(与 `default_profile` 同级);门面同步等待上限。未配置时按 `sum(候选 timeout) + 10s` 自适应,也可用环境变量 `KIT_AI_CHAIN_TIMEOUT` 全局覆盖 |
|
|
134
|
+
|
|
135
|
+
### 候选链切换语义(两类,勿混淆)
|
|
136
|
+
|
|
137
|
+
- **跨调用切换**:`AI.use()` 与其它请求产生的失败冷却均只影响**下一次**请求——每次调用的候选计划在开始时定格;
|
|
138
|
+
- **一次调用内降级**:本次调用按开始时定格的计划串行尝试(首帧前失败/首帧超时切下一候选),中途的 `use` / 冷却变化不影响它。
|
|
139
|
+
|
|
140
|
+
全部候选失败(或整链预算耗尽)抛 `AIAggregateError`(`AIRequestError` 子类):
|
|
141
|
+
全候选状态码一致时透传(如整链 429 可用 `exc.status_code == 429` 判定),
|
|
142
|
+
429 的 `Retry-After` 解析后挂于 `exc.retry_after`(冷却钳制 5–300s)。
|
|
143
|
+
|
|
101
144
|
## 快速开始
|
|
102
145
|
|
|
103
146
|
### 日志与配置
|
|
@@ -7,11 +7,26 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:统一数
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pip install sdpy-kit
|
|
10
|
+
pip install sdpy-kit # 基础版:config/log + kit.db 核心(SQLite 常规操作零额外依赖)
|
|
11
11
|
# 或
|
|
12
12
|
uv add sdpy-kit
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
可选能力按 extras 按需安装(均为延迟导入,不装不影响基础使用,缺依赖时报错会提示安装命令):
|
|
16
|
+
|
|
17
|
+
| extra | 能力 | 安装 |
|
|
18
|
+
|-------|------|------|
|
|
19
|
+
| `pg` | PgKit(PostgreSQL 驱动) | `pip install "sdpy-kit[pg]"` |
|
|
20
|
+
| `mysql` | MySqlKit(MySQL 驱动) | `pip install "sdpy-kit[mysql]"` |
|
|
21
|
+
| `vec` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[vec]"` |
|
|
22
|
+
| `zvec` | ZvecKit(zvec,阿里独立向量库,与 `vec` 完全独立) | `pip install "sdpy-kit[zvec]"` |
|
|
23
|
+
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
24
|
+
| `mcp` | McpKit(fastmcp) | `pip install "sdpy-kit[mcp]"` |
|
|
25
|
+
| `ai` | AI 服务池(httpx) | `pip install "sdpy-kit[ai]"` |
|
|
26
|
+
| `doc` | DocKit 文档转换 | `pip install "sdpy-kit[doc]"` |
|
|
27
|
+
| `mineru` | MineruKit 解析 API | `pip install "sdpy-kit[mineru]"` |
|
|
28
|
+
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
29
|
+
|
|
15
30
|
## 配置(.env)
|
|
16
31
|
|
|
17
32
|
kit 通过 pydantic-settings 读取项目根目录 `.env`(默认值见下,均可省略):
|
|
@@ -49,6 +64,24 @@ AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
|
|
|
49
64
|
文件缺失时,首次使用相应功能会报错,**报错信息中附带可直接复制的最小模板**;
|
|
50
65
|
其中 `api_key` / `token_env` 字段填写环境变量名,真实密钥配置在 `.env`。
|
|
51
66
|
|
|
67
|
+
### ai_models.json 可选字段
|
|
68
|
+
|
|
69
|
+
| 层级 | 字段 | 说明 |
|
|
70
|
+
|------|------|------|
|
|
71
|
+
| 档案级 | `timeout` | 请求超时秒数(默认 60) |
|
|
72
|
+
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
73
|
+
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
74
|
+
| 类型级 | `chain_timeout` | 整链预算秒数(与 `default_profile` 同级);门面同步等待上限。未配置时按 `sum(候选 timeout) + 10s` 自适应,也可用环境变量 `KIT_AI_CHAIN_TIMEOUT` 全局覆盖 |
|
|
75
|
+
|
|
76
|
+
### 候选链切换语义(两类,勿混淆)
|
|
77
|
+
|
|
78
|
+
- **跨调用切换**:`AI.use()` 与其它请求产生的失败冷却均只影响**下一次**请求——每次调用的候选计划在开始时定格;
|
|
79
|
+
- **一次调用内降级**:本次调用按开始时定格的计划串行尝试(首帧前失败/首帧超时切下一候选),中途的 `use` / 冷却变化不影响它。
|
|
80
|
+
|
|
81
|
+
全部候选失败(或整链预算耗尽)抛 `AIAggregateError`(`AIRequestError` 子类):
|
|
82
|
+
全候选状态码一致时透传(如整链 429 可用 `exc.status_code == 429` 判定),
|
|
83
|
+
429 的 `Retry-After` 解析后挂于 `exc.retry_after`(冷却钳制 5–300s)。
|
|
84
|
+
|
|
52
85
|
## 快速开始
|
|
53
86
|
|
|
54
87
|
### 日志与配置
|
|
@@ -113,8 +113,16 @@ class BaseAIService[RequestT, ResponseT](ABC):
|
|
|
113
113
|
def _parse_response(self, data: JSONObject) -> ResponseT:
|
|
114
114
|
"""将 JSON 响应体解析为业务结果。"""
|
|
115
115
|
|
|
116
|
-
async def _call(
|
|
117
|
-
|
|
116
|
+
async def _call(
|
|
117
|
+
self, request: RequestT, *, deadline: float | None = None
|
|
118
|
+
) -> ResponseT:
|
|
119
|
+
"""统一编排:构造请求体 → 发送 → 解析响应(含中文 info 日志)。
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
request: 请求模型
|
|
123
|
+
deadline: 整链预算截止时刻(``time.monotonic()`` 秒),透传给
|
|
124
|
+
``post_json`` 的重试预算检查;None 表示不限制
|
|
125
|
+
"""
|
|
118
126
|
desc = self._log_desc()
|
|
119
127
|
payload = self._build_payload(request)
|
|
120
128
|
params = _truncate(
|
|
@@ -124,7 +132,7 @@ class BaseAIService[RequestT, ResponseT](ABC):
|
|
|
124
132
|
logger.info(f"请求参数: {params}")
|
|
125
133
|
start = time.perf_counter()
|
|
126
134
|
try:
|
|
127
|
-
data = await self._client.post_json(payload)
|
|
135
|
+
data = await self._client.post_json(payload, deadline=deadline)
|
|
128
136
|
result = self._parse_response(data)
|
|
129
137
|
except AIError as exc:
|
|
130
138
|
logger.error(
|
|
@@ -65,8 +65,14 @@ class ChatService(BaseAIService[ChatRequest, str]):
|
|
|
65
65
|
}
|
|
66
66
|
if request.temperature is not None:
|
|
67
67
|
payload["temperature"] = request.temperature
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
# max_tokens 逐级回落:调用点显式传入 → 档案配置 → 不写(provider 默认)
|
|
69
|
+
max_tokens = (
|
|
70
|
+
request.max_tokens
|
|
71
|
+
if request.max_tokens is not None
|
|
72
|
+
else self._profile.max_tokens
|
|
73
|
+
)
|
|
74
|
+
if max_tokens is not None:
|
|
75
|
+
payload["max_tokens"] = max_tokens
|
|
70
76
|
return cast(JSONObject, payload)
|
|
71
77
|
|
|
72
78
|
@override
|
|
@@ -82,9 +88,16 @@ class ChatService(BaseAIService[ChatRequest, str]):
|
|
|
82
88
|
*,
|
|
83
89
|
temperature: float | None = None,
|
|
84
90
|
max_tokens: int | None = None,
|
|
91
|
+
deadline: float | None = None,
|
|
85
92
|
) -> str:
|
|
93
|
+
"""多轮对话,返回模型回答文本。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
deadline: 整链预算截止时刻(``time.monotonic()`` 秒),约束
|
|
97
|
+
5xx 重试预算;None 表示不限制
|
|
98
|
+
"""
|
|
86
99
|
request = ChatRequest(messages, temperature=temperature, max_tokens=max_tokens)
|
|
87
|
-
return await self._call(request)
|
|
100
|
+
return await self._call(request, deadline=deadline)
|
|
88
101
|
|
|
89
102
|
async def complete(
|
|
90
103
|
self,
|
|
@@ -92,9 +105,12 @@ class ChatService(BaseAIService[ChatRequest, str]):
|
|
|
92
105
|
*,
|
|
93
106
|
temperature: float | None = None,
|
|
94
107
|
max_tokens: int | None = None,
|
|
108
|
+
deadline: float | None = None,
|
|
95
109
|
) -> str:
|
|
96
110
|
messages: list[Message] = [{"role": "user", "content": prompt}]
|
|
97
|
-
return await self.ask(
|
|
111
|
+
return await self.ask(
|
|
112
|
+
messages, temperature=temperature, max_tokens=max_tokens, deadline=deadline
|
|
113
|
+
)
|
|
98
114
|
|
|
99
115
|
async def stream(
|
|
100
116
|
self,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-08-21
|
|
21
|
+
"""kit/ai 统一异常体系
|
|
22
|
+
|
|
23
|
+
继承链:
|
|
24
|
+
AIError 基类
|
|
25
|
+
├── AIConfigError 档案文件缺失/非法、档案不存在、密钥未配置、必填字段缺失
|
|
26
|
+
├── AIRequestError 网络异常、HTTP >= 400
|
|
27
|
+
│ ├── AITimeoutError 请求超时(可用于重试策略)
|
|
28
|
+
│ └── AIAggregateError 候选链聚合异常(全部候选失败或整链预算耗尽)
|
|
29
|
+
└── AIResponseError 响应结构解析失败、流式数据无法解析
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AIError(Exception):
|
|
34
|
+
"""AI 模型服务基类异常"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AIConfigError(AIError):
|
|
38
|
+
"""模型档案配置异常(文件缺失/非法、档案不存在、密钥未配置、必填字段缺失)"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AIRequestError(AIError):
|
|
42
|
+
"""模型服务请求异常(网络异常、HTTP >= 400)。
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
status_code: HTTP 状态码;非 HTTP 错误(超时、连接失败等)为 None。
|
|
46
|
+
retry_after: 服务端 ``Retry-After`` 建议等待秒数(429 时解析,
|
|
47
|
+
整数秒与 HTTP-date 两种格式);未提供为 None。
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
message: str,
|
|
53
|
+
*,
|
|
54
|
+
status_code: int | None = None,
|
|
55
|
+
retry_after: float | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
super().__init__(message)
|
|
58
|
+
self.status_code: int | None = status_code
|
|
59
|
+
self.retry_after: float | None = retry_after
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AITimeoutError(AIRequestError):
|
|
63
|
+
"""模型服务请求超时异常(可用于重试策略)"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AIAggregateError(AIRequestError):
|
|
67
|
+
"""候选链聚合异常:全部候选失败(或整链预算耗尽)时抛出。
|
|
68
|
+
|
|
69
|
+
继承 ``AIRequestError``:业务按 ``AIRequestError`` 捕获零改动。
|
|
70
|
+
``status_code`` 在「全部候选状态码一致」时透传该值(如整链 429),
|
|
71
|
+
否则为 None——业务可据此一行判定限流。
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
tried: 已尝试的档案名列表。
|
|
75
|
+
statuses: 各候选异常的 HTTP 状态码(非 HTTP 错误为 None)。
|
|
76
|
+
last_error: 最后一个候选的异常;预算耗尽且无候选失败时为 None。
|
|
77
|
+
budget_exhausted: 是否因整链预算耗尽而提前终止(未再发起新候选)。
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str,
|
|
83
|
+
*,
|
|
84
|
+
status_code: int | None = None,
|
|
85
|
+
retry_after: float | None = None,
|
|
86
|
+
tried: list[str] | None = None,
|
|
87
|
+
statuses: list[int | None] | None = None,
|
|
88
|
+
last_error: AIError | None = None,
|
|
89
|
+
budget_exhausted: bool = False,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(message, status_code=status_code, retry_after=retry_after)
|
|
92
|
+
self.tried: list[str] = list(tried or [])
|
|
93
|
+
self.statuses: list[int | None] = list(statuses or [])
|
|
94
|
+
self.last_error: AIError | None = last_error
|
|
95
|
+
self.budget_exhausted: bool = budget_exhausted
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AIResponseError(AIError):
|
|
99
|
+
"""模型服务响应异常(响应结构解析失败、流式数据无法解析)"""
|