sdpy-kit 0.7.2__tar.gz → 0.7.4__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 (58) hide show
  1. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/PKG-INFO +11 -9
  2. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/README.md +10 -8
  3. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/__init__.py +1 -1
  4. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/runner.py +36 -7
  5. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/pyproject.toml +2 -2
  6. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/.gitignore +0 -0
  7. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/__init__.py +0 -0
  8. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/_retry_after.py +0 -0
  9. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/_service_base.py +0 -0
  10. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/chat_service.py +0 -0
  11. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/embedding_service.py +0 -0
  12. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/exceptions.py +0 -0
  13. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/failover.py +0 -0
  14. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/image_service.py +0 -0
  15. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/model_builder.py +0 -0
  16. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/model_client.py +0 -0
  17. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/pool.py +0 -0
  18. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/profiles.py +0 -0
  19. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/rerank_service.py +0 -0
  20. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/rerank_transport.py +0 -0
  21. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/sdk_client.py +0 -0
  22. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/ai/types.py +0 -0
  23. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/common_kit.py +0 -0
  24. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/config/__init__.py +0 -0
  25. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/config/kit_config.py +0 -0
  26. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/config/log_config.py +0 -0
  27. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/__init__.py +0 -0
  28. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/article_io.py +0 -0
  29. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/article_models.py +0 -0
  30. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/deepseek_kit.py +0 -0
  31. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/web_article_kit.py +0 -0
  32. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/crawl/wx_article_kit.py +0 -0
  33. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/__init__.py +0 -0
  34. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/audit.py +0 -0
  35. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/base_db_kit.py +0 -0
  36. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/crud.py +0 -0
  37. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/engine.py +0 -0
  38. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/exceptions.py +0 -0
  39. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/models/__init__.py +0 -0
  40. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/models/base.py +0 -0
  41. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/mysql_kit.py +0 -0
  42. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/pg_kit.py +0 -0
  43. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/query.py +0 -0
  44. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/session.py +0 -0
  45. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/sqlite_kit.py +0 -0
  46. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/db/transaction.py +0 -0
  47. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/decima_kit.py +0 -0
  48. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/__init__.py +0 -0
  49. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/anydoc_kit.py +0 -0
  50. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/docx_kit.py +0 -0
  51. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/marker_kit.py +0 -0
  52. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/md2doc_kit.py +0 -0
  53. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/mineru_kit.py +0 -0
  54. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/docs/rc_docparser_kit.py +0 -0
  55. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/file_kit.py +0 -0
  56. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/laya_kit.py +0 -0
  57. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/license_kit.py +0 -0
  58. {sdpy_kit-0.7.2 → sdpy_kit-0.7.4}/kit/redis_kit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sdpy-kit
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
5
5
  Author-email: Clark <changhongyuan@126.com>
6
6
  License-Expression: MIT
@@ -180,7 +180,7 @@ SQLITE_DB_PATH=./data/sqlite.db
180
180
  # MinerU(仅精准解析需要 Key,Agent 轻量解析免登录)
181
181
  # MINERU_API_KEY=
182
182
 
183
- # Marker(本地解析,可选依赖 sdpy-kit[marker])
183
+ # Marker(0.7.0 起暂停发布,无 marker extra;需要请用 sdpy-kit==0.6.10)
184
184
  # MARKER_MODE=fast # None=按设备自动(GPU→balanced / CPU、MPS→fast)
185
185
  # MARKER_HF_ENDPOINT=https://hf-mirror.com
186
186
  # MARKER_HF_DISABLE_XET=true
@@ -812,7 +812,7 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
812
812
  传输:chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
813
813
  自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
814
814
  档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
815
- `/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
815
+ `/embeddings`、`/images/generations`,仅 rerank 仍使用该字段(探活走 openai SDK 的 `GET {base_url}/models`,不读本字段)。
816
816
 
817
817
  > 安装提示:`openai` 3.x 会引入 `httpx2`,与 rerank 用的 `httpx` 并存(两个包名不同,不冲突)。
818
818
 
@@ -838,10 +838,10 @@ shutdown() # 应用退出前
838
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` / `requested_max_tokens` / `truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied` |
839
839
  | embedding | `embed_text(text) -> list[float]` | 单文本向量化 |
840
840
  | embedding | `embed_texts(texts) -> list[list[float]]` | 批量向量化 |
841
- | rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中 |
841
+ | rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中,`top_n<=0` 直接返回空列表 |
842
842
  | image | `generate_image(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` | 文生图,返回 `ImageResult`(`url` / `b64` 至少一个非空) |
843
843
  | 管理 | `use(model_type, profile) -> None` | 切换该类型当前档案(`chat` / `embedding` / `rerank` / `image`) |
844
- | 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法仍抛 `AIConfigError` |
844
+ | 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法抛 `AIModelTypeNotConfiguredError`(`AIConfigError` 子类) |
845
845
  | 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 / 档案后无需重启;同时清空思考/结构化输出的告警去重表(修正档案后同一原因会重新提示) |
846
846
 
847
847
  **可用性查询(`available`)**:`available` 返回的是**状态快照**,不是「可用档案列表」。
@@ -885,7 +885,8 @@ with AI.chat_stream([{"role": "user", "content": "写一首短诗"}]) as stream:
885
885
  | `no_finish` | 结束原因缺失或非 `stop`(端点未发送 `finish_reason`) | 可原样重试;连续出现检查端点 |
886
886
  | `stopped_empty` | 无推理、`finish_reason=stop`、正文 0 | 检查提示词与模型输出 |
887
887
 
888
- 正文非空时 `empty_kind is None`。判定只用「是否截断 + 是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`):
888
+ 正文非空时 `empty_kind is None`。判定只用「是否截断 + 是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`)。
889
+ 注意:该属性**仅在流正常结束后可读**,迭代中与异常 / 中断后为 `None`(避免把「正文还没到」误判成 `no_finish`);`requested_max_tokens` 无此限制。
889
890
 
890
891
  ```python
891
892
  with AI.chat_stream(messages, max_tokens=450) as stream:
@@ -1010,7 +1011,7 @@ await service.close()
1010
1011
  | `RerankService` | `rerank(query, documents, *, top_n=None) -> list[RerankHit]`(已按 score 降序) |
1011
1012
  | `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` |
1012
1013
  | `ModelBuilder` | `build(model_type, model=None, *, api_key=None)` 及各类型专用 `build_chat / build_embedding / build_rerank / build_image` |
1013
- | `BaseAIService` | `profile` 属性、`await close()`、支持 `async with` |
1014
+ | `SdkServiceBase` | `profile` 属性、`await close()`、支持 `async with` |
1014
1015
 
1015
1016
  **embedding 维度契约**:`ai_models.json` 里 embedding 档案的 `dimension` 是**目标向量维度**
1016
1017
  (不是模型原生维度),`EmbeddingService` 保证输出恒等于它:
@@ -1039,7 +1040,8 @@ from kit.ai.exceptions import (
1039
1040
  | 异常 | 触发场景 / 可用属性 |
1040
1041
  |------|---------------------|
1041
1042
  | `AIError` | 基类 |
1042
- | `AIConfigError` | 档案缺失或非法、档案不存在、密钥未配置、必填字段缺失 |
1043
+ | `AIConfigError` | 档案缺失或非法、档案不存在、密钥未配置、必填字段缺失、`default_profile` 未在 `profiles` 中定义(加载期校验) |
1044
+ | `AIModelTypeNotConfiguredError` | 模型类型名非法(合法类型但未配置返回 `[]` 不抛);`AIConfigError` 子类 |
1043
1045
  | `AIRequestError` | 单次请求失败;`status_code`、`retry_after`(429 时解析 `Retry-After`) |
1044
1046
  | `AITimeoutError` | 请求超时(继承 `AIRequestError`) |
1045
1047
  | `AIAggregateError` | 全部候选失败或整链预算耗尽;`tried`、`statuses`、`last_error`、`budget_exhausted` |
@@ -1644,7 +1646,7 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
1644
1646
  | 层级 | 字段 | 说明 |
1645
1647
  |------|------|------|
1646
1648
  | 档案级 | `endpoint` | 请求路径。**仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
1647
- | 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
1649
+ | 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时。**流式例外**:流式请求 `timeout=None`,由门面首帧 60s + 帧间隔 120s 兜底 |
1648
1650
  | 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
1649
1651
  | 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
1650
1652
  | 档案级 | `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)` 生效 |
@@ -98,7 +98,7 @@ SQLITE_DB_PATH=./data/sqlite.db
98
98
  # MinerU(仅精准解析需要 Key,Agent 轻量解析免登录)
99
99
  # MINERU_API_KEY=
100
100
 
101
- # Marker(本地解析,可选依赖 sdpy-kit[marker])
101
+ # Marker(0.7.0 起暂停发布,无 marker extra;需要请用 sdpy-kit==0.6.10)
102
102
  # MARKER_MODE=fast # None=按设备自动(GPU→balanced / CPU、MPS→fast)
103
103
  # MARKER_HF_ENDPOINT=https://hf-mirror.com
104
104
  # MARKER_HF_DISABLE_XET=true
@@ -730,7 +730,7 @@ page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
730
730
  传输:chat / embedding / image 走 `openai` SDK(`>=3.44,<4`,
731
731
  自带 `httpx2`),rerank 走 httpx 自研传输(SDK 无 `/rerank` 资源)。
732
732
  档案里的 `endpoint` **对前三类不再生效**——SDK 固定拼 `/chat/completions`、
733
- `/embeddings`、`/images/generations`,仅 rerank 与探活仍使用该字段。
733
+ `/embeddings`、`/images/generations`,仅 rerank 仍使用该字段(探活走 openai SDK 的 `GET {base_url}/models`,不读本字段)。
734
734
 
735
735
  > 安装提示:`openai` 3.x 会引入 `httpx2`,与 rerank 用的 `httpx` 并存(两个包名不同,不冲突)。
736
736
 
@@ -756,10 +756,10 @@ shutdown() # 应用退出前
756
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` / `requested_max_tokens` / `truncated` / `empty_kind` / `thinking_applied` / `json_mode_applied` |
757
757
  | embedding | `embed_text(text) -> list[float]` | 单文本向量化 |
758
758
  | embedding | `embed_texts(texts) -> list[list[float]]` | 批量向量化 |
759
- | rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中 |
759
+ | rerank | `rerank(query, documents, top_n=None) -> list[RerankHit]` | 返回 `RerankHit`(`index` / `score` / `document`)降序列表;**`top_n` 是返回条数上限,由 kit 本地裁剪**(服务端返回超量也按分数截断),`top_n=None` 返回全部命中,`top_n<=0` 直接返回空列表 |
760
760
  | image | `generate_image(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` | 文生图,返回 `ImageResult`(`url` / `b64` 至少一个非空) |
761
761
  | 管理 | `use(model_type, profile) -> None` | 切换该类型当前档案(`chat` / `embedding` / `rerank` / `image`) |
762
- | 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法仍抛 `AIConfigError` |
762
+ | 管理 | `available(model_type) -> list[ProfileStatus]` | 各档案状态快照(含未配置密钥与被剔除者);**类型未在 `ai_models.json` 中配置时返回 `[]`**(不再抛异常),类型名非法抛 `AIModelTypeNotConfiguredError`(`AIConfigError` 子类) |
763
763
  | 管理 | `reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult` | 配置热重载,改密钥 / 档案后无需重启;同时清空思考/结构化输出的告警去重表(修正档案后同一原因会重新提示) |
764
764
 
765
765
  **可用性查询(`available`)**:`available` 返回的是**状态快照**,不是「可用档案列表」。
@@ -803,7 +803,8 @@ with AI.chat_stream([{"role": "user", "content": "写一首短诗"}]) as stream:
803
803
  | `no_finish` | 结束原因缺失或非 `stop`(端点未发送 `finish_reason`) | 可原样重试;连续出现检查端点 |
804
804
  | `stopped_empty` | 无推理、`finish_reason=stop`、正文 0 | 检查提示词与模型输出 |
805
805
 
806
- 正文非空时 `empty_kind is None`。判定只用「是否截断 + 是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`):
806
+ 正文非空时 `empty_kind is None`。判定只用「是否截断 + 是否有推理 + 结束原因」,不依赖 token 数(kit 目前不采集 `usage`)。
807
+ 注意:该属性**仅在流正常结束后可读**,迭代中与异常 / 中断后为 `None`(避免把「正文还没到」误判成 `no_finish`);`requested_max_tokens` 无此限制。
807
808
 
808
809
  ```python
809
810
  with AI.chat_stream(messages, max_tokens=450) as stream:
@@ -928,7 +929,7 @@ await service.close()
928
929
  | `RerankService` | `rerank(query, documents, *, top_n=None) -> list[RerankHit]`(已按 score 降序) |
929
930
  | `ImageService` | `generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> ImageResult` |
930
931
  | `ModelBuilder` | `build(model_type, model=None, *, api_key=None)` 及各类型专用 `build_chat / build_embedding / build_rerank / build_image` |
931
- | `BaseAIService` | `profile` 属性、`await close()`、支持 `async with` |
932
+ | `SdkServiceBase` | `profile` 属性、`await close()`、支持 `async with` |
932
933
 
933
934
  **embedding 维度契约**:`ai_models.json` 里 embedding 档案的 `dimension` 是**目标向量维度**
934
935
  (不是模型原生维度),`EmbeddingService` 保证输出恒等于它:
@@ -957,7 +958,8 @@ from kit.ai.exceptions import (
957
958
  | 异常 | 触发场景 / 可用属性 |
958
959
  |------|---------------------|
959
960
  | `AIError` | 基类 |
960
- | `AIConfigError` | 档案缺失或非法、档案不存在、密钥未配置、必填字段缺失 |
961
+ | `AIConfigError` | 档案缺失或非法、档案不存在、密钥未配置、必填字段缺失、`default_profile` 未在 `profiles` 中定义(加载期校验) |
962
+ | `AIModelTypeNotConfiguredError` | 模型类型名非法(合法类型但未配置返回 `[]` 不抛);`AIConfigError` 子类 |
961
963
  | `AIRequestError` | 单次请求失败;`status_code`、`retry_after`(429 时解析 `Retry-After`) |
962
964
  | `AITimeoutError` | 请求超时(继承 `AIRequestError`) |
963
965
  | `AIAggregateError` | 全部候选失败或整链预算耗尽;`tried`、`statuses`、`last_error`、`budget_exhausted` |
@@ -1562,7 +1564,7 @@ AI 功能需要在项目根目录提供 `config/ai_models.json`(chat / embeddi
1562
1564
  | 层级 | 字段 | 说明 |
1563
1565
  |------|------|------|
1564
1566
  | 档案级 | `endpoint` | 请求路径。**仅 rerank 生效**:chat / embedding / image 由 openai SDK 固定拼路径,本字段被忽略(保留仅为兼容旧配置,不报错) |
1565
- | 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时 |
1567
+ | 档案级 | `timeout` | 请求超时秒数(默认 60);SDK 侧为单次调用总超时。**流式例外**:流式请求 `timeout=None`,由门面首帧 60s + 帧间隔 120s 兜底 |
1566
1568
  | 档案级 | `max_tokens` | 输出上限(chat 用);`AI.chat_ask/chat_complete/chat_stream` 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
1567
1569
  | 档案级 | `retries` | 5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
1568
1570
  | 档案级 | `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)` 生效 |
@@ -23,7 +23,7 @@ Author: Clark Chang
23
23
  数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
24
  """
25
25
 
26
- __version__ = "0.7.2"
26
+ __version__ = "0.7.4"
27
27
 
28
28
  from kit.db import (
29
29
  MYSQL,
@@ -816,9 +816,10 @@ class AI:
816
816
  if done:
817
817
  break
818
818
 
819
- 流结束后可读 ``reasoning_chars`` / ``content_chars`` / ``finish_reason`` /
820
- ``truncated`` / ``thinking_applied`` / ``json_mode_applied``,用于区分
821
- 「模型没返回内容」与「输出预算被思考吃光」,以及确认开关是否真的生效::
819
+ 流结束后可读八个只读属性:``reasoning_chars`` / ``content_chars`` /
820
+ ``finish_reason`` / ``requested_max_tokens`` / ``truncated`` / ``empty_kind`` /
821
+ ``thinking_applied`` / ``json_mode_applied``,用于区分「模型没返回内容」与
822
+ 「输出预算被思考吃光」(``empty_kind`` 给出五类归因),以及确认开关是否真的生效::
822
823
 
823
824
  with AI.chat_stream(messages, max_tokens=450) as stream:
824
825
  body = "".join(stream)
@@ -846,10 +847,14 @@ class _StreamIterator:
846
847
  直接 ``for`` 中途 ``break`` 或迭代器被 GC,``close``/``__del__`` 也会
847
848
  兜底取消后台 producer,避免任务残留。
848
849
 
849
- 流结束后可读 ``reasoning_chars`` / ``content_chars`` / ``finish_reason`` /
850
- ``truncated`` / ``thinking_applied`` / ``json_mode_applied`` 六个只读属性
851
- (跨线程共享同一累加器:结束后为最终值,流式过程中为尽力而为的中间值;
852
- 提前 ``break`` 时为截至中断的累计值)。
850
+ 流结束后可读八个只读属性:``reasoning_chars`` / ``content_chars`` /
851
+ ``finish_reason`` / ``requested_max_tokens`` / ``truncated`` / ``empty_kind`` /
852
+ ``thinking_applied`` / ``json_mode_applied``(跨线程共享同一累加器:
853
+ 结束后为最终值,流式过程中为尽力而为的中间值;提前 ``break`` 时为截至
854
+ 中断的累计值)。
855
+
856
+ ``empty_kind`` 例外:**仅在流正常结束后可读**,迭代中与异常 / 中断后
857
+ 恒为 ``None``(避免把「正文还没到」误判成 ``no_finish``)。
853
858
  """
854
859
 
855
860
  def __init__(
@@ -871,6 +876,8 @@ class _StreamIterator:
871
876
  self._started = False
872
877
  self._closed = False
873
878
  self._got_first = False
879
+ # 正常收尾(收到哨兵)才置真:empty_kind 只在结束后给结论
880
+ self._exhausted = False
874
881
  self._task: concurrent.futures.Future[None] | None = None
875
882
 
876
883
  def __iter__(self) -> Iterator[str]:
@@ -931,6 +938,7 @@ class _StreamIterator:
931
938
  f"流式帧间隔超过 {_STREAM_FRAME_INTERVAL_TIMEOUT:g}s 未收到新帧"
932
939
  ) from exc
933
940
  if item is _SENTINEL:
941
+ self._exhausted = True
934
942
  self.close()
935
943
  raise StopIteration
936
944
  if isinstance(item, BaseException):
@@ -971,6 +979,27 @@ class _StreamIterator:
971
979
  """结构化输出是否生效:None 未要求注入 / True 已注入 / False 被忽略。"""
972
980
  return self._stats.json_mode_applied
973
981
 
982
+ @property
983
+ def requested_max_tokens(self) -> int | None:
984
+ """本次实际下发的输出上限(调用方 → 档案回落);未发请求为 None。"""
985
+ return self._stats.requested_max_tokens
986
+
987
+ @property
988
+ def empty_kind(self) -> str | None:
989
+ """正文为空时的诊断分级;**仅在流正常结束后可读,其余时刻为 None**。
990
+
991
+ 取值同 ``StreamStats.empty_kind``(``budget_exhausted`` /
992
+ ``truncated_empty`` / ``reasoning_only`` / ``no_finish`` /
993
+ ``stopped_empty``;正文非空为 None)。
994
+
995
+ 迭代中不给中间结论:此时正文尚未产出,``content_chars`` 恒为 0,
996
+ 直接转发会把「还没出正文」误报成 ``no_finish``;异常 / 中途 ``break``
997
+ 同理不置收尾标志。
998
+ """
999
+ if not self._exhausted:
1000
+ return None
1001
+ return self._stats.empty_kind
1002
+
974
1003
 
975
1004
  async def _delayed_close(services: list[Service]) -> None:
976
1005
  """延迟回收旧 service:给在途调用留宽限期,到期统一关闭(须在 loop 内)。"""
@@ -52,8 +52,8 @@ dynamic = ["version"]
52
52
  # docx → kit.docs.docx_kit(docxtpl + python-docx,模板动态填充)
53
53
  # docx-parse → kit.docs.rc_docparser_kit(rc-docparser,离线 docx → md + images)
54
54
  # mineru → kit.docs.mineru_kit(requests)
55
- # marker → kit.docs.marker_kit(marker-pdf,本地模型解析 PDF/图片→Markdown+Images;
56
- # 不含 [full]——其 docx/pptx/xlsx/html/epub 渲染依赖 weasyprint/GTK,已不在支持面)
55
+ # (marker 自 0.7.0 起暂停:marker-pdf 移出依赖、extra 与 wheel 内容均不包含,
56
+ # 需要请用 sdpy-kit[marker]==0.6.10)
57
57
  # laya → laya_kit(laya + torch,本地类型化决策引擎 choice/score/noul)
58
58
  # decima → decima_kit(decima-ai + onnxruntime,选项乱序不变、置信度已标定的决策模型)
59
59
  # web-article → crawl/web_article_kit(trafilatura,通用网页文章抓取转存)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes