sdpy-kit 0.2.6__tar.gz → 0.3.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.
Files changed (49) hide show
  1. sdpy_kit-0.2.6/README.md → sdpy_kit-0.3.1/PKG-INFO +160 -3
  2. sdpy_kit-0.2.6/PKG-INFO → sdpy_kit-0.3.1/README.md +82 -73
  3. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/__init__.py +1 -1
  4. sdpy_kit-0.3.1/kit/article_models.py +162 -0
  5. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/kit_config.py +20 -0
  6. sdpy_kit-0.3.1/kit/laya_kit.py +302 -0
  7. sdpy_kit-0.3.1/kit/web_article_kit.py +784 -0
  8. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/wx_article_kit.py +37 -75
  9. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/pyproject.toml +10 -0
  10. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/.gitignore +0 -0
  11. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/__init__.py +0 -0
  12. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/base_service.py +0 -0
  13. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/chat_service.py +0 -0
  14. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/embedding_service.py +0 -0
  15. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/exceptions.py +0 -0
  16. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/failover.py +0 -0
  17. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/image_service.py +0 -0
  18. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/model_builder.py +0 -0
  19. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/model_client.py +0 -0
  20. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/pool.py +0 -0
  21. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/profiles.py +0 -0
  22. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/rerank_service.py +0 -0
  23. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/runner.py +0 -0
  24. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/types.py +0 -0
  25. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/anydoc_kit.py +0 -0
  26. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/common_kit.py +0 -0
  27. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/__init__.py +0 -0
  28. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/log_config.py +0 -0
  29. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/__init__.py +0 -0
  30. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/base_db_kit.py +0 -0
  31. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/crud.py +0 -0
  32. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/engine.py +0 -0
  33. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/exceptions.py +0 -0
  34. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/models/__init__.py +0 -0
  35. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/models/base.py +0 -0
  36. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/mysql_kit.py +0 -0
  37. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/pg_kit.py +0 -0
  38. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/query.py +0 -0
  39. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/session.py +0 -0
  40. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/sqlite_kit.py +0 -0
  41. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/transaction.py +0 -0
  42. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/docx_kit.py +0 -0
  43. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/file_kit.py +0 -0
  44. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/marker_kit.py +0 -0
  45. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/mcp_kit.py +0 -0
  46. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/md2doc_kit.py +0 -0
  47. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/mineru_kit.py +0 -0
  48. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/redis_kit.py +0 -0
  49. {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/zvec_kit.py +0 -0
@@ -1,3 +1,81 @@
1
+ Metadata-Version: 2.5
2
+ Name: sdpy-kit
3
+ Version: 0.3.1
4
+ Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
5
+ Author-email: Clark <changhongyuan@126.com>
6
+ License-Expression: MIT
7
+ Keywords: ai,kit,mcp,mysql,postgresql,redis,sdpy,sqlalchemy,sqlite,sqlmodel,向量检索,工具集,开发工具,数据库
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Natural Language :: Chinese (Simplified)
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.13
20
+ Requires-Dist: pydantic-settings>=2.15.0
21
+ Requires-Dist: pydantic>=2.14.1
22
+ Requires-Dist: sqlalchemy~=2.0.54
23
+ Requires-Dist: sqlmodel>=0.0.48
24
+ Provides-Extra: ai
25
+ Requires-Dist: httpx>=0.28.1; extra == 'ai'
26
+ Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
27
+ Provides-Extra: all
28
+ Requires-Dist: docxtpl>=0.20.2; extra == 'all'
29
+ Requires-Dist: fastmcp>=4.1.0; extra == 'all'
30
+ Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
31
+ Requires-Dist: httpx>=0.28.1; extra == 'all'
32
+ Requires-Dist: huggingface-hub<2,>=1.5; extra == 'all'
33
+ Requires-Dist: laya>=0.4.6; extra == 'all'
34
+ Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
35
+ Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'all'
36
+ Requires-Dist: numpy>=2.5.4; extra == 'all'
37
+ Requires-Dist: pikepdf>=10.17.0; extra == 'all'
38
+ Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
39
+ Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
40
+ Requires-Dist: pymysql>=1.2.3; extra == 'all'
41
+ Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
42
+ Requires-Dist: redis>=8.1.0; extra == 'all'
43
+ Requires-Dist: requests>=2.34.2; extra == 'all'
44
+ Requires-Dist: sqliteai-vector>=1.1.2; extra == 'all'
45
+ Requires-Dist: trafilatura>=2.3.1; extra == 'all'
46
+ Requires-Dist: zvec>=0.7.0; extra == 'all'
47
+ Provides-Extra: anydoc
48
+ Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
49
+ Requires-Dist: pikepdf>=10.17.0; extra == 'anydoc'
50
+ Provides-Extra: docx
51
+ Requires-Dist: docxtpl>=0.20.2; extra == 'docx'
52
+ Provides-Extra: laya
53
+ Requires-Dist: huggingface-hub<2,>=1.5; extra == 'laya'
54
+ Requires-Dist: laya>=0.4.6; extra == 'laya'
55
+ Provides-Extra: marker
56
+ Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'marker'
57
+ Provides-Extra: mcp
58
+ Requires-Dist: fastmcp>=4.1.0; extra == 'mcp'
59
+ Provides-Extra: md2doc
60
+ Requires-Dist: markdown-it-py>=4.2.0; extra == 'md2doc'
61
+ Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'md2doc'
62
+ Provides-Extra: mineru
63
+ Requires-Dist: requests>=2.34.2; extra == 'mineru'
64
+ Provides-Extra: mysql
65
+ Requires-Dist: pymysql>=1.2.3; extra == 'mysql'
66
+ Provides-Extra: pg
67
+ Requires-Dist: psycopg2-binary>=2.9.13; extra == 'pg'
68
+ Provides-Extra: redis
69
+ Requires-Dist: redis>=8.1.0; extra == 'redis'
70
+ Provides-Extra: sqliteai
71
+ Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
72
+ Requires-Dist: sqliteai-vector>=1.1.2; extra == 'sqliteai'
73
+ Provides-Extra: web-article
74
+ Requires-Dist: trafilatura>=2.3.1; extra == 'web-article'
75
+ Provides-Extra: zvec
76
+ Requires-Dist: zvec>=0.7.0; extra == 'zvec'
77
+ Description-Content-Type: text/markdown
78
+
1
79
  # sdpy-kit
2
80
 
3
81
  SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
@@ -5,7 +83,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
5
83
  - **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
6
84
  - **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
7
85
  - **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
8
- - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 / 微信公众号文章抓取存储。
86
+ - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
9
87
 
10
88
  > 要求 Python 3.13+
11
89
 
@@ -25,8 +103,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
25
103
  | `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
26
104
  | `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
27
105
  | `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
106
+ | `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
28
107
  | `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
108
+ | `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
29
109
  | `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
110
+ | `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
30
111
 
31
112
  ## 安装
32
113
 
@@ -52,6 +133,8 @@ uv add sdpy-kit
52
133
  | `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
53
134
  | `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
54
135
  | `marker` | MarkerKit 本地文档解析(marker-pdf[full] → Markdown + Images,依赖体积大) | `pip install "sdpy-kit[marker]"` |
136
+ | `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / score / noul) | `pip install "sdpy-kit[laya]"` |
137
+ | `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
55
138
  | `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
56
139
 
57
140
  ## 配置(.env)
@@ -102,11 +185,17 @@ SQLITE_DB_PATH=./data/sqlite.db
102
185
  # MARKER_HF_ENDPOINT=https://hf-mirror.com
103
186
  # MARKER_HF_DISABLE_XET=true
104
187
  # MARKER_LLAMA_CPP_BINARY=
188
+
189
+ # Laya(类型化决策,可选依赖 sdpy-kit[laya])
190
+ # LAYA_MODEL=multilingual # multilingual / english / typed-decisions
191
+ # LAYA_DEVICE= # 留空由 laya 自动选择
192
+ # LAYA_HF_ENDPOINT=https://hf-mirror.com
193
+ # LAYA_HF_DISABLE_XET=true
105
194
  ```
106
195
 
107
196
  ### 环境变量 ↔ 能力对照表
108
197
 
109
- > 表中未出现的模块(`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`)不读取任何环境变量。
198
+ > 表中未出现的模块(`kit.article_models`、`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`、`kit.web_article_kit`)不读取任何环境变量。
110
199
 
111
200
  **基础与日志**(影响 `kit.config` 及所有模块的日志输出)
112
201
 
@@ -199,6 +288,18 @@ SQLITE_DB_PATH=./data/sqlite.db
199
288
 
200
289
  > 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
201
290
 
291
+ **Laya** → `kit.laya_kit`
292
+
293
+ | 环境变量 | 默认值 | 作用 |
294
+ |----------|--------|------|
295
+ | `LAYA_MODEL` | `multilingual` | 检查点:`multilingual`(多语言含中文)/ `english` / `typed-decisions` |
296
+ | `LAYA_DEVICE` | 无(不写入 config) | 推理设备(`cpu` / `cuda`);不配置则由 laya 自动选择 |
297
+ | `LAYA_HF_ENDPOINT` | `https://hf-mirror.com` | 检查点权重下载入口(国内镜像);置空则用 HuggingFace 官方源 |
298
+ | `LAYA_HF_DISABLE_XET` | `true` | 禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起) |
299
+
300
+ > `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
301
+ > `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
302
+
202
303
  **云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
203
304
 
204
305
  | 环境变量 | 默认值 | 作用 |
@@ -231,6 +332,7 @@ SQLITE_DB_PATH=./data/sqlite.db
231
332
  |----------|----------|
232
333
  | `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
233
334
  | `MARKER_HF_ENDPOINT` / `MARKER_HF_DISABLE_XET` / `MARKER_LLAMA_CPP_BINARY` | 仅在 `import kit.marker_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
335
+ | `LAYA_HF_ENDPOINT` / `LAYA_HF_DISABLE_XET` | 仅在 `import kit.laya_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
234
336
  | AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
235
337
  | AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
236
338
  | `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
@@ -1043,7 +1145,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
1043
1145
 
1044
1146
  ```
1045
1147
  <output_dir>/<file_name>/
1046
- ├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
1148
+ ├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
1047
1149
  ├── images/ # 图片转存(NNN.ext,正文引用已本地化)
1048
1150
  └── meta.json # 可选:结构化元信息(write_meta=True 时产出)
1049
1151
  ```
@@ -1052,10 +1154,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
1052
1154
  `ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
1053
1155
  - 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
1054
1156
  抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
1157
+ - 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
1158
+ 失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
1055
1159
  - 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
1056
1160
 
1057
1161
  ---
1058
1162
 
1163
+ ## kit.web_article_kit —— 通用网页文章抓取存储
1164
+
1165
+ 可选依赖(`trafilatura`),需安装 `pip install "sdpy-kit[web-article]"`,按需 import:
1166
+
1167
+ ```python
1168
+ from kit.web_article_kit import WebArticleKit
1169
+
1170
+ # 抓取解析(不落盘)
1171
+ result = WebArticleKit.fetch("https://blog.csdn.net/xxx/article/details/123")
1172
+ if result.ok:
1173
+ print(result.info.source, result.info.title, result.info.publish_time)
1174
+
1175
+ # 抓取并存储:<output_dir>/<file_name>/ 下 <file_name>.md + images/
1176
+ saved = WebArticleKit.save(url, output_dir="./output", file_name="12345")
1177
+ print(saved.md_path, saved.images_saved)
1178
+
1179
+ # 需要结构化元信息时再产出 meta.json(默认不产出)
1180
+ WebArticleKit.save(url, "./output", "12345", write_meta=True)
1181
+ ```
1182
+
1183
+ | 方法 | 说明 |
1184
+ |------|------|
1185
+ | `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
1186
+ | `save(url, output_dir, file_name, write_meta=False, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<file_name>/`;字段与 `WxArticleKit.save` 一致 |
1187
+
1188
+ 落盘结构与 `WxArticleKit` 完全一致(`<file_name>.md` + `images/`,`write_meta=True` 时多出 `meta.json`);
1189
+ `<file_name>.md` 的 YAML 头为 `title / author / publish_time / source / source_url`(`publish_time` 渲染为中文日期)。
1190
+
1191
+ **站点覆盖范围**(规则表只允许「转交」与「接口适配」两类条目,不做逐站 DOM 补丁):
1192
+
1193
+ | 站点类型 | 行为 |
1194
+ |---|---|
1195
+ | 通用静态 / 服务端渲染页面(博客、文档站、CSDN 等) | ✅ 直接抽取 |
1196
+ | 头条(文章页为 JS 空壳) | ✅ **明确支持**:走移动端 JSON 接口 |
1197
+ | 微信公众号 | ❌ **不覆盖**:返回提示,请改用 `kit.wx_article_kit.WxArticleKit`(该来源有专属解析,二者各自独立) |
1198
+ | 正文依赖 JavaScript 动态渲染或需登录态的站点 | ❌ 失败并给出**类型化说明**(不维护域名名单) |
1199
+
1200
+ `ArticleInfo` 为平台中立的通用字段集:`url` / `title` / `author` / `publish_time`(ISO 8601)/ `source`
1201
+ (来源平台名)/ `site_host` / `summary` / `images` / `links` / `code_blocks`,便于多平台抓取后写入同一张表;
1202
+ `to_dict()` 可直接 JSON 化(即 `meta.json` 的内容)。模型定义在 `kit.article_models`,与
1203
+ `WxArticleKit` **共用同一套**。
1204
+
1205
+ 质量信号(均不改变成功语义):
1206
+
1207
+ | 字段 | 取值 / 含义 |
1208
+ |---|---|
1209
+ | `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control`(成功为空串) |
1210
+ | `warnings` | **非致命**抽取自检提示(`tuple[str, ...]`):正文疑似过短、页面含图片但产物无图;同时写入 warning 日志 |
1211
+
1212
+ 标题统一去除站点后缀(如 CSDN 的 `标题-CSDN博客`);刻意不做「与页面 h1 择优」(h1 可能是导航文案)。
1213
+
1214
+ ---
1215
+
1059
1216
  ## AI / MCP 档案文件
1060
1217
 
1061
1218
  AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
@@ -1,73 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: sdpy-kit
3
- Version: 0.2.6
4
- Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
5
- Author-email: Clark <changhongyuan@126.com>
6
- License-Expression: MIT
7
- Keywords: ai,kit,mcp,mysql,postgresql,redis,sdpy,sqlalchemy,sqlite,sqlmodel,向量检索,工具集,开发工具,数据库
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Natural Language :: Chinese (Simplified)
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3 :: Only
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3.14
16
- Classifier: Topic :: Database
17
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
- Requires-Python: >=3.13
20
- Requires-Dist: pydantic-settings>=2.15.0
21
- Requires-Dist: pydantic>=2.14.1
22
- Requires-Dist: sqlalchemy~=2.0.54
23
- Requires-Dist: sqlmodel>=0.0.48
24
- Provides-Extra: ai
25
- Requires-Dist: httpx>=0.28.1; extra == 'ai'
26
- Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
27
- Provides-Extra: all
28
- Requires-Dist: docxtpl>=0.20.2; extra == 'all'
29
- Requires-Dist: fastmcp>=4.1.0; extra == 'all'
30
- Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
31
- Requires-Dist: httpx>=0.28.1; extra == 'all'
32
- Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
33
- Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'all'
34
- Requires-Dist: numpy>=2.5.4; extra == 'all'
35
- Requires-Dist: pikepdf>=10.17.0; extra == 'all'
36
- Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
37
- Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
38
- Requires-Dist: pymysql>=1.2.3; extra == 'all'
39
- Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
40
- Requires-Dist: redis>=8.1.0; extra == 'all'
41
- Requires-Dist: requests>=2.34.2; extra == 'all'
42
- Requires-Dist: sqliteai-vector>=1.1.2; extra == 'all'
43
- Requires-Dist: zvec>=0.7.0; extra == 'all'
44
- Provides-Extra: anydoc
45
- Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
46
- Requires-Dist: pikepdf>=10.17.0; extra == 'anydoc'
47
- Provides-Extra: docx
48
- Requires-Dist: docxtpl>=0.20.2; extra == 'docx'
49
- Provides-Extra: marker
50
- Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'marker'
51
- Provides-Extra: mcp
52
- Requires-Dist: fastmcp>=4.1.0; extra == 'mcp'
53
- Provides-Extra: md2doc
54
- Requires-Dist: markdown-it-py>=4.2.0; extra == 'md2doc'
55
- Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'md2doc'
56
- Provides-Extra: mineru
57
- Requires-Dist: requests>=2.34.2; extra == 'mineru'
58
- Provides-Extra: mysql
59
- Requires-Dist: pymysql>=1.2.3; extra == 'mysql'
60
- Provides-Extra: pg
61
- Requires-Dist: psycopg2-binary>=2.9.13; extra == 'pg'
62
- Provides-Extra: redis
63
- Requires-Dist: redis>=8.1.0; extra == 'redis'
64
- Provides-Extra: sqliteai
65
- Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
66
- Requires-Dist: sqliteai-vector>=1.1.2; extra == 'sqliteai'
67
- Provides-Extra: zvec
68
- Requires-Dist: zvec>=0.7.0; extra == 'zvec'
69
- Description-Content-Type: text/markdown
70
-
71
1
  # sdpy-kit
72
2
 
73
3
  SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
@@ -75,7 +5,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
75
5
  - **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
76
6
  - **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
77
7
  - **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
78
- - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 / 微信公众号文章抓取存储。
8
+ - **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
79
9
 
80
10
  > 要求 Python 3.13+
81
11
 
@@ -95,8 +25,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
95
25
  | `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
96
26
  | `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
97
27
  | `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
28
+ | `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
98
29
  | `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
30
+ | `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
99
31
  | `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
32
+ | `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
100
33
 
101
34
  ## 安装
102
35
 
@@ -122,6 +55,8 @@ uv add sdpy-kit
122
55
  | `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
123
56
  | `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
124
57
  | `marker` | MarkerKit 本地文档解析(marker-pdf[full] → Markdown + Images,依赖体积大) | `pip install "sdpy-kit[marker]"` |
58
+ | `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / score / noul) | `pip install "sdpy-kit[laya]"` |
59
+ | `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
125
60
  | `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
126
61
 
127
62
  ## 配置(.env)
@@ -172,11 +107,17 @@ SQLITE_DB_PATH=./data/sqlite.db
172
107
  # MARKER_HF_ENDPOINT=https://hf-mirror.com
173
108
  # MARKER_HF_DISABLE_XET=true
174
109
  # MARKER_LLAMA_CPP_BINARY=
110
+
111
+ # Laya(类型化决策,可选依赖 sdpy-kit[laya])
112
+ # LAYA_MODEL=multilingual # multilingual / english / typed-decisions
113
+ # LAYA_DEVICE= # 留空由 laya 自动选择
114
+ # LAYA_HF_ENDPOINT=https://hf-mirror.com
115
+ # LAYA_HF_DISABLE_XET=true
175
116
  ```
176
117
 
177
118
  ### 环境变量 ↔ 能力对照表
178
119
 
179
- > 表中未出现的模块(`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`)不读取任何环境变量。
120
+ > 表中未出现的模块(`kit.article_models`、`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`、`kit.web_article_kit`)不读取任何环境变量。
180
121
 
181
122
  **基础与日志**(影响 `kit.config` 及所有模块的日志输出)
182
123
 
@@ -269,6 +210,18 @@ SQLITE_DB_PATH=./data/sqlite.db
269
210
 
270
211
  > 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
271
212
 
213
+ **Laya** → `kit.laya_kit`
214
+
215
+ | 环境变量 | 默认值 | 作用 |
216
+ |----------|--------|------|
217
+ | `LAYA_MODEL` | `multilingual` | 检查点:`multilingual`(多语言含中文)/ `english` / `typed-decisions` |
218
+ | `LAYA_DEVICE` | 无(不写入 config) | 推理设备(`cpu` / `cuda`);不配置则由 laya 自动选择 |
219
+ | `LAYA_HF_ENDPOINT` | `https://hf-mirror.com` | 检查点权重下载入口(国内镜像);置空则用 HuggingFace 官方源 |
220
+ | `LAYA_HF_DISABLE_XET` | `true` | 禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起) |
221
+
222
+ > `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
223
+ > `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
224
+
272
225
  **云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
273
226
 
274
227
  | 环境变量 | 默认值 | 作用 |
@@ -301,6 +254,7 @@ SQLITE_DB_PATH=./data/sqlite.db
301
254
  |----------|----------|
302
255
  | `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
303
256
  | `MARKER_HF_ENDPOINT` / `MARKER_HF_DISABLE_XET` / `MARKER_LLAMA_CPP_BINARY` | 仅在 `import kit.marker_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
257
+ | `LAYA_HF_ENDPOINT` / `LAYA_HF_DISABLE_XET` | 仅在 `import kit.laya_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
304
258
  | AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
305
259
  | AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
306
260
  | `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
@@ -1113,7 +1067,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
1113
1067
 
1114
1068
  ```
1115
1069
  <output_dir>/<file_name>/
1116
- ├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
1070
+ ├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
1117
1071
  ├── images/ # 图片转存(NNN.ext,正文引用已本地化)
1118
1072
  └── meta.json # 可选:结构化元信息(write_meta=True 时产出)
1119
1073
  ```
@@ -1122,10 +1076,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
1122
1076
  `ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
1123
1077
  - 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
1124
1078
  抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
1079
+ - 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
1080
+ 失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
1125
1081
  - 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
1126
1082
 
1127
1083
  ---
1128
1084
 
1085
+ ## kit.web_article_kit —— 通用网页文章抓取存储
1086
+
1087
+ 可选依赖(`trafilatura`),需安装 `pip install "sdpy-kit[web-article]"`,按需 import:
1088
+
1089
+ ```python
1090
+ from kit.web_article_kit import WebArticleKit
1091
+
1092
+ # 抓取解析(不落盘)
1093
+ result = WebArticleKit.fetch("https://blog.csdn.net/xxx/article/details/123")
1094
+ if result.ok:
1095
+ print(result.info.source, result.info.title, result.info.publish_time)
1096
+
1097
+ # 抓取并存储:<output_dir>/<file_name>/ 下 <file_name>.md + images/
1098
+ saved = WebArticleKit.save(url, output_dir="./output", file_name="12345")
1099
+ print(saved.md_path, saved.images_saved)
1100
+
1101
+ # 需要结构化元信息时再产出 meta.json(默认不产出)
1102
+ WebArticleKit.save(url, "./output", "12345", write_meta=True)
1103
+ ```
1104
+
1105
+ | 方法 | 说明 |
1106
+ |------|------|
1107
+ | `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
1108
+ | `save(url, output_dir, file_name, write_meta=False, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<file_name>/`;字段与 `WxArticleKit.save` 一致 |
1109
+
1110
+ 落盘结构与 `WxArticleKit` 完全一致(`<file_name>.md` + `images/`,`write_meta=True` 时多出 `meta.json`);
1111
+ `<file_name>.md` 的 YAML 头为 `title / author / publish_time / source / source_url`(`publish_time` 渲染为中文日期)。
1112
+
1113
+ **站点覆盖范围**(规则表只允许「转交」与「接口适配」两类条目,不做逐站 DOM 补丁):
1114
+
1115
+ | 站点类型 | 行为 |
1116
+ |---|---|
1117
+ | 通用静态 / 服务端渲染页面(博客、文档站、CSDN 等) | ✅ 直接抽取 |
1118
+ | 头条(文章页为 JS 空壳) | ✅ **明确支持**:走移动端 JSON 接口 |
1119
+ | 微信公众号 | ❌ **不覆盖**:返回提示,请改用 `kit.wx_article_kit.WxArticleKit`(该来源有专属解析,二者各自独立) |
1120
+ | 正文依赖 JavaScript 动态渲染或需登录态的站点 | ❌ 失败并给出**类型化说明**(不维护域名名单) |
1121
+
1122
+ `ArticleInfo` 为平台中立的通用字段集:`url` / `title` / `author` / `publish_time`(ISO 8601)/ `source`
1123
+ (来源平台名)/ `site_host` / `summary` / `images` / `links` / `code_blocks`,便于多平台抓取后写入同一张表;
1124
+ `to_dict()` 可直接 JSON 化(即 `meta.json` 的内容)。模型定义在 `kit.article_models`,与
1125
+ `WxArticleKit` **共用同一套**。
1126
+
1127
+ 质量信号(均不改变成功语义):
1128
+
1129
+ | 字段 | 取值 / 含义 |
1130
+ |---|---|
1131
+ | `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control`(成功为空串) |
1132
+ | `warnings` | **非致命**抽取自检提示(`tuple[str, ...]`):正文疑似过短、页面含图片但产物无图;同时写入 warning 日志 |
1133
+
1134
+ 标题统一去除站点后缀(如 CSDN 的 `标题-CSDN博客`);刻意不做「与页面 h1 择优」(h1 可能是导航文案)。
1135
+
1136
+ ---
1137
+
1129
1138
  ## AI / MCP 档案文件
1130
1139
 
1131
1140
  AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
@@ -23,7 +23,7 @@ Author: Clark Chang
23
23
  数据库访问通过 kit.db 子包提供(基于 SQLModel)。
24
24
  """
25
25
 
26
- __version__ = "0.2.6"
26
+ __version__ = "0.3.1"
27
27
 
28
28
  from kit.db import (
29
29
  MYSQL,
@@ -0,0 +1,162 @@
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-10-14
21
+ """文章抓取通用模型(多平台共用,零第三方依赖)
22
+
23
+ `kit.wx_article_kit.WxArticleKit`(微信公众号)与 `kit.web_article_kit.WebArticleKit`
24
+ (通用网页)共用同一套模型,便于**多平台抓取后写入同一张表**:
25
+
26
+ - `ArticleImage` / `ArticleLink` / `CodeBlock`:正文清单条目;
27
+ - `ArticleInfo`:平台中立的通用字段集(`publish_time` 保持 ISO 8601,
28
+ 仅落盘 YAML 头渲染为中文日期);
29
+ - `ArticleResult`:`fetch()` 的非落盘结果;
30
+ - `SavedOutput`:`save()` 的落盘结果。
31
+
32
+ 失败原因(`ArticleResult.reason` / `SavedOutput.reason`)取值:
33
+
34
+ | 取值 | 含义 |
35
+ |---|---|
36
+ | `""` | 成功 |
37
+ | `unsupported_source` | 该来源有专属工具类(如微信 → `WxArticleKit`) |
38
+ | `fetch_blocked` | 抓取被站点拒绝(403 等):正文通常依赖 JavaScript 渲染或需登录态 |
39
+ | `fetch_failed` | 网络/超时等抓取失败 |
40
+ | `no_content` | 抓到页面但无法识别正文(非文章页或正文由 JavaScript 动态渲染) |
41
+ | `risk_control` | 命中站点风控特征(访问过于频繁/环境异常) |
42
+
43
+ `warnings` 为**非致命**自检提示(如「页面含图片但产物无图」「正文疑似过短」),
44
+ **不改变 `ok` 语义**:抓取成功一律 `ok=True`,提示仅供调用方判断质量。
45
+
46
+ 使用方式(按需 import,无需经 kit 包顶层导出):
47
+
48
+ from kit.article_models import ArticleInfo, ArticleResult
49
+ """
50
+
51
+ from dataclasses import dataclass, field
52
+ from typing import Any, NamedTuple
53
+
54
+ # 失败原因取值(字符串常量,便于业务侧分支判断;成功为空串)
55
+ REASON_UNSUPPORTED_SOURCE = "unsupported_source"
56
+ REASON_FETCH_BLOCKED = "fetch_blocked"
57
+ REASON_FETCH_FAILED = "fetch_failed"
58
+ REASON_NO_CONTENT = "no_content"
59
+ REASON_RISK_CONTROL = "risk_control"
60
+
61
+ # 全部合法取值(便于校验收敛)
62
+ REASONS: tuple[str, ...] = (
63
+ "",
64
+ REASON_UNSUPPORTED_SOURCE,
65
+ REASON_FETCH_BLOCKED,
66
+ REASON_FETCH_FAILED,
67
+ REASON_NO_CONTENT,
68
+ REASON_RISK_CONTROL,
69
+ )
70
+
71
+
72
+ class ArticleImage(NamedTuple):
73
+ """图片条目(转存后地址 + 原始地址 + alt + 本地相对路径)"""
74
+
75
+ src: str
76
+ original: str
77
+ alt: str = ""
78
+ local: str = "" # 转存后的本地相对路径(如 images/001.png),失败为空
79
+
80
+
81
+ class ArticleLink(NamedTuple):
82
+ """正文链接条目"""
83
+
84
+ text: str
85
+ href: str
86
+
87
+
88
+ class CodeBlock(NamedTuple):
89
+ """代码块条目"""
90
+
91
+ language: str
92
+ code: str
93
+
94
+
95
+ @dataclass
96
+ class ArticleInfo:
97
+ """文章元信息(平台中立的通用字段集,`to_dict()` 可 JSON 化后自行持久化)
98
+
99
+ 字段说明:
100
+
101
+ - `url`:原文链接;
102
+ - `title` / `author` / `summary`:标题 / 作者 / 摘要,取不到时为空串;
103
+ - `publish_time`:ISO 8601(精度不足时退化为 `YYYY-MM-DD`),
104
+ 落盘 YAML 头渲染中文日期;
105
+ - `source`:来源平台名(如「今日头条」「CSDN博客」「微信公众平台」);
106
+ - `site_host`:站点域名(DB 归类/去重辅助);
107
+ - `images` / `links` / `code_blocks`:正文清单(图片 `local` 在落盘阶段填充)。
108
+ """
109
+
110
+ url: str
111
+ title: str
112
+ author: str = ""
113
+ publish_time: str = ""
114
+ source: str = ""
115
+ site_host: str = ""
116
+ summary: str = ""
117
+ images: list[ArticleImage] = field(default_factory=list)
118
+ links: list[ArticleLink] = field(default_factory=list)
119
+ code_blocks: list[CodeBlock] = field(default_factory=list)
120
+
121
+ def to_dict(self) -> dict[str, Any]:
122
+ """序列化为可 JSON 化的 dict"""
123
+ return {
124
+ "url": self.url,
125
+ "title": self.title,
126
+ "author": self.author,
127
+ "publish_time": self.publish_time,
128
+ "source": self.source,
129
+ "site_host": self.site_host,
130
+ "summary": self.summary,
131
+ "images": [img._asdict() for img in self.images],
132
+ "links": [link._asdict() for link in self.links],
133
+ "code_blocks": [code._asdict() for code in self.code_blocks],
134
+ }
135
+
136
+
137
+ class ArticleResult(NamedTuple):
138
+ """fetch() 的非落盘结果
139
+
140
+ `reason` 见模块 docstring;`warnings` 为非致命自检提示(不可变元组)。
141
+ """
142
+
143
+ ok: bool
144
+ error: str = ""
145
+ info: ArticleInfo | None = None
146
+ markdown: str = ""
147
+ reason: str = ""
148
+ warnings: tuple[str, ...] = ()
149
+
150
+
151
+ class SavedOutput(NamedTuple):
152
+ """save() 的落盘结果"""
153
+
154
+ ok: bool
155
+ error: str = ""
156
+ output_dir: str = "" # 实际写入的文章子目录(<output_dir>/<file_name>)
157
+ md_path: str = ""
158
+ images_dir: str = ""
159
+ title: str = ""
160
+ images_saved: int = 0 # 成功转存的图片张数
161
+ reason: str = ""
162
+ warnings: tuple[str, ...] = ()
@@ -142,6 +142,26 @@ class Settings(BaseSettings):
142
142
  description="llama-server 可执行文件路径(None 时沿用系统 LLAMA_CPP_BINARY)",
143
143
  )
144
144
 
145
+ # Laya 决策引擎配置(本地小模型,把任意文本变成 choice/score/noul 类型化决策)
146
+ LAYA_MODEL: str = Field(
147
+ default="multilingual",
148
+ description=(
149
+ "生效检查点:multilingual(多语言含中文)/ english / typed-decisions"
150
+ ),
151
+ )
152
+ LAYA_DEVICE: str | None = Field(
153
+ default=None,
154
+ description="推理设备(如 cpu / cuda),None 表示交给 laya 自动选择",
155
+ )
156
+ LAYA_HF_ENDPOINT: str = Field(
157
+ default="https://hf-mirror.com",
158
+ description="检查点权重下载入口(默认国内镜像),置空则使用 HuggingFace 官方源",
159
+ )
160
+ LAYA_HF_DISABLE_XET: bool = Field(
161
+ default=True,
162
+ description="禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起)",
163
+ )
164
+
145
165
  class Config:
146
166
  env_file = ".env"
147
167
  env_file_encoding = "utf-8"