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.
Files changed (44) hide show
  1. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/PKG-INFO +53 -10
  2. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/README.md +34 -1
  3. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/__init__.py +1 -1
  4. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/base_service.py +11 -3
  5. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/chat_service.py +20 -4
  6. sdpy_kit-0.1.6/kit/ai/exceptions.py +99 -0
  7. sdpy_kit-0.1.6/kit/ai/failover.py +350 -0
  8. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/image_service.py +1 -3
  9. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/model_client.py +69 -13
  10. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/pool.py +27 -17
  11. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/profiles.py +18 -0
  12. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/runner.py +149 -39
  13. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/crud.py +8 -14
  14. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/engine.py +1 -3
  15. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/sqlite_kit.py +6 -1
  16. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/doc_kit.py +9 -6
  17. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/mineru_kit.py +6 -1
  18. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/pyproject.toml +35 -15
  19. sdpy_kit-0.1.4/kit/ai/exceptions.py +0 -57
  20. sdpy_kit-0.1.4/kit/ai/failover.py +0 -244
  21. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/.gitignore +0 -0
  22. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/__init__.py +0 -0
  23. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/embedding_service.py +0 -0
  24. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/model_builder.py +0 -0
  25. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/rerank_service.py +0 -0
  26. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/ai/types.py +0 -0
  27. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/common_kit.py +0 -0
  28. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/__init__.py +0 -0
  29. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/kit_config.py +0 -0
  30. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/config/log_config.py +0 -0
  31. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/__init__.py +0 -0
  32. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/base_db_kit.py +0 -0
  33. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/exceptions.py +0 -0
  34. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/models/__init__.py +0 -0
  35. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/models/base.py +0 -0
  36. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/mysql_kit.py +0 -0
  37. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/pg_kit.py +0 -0
  38. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/query.py +0 -0
  39. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/session.py +0 -0
  40. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/db/transaction.py +0 -0
  41. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/file_kit.py +0 -0
  42. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/mcp_kit.py +0 -0
  43. {sdpy_kit-0.1.4 → sdpy_kit-0.1.6}/kit/redis_kit.py +0 -0
  44. {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.4
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
  ### 日志与配置
@@ -23,7 +23,7 @@ Author: Clark Chang
23
23
  数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
24
  """
25
25
 
26
- __version__ = "0.1.4"
26
+ __version__ = "0.1.6"
27
27
 
28
28
  from kit.db import (
29
29
  MYSQL,
@@ -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(self, request: RequestT) -> ResponseT:
117
- """统一编排:构造请求体 → 发送 → 解析响应(含中文 info 日志)。"""
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
- if request.max_tokens is not None:
69
- payload["max_tokens"] = request.max_tokens
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(messages, temperature=temperature, max_tokens=max_tokens)
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
+ """模型服务响应异常(响应结构解析失败、流式数据无法解析)"""