sdpy-kit 0.6.11__tar.gz → 0.7.1__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.11 → sdpy_kit-0.7.1}/PKG-INFO +87 -58
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/README.md +64 -34
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/__init__.py +1 -1
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/__init__.py +2 -1
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/chat_service.py +143 -27
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/exceptions.py +10 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/failover.py +13 -5
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/image_service.py +18 -6
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/model_builder.py +7 -5
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/model_client.py +12 -9
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/pool.py +18 -5
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/profiles.py +29 -12
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/rerank_service.py +28 -10
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/rerank_transport.py +1 -1
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/runner.py +58 -22
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/sdk_client.py +33 -27
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/__init__.py +1 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/md2doc_kit.py +1 -10
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/pyproject.toml +8 -2
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/.gitignore +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/_retry_after.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/_service_base.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/ai/types.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/common_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/__init__.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/article_io.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/article_models.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/deepseek_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/web_article_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/crawl/wx_article_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/audit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/crud.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/engine.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/query.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/session.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/decima_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/anydoc_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/docx_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/marker_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/mineru_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/docs/rc_docparser_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/file_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/laya_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/license_kit.py +0 -0
- {sdpy_kit-0.6.11 → sdpy_kit-0.7.1}/kit/redis_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,27 +26,6 @@ Provides-Extra: ai
|
|
|
26
26
|
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
27
27
|
Requires-Dist: openai<4,>=3.44; extra == 'ai'
|
|
28
28
|
Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
|
|
29
|
-
Provides-Extra: all
|
|
30
|
-
Requires-Dist: decima-ai>=2.5.1; extra == 'all'
|
|
31
|
-
Requires-Dist: docxtpl>=0.20.2; extra == 'all'
|
|
32
|
-
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
33
|
-
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
34
|
-
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'all'
|
|
35
|
-
Requires-Dist: laya>=0.4.22; extra == 'all'
|
|
36
|
-
Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
|
|
37
|
-
Requires-Dist: numpy>=2.5.4; extra == 'all'
|
|
38
|
-
Requires-Dist: onnxruntime>=1.18; extra == 'all'
|
|
39
|
-
Requires-Dist: openai<4,>=3.44; extra == 'all'
|
|
40
|
-
Requires-Dist: pikepdf>=10.17.0; extra == 'all'
|
|
41
|
-
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
|
|
42
|
-
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
|
|
43
|
-
Requires-Dist: pymysql>=1.2.3; extra == 'all'
|
|
44
|
-
Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
|
|
45
|
-
Requires-Dist: rc-docparser>=0.2.2; extra == 'all'
|
|
46
|
-
Requires-Dist: redis>=8.1.0; extra == 'all'
|
|
47
|
-
Requires-Dist: requests>=2.34.2; extra == 'all'
|
|
48
|
-
Requires-Dist: sqliteai-vector>=1.2.0; extra == 'all'
|
|
49
|
-
Requires-Dist: trafilatura>=2.3.1; extra == 'all'
|
|
50
29
|
Provides-Extra: anydoc
|
|
51
30
|
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
|
|
52
31
|
Requires-Dist: pikepdf>=10.17.0; extra == 'anydoc'
|
|
@@ -61,10 +40,9 @@ Requires-Dist: rc-docparser>=0.2.2; extra == 'docx-parse'
|
|
|
61
40
|
Provides-Extra: laya
|
|
62
41
|
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'laya'
|
|
63
42
|
Requires-Dist: laya>=0.4.22; extra == 'laya'
|
|
64
|
-
Provides-Extra: marker
|
|
65
|
-
Requires-Dist: marker-pdf>=2.0.0; extra == 'marker'
|
|
66
43
|
Provides-Extra: md2doc
|
|
67
44
|
Requires-Dist: markdown-it-py>=4.2.0; extra == 'md2doc'
|
|
45
|
+
Requires-Dist: pillow>=13.0.0; extra == 'md2doc'
|
|
68
46
|
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'md2doc'
|
|
69
47
|
Provides-Extra: mineru
|
|
70
48
|
Requires-Dist: requests>=2.34.2; extra == 'mineru'
|
|
@@ -72,6 +50,27 @@ Provides-Extra: mysql
|
|
|
72
50
|
Requires-Dist: pymysql>=1.2.3; extra == 'mysql'
|
|
73
51
|
Provides-Extra: pg
|
|
74
52
|
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'pg'
|
|
53
|
+
Provides-Extra: recommended
|
|
54
|
+
Requires-Dist: decima-ai>=2.5.1; extra == 'recommended'
|
|
55
|
+
Requires-Dist: docxtpl>=0.20.2; extra == 'recommended'
|
|
56
|
+
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'recommended'
|
|
57
|
+
Requires-Dist: httpx>=0.28.1; extra == 'recommended'
|
|
58
|
+
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'recommended'
|
|
59
|
+
Requires-Dist: laya>=0.4.22; extra == 'recommended'
|
|
60
|
+
Requires-Dist: markdown-it-py>=4.2.0; extra == 'recommended'
|
|
61
|
+
Requires-Dist: numpy>=2.5.4; extra == 'recommended'
|
|
62
|
+
Requires-Dist: onnxruntime>=1.18; extra == 'recommended'
|
|
63
|
+
Requires-Dist: openai<4,>=3.44; extra == 'recommended'
|
|
64
|
+
Requires-Dist: pikepdf>=10.17.0; extra == 'recommended'
|
|
65
|
+
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'recommended'
|
|
66
|
+
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'recommended'
|
|
67
|
+
Requires-Dist: pymysql>=1.2.3; extra == 'recommended'
|
|
68
|
+
Requires-Dist: python-dotenv>=1.2.4; extra == 'recommended'
|
|
69
|
+
Requires-Dist: rc-docparser>=0.2.2; extra == 'recommended'
|
|
70
|
+
Requires-Dist: redis>=8.1.0; extra == 'recommended'
|
|
71
|
+
Requires-Dist: requests>=2.34.2; extra == 'recommended'
|
|
72
|
+
Requires-Dist: sqliteai-vector>=1.2.0; extra == 'recommended'
|
|
73
|
+
Requires-Dist: trafilatura>=2.3.1; extra == 'recommended'
|
|
75
74
|
Provides-Extra: redis
|
|
76
75
|
Requires-Dist: redis>=8.1.0; extra == 'redis'
|
|
77
76
|
Provides-Extra: sqliteai
|
|
@@ -87,7 +86,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
87
86
|
|
|
88
87
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;内置审计字段自动填充(`created_*` / `updated_*` / `deleted`)与软删除查询过滤;
|
|
89
88
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
90
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(
|
|
89
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(0.7.0 起暂停发布,需要请用 `sdpy-kit==0.6.10`) / 离线 docx 解析(md + images/) / Laya 类型化决策 / Decima 决策模型(选项乱序不变、置信度可门控) / 微信公众号文章抓取存储 / 通用网页文章抓取存储 / DeepSeek 搜索与抓取。
|
|
91
90
|
|
|
92
91
|
> 要求 Python 3.13+
|
|
93
92
|
|
|
@@ -107,7 +106,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
107
106
|
| `kit.docs.rc_docparser_kit` | `RcDocParserKit` | 离线 docx → Markdown + Images(无模型) | `docx-parse` |
|
|
108
107
|
| `kit.docs.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML(含图片) | `md2doc` |
|
|
109
108
|
| `kit.docs.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
110
|
-
| `kit.docs.marker_kit` | `MarkerKit` | 本地解析(PDF / 图片 → Markdown + Images
|
|
109
|
+
| `kit.docs.marker_kit` | `MarkerKit` | 本地解析(PDF / 图片 → Markdown + Images);**0.7.0 起暂停发布**,需要请用 `sdpy-kit==0.6.10` | — |
|
|
111
110
|
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / multi_choice 多选 / score / noul,另含 decide 多字段 / decide_batch 批量 / predict_long 长文本 | `laya` |
|
|
112
111
|
| `kit.decima_kit` | `DecimaKit` | 决策模型:choice / score / noul / multi_choice(原生 rank);选项乱序不变、置信度可门控;另含 predict / predict_long / brief / choice_batch(方法名与 `LayaKit` 对齐) | `decima` |
|
|
113
112
|
| `kit.crawl.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(三个 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
@@ -138,11 +137,10 @@ uv add sdpy-kit
|
|
|
138
137
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
139
138
|
| `docx-parse` | RcDocParserKit 离线 docx 解析(rc-docparser → Markdown + images/) | `pip install "sdpy-kit[docx-parse]"` |
|
|
140
139
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
141
|
-
| `marker` | MarkerKit 本地解析(marker-pdf → Markdown + Images;PDF + 图片,依赖体积大)。**与 `[ai]` 互斥**:marker-pdf 锁 `openai<3`,不能与 `openai>=3.44` 同环境安装 | `pip install "sdpy-kit[marker]"` |
|
|
142
140
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
143
141
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
144
142
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
145
|
-
| `
|
|
143
|
+
| `recommended` | 常用能力集合(DB / 缓存 / AI / 文档 / 抓取);不含重依赖(laya / decima)与 Marker(0.7.0 起暂停发布) | `pip install "sdpy-kit[recommended]"` |
|
|
146
144
|
|
|
147
145
|
## 配置(.env)
|
|
148
146
|
|
|
@@ -811,7 +809,7 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
|
|
|
811
809
|
|
|
812
810
|
安装:`pip install "sdpy-kit[ai]"`
|
|
813
811
|
|
|
814
|
-
|
|
812
|
+
传输:chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
|
|
815
813
|
自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
|
|
816
814
|
档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
|
|
817
815
|
`/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
|
|
@@ -837,14 +835,29 @@ shutdown() # 应用退出前
|
|
|
837
835
|
|------|------|------|
|
|
838
836
|
| chat | `chat_complete(prompt, *, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats=None) -> str` | 单轮对话(仅 user 消息) |
|
|
839
837
|
| chat | `chat_ask(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats=None) -> str` | 多轮对话,`messages` 为 `[{"role": "user", "content": "..."}]` |
|
|
840
|
-
| chat | `chat_stream(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None) -> Iterator[str]` | 同步流式逐 token 迭代器(建议 `with` 使用);结束后可读 `reasoning_chars` / `content_chars` / `finish_reason` / `truncated` / `thinking_applied` / `json_mode_applied` |
|
|
838
|
+
| chat | `chat_stream(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None) -> Iterator[str]` | 同步流式逐 token 迭代器(建议 `with` 使用);结束后可读 `reasoning_chars` / `content_chars` / `finish_reason` / `truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied` |
|
|
841
839
|
| embedding | `embed_text(text) -> list[float]` | 单文本向量化 |
|
|
842
840
|
| embedding | `embed_texts(texts) -> list[list[float]]` | 批量向量化 |
|
|
843
|
-
| rerank | `rerank(query, documents, top_n=None) -> list[
|
|
844
|
-
| image | `generate_image(prompt, *, size=None, n=1) ->
|
|
841
|
+
| rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中 |
|
|
842
|
+
| image | `generate_image(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` | 文生图,返回 `ImageResult`(`url` / `b64` 至少一个非空) |
|
|
845
843
|
| 管理 | `use(model_type, profile) -> None` | 切换该类型当前档案(`chat` / `embedding` / `rerank` / `image`) |
|
|
846
|
-
| 管理 | `available(model_type) -> list[ProfileStatus]` |
|
|
847
|
-
| 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 /
|
|
844
|
+
| 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法仍抛 `AIConfigError` |
|
|
845
|
+
| 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 / 档案后无需重启;同时清空思考/结构化输出的告警去重表(修正档案后同一原因会重新提示) |
|
|
846
|
+
|
|
847
|
+
**可用性查询(`available`)**:`available` 返回的是**状态快照**,不是「可用档案列表」。
|
|
848
|
+
判断某类型是否已配置用 `if not AI.available(t)`,不要靠捕获异常:
|
|
849
|
+
|
|
850
|
+
```python
|
|
851
|
+
from kit.ai.runner import AI
|
|
852
|
+
|
|
853
|
+
profiles = AI.available("image") # ai_models.json 无 image 段 → []
|
|
854
|
+
if not profiles:
|
|
855
|
+
raise RuntimeError("尚未配置图像生成模型")
|
|
856
|
+
current = next((p for p in profiles if p.current), None)
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
> 旧写法 `try: AI.available(t) except AIConfigError: ...` 仍可用(异常分支不再触发);
|
|
860
|
+
> `AI.available(t)[0]` 在类型未配置时会抛 `IndexError`,请改用上面的判空写法。
|
|
848
861
|
|
|
849
862
|
```python
|
|
850
863
|
from kit.ai.runner import AI
|
|
@@ -856,30 +869,42 @@ with AI.chat_stream([{"role": "user", "content": "写一首短诗"}]) as stream:
|
|
|
856
869
|
|
|
857
870
|
#### 流式收尾统计(思考型模型必备)
|
|
858
871
|
|
|
859
|
-
|
|
860
|
-
`truncated` / `thinking_applied` / `json_mode_applied`),用于区分「模型真的没返回」与「输出预算被思考吃光」——思考型模型的推理
|
|
872
|
+
迭代器在流结束后可读七个只读属性(`reasoning_chars` / `content_chars` / `finish_reason` /
|
|
873
|
+
`truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied`),用于区分「模型真的没返回」与「输出预算被思考吃光」——思考型模型的推理
|
|
861
874
|
token 计入 `max_tokens`,长上下文 + 小预算时常出现「返回 200 但正文为空」(同一套字段也可用于非流式 `ask` / `complete`,
|
|
862
|
-
|
|
875
|
+
见下文「结构化输出」)。
|
|
876
|
+
|
|
877
|
+
`empty_kind` 把「正文为空」分成四类,不再一律归因为预算耗尽:
|
|
878
|
+
|
|
879
|
+
| 取值 | 判定 | 建议处置 |
|
|
880
|
+
|---|---|---|
|
|
881
|
+
| `budget_exhausted` | 被长度截断且有推理 | 调大 `max_tokens`,或对该调用传 `thinking=False` |
|
|
882
|
+
| `reasoning_only` | 有推理、`finish_reason=stop`、正文 0 | 提高预算或换模型 |
|
|
883
|
+
| `no_finish` | 有推理但没收到结束原因(链路中断 / 端点未收尾) | 优先重试,连续出现检查端点/代理超时 |
|
|
884
|
+
| `stopped_empty` | 无推理、`finish_reason=stop`、正文 0 | 检查提示词与模型输出 |
|
|
885
|
+
|
|
886
|
+
正文非空时 `empty_kind is None`。判定只用「是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`):
|
|
863
887
|
|
|
864
888
|
```python
|
|
865
889
|
with AI.chat_stream(messages, max_tokens=450) as stream:
|
|
866
890
|
body = "".join(stream)
|
|
867
|
-
if not body
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
891
|
+
if not body:
|
|
892
|
+
match stream.empty_kind:
|
|
893
|
+
case "budget_exhausted":
|
|
894
|
+
... # 调大 max_tokens 或 thinking=False
|
|
895
|
+
case "no_finish":
|
|
896
|
+
... # 优先重试
|
|
872
897
|
```
|
|
873
898
|
|
|
874
899
|
- 推理字段兼容 `delta.reasoning_content`(GLM / DeepSeek / Qwen)与 `delta.reasoning`(sensenova):只计数不产出;
|
|
875
|
-
- 推理字段名是 kit 内部白名单(当前就是上面两种):新 provider 若用别的字段名,`reasoning_chars` 会恒为 0——此时仍可用 `truncated`
|
|
876
|
-
-
|
|
900
|
+
- 推理字段名是 kit 内部白名单(当前就是上面两种):新 provider 若用别的字段名,`reasoning_chars` 会恒为 0——此时仍可用 `truncated` / `empty_kind` 判定;
|
|
901
|
+
- 正文为空时 kit 按 `empty_kind` 分级打 `WARNING`(含模型、`max_tokens`、`finish_reason` 与推荐处置),不再统一归因「预算被思考耗尽」;
|
|
877
902
|
- `truncated` 覆盖 `finish_reason` 为 `length` 或 `max_tokens` 两种口径;
|
|
878
903
|
- **字符数 ≠ token 数**(实测中文约 2~2.5 字符/token,各家口径不一),不要用 `reasoning_chars` 反推还差多少预算;
|
|
879
904
|
- provider 不返回 `finish_reason` 时 `finish_reason is None`、`truncated is False`;
|
|
880
905
|
- 流式过程中读到的是尽力而为的中间值,**结束后才是最终值**;提前 `break` 时为截至中断的累计值。
|
|
881
906
|
|
|
882
|
-
####
|
|
907
|
+
#### 逐调用思考开关
|
|
883
908
|
|
|
884
909
|
同一档案下按调用点切换思考(入库抽取关、章节生成开),不必复制档案:
|
|
885
910
|
|
|
@@ -898,14 +923,14 @@ kit 不认识任何厂商字段名,请求体字段由档案 `thinking` 声明
|
|
|
898
923
|
|
|
899
924
|
`thinking_applied`(流式迭代器属性 / `StreamStats` 字段):`None` 未要求干预 / `True` 已按声明注入 / `False` 要求了但被忽略。
|
|
900
925
|
|
|
901
|
-
>
|
|
902
|
-
>
|
|
926
|
+
> 设计说明:曾否决「新增 thinking / reasoning_effort 具名字段、只用 `extra_body`」,
|
|
927
|
+
> 该否决理由(候选链跨厂商,备胎收到未知字段会 400 → 冷却)已由「档案能力声明」机制解除——
|
|
903
928
|
> 字段只发给声明了它的候选,未声明者不注入。思考**档位**(`low/medium/high`)仍然不做。
|
|
904
929
|
|
|
905
930
|
> 首帧等待语义:每候选 60 秒内**一帧都没有**(含推理帧)才切下一候选;思考阶段的
|
|
906
931
|
> 推理帧算活动并重置窗口,不会误判健康模型失败、也不会重放整段 prompt。
|
|
907
932
|
|
|
908
|
-
#### 结构化输出(JSON
|
|
933
|
+
#### 结构化输出(JSON 模式)
|
|
909
934
|
|
|
910
935
|
只要「合法 JSON 对象」用 `json_mode=True`;要约束字段用协议的 `response_format` 原样透传:
|
|
911
936
|
|
|
@@ -965,7 +990,7 @@ if stats.truncated: # finish_reason 为 length / max_tokens
|
|
|
965
990
|
from kit.ai import (
|
|
966
991
|
ChatService, EmbeddingService, RerankService, ImageService, # 服务类
|
|
967
992
|
ChatRequest, EmbeddingRequest, RerankRequest, ImageRequest, # 请求模型
|
|
968
|
-
RerankHit, StreamStats, ModelBuilder,
|
|
993
|
+
RerankHit, ImageResult, StreamStats, ModelBuilder, # 结果类型 / 构建器
|
|
969
994
|
)
|
|
970
995
|
|
|
971
996
|
service = ModelBuilder.build_chat() # 老按类型构建:build_chat / build_embedding / build_rerank / build_image
|
|
@@ -978,7 +1003,7 @@ await service.close()
|
|
|
978
1003
|
| `ChatService` | `ask(messages, *, temperature=None, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats: StreamStats \| None = None, deadline=None) -> str`、`complete(prompt, *, ...) -> str`、`stream(messages, *, ..., stats: StreamStats \| None = None) -> AsyncIterator[str]`(传 `StreamStats()` 即可读收尾统计);`thinking` / `json_mode` / `response_format` 语义与门面一致 |
|
|
979
1004
|
| `EmbeddingService` | `embed_text(text) -> list[float]`、`embed_texts(texts, *, batch_size=None, concurrency=4) -> list[list[float]]`、`dimension` 属性(**目标维度**,见下) |
|
|
980
1005
|
| `RerankService` | `rerank(query, documents, *, top_n=None) -> list[RerankHit]`(已按 score 降序) |
|
|
981
|
-
| `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) ->
|
|
1006
|
+
| `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` |
|
|
982
1007
|
| `ModelBuilder` | `build(model_type, model=None, *, api_key=None)` 及各类型专用 `build_chat / build_embedding / build_rerank / build_image` |
|
|
983
1008
|
| `BaseAIService` | `profile` 属性、`await close()`、支持 `async with` |
|
|
984
1009
|
|
|
@@ -1230,7 +1255,7 @@ Md2DocKit.convert_md_to_pdf("report.md", mainfont="KaiTi")
|
|
|
1230
1255
|
|
|
1231
1256
|
| 方法 | 说明 |
|
|
1232
1257
|
|------|------|
|
|
1233
|
-
| `convert_md_to_docx(src_path, dest_path=None, *, generate_toc=False, mainfont="宋体"
|
|
1258
|
+
| `convert_md_to_docx(src_path, dest_path=None, *, generate_toc=False, mainfont="宋体") -> str` | Markdown → Word(可插目录、统一字体、图片内嵌) |
|
|
1234
1259
|
| `convert_md_to_html(src_path, dest_path=None, *, title=None) -> str` | Markdown → 独立 HTML5 文件(图片输出 `<figure><img>`) |
|
|
1235
1260
|
| `convert_md_to_pdf(src_path, dest_path=None, *, mainfont="SimSun") -> str` | Markdown → PDF(实验性,内部使用;图片等比限宽居中) |
|
|
1236
1261
|
|
|
@@ -1278,7 +1303,11 @@ if r.state == "done":
|
|
|
1278
1303
|
|
|
1279
1304
|
## kit.docs.marker_kit —— 本地解析:PDF + 图片 → Markdown + Images
|
|
1280
1305
|
|
|
1281
|
-
|
|
1306
|
+
> ⚠ **0.7.0 起暂停发布**:`marker-pdf` 仍锁 `openai<3`,与 `ai` extra 冲突。本包**不包含**该模块,
|
|
1307
|
+
> 也不提供 `marker` extra。需要此能力请用 `pip install "sdpy-kit[marker]==0.6.10"`。
|
|
1308
|
+
> 以下接口说明仅作参考。
|
|
1309
|
+
|
|
1310
|
+
0.6.10 的安装方式:`pip install "sdpy-kit[marker]"`(依赖 `marker-pdf`,含 torch 等 80+ 包,体积较大)
|
|
1282
1311
|
|
|
1283
1312
|
支持面:`.pdf`、`.png`、`.jpg`、`.jpeg`、`.webp`、`.gif`、`.bmp`。docx/pptx/xlsx/html/epub 不在支持面
|
|
1284
1313
|
(marker 侧实现依赖 weasyprint/GTK),传入会抛 `ValueError`。
|
|
@@ -1609,13 +1638,13 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1609
1638
|
|
|
1610
1639
|
| 层级 | 字段 | 说明 |
|
|
1611
1640
|
|------|------|------|
|
|
1612
|
-
| 档案级 | `endpoint` |
|
|
1641
|
+
| 档案级 | `endpoint` | 请求路径。**仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
|
|
1613
1642
|
| 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
|
|
1614
1643
|
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
1615
1644
|
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
1616
1645
|
| 档案级 | `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)` 生效 |
|
|
1617
|
-
| 档案级 | `thinking` |
|
|
1618
|
-
| 档案级 | `structured_output` |
|
|
1646
|
+
| 档案级 | `thinking` | 思考开关的注入声明:`{"arg": "thinking", "off": {"type": "disabled"}, "on": {"type": "enabled"}, "default": false}`。`arg` 是请求体顶层键名(不得占用 `model` / `messages` / `stream` / `max_tokens` / `temperature` 等保留键,配置期与运行期双重校验);`off` / `on` 可为**任意 JSON 值**(双方都是 dict 时浅合并,否则整键替换);`on` 缺省表示不能显式开启;`default` 为档案默认三态(`null` 不干预 / `false` 默认关 / `true` 默认开)。**未声明 = 不支持按调用切换**:逐调用 `thinking` 被忽略并告警(不报错、不影响候选链冷却)。kit 只做翻译,不含任何厂商名 |
|
|
1647
|
+
| 档案级 | `structured_output` | 结构化输出的注入声明:`{"arg": "response_format", "json_mode": {"type": "json_object"}}`。`arg` 是请求体顶层键名(不得占用保留键);`json_mode` 是 `json_mode=True` 时写入该键的取值。**未声明 = 用内置 OpenAI 兼容默认**(`response_format: {"type":"json_object"}`,开箱即用);`"structured_output": false` 表示该档案不支持(`json_mode=True` 被忽略 + 告警,不发任何字段)。逐调用 `response_format` 原样透传优先于本声明。kit 只做翻译,不含任何厂商名 |
|
|
1619
1648
|
| 类型级 | `chain_timeout` | 整链预算秒数(与 `default_profile` 同级);门面同步等待上限。未配置时按 `sum(候选 timeout) + 10s` 自适应,也可用环境变量 `KIT_AI_CHAIN_TIMEOUT` 全局覆盖 |
|
|
1620
1649
|
|
|
1621
1650
|
`thinking` 声明两种实测形态(按平台各自写入档案):
|
|
@@ -1637,11 +1666,11 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1637
1666
|
```
|
|
1638
1667
|
|
|
1639
1668
|
> **`extra_body` 的用法边界**:`extra_body` 是**档案级**的固有配置,无法按调用点切换。需要
|
|
1640
|
-
> 「同一模型,长文开思考、短文关思考」并存时用档案 `thinking` 声明 + 逐调用 `thinking=False/True
|
|
1641
|
-
>
|
|
1669
|
+
> 「同一模型,长文开思考、短文关思考」并存时用档案 `thinking` 声明 + 逐调用 `thinking=False/True`;
|
|
1670
|
+
> 仅在无法用开关表达时才考虑复制档案 + `ModelBuilder.build_chat("<档案名>")`
|
|
1642
1671
|
> (门面侧 `AI.use("chat", "<档案名>")` 是进程级切换,非并发安全)。
|
|
1643
1672
|
> 结构化输出同理:档案声明只给 `json_mode=True` 提供取值,按调用点变化的 schema 一律走
|
|
1644
|
-
> 逐调用 `response_format
|
|
1673
|
+
> 逐调用 `response_format`。
|
|
1645
1674
|
|
|
1646
1675
|
### 候选链切换语义(两类,勿混淆)
|
|
1647
1676
|
|
|
@@ -4,7 +4,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
4
4
|
|
|
5
5
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;内置审计字段自动填充(`created_*` / `updated_*` / `deleted`)与软删除查询过滤;
|
|
6
6
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
7
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(
|
|
7
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 离线授权签发与校验 / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(0.7.0 起暂停发布,需要请用 `sdpy-kit==0.6.10`) / 离线 docx 解析(md + images/) / Laya 类型化决策 / Decima 决策模型(选项乱序不变、置信度可门控) / 微信公众号文章抓取存储 / 通用网页文章抓取存储 / DeepSeek 搜索与抓取。
|
|
8
8
|
|
|
9
9
|
> 要求 Python 3.13+
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
24
24
|
| `kit.docs.rc_docparser_kit` | `RcDocParserKit` | 离线 docx → Markdown + Images(无模型) | `docx-parse` |
|
|
25
25
|
| `kit.docs.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML(含图片) | `md2doc` |
|
|
26
26
|
| `kit.docs.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
27
|
-
| `kit.docs.marker_kit` | `MarkerKit` | 本地解析(PDF / 图片 → Markdown + Images
|
|
27
|
+
| `kit.docs.marker_kit` | `MarkerKit` | 本地解析(PDF / 图片 → Markdown + Images);**0.7.0 起暂停发布**,需要请用 `sdpy-kit==0.6.10` | — |
|
|
28
28
|
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / multi_choice 多选 / score / noul,另含 decide 多字段 / decide_batch 批量 / predict_long 长文本 | `laya` |
|
|
29
29
|
| `kit.decima_kit` | `DecimaKit` | 决策模型:choice / score / noul / multi_choice(原生 rank);选项乱序不变、置信度可门控;另含 predict / predict_long / brief / choice_batch(方法名与 `LayaKit` 对齐) | `decima` |
|
|
30
30
|
| `kit.crawl.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(三个 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
@@ -55,11 +55,10 @@ uv add sdpy-kit
|
|
|
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 + 图片,依赖体积大)。**与 `[ai]` 互斥**:marker-pdf 锁 `openai<3`,不能与 `openai>=3.44` 同环境安装 | `pip install "sdpy-kit[marker]"` |
|
|
59
58
|
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / multi_choice / score / noul;支持多字段 / 批量 / 长文本) | `pip install "sdpy-kit[laya]"` |
|
|
60
59
|
| `decima` | DecimaKit 决策模型(decima-ai + onnxruntime,**无 torch**;选项乱序不变、置信度可门控;磁盘约 137 MB) | `pip install "sdpy-kit[decima]"` |
|
|
61
60
|
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
62
|
-
| `
|
|
61
|
+
| `recommended` | 常用能力集合(DB / 缓存 / AI / 文档 / 抓取);不含重依赖(laya / decima)与 Marker(0.7.0 起暂停发布) | `pip install "sdpy-kit[recommended]"` |
|
|
63
62
|
|
|
64
63
|
## 配置(.env)
|
|
65
64
|
|
|
@@ -728,7 +727,7 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
|
|
|
728
727
|
|
|
729
728
|
安装:`pip install "sdpy-kit[ai]"`
|
|
730
729
|
|
|
731
|
-
|
|
730
|
+
传输:chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
|
|
732
731
|
自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
|
|
733
732
|
档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
|
|
734
733
|
`/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
|
|
@@ -754,14 +753,29 @@ shutdown() # 应用退出前
|
|
|
754
753
|
|------|------|------|
|
|
755
754
|
| chat | `chat_complete(prompt, *, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats=None) -> str` | 单轮对话(仅 user 消息) |
|
|
756
755
|
| chat | `chat_ask(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats=None) -> str` | 多轮对话,`messages` 为 `[{"role": "user", "content": "..."}]` |
|
|
757
|
-
| chat | `chat_stream(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None) -> Iterator[str]` | 同步流式逐 token 迭代器(建议 `with` 使用);结束后可读 `reasoning_chars` / `content_chars` / `finish_reason` / `truncated` / `thinking_applied` / `json_mode_applied` |
|
|
756
|
+
| chat | `chat_stream(messages, *, max_tokens=None, thinking=None, json_mode=None, response_format=None) -> Iterator[str]` | 同步流式逐 token 迭代器(建议 `with` 使用);结束后可读 `reasoning_chars` / `content_chars` / `finish_reason` / `truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied` |
|
|
758
757
|
| embedding | `embed_text(text) -> list[float]` | 单文本向量化 |
|
|
759
758
|
| embedding | `embed_texts(texts) -> list[list[float]]` | 批量向量化 |
|
|
760
|
-
| rerank | `rerank(query, documents, top_n=None) -> list[
|
|
761
|
-
| image | `generate_image(prompt, *, size=None, n=1) ->
|
|
759
|
+
| rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中 |
|
|
760
|
+
| image | `generate_image(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` | 文生图,返回 `ImageResult`(`url` / `b64` 至少一个非空) |
|
|
762
761
|
| 管理 | `use(model_type, profile) -> None` | 切换该类型当前档案(`chat` / `embedding` / `rerank` / `image`) |
|
|
763
|
-
| 管理 | `available(model_type) -> list[ProfileStatus]` |
|
|
764
|
-
| 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 /
|
|
762
|
+
| 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法仍抛 `AIConfigError` |
|
|
763
|
+
| 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 / 档案后无需重启;同时清空思考/结构化输出的告警去重表(修正档案后同一原因会重新提示) |
|
|
764
|
+
|
|
765
|
+
**可用性查询(`available`)**:`available` 返回的是**状态快照**,不是「可用档案列表」。
|
|
766
|
+
判断某类型是否已配置用 `if not AI.available(t)`,不要靠捕获异常:
|
|
767
|
+
|
|
768
|
+
```python
|
|
769
|
+
from kit.ai.runner import AI
|
|
770
|
+
|
|
771
|
+
profiles = AI.available("image") # ai_models.json 无 image 段 → []
|
|
772
|
+
if not profiles:
|
|
773
|
+
raise RuntimeError("尚未配置图像生成模型")
|
|
774
|
+
current = next((p for p in profiles if p.current), None)
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
> 旧写法 `try: AI.available(t) except AIConfigError: ...` 仍可用(异常分支不再触发);
|
|
778
|
+
> `AI.available(t)[0]` 在类型未配置时会抛 `IndexError`,请改用上面的判空写法。
|
|
765
779
|
|
|
766
780
|
```python
|
|
767
781
|
from kit.ai.runner import AI
|
|
@@ -773,30 +787,42 @@ with AI.chat_stream([{"role": "user", "content": "写一首短诗"}]) as stream:
|
|
|
773
787
|
|
|
774
788
|
#### 流式收尾统计(思考型模型必备)
|
|
775
789
|
|
|
776
|
-
|
|
777
|
-
`truncated` / `thinking_applied` / `json_mode_applied`),用于区分「模型真的没返回」与「输出预算被思考吃光」——思考型模型的推理
|
|
790
|
+
迭代器在流结束后可读七个只读属性(`reasoning_chars` / `content_chars` / `finish_reason` /
|
|
791
|
+
`truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied`),用于区分「模型真的没返回」与「输出预算被思考吃光」——思考型模型的推理
|
|
778
792
|
token 计入 `max_tokens`,长上下文 + 小预算时常出现「返回 200 但正文为空」(同一套字段也可用于非流式 `ask` / `complete`,
|
|
779
|
-
|
|
793
|
+
见下文「结构化输出」)。
|
|
794
|
+
|
|
795
|
+
`empty_kind` 把「正文为空」分成四类,不再一律归因为预算耗尽:
|
|
796
|
+
|
|
797
|
+
| 取值 | 判定 | 建议处置 |
|
|
798
|
+
|---|---|---|
|
|
799
|
+
| `budget_exhausted` | 被长度截断且有推理 | 调大 `max_tokens`,或对该调用传 `thinking=False` |
|
|
800
|
+
| `reasoning_only` | 有推理、`finish_reason=stop`、正文 0 | 提高预算或换模型 |
|
|
801
|
+
| `no_finish` | 有推理但没收到结束原因(链路中断 / 端点未收尾) | 优先重试,连续出现检查端点/代理超时 |
|
|
802
|
+
| `stopped_empty` | 无推理、`finish_reason=stop`、正文 0 | 检查提示词与模型输出 |
|
|
803
|
+
|
|
804
|
+
正文非空时 `empty_kind is None`。判定只用「是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`):
|
|
780
805
|
|
|
781
806
|
```python
|
|
782
807
|
with AI.chat_stream(messages, max_tokens=450) as stream:
|
|
783
808
|
body = "".join(stream)
|
|
784
|
-
if not body
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
809
|
+
if not body:
|
|
810
|
+
match stream.empty_kind:
|
|
811
|
+
case "budget_exhausted":
|
|
812
|
+
... # 调大 max_tokens 或 thinking=False
|
|
813
|
+
case "no_finish":
|
|
814
|
+
... # 优先重试
|
|
789
815
|
```
|
|
790
816
|
|
|
791
817
|
- 推理字段兼容 `delta.reasoning_content`(GLM / DeepSeek / Qwen)与 `delta.reasoning`(sensenova):只计数不产出;
|
|
792
|
-
- 推理字段名是 kit 内部白名单(当前就是上面两种):新 provider 若用别的字段名,`reasoning_chars` 会恒为 0——此时仍可用 `truncated`
|
|
793
|
-
-
|
|
818
|
+
- 推理字段名是 kit 内部白名单(当前就是上面两种):新 provider 若用别的字段名,`reasoning_chars` 会恒为 0——此时仍可用 `truncated` / `empty_kind` 判定;
|
|
819
|
+
- 正文为空时 kit 按 `empty_kind` 分级打 `WARNING`(含模型、`max_tokens`、`finish_reason` 与推荐处置),不再统一归因「预算被思考耗尽」;
|
|
794
820
|
- `truncated` 覆盖 `finish_reason` 为 `length` 或 `max_tokens` 两种口径;
|
|
795
821
|
- **字符数 ≠ token 数**(实测中文约 2~2.5 字符/token,各家口径不一),不要用 `reasoning_chars` 反推还差多少预算;
|
|
796
822
|
- provider 不返回 `finish_reason` 时 `finish_reason is None`、`truncated is False`;
|
|
797
823
|
- 流式过程中读到的是尽力而为的中间值,**结束后才是最终值**;提前 `break` 时为截至中断的累计值。
|
|
798
824
|
|
|
799
|
-
####
|
|
825
|
+
#### 逐调用思考开关
|
|
800
826
|
|
|
801
827
|
同一档案下按调用点切换思考(入库抽取关、章节生成开),不必复制档案:
|
|
802
828
|
|
|
@@ -815,14 +841,14 @@ kit 不认识任何厂商字段名,请求体字段由档案 `thinking` 声明
|
|
|
815
841
|
|
|
816
842
|
`thinking_applied`(流式迭代器属性 / `StreamStats` 字段):`None` 未要求干预 / `True` 已按声明注入 / `False` 要求了但被忽略。
|
|
817
843
|
|
|
818
|
-
>
|
|
819
|
-
>
|
|
844
|
+
> 设计说明:曾否决「新增 thinking / reasoning_effort 具名字段、只用 `extra_body`」,
|
|
845
|
+
> 该否决理由(候选链跨厂商,备胎收到未知字段会 400 → 冷却)已由「档案能力声明」机制解除——
|
|
820
846
|
> 字段只发给声明了它的候选,未声明者不注入。思考**档位**(`low/medium/high`)仍然不做。
|
|
821
847
|
|
|
822
848
|
> 首帧等待语义:每候选 60 秒内**一帧都没有**(含推理帧)才切下一候选;思考阶段的
|
|
823
849
|
> 推理帧算活动并重置窗口,不会误判健康模型失败、也不会重放整段 prompt。
|
|
824
850
|
|
|
825
|
-
#### 结构化输出(JSON
|
|
851
|
+
#### 结构化输出(JSON 模式)
|
|
826
852
|
|
|
827
853
|
只要「合法 JSON 对象」用 `json_mode=True`;要约束字段用协议的 `response_format` 原样透传:
|
|
828
854
|
|
|
@@ -882,7 +908,7 @@ if stats.truncated: # finish_reason 为 length / max_tokens
|
|
|
882
908
|
from kit.ai import (
|
|
883
909
|
ChatService, EmbeddingService, RerankService, ImageService, # 服务类
|
|
884
910
|
ChatRequest, EmbeddingRequest, RerankRequest, ImageRequest, # 请求模型
|
|
885
|
-
RerankHit, StreamStats, ModelBuilder,
|
|
911
|
+
RerankHit, ImageResult, StreamStats, ModelBuilder, # 结果类型 / 构建器
|
|
886
912
|
)
|
|
887
913
|
|
|
888
914
|
service = ModelBuilder.build_chat() # 老按类型构建:build_chat / build_embedding / build_rerank / build_image
|
|
@@ -895,7 +921,7 @@ await service.close()
|
|
|
895
921
|
| `ChatService` | `ask(messages, *, temperature=None, max_tokens=None, thinking=None, json_mode=None, response_format=None, stats: StreamStats \| None = None, deadline=None) -> str`、`complete(prompt, *, ...) -> str`、`stream(messages, *, ..., stats: StreamStats \| None = None) -> AsyncIterator[str]`(传 `StreamStats()` 即可读收尾统计);`thinking` / `json_mode` / `response_format` 语义与门面一致 |
|
|
896
922
|
| `EmbeddingService` | `embed_text(text) -> list[float]`、`embed_texts(texts, *, batch_size=None, concurrency=4) -> list[list[float]]`、`dimension` 属性(**目标维度**,见下) |
|
|
897
923
|
| `RerankService` | `rerank(query, documents, *, top_n=None) -> list[RerankHit]`(已按 score 降序) |
|
|
898
|
-
| `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) ->
|
|
924
|
+
| `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` |
|
|
899
925
|
| `ModelBuilder` | `build(model_type, model=None, *, api_key=None)` 及各类型专用 `build_chat / build_embedding / build_rerank / build_image` |
|
|
900
926
|
| `BaseAIService` | `profile` 属性、`await close()`、支持 `async with` |
|
|
901
927
|
|
|
@@ -1147,7 +1173,7 @@ Md2DocKit.convert_md_to_pdf("report.md", mainfont="KaiTi")
|
|
|
1147
1173
|
|
|
1148
1174
|
| 方法 | 说明 |
|
|
1149
1175
|
|------|------|
|
|
1150
|
-
| `convert_md_to_docx(src_path, dest_path=None, *, generate_toc=False, mainfont="宋体"
|
|
1176
|
+
| `convert_md_to_docx(src_path, dest_path=None, *, generate_toc=False, mainfont="宋体") -> str` | Markdown → Word(可插目录、统一字体、图片内嵌) |
|
|
1151
1177
|
| `convert_md_to_html(src_path, dest_path=None, *, title=None) -> str` | Markdown → 独立 HTML5 文件(图片输出 `<figure><img>`) |
|
|
1152
1178
|
| `convert_md_to_pdf(src_path, dest_path=None, *, mainfont="SimSun") -> str` | Markdown → PDF(实验性,内部使用;图片等比限宽居中) |
|
|
1153
1179
|
|
|
@@ -1195,7 +1221,11 @@ if r.state == "done":
|
|
|
1195
1221
|
|
|
1196
1222
|
## kit.docs.marker_kit —— 本地解析:PDF + 图片 → Markdown + Images
|
|
1197
1223
|
|
|
1198
|
-
|
|
1224
|
+
> ⚠ **0.7.0 起暂停发布**:`marker-pdf` 仍锁 `openai<3`,与 `ai` extra 冲突。本包**不包含**该模块,
|
|
1225
|
+
> 也不提供 `marker` extra。需要此能力请用 `pip install "sdpy-kit[marker]==0.6.10"`。
|
|
1226
|
+
> 以下接口说明仅作参考。
|
|
1227
|
+
|
|
1228
|
+
0.6.10 的安装方式:`pip install "sdpy-kit[marker]"`(依赖 `marker-pdf`,含 torch 等 80+ 包,体积较大)
|
|
1199
1229
|
|
|
1200
1230
|
支持面:`.pdf`、`.png`、`.jpg`、`.jpeg`、`.webp`、`.gif`、`.bmp`。docx/pptx/xlsx/html/epub 不在支持面
|
|
1201
1231
|
(marker 侧实现依赖 weasyprint/GTK),传入会抛 `ValueError`。
|
|
@@ -1526,13 +1556,13 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1526
1556
|
|
|
1527
1557
|
| 层级 | 字段 | 说明 |
|
|
1528
1558
|
|------|------|------|
|
|
1529
|
-
| 档案级 | `endpoint` |
|
|
1559
|
+
| 档案级 | `endpoint` | 请求路径。**仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
|
|
1530
1560
|
| 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
|
|
1531
1561
|
| 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
|
|
1532
1562
|
| 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
|
|
1533
1563
|
| 档案级 | `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)` 生效 |
|
|
1534
|
-
| 档案级 | `thinking` |
|
|
1535
|
-
| 档案级 | `structured_output` |
|
|
1564
|
+
| 档案级 | `thinking` | 思考开关的注入声明:`{"arg": "thinking", "off": {"type": "disabled"}, "on": {"type": "enabled"}, "default": false}`。`arg` 是请求体顶层键名(不得占用 `model` / `messages` / `stream` / `max_tokens` / `temperature` 等保留键,配置期与运行期双重校验);`off` / `on` 可为**任意 JSON 值**(双方都是 dict 时浅合并,否则整键替换);`on` 缺省表示不能显式开启;`default` 为档案默认三态(`null` 不干预 / `false` 默认关 / `true` 默认开)。**未声明 = 不支持按调用切换**:逐调用 `thinking` 被忽略并告警(不报错、不影响候选链冷却)。kit 只做翻译,不含任何厂商名 |
|
|
1565
|
+
| 档案级 | `structured_output` | 结构化输出的注入声明:`{"arg": "response_format", "json_mode": {"type": "json_object"}}`。`arg` 是请求体顶层键名(不得占用保留键);`json_mode` 是 `json_mode=True` 时写入该键的取值。**未声明 = 用内置 OpenAI 兼容默认**(`response_format: {"type":"json_object"}`,开箱即用);`"structured_output": false` 表示该档案不支持(`json_mode=True` 被忽略 + 告警,不发任何字段)。逐调用 `response_format` 原样透传优先于本声明。kit 只做翻译,不含任何厂商名 |
|
|
1536
1566
|
| 类型级 | `chain_timeout` | 整链预算秒数(与 `default_profile` 同级);门面同步等待上限。未配置时按 `sum(候选 timeout) + 10s` 自适应,也可用环境变量 `KIT_AI_CHAIN_TIMEOUT` 全局覆盖 |
|
|
1537
1567
|
|
|
1538
1568
|
`thinking` 声明两种实测形态(按平台各自写入档案):
|
|
@@ -1554,11 +1584,11 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
|
|
|
1554
1584
|
```
|
|
1555
1585
|
|
|
1556
1586
|
> **`extra_body` 的用法边界**:`extra_body` 是**档案级**的固有配置,无法按调用点切换。需要
|
|
1557
|
-
> 「同一模型,长文开思考、短文关思考」并存时用档案 `thinking` 声明 + 逐调用 `thinking=False/True
|
|
1558
|
-
>
|
|
1587
|
+
> 「同一模型,长文开思考、短文关思考」并存时用档案 `thinking` 声明 + 逐调用 `thinking=False/True`;
|
|
1588
|
+
> 仅在无法用开关表达时才考虑复制档案 + `ModelBuilder.build_chat("<档案名>")`
|
|
1559
1589
|
> (门面侧 `AI.use("chat", "<档案名>")` 是进程级切换,非并发安全)。
|
|
1560
1590
|
> 结构化输出同理:档案声明只给 `json_mode=True` 提供取值,按调用点变化的 schema 一律走
|
|
1561
|
-
> 逐调用 `response_format
|
|
1591
|
+
> 逐调用 `response_format`。
|
|
1562
1592
|
|
|
1563
1593
|
### 候选链切换语义(两类,勿混淆)
|
|
1564
1594
|
|
|
@@ -29,7 +29,7 @@ Author: Clark Chang
|
|
|
29
29
|
|
|
30
30
|
from kit.ai.chat_service import ChatRequest, ChatService, StreamStats
|
|
31
31
|
from kit.ai.embedding_service import EmbeddingRequest, EmbeddingService
|
|
32
|
-
from kit.ai.image_service import ImageRequest, ImageService
|
|
32
|
+
from kit.ai.image_service import ImageRequest, ImageResult, ImageService
|
|
33
33
|
from kit.ai.model_builder import ModelBuilder
|
|
34
34
|
from kit.ai.rerank_service import RerankHit, RerankRequest, RerankService
|
|
35
35
|
|
|
@@ -39,6 +39,7 @@ __all__ = [
|
|
|
39
39
|
"EmbeddingRequest",
|
|
40
40
|
"EmbeddingService",
|
|
41
41
|
"ImageRequest",
|
|
42
|
+
"ImageResult",
|
|
42
43
|
"ImageService",
|
|
43
44
|
"ModelBuilder",
|
|
44
45
|
"RerankHit",
|