sdpy-kit 0.6.9__tar.gz → 0.6.11__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.6.9 → sdpy_kit-0.6.11}/PKG-INFO +20 -10
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/README.md +13 -4
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/__init__.py +1 -1
- sdpy_kit-0.6.11/kit/ai/_retry_after.py +51 -0
- sdpy_kit-0.6.11/kit/ai/_service_base.py +115 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/chat_service.py +164 -199
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/embedding_service.py +30 -40
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/image_service.py +28 -38
- sdpy_kit-0.6.11/kit/ai/model_client.py +59 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/rerank_service.py +29 -6
- sdpy_kit-0.6.11/kit/ai/rerank_transport.py +138 -0
- sdpy_kit-0.6.11/kit/ai/sdk_client.py +379 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/pyproject.toml +6 -5
- sdpy_kit-0.6.9/kit/ai/base_service.py +0 -215
- sdpy_kit-0.6.9/kit/ai/model_client.py +0 -278
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/.gitignore +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/ai/types.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/common_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/article_io.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/article_models.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/deepseek_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/web_article_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/crawl/wx_article_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/audit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/crud.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/engine.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/query.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/session.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/decima_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/__init__.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/anydoc_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/docx_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/marker_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/md2doc_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/mineru_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/docs/rc_docparser_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/file_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/laya_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/license_kit.py +0 -0
- {sdpy_kit-0.6.9 → sdpy_kit-0.6.11}/kit/redis_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.11
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -24,6 +24,7 @@ Requires-Dist: sqlalchemy~=2.0.54
|
|
|
24
24
|
Requires-Dist: sqlmodel>=0.0.48
|
|
25
25
|
Provides-Extra: ai
|
|
26
26
|
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
27
|
+
Requires-Dist: openai<4,>=3.44; extra == 'ai'
|
|
27
28
|
Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
|
|
28
29
|
Provides-Extra: all
|
|
29
30
|
Requires-Dist: decima-ai>=2.5.1; extra == 'all'
|
|
@@ -31,11 +32,11 @@ Requires-Dist: docxtpl>=0.20.2; extra == 'all'
|
|
|
31
32
|
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
32
33
|
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
33
34
|
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'all'
|
|
34
|
-
Requires-Dist: laya>=0.4.
|
|
35
|
+
Requires-Dist: laya>=0.4.22; extra == 'all'
|
|
35
36
|
Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
|
|
36
|
-
Requires-Dist: marker-pdf>=2.0.0; extra == 'all'
|
|
37
37
|
Requires-Dist: numpy>=2.5.4; extra == 'all'
|
|
38
38
|
Requires-Dist: onnxruntime>=1.18; extra == 'all'
|
|
39
|
+
Requires-Dist: openai<4,>=3.44; extra == 'all'
|
|
39
40
|
Requires-Dist: pikepdf>=10.17.0; extra == 'all'
|
|
40
41
|
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
|
|
41
42
|
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
|
|
@@ -44,7 +45,7 @@ Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
|
|
|
44
45
|
Requires-Dist: rc-docparser>=0.2.2; extra == 'all'
|
|
45
46
|
Requires-Dist: redis>=8.1.0; extra == 'all'
|
|
46
47
|
Requires-Dist: requests>=2.34.2; extra == 'all'
|
|
47
|
-
Requires-Dist: sqliteai-vector>=1.
|
|
48
|
+
Requires-Dist: sqliteai-vector>=1.2.0; extra == 'all'
|
|
48
49
|
Requires-Dist: trafilatura>=2.3.1; extra == 'all'
|
|
49
50
|
Provides-Extra: anydoc
|
|
50
51
|
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
|
|
@@ -59,7 +60,7 @@ Provides-Extra: docx-parse
|
|
|
59
60
|
Requires-Dist: rc-docparser>=0.2.2; extra == 'docx-parse'
|
|
60
61
|
Provides-Extra: laya
|
|
61
62
|
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'laya'
|
|
62
|
-
Requires-Dist: laya>=0.4.
|
|
63
|
+
Requires-Dist: laya>=0.4.22; extra == 'laya'
|
|
63
64
|
Provides-Extra: marker
|
|
64
65
|
Requires-Dist: marker-pdf>=2.0.0; extra == 'marker'
|
|
65
66
|
Provides-Extra: md2doc
|
|
@@ -75,7 +76,7 @@ Provides-Extra: redis
|
|
|
75
76
|
Requires-Dist: redis>=8.1.0; extra == 'redis'
|
|
76
77
|
Provides-Extra: sqliteai
|
|
77
78
|
Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
|
|
78
|
-
Requires-Dist: sqliteai-vector>=1.
|
|
79
|
+
Requires-Dist: sqliteai-vector>=1.2.0; extra == 'sqliteai'
|
|
79
80
|
Provides-Extra: web-article
|
|
80
81
|
Requires-Dist: trafilatura>=2.3.1; extra == 'web-article'
|
|
81
82
|
Description-Content-Type: text/markdown
|
|
@@ -131,17 +132,17 @@ uv add sdpy-kit
|
|
|
131
132
|
| `mysql` | MySqlKit(MySQL 驱动;向量仅存储,需 MySQL 9.x) | `pip install "sdpy-kit[mysql]"` |
|
|
132
133
|
| `sqliteai` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[sqliteai]"` |
|
|
133
134
|
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
134
|
-
| `ai` | AI 服务池(httpx + python-dotenv) | `pip install "sdpy-kit[ai]"` |
|
|
135
|
+
| `ai` | AI 服务池(openai>=3.44 + httpx + python-dotenv;安装体积约 +10MB,SDK 自带 httpx2) | `pip install "sdpy-kit[ai]"` |
|
|
135
136
|
| `md2doc` | Md2DocKit:Markdown → Word / PDF / HTML(pimd / fpdf2,含图片) | `pip install "sdpy-kit[md2doc]"` |
|
|
136
137
|
| `anydoc` | AnyDocKit 文档转 Markdown(firecrawl-anydoc + pikepdf) | `pip install "sdpy-kit[anydoc]"` |
|
|
137
138
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
138
139
|
| `docx-parse` | RcDocParserKit 离线 docx 解析(rc-docparser → Markdown + images/) | `pip install "sdpy-kit[docx-parse]"` |
|
|
139
140
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
140
|
-
| `marker` | MarkerKit 本地解析(marker-pdf → Markdown + Images;PDF +
|
|
141
|
+
| `marker` | MarkerKit 本地解析(marker-pdf → Markdown + Images;PDF + 图片,依赖体积大)。**与 `[ai]` 互斥**:marker-pdf 锁 `openai<3`,不能与 `openai>=3.44` 同环境安装 | `pip install "sdpy-kit[marker]"` |
|
|
141
142
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
142
143
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
143
144
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
144
|
-
| `all` |
|
|
145
|
+
| `all` | 常用能力集合,**不含 `marker`**(0.6.11 起,避免与 `[ai]` 的 openai 约束冲突);需要 Marker 请单独装 `[marker]` | `pip install "sdpy-kit[all]"` |
|
|
145
146
|
|
|
146
147
|
## 配置(.env)
|
|
147
148
|
|
|
@@ -809,6 +810,14 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
|
|
|
809
810
|
## kit.ai —— AI 多模型服务池
|
|
810
811
|
|
|
811
812
|
安装:`pip install "sdpy-kit[ai]"`
|
|
813
|
+
|
|
814
|
+
传输(0.6.11):chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
|
|
815
|
+
自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
|
|
816
|
+
档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
|
|
817
|
+
`/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
|
|
818
|
+
|
|
819
|
+
> 安装提示:`openai` 3.x 会引入 `httpx2`,与 rerank 用的 `httpx` 并存(两个包名不同,不冲突)。
|
|
820
|
+
|
|
812
821
|
档案:项目根 `config/ai_models.json`(见文末「AI 档案文件」)
|
|
813
822
|
|
|
814
823
|
### 1. 同步门面 `AI`(推荐)
|
|
@@ -1600,7 +1609,8 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1600
1609
|
|
|
1601
1610
|
| 层级 | 字段 | 说明 |
|
|
1602
1611
|
|------|------|------|
|
|
1603
|
-
| 档案级 | `
|
|
1612
|
+
| 档案级 | `endpoint` | 请求路径。**0.6.10 起仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
|
|
1613
|
+
| 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
|
|
1604
1614
|
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
1605
1615
|
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
1606
1616
|
| 档案级 | `extra_body` | 厂商专有请求体字段透传(如 GLM/Qwen 关闭思考的 `{"thinking": {"type": "disabled"}}`、`{"reasoning_effort": "low"}`);与服务构造的同名键冲突时**以服务的值为准**,故 `model` / `messages` / `stream` 等保留键不可覆盖;改名类需求(如 OpenAI o 系列的 `max_completion_tokens`)无法用它实现。逐调用 `thinking` 与之同键时**以逐调用为准**并打 `WARNING`。改文件后需 `reload_config(reload_file=True)` 生效 |
|
|
@@ -49,17 +49,17 @@ uv add sdpy-kit
|
|
|
49
49
|
| `mysql` | MySqlKit(MySQL 驱动;向量仅存储,需 MySQL 9.x) | `pip install "sdpy-kit[mysql]"` |
|
|
50
50
|
| `sqliteai` | SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | `pip install "sdpy-kit[sqliteai]"` |
|
|
51
51
|
| `redis` | RedisKit | `pip install "sdpy-kit[redis]"` |
|
|
52
|
-
| `ai` | AI 服务池(httpx + python-dotenv) | `pip install "sdpy-kit[ai]"` |
|
|
52
|
+
| `ai` | AI 服务池(openai>=3.44 + httpx + python-dotenv;安装体积约 +10MB,SDK 自带 httpx2) | `pip install "sdpy-kit[ai]"` |
|
|
53
53
|
| `md2doc` | Md2DocKit:Markdown → Word / PDF / HTML(pimd / fpdf2,含图片) | `pip install "sdpy-kit[md2doc]"` |
|
|
54
54
|
| `anydoc` | AnyDocKit 文档转 Markdown(firecrawl-anydoc + pikepdf) | `pip install "sdpy-kit[anydoc]"` |
|
|
55
55
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
56
56
|
| `docx-parse` | RcDocParserKit 离线 docx 解析(rc-docparser → Markdown + images/) | `pip install "sdpy-kit[docx-parse]"` |
|
|
57
57
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
58
|
-
| `marker` | MarkerKit 本地解析(marker-pdf → Markdown + Images;PDF +
|
|
58
|
+
| `marker` | MarkerKit 本地解析(marker-pdf → Markdown + Images;PDF + 图片,依赖体积大)。**与 `[ai]` 互斥**:marker-pdf 锁 `openai<3`,不能与 `openai>=3.44` 同环境安装 | `pip install "sdpy-kit[marker]"` |
|
|
59
59
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
60
60
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
61
61
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
62
|
-
| `all` |
|
|
62
|
+
| `all` | 常用能力集合,**不含 `marker`**(0.6.11 起,避免与 `[ai]` 的 openai 约束冲突);需要 Marker 请单独装 `[marker]` | `pip install "sdpy-kit[all]"` |
|
|
63
63
|
|
|
64
64
|
## 配置(.env)
|
|
65
65
|
|
|
@@ -727,6 +727,14 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
|
|
|
727
727
|
## kit.ai —— AI 多模型服务池
|
|
728
728
|
|
|
729
729
|
安装:`pip install "sdpy-kit[ai]"`
|
|
730
|
+
|
|
731
|
+
传输(0.6.11):chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
|
|
732
|
+
自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
|
|
733
|
+
档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
|
|
734
|
+
`/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
|
|
735
|
+
|
|
736
|
+
> 安装提示:`openai` 3.x 会引入 `httpx2`,与 rerank 用的 `httpx` 并存(两个包名不同,不冲突)。
|
|
737
|
+
|
|
730
738
|
档案:项目根 `config/ai_models.json`(见文末「AI 档案文件」)
|
|
731
739
|
|
|
732
740
|
### 1. 同步门面 `AI`(推荐)
|
|
@@ -1518,7 +1526,8 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1518
1526
|
|
|
1519
1527
|
| 层级 | 字段 | 说明 |
|
|
1520
1528
|
|------|------|------|
|
|
1521
|
-
| 档案级 | `
|
|
1529
|
+
| 档案级 | `endpoint` | 请求路径。**0.6.10 起仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
|
|
1530
|
+
| 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
|
|
1522
1531
|
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
1523
1532
|
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
1524
1533
|
| 档案级 | `extra_body` | 厂商专有请求体字段透传(如 GLM/Qwen 关闭思考的 `{"thinking": {"type": "disabled"}}`、`{"reasoning_effort": "low"}`);与服务构造的同名键冲突时**以服务的值为准**,故 `model` / `messages` / `stream` 等保留键不可覆盖;改名类需求(如 OpenAI o 系列的 `max_completion_tokens`)无法用它实现。逐调用 `thinking` 与之同键时**以逐调用为准**并打 `WARNING`。改文件后需 `reload_config(reload_file=True)` 生效 |
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
"""``Retry-After`` 响应头解析(SDK 传输与 rerank 传输共用)。"""
|
|
21
|
+
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from email.utils import parsedate_to_datetime
|
|
24
|
+
from typing import Final
|
|
25
|
+
|
|
26
|
+
__all__ = ["parse_retry_after"]
|
|
27
|
+
|
|
28
|
+
# 重试前剩余整链预算的最小门槛(秒):不足则放弃重试直接抛出
|
|
29
|
+
MIN_RETRY_BUDGET: Final[float] = 1.0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_retry_after(raw: str | None) -> float | None:
|
|
33
|
+
"""解析 ``Retry-After`` 响应头为等待秒数。
|
|
34
|
+
|
|
35
|
+
支持 RFC 7231 两种格式:整数秒与 HTTP-date(按 UTC 计算剩余秒,
|
|
36
|
+
过去时刻钳为 0);缺失或非法返回 None。
|
|
37
|
+
"""
|
|
38
|
+
if not raw:
|
|
39
|
+
return None
|
|
40
|
+
raw = raw.strip()
|
|
41
|
+
if raw.isdigit():
|
|
42
|
+
return float(raw)
|
|
43
|
+
try:
|
|
44
|
+
dt = parsedate_to_datetime(raw)
|
|
45
|
+
except (TypeError, ValueError):
|
|
46
|
+
return None
|
|
47
|
+
if dt is None:
|
|
48
|
+
return None
|
|
49
|
+
if dt.tzinfo is None:
|
|
50
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
51
|
+
return max((dt - datetime.now(timezone.utc)).total_seconds(), 0.0)
|
|
@@ -0,0 +1,115 @@
|
|
|
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-11-04
|
|
21
|
+
"""AI 服务公共基类:档案属性、生命周期与统一日志骨架。
|
|
22
|
+
|
|
23
|
+
0.6.10 起不再包含 HTTP 传输与请求体编排:chat / embedding / image 由
|
|
24
|
+
``SdkClient`` 承担,rerank 由 ``RerankTransport`` 承担。本模块只保留
|
|
25
|
+
四类服务共用的日志与资源管理能力。
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import time
|
|
29
|
+
from abc import ABC
|
|
30
|
+
from collections.abc import Iterator
|
|
31
|
+
from contextlib import contextmanager
|
|
32
|
+
from typing import ClassVar
|
|
33
|
+
|
|
34
|
+
from kit.ai.exceptions import AIError, AIResponseError
|
|
35
|
+
from kit.ai.profiles import ModelProfile
|
|
36
|
+
from kit.ai.types import JSONValue
|
|
37
|
+
from kit.config.log_config import get_default_logger
|
|
38
|
+
|
|
39
|
+
__all__ = ["LoggingServiceBase", "safe_get"]
|
|
40
|
+
|
|
41
|
+
logger = get_default_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def safe_get(data: JSONValue, *path: str | int) -> JSONValue:
|
|
45
|
+
"""按层级路径安全取值,支持 dict 键(str)与 list 索引(int)。
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
AIResponseError: 路径任一环节缺失或类型不符。
|
|
49
|
+
"""
|
|
50
|
+
cur: JSONValue = data
|
|
51
|
+
for key in path:
|
|
52
|
+
if isinstance(key, int):
|
|
53
|
+
if not isinstance(cur, list) or key < 0 or key >= len(cur):
|
|
54
|
+
raise AIResponseError(f"响应缺少索引 {path}: {str(data)[:200]}")
|
|
55
|
+
cur = cur[key]
|
|
56
|
+
else:
|
|
57
|
+
if not isinstance(cur, dict) or key not in cur:
|
|
58
|
+
raise AIResponseError(
|
|
59
|
+
f"响应缺少字段 {'.'.join(map(str, path))}: {str(data)[:200]}"
|
|
60
|
+
)
|
|
61
|
+
cur = cur[key]
|
|
62
|
+
return cur
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LoggingServiceBase(ABC):
|
|
66
|
+
"""四类模型服务共用的档案属性、日志骨架与异步上下文。"""
|
|
67
|
+
|
|
68
|
+
_LABEL: ClassVar[str] = "模型"
|
|
69
|
+
|
|
70
|
+
def __init__(self, profile: ModelProfile) -> None:
|
|
71
|
+
self._profile: ModelProfile = profile
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def profile(self) -> ModelProfile:
|
|
75
|
+
return self._profile
|
|
76
|
+
|
|
77
|
+
def _log_desc(self, *, suffix: str = "") -> str:
|
|
78
|
+
"""日志中的「模型及描述」:服务中文名 + 模型标识 + 实例名。"""
|
|
79
|
+
return (
|
|
80
|
+
f"{self._LABEL} {self._profile.model}"
|
|
81
|
+
f"(实例 {self._profile.name}{suffix})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@contextmanager
|
|
85
|
+
def _log_call(self, *, log: bool = True) -> Iterator[None]:
|
|
86
|
+
"""统一日志骨架:Start → 成功耗时 / 失败 ERROR。"""
|
|
87
|
+
desc = self._log_desc()
|
|
88
|
+
if log:
|
|
89
|
+
logger.info(f"********** {desc} ********** Start")
|
|
90
|
+
start = time.perf_counter()
|
|
91
|
+
try:
|
|
92
|
+
yield
|
|
93
|
+
except AIError as exc:
|
|
94
|
+
logger.error(
|
|
95
|
+
f"********** {desc} ********** "
|
|
96
|
+
f"耗时 {time.perf_counter() - start:.1f}秒(失败:{exc})"
|
|
97
|
+
)
|
|
98
|
+
raise
|
|
99
|
+
if log:
|
|
100
|
+
logger.info(
|
|
101
|
+
f"********** {desc} ********** 耗时 {time.perf_counter() - start:.1f}秒"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
async def close(self) -> None:
|
|
105
|
+
"""释放底层资源(子类实现)。"""
|
|
106
|
+
raise NotImplementedError
|
|
107
|
+
|
|
108
|
+
async def __aenter__(self) -> "LoggingServiceBase":
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
async def __aexit__(self, *exc_info: object) -> None:
|
|
112
|
+
await self.close()
|
|
113
|
+
|
|
114
|
+
def __str__(self) -> str:
|
|
115
|
+
return f"{type(self).__name__}({self._profile.name})"
|