sdpy-kit
SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
- 统一数据库访问层:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
- AI 多模型服务池:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
- MCP 客户端:streamable-http / sse 双传输,档案驱动多服务器;
- 工具集:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 / 微信公众号文章抓取存储。
要求 Python 3.13+
模块与方法入口
| 模块 | 类 / 入口 | 能力 | 需要 extra |
|---|---|---|---|
kit.config |
get_settings / get_default_logger |
配置单例与统一日志 | 无 |
kit.db |
SQLiteKit / PgKit / MySqlKit / CRUDBase |
统一 CRUD、原生 SQL、原生表操作、事务、表管理 | pg / mysql / sqliteai |
kit.ai |
AI(同步门面)/ 4 个原生异步 Service |
对话 / 向量化 / 重排序 / 文生图 | ai |
kit.mcp_kit |
McpKit |
MCP 工具发现与调用 | mcp |
kit.redis_kit |
RedisKit |
Redis string / hash / list / set / zset | redis |
kit.file_kit |
FileKit |
文件与目录读写、移动、遍历 | 无 |
kit.common_kit |
CommonKit |
雪花 ID、时间戳 ID、时间格式化 | 无 |
kit.anydoc_kit |
AnyDocKit |
docx / pdf / xlsx 等 → Markdown | anydoc |
kit.docx_kit |
DocxKit |
Word 模板动态填充(章节 / 列表 / 表格) | docx |
kit.md2doc_kit |
Md2DocKit |
Markdown → Word / PDF / HTML | md2doc |
kit.mineru_kit |
MinerUKit |
MinerU 文档解析(精准 + Agent 轻量) | mineru |
kit.marker_kit |
MarkerKit |
本地文档解析(PDF / 图片 / Office → Markdown + Images) | marker |
kit.zvec_kit |
ZvecKit |
zvec 本地向量库 | zvec |
kit.wx_article_kit |
WxArticleKit |
微信公众号文章 → Markdown + 图片转存 | 无 |
安装
pip install sdpy-kit # 基础版:config/log + kit.db 核心(SQLite 常规操作零额外依赖)
# 或
uv add sdpy-kit
可选能力按 extras 按需安装(均为延迟导入,不装不影响基础使用,缺依赖时报错会提示安装命令):
| extra | 能力 | 安装 |
|---|---|---|
pg |
PgKit(PostgreSQL 驱动;向量方法另需服务端 pgvector) | pip install "sdpy-kit[pg]" |
mysql |
MySqlKit(MySQL 驱动;向量仅存储,需 MySQL 9.x) | pip install "sdpy-kit[mysql]" |
sqliteai |
SQLiteKit 向量方法(numpy / sqliteai-vector,SQLite 向量扩展) | pip install "sdpy-kit[sqliteai]" |
zvec |
ZvecKit(zvec,阿里独立向量库) | pip install "sdpy-kit[zvec]" |
redis |
RedisKit | pip install "sdpy-kit[redis]" |
mcp |
McpKit(fastmcp) | pip install "sdpy-kit[mcp]" |
ai |
AI 服务池(httpx + python-dotenv) | pip install "sdpy-kit[ai]" |
md2doc |
Md2DocKit:Markdown → Word / PDF / HTML(pimd / fpdf2) | pip install "sdpy-kit[md2doc]" |
anydoc |
AnyDocKit 文档转 Markdown(firecrawl-anydoc + pikepdf) | pip install "sdpy-kit[anydoc]" |
docx |
DocxKit 模板动态填充(docxtpl) | pip install "sdpy-kit[docx]" |
mineru |
MinerUKit 解析 API(精准解析 + Agent 轻量解析) | pip install "sdpy-kit[mineru]" |
marker |
MarkerKit 本地文档解析(marker-pdf[full] → Markdown + Images,依赖体积大) | pip install "sdpy-kit[marker]" |
all |
全部能力 | pip install "sdpy-kit[all]" |
配置(.env)
kit 通过 pydantic-settings 读取项目根目录 .env;同名真实环境变量优先于 .env。全部变量都有默认值,按需覆盖即可。
最小样例 .env
# 日志(可选)
LOG_LEVEL=INFO
LOG_TO_FILE=false
# 数据库(三选一,切换只改 DEFAULT_DB_TYPE:postgresql / mysql / sqlite)
DEFAULT_DB_TYPE=sqlite
SQLITE_DB_PATH=./data/sqlite.db
# DB_HOST=localhost
# DB_PORT=5432
# DB_DATABASE=postgres
# DB_USER=admin
# DB_PASSWORD=
# MYSQL_PORT=3306
# MYSQL_DATABASE=mydb
# MYSQL_USER=root
# MYSQL_CHARSET=utf8mb4
# 按需启用以下能力
# REDIS_HOST=localhost
# REDIS_PORT=6379
# REDIS_DB=0
# REDIS_PASSWORD=
# FILE_STORAGE_ROOT=./data
# ZVEC_DATA_DIR=./data/zvec
# AI:档案路径 + 你为各模型档案自定的密钥变量名(变量名在 ai_models.json 里声明)
# AI_MODELS_CONFIG_PATH=./config/ai_models.json
# SILICONFLOW_API_KEY=sk-xxxxxx
# MCP:档案路径 + 你为各服务器自定的 token 变量名(变量名在 mcp_servers.json 里声明)
# MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.json
# AMINER_TOKEN=xxxxxx
# MinerU(仅精准解析需要 Key,Agent 轻量解析免登录)
# MINERU_API_KEY=
# Marker(本地解析,可选依赖 sdpy-kit[marker])
# MARKER_MODE=fast # None=按设备自动(GPU→balanced / CPU、MPS→fast)
# MARKER_HF_ENDPOINT=https://hf-mirror.com
# MARKER_HF_DISABLE_XET=true
# MARKER_LLAMA_CPP_BINARY=
环境变量 ↔ 能力对照表
表中未出现的模块(
kit.common_kit、kit.docx_kit、kit.md2doc_kit、kit.wx_article_kit)不读取任何环境变量。
基础与日志(影响 kit.config 及所有模块的日志输出)
| 环境变量 | 默认值 | 作用 |
|---|---|---|
LOG_LEVEL |
INFO |
kit 统一日志级别 |
LOG_TO_CONSOLE |
true |
是否输出到控制台 |
LOG_TO_FILE |
false |
是否输出到文件 |
LOG_DIR |
./logs |
日志目录 |
LOG_FILE |
app.log |
日志文件名 |
数据库 → kit.db、SQLiteKit / PgKit / MySqlKit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
DEFAULT_DB_TYPE |
postgresql |
默认数据库类型:postgresql / mysql / sqlite;切换数据库只改这一项 |
DB_HOST |
localhost |
PG / MySQL 共用主机 |
DB_PORT |
5432 |
PostgreSQL 端口 |
DB_DATABASE |
postgres |
PostgreSQL 库名 |
DB_USER |
admin |
PostgreSQL 用户 |
DB_PASSWORD |
1qaz@WSX3edc |
PG / MySQL 共用密码 |
MYSQL_PORT |
3306 |
MySQL 端口(与 PG 不同时覆盖) |
MYSQL_DATABASE |
mydb |
MySQL 库名 |
MYSQL_USER |
root |
MySQL 用户 |
MYSQL_CHARSET |
utf8mb4 |
MySQL 字符集 |
SQLITE_DB_PATH |
./data/sqlite.db |
SQLite 文件路径(:memory: 时自动共享单连接) |
Redis → kit.redis_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
REDIS_HOST |
localhost |
Redis 主机 |
REDIS_PORT |
6379 |
Redis 端口 |
REDIS_DB |
0 |
数据库编号 |
REDIS_PASSWORD |
None |
密码,无密码留空 |
文件存储 → kit.file_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
FILE_STORAGE_ROOT |
./data |
FileKit 全部相对路径的根目录。⚠ 该目录必须已存在,否则首次调用抛 RuntimeError(根路径不存在) |
向量库 → kit.zvec_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
ZVEC_DATA_DIR |
./data/zvec |
Collection 根目录 |
ZVEC_DEFAULT_METRIC |
cosine |
默认距离度量(cosine / l2 / ip) |
ZVEC_QUERY_THREADS |
None(自适应) |
查询线程数 |
ZVEC_OPTIMIZE_THREADS |
None(自适应) |
索引构建线程数 |
ZVEC_MEMORY_LIMIT_MB |
None(自适应) |
内存软上限 |
ZVEC_LOG_DIR |
./logs/zvec |
zvec 引擎独立日志目录 |
ZVEC_LOG_LEVEL |
warn |
zvec 引擎日志级别 |
AI → kit.ai
| 环境变量 | 默认值 | 作用 |
|---|---|---|
AI_MODELS_CONFIG_PATH |
./config/ai_models.json |
档案文件位置;文件不存在时调用抛 AIConfigError |
KIT_AI_CHAIN_TIMEOUT |
未设置 | 全局整链预算(秒)= 门面同步等待上限。⚠ 不是 Settings 字段,由代码直读 os.environ,无类型/范围校验,非法值仅告警后回退 |
| (密钥,变量名由档案决定) | 无 | 名字写在 ai_models.json 各档案的 api_key 字段(如 SILICONFLOW_API_KEY),值放 .env 或系统环境变量。未配置的档案会被剔除出候选链;该类型全部未配置则调用失败 |
MCP → kit.mcp_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
MCP_SERVERS_CONFIG_PATH |
./config/mcp_servers.json |
服务器档案位置 |
MCP_CALL_TIMEOUT |
60(1–600) |
档案未声明 timeout 时的调用超时 |
| (令牌,变量名由档案决定) | 无 | 名字写在 mcp_servers.json 各服务器的 token_env 字段(如 AMINER_TOKEN);档案声明了却两处都未配置时抛 McpKitError |
MinerU → kit.mineru_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
MINERU_API_KEY |
无 | 精准解析 Token(Agent 轻量解析免登录,不读该变量);缺失时抛 MinerUError |
MINERU_BASE_API_URL |
https://mineru.net |
API 基础地址 |
MINERU_TIMEOUT |
60(1–600) |
单次 HTTP 请求连接超时 |
MINERU_TRANSFER_TIMEOUT |
300(1–3600) |
文件上传 / 结果下载读取超时 |
MINERU_POLL_INTERVAL |
2.0(≥0.5) |
任务轮询间隔 |
MINERU_MAX_WAIT |
600(≥10) |
轮询总超时,超时抛 MinerUError |
Marker → kit.marker_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
MARKER_MODE |
无(不写入 config) | 解析模式 balanced / fast;不配置则由 marker 按设备自动选择(GPU→balanced,CPU、MPS→fast) |
MARKER_HF_ENDPOINT |
https://hf-mirror.com |
模型权重下载入口(国内镜像);置空则用 HuggingFace 官方源 |
MARKER_HF_DISABLE_XET |
true |
禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起) |
MARKER_LLAMA_CPP_BINARY |
无 | llama-server 路径(非 NVIDIA 平台完整 VLM 能力);未设置时沿用系统 LLAMA_CPP_BINARY |
这四项由 kit 在导入 marker 之前经
os.environ.setdefault应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
云 OCR(任何 ocr="hosted" 的转换)→ kit.anydoc_kit
| 环境变量 | 默认值 | 作用 |
|---|---|---|
FIRECRAWL_API_KEY |
无 | 扫描件云 OCR 密钥。kit 不读该变量:仅在方法参数 api_key=None 时由 firecrawl-anydoc 自行回落读取 |
FIRECRAWL_API_URL |
无 | 云 OCR 服务地址,回落规则同上 |
环境变量的四类来源
| 类别 | 命名方式 | 当前变量 | 读取方与语义 |
|---|---|---|---|
| ① Settings 变量 | 固定,kit 定义 | 上表中除 KIT_AI_CHAIN_TIMEOUT 与密钥类之外的全部 |
pydantic-settings 读 .env 与真实环境变量(后者优先),带类型与范围校验,缺失用默认值 |
| ② 直读变量 | 固定,kit 代码直接读 os.environ |
KIT_AI_CHAIN_TIMEOUT |
无类型校验,非法值告警后回退 |
| ③ 档案声明变量 | 动态,名字写在 JSON 里 | AI 的 api_key、MCP 的 token_env |
名字进 JSON、值进 .env——所以档案文件可以安全入库,密钥留在环境里 |
| ④ 第三方库变量 | 由依赖库定义 | FIRECRAWL_API_KEY、FIRECRAWL_API_URL(anydoc);HF_ENDPOINT / HF_HUB_DISABLE_XET / LLAMA_CPP_BINARY(marker) |
anydoc:kit 只透传 None,由其自行回落;marker:kit 在导入前按 MARKER_* 配置做 os.environ.setdefault(不覆盖显式设置) |
密钥解析优先级
| 能力 | 解析顺序 | 全部缺失时 |
|---|---|---|
| AI | 真实环境变量 > 项目根 .env(首次加载档案时经 load_dotenv(override=False) 注入 os.environ) |
该档案被剔除出候选链;该类型无可用档案则调用报 AIAggregateError |
| MCP | os.environ > 项目根 .env(kit 自行解析文件) |
抛 McpKitError |
| MinerU | 仅 Settings(.env / 真实环境变量) |
精准解析接口抛 MinerUError |
AI 的
load_dotenv(override=False)表示已存在的真实环境变量不会被.env覆盖——生产用容器 / 系统环境变量注入时,可以放心保留开发用的.env。
改了配置何时生效
| 改动内容 | 生效时机 |
|---|---|
LOG_*、DB_*、MYSQL_*、REDIS_*、FILE_STORAGE_ROOT、ZVEC_*、MINERU_*、MARKER_MODE |
get_settings() 带 lru_cache,进程内首次读取后固定 → 重启进程(或调用 AI.reload_config(),它会顺带清空该缓存) |
MARKER_HF_ENDPOINT / MARKER_HF_DISABLE_XET / MARKER_LLAMA_CPP_BINARY |
仅在 import kit.marker_kit 时经 os.environ.setdefault 应用 → 改动需重启进程重新导入模块 |
AI 密钥写在 .env 里 |
.env 只在加载档案时注入一次 → 重启,或 AI.reload_config(reload_file=True) |
AI 密钥由自己写入 os.environ |
AI.reload_config()(默认 reload_file=False,只重读 os.environ) |
ai_models.json(增删档案、换 model) |
AI.reload_config(reload_file=True) |
KIT_AI_CHAIN_TIMEOUT |
每次调用现读 os.environ → 改 os.environ 立即生效;写在 .env 里的新值要等下一次档案加载 |
| MCP token | 每次调用现读 os.environ 并回退解析 .env → 改 .env 立即生效,无需重启 |
kit.config —— 配置与日志
from kit.config import get_settings, get_default_logger
settings = get_settings() # Settings 单例(lru_cache,读取 .env)
logger = get_default_logger(__name__) # 统一日志入口(禁止自建 logging handler)
logger.info("hello %s", settings.FILE_STORAGE_ROOT)
| 入口 | 说明 |
|---|---|
get_settings() -> Settings |
返回全局配置单例,字段名与环境变量同名(如 settings.SQLITE_DB_PATH、settings.DEFAULT_DB_TYPE),完整清单见上文对照表 |
get_default_logger(name=None, log_level=None, log_file=None) -> logging.Logger |
获取统一格式日志器(控制台 + 轮转文件) |
setup_logger(name=None, log_level=None, log_file=None, log_format=..., date_format=..., max_bytes=5*1024*1024, backup_count=5) -> logging.Logger |
完整参数版本,一般无需直接使用 |
kit.db —— 统一数据库访问层
kit/__init__.py 直接重导出 kit.db 的全部公开符号,下列两种写法等价:
from kit.db import session_scope, transactional, DBError # 推荐
from kit import session_scope, transactional, DBError # 等价
1. 三个数据库 Kit(24 个共享方法 API 完全一致)
| 类 | 导入 | 数据库 | extra |
|---|---|---|---|
SQLiteKit |
from kit.db.sqlite_kit import SQLiteKit |
SQLite | 无(常规操作) |
PgKit |
from kit.db.pg_kit import PgKit |
PostgreSQL | pg |
MySqlKit |
from kit.db.mysql_kit import MySqlKit |
MySQL | mysql |
共有方法(23 个,全部为 classmethod,内部自动提交)
| 分组 | 方法 | 说明 |
|---|---|---|
| 连接 | connect() -> bool |
建引擎并验证连通 |
| 连接 | close() -> None |
关闭引擎 |
| 连接 | is_connected() -> bool |
探测当前是否可连 |
| 连接 | raw_connection() |
取原生 DBAPI 连接(PG=psycopg2 / MySQL=pymysql / SQLite=sqlite3);调用方负责 commit 与 close,psycopg2 / pymysql 默认不自动提交 |
| ORM | create(model, data) -> Model |
插入单条 |
| ORM | create_multi(model, data_list) -> list[Model] |
批量插入 |
| ORM | get(model, id) -> Model | None |
按主键查询 |
| ORM | get_by(model, field_name, field_value) -> Model | None |
按字段等值查询 |
| ORM | list(model, *, skip=0, limit=100, order_by=None, desc=False) -> list[Model] |
分页 + 排序查询 |
| ORM | update(model, id, data) -> Model |
按主键更新 |
| ORM | delete(model, id) -> bool |
按主键删除 |
| ORM | upsert(model, data, conflict_fields) -> Model |
原子插入或更新(conflict_fields 须为唯一约束) |
| ORM | count(model) -> int |
记录总数 |
| 原生 SQL | query(sql, params=None) -> list[dict] |
原生查询,返回字典列表 |
| 原生 SQL | execute(sql, params=None) -> int |
原生写入,返回影响行数 |
| 原生表 | create_raw_table(table_name, columns, if_not_exists=True) -> None |
按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
| 原生表 | raw_insert(table_name, data) -> int |
插入一行,返回影响行数 |
| 原生表 | raw_get_by(table_name, column, value) -> dict | None |
按单列等值取首行 |
| 事务 | transaction() |
事务上下文管理器,with PgKit.transaction() as session: |
| 表 | list_tables() -> list[str] |
列出全部表名 |
| 表 | table_exists(table_name) -> bool |
判断表是否存在 |
| 表 | create_tables(models) -> None |
按模型列表建表 |
| 表 | get_table_info(table_name) -> list[dict] |
获取列信息(按列序;公共键 4 个:column_name/data_type/is_nullable/column_default) |
列表参数(
IN):query/execute的params中值为list/tuple/set/frozenset时按IN展开,可直接写WHERE id IN :ids(三库通用)。IN不保证顺序;参数列表过大 应分批(PG 单语句上限 65535 参数)。
LIKE的方言差异:SQLite 对 ASCII 不区分大小写,PostgreSQL 区分(用ILIKE); 跨库一致写法:LOWER(col) LIKE LOWER(:pattern)。导出面:
from kit.db import PgKit, SQLiteKit, build_pg_url, to_vector_literal, …(kit顶层与kit.db导出同一套符号)。
from sqlalchemy import BigInteger
from sqlmodel import SQLModel, Field
from kit.common_kit import CommonKit
from kit.db import SQLiteKit # 也可 from kit.db.sqlite_kit import SQLiteKit
class User(SQLModel, table=True):
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
primary_key=True, sa_type=BigInteger)
name: str
SQLiteKit.connect()
SQLiteKit.create_tables([User])
user = SQLiteKit.create(User, {"name": "张三"}) # 自动提交
SQLiteKit.update(User, user.id, {"name": "李四"})
users = SQLiteKit.list(User, skip=0, limit=10, order_by="id", desc=True)
rows = SQLiteKit.query("SELECT * FROM user WHERE name = :name", {"name": "李四"})
affected = SQLiteKit.execute("UPDATE user SET name = '王五' WHERE id = :id", {"id": user.id})
rows = SQLiteKit.query("SELECT * FROM user WHERE id IN :ids", {"ids": [1, 2, 3]}) # 列表参数
with SQLiteKit.transaction() as session: # 多操作原子提交
SQLiteKit.create(User, {"name": "批量一"})
SQLiteKit.create(User, {"name": "批量二"})
切换数据库:把 import 的
SQLiteKit换成PgKit/MySqlKit即可,其余代码不变。 注意三点例外:①create_raw_table的列定义文本须按目标库方言改写;② 向量能力 三库同名同参但能力不同(可由VECTOR_CAPABILITIESfeature-detect),尤其 MySQL 侧检索不可用;③create_vector_table的dims为必填参数。
2. 向量方法(三库同名同参,能力差异化)
签名在
SQLiteKit/PgKit/MySqlKit上完全一致;能力按后端不同(见下表)。 extra 前提:SQLite 需sdpy-kit[sqliteai];PG 需sdpy-kit[pg]且服务端已装 pgvector (create_vector_table会自动尝试CREATE EXTENSION);MySQL 需 9.x(Community 即可存, 但检索需 HeatWave on OCI / MySQL AI)——建表前会校验服务端版本,低于 9.x 抛DBError。
| 方法 | 说明 |
|---|---|
create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None |
建含向量列的表;dims 必填(SQLite 用 CHECK 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL 为上限语义)。extra_cols 支持 {列名: 类型串}(推荐,与 create_raw_table 同形)与 [{"name","type"}] 两种形态 |
insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int |
批量插入向量(自成一事务并提交);ids=None 时由 Kit 生成雪花 ID;接受 list[list[float]] 或二维 numpy 数组 |
build_insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> list[tuple[str, list[dict]]] |
只拼装、不执行、不管事务:返回按列集分组的 (SQL, 参数列表),供调用方在自有事务内写向量行(见「自有事务内写向量」) |
search_vectors(table_name, query_vector, top_k=10, distance_type="L2", filter_sql=None, exact=True, id_col="id", column="embedding") -> list[dict] |
KNN 检索,按距离升序返回 {"id", "distance"};exact=True(默认)禁止近似加速;MySQL 侧抛 VectorNotSupportedError |
vector_version() -> str |
向量后端版本;不可用时返回 "unavailable"(MySQL 侧为服务端版本号) |
id_col / column(逃生口):让自建 DDL 的向量表也能读写/检索。需要复合主键、额外约束、
分区或与既有业务表合表时,用 execute() 自建 DDL 再用这两个参数接入;SQLite 侧要求 id_col
为 rowid 别名(INTEGER PRIMARY KEY)。
exact 参数(默认 True = 禁止近似加速):
| 后端 | exact=True(默认) |
exact=False |
是否提速 |
|---|---|---|---|
PgKit |
同事务 SET LOCAL enable_indexscan = off(有索引也精确;代价:该查询不使用任何索引,含过滤列索引) |
不干预查询计划(有索引即近似召回,distance 仍为真值) |
✅ 仅"大表 + 已建索引"时 |
SQLiteKit |
vector_full_scan(真值 L2) |
vector_quantize_scan(量化 L2 = 真值 × 255) |
❌ 不提速(每次调用重建量化) |
MySqlKit |
不适用 | 同左 | — |
能力差异(仅 L2 可跨库比较数值;float32 vs float64,需带容差):
| 项 | SQLiteKit(sqlite-vector) |
PgKit(pgvector) |
MySqlKit(9.x 原生) |
|---|---|---|---|
支持的 distance_type |
仅 L2 | COSINE / L2 / L1 / DOT(负内积) / SQUARED_L2 |
无(检索不可用) |
默认 distance_type |
"L2" |
"L2"(三库统一,不按后端覆写) |
"L2"(形参保留,调用即抛错) |
| 非法取值 | 抛 ValueError |
抛 ValueError |
search_vectors 抛 VectorNotSupportedError |
distance 口径 |
exact=True 真值 L2 / exact=False 量化(×255) |
真实距离 | — |
| 维度约束 | CHECK(仅新表;旧表靠方法内校验) |
强制(不匹配即报错;建表时回读比对) | 上限约束(超限报错) |
filter_sql 与 top_k |
先过滤再取 top_k(与 PG 一致) | 先过滤再取 top_k | 不适用 |
| 索引 | 无(精确路径为全表扫描) | kit 不建索引;自建后须 exact=False 才会被使用 |
无法建(B-Tree 拒绝向量列) |
MySQL
DISTANCE()/VECTOR_DISTANCE()仅 HeatWave on OCI 与 MySQL AI 提供 (Community 实测报1305 FUNCTION ... does not exist);故MySqlKit只覆盖存储侧。
主键与 id(三库统一):向量表主键均为 BIGINT(SQLite 用等价的 64 位 INTEGER PRIMARY KEY),
不使用数据库自增;ids=None 时由 Kit 用 CommonKit.generate_snowflake_id() 生成雪花 ID
(18~19 位,int64)。
⚠ 雪花 ID 超过 2^53,经 JSON 传给 JavaScript 会丢精度,需按字符串传输;
多实例部署需各自 CommonKit.configure(datacenter_id, machine_id)(默认 (1,1) 会重复)。
# SQLite(默认精确:distance 为真值 L2)
from kit.db.sqlite_kit import SQLiteKit
SQLiteKit.create_vector_table("images", 384) # dims 必填
SQLiteKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5)
rows = SQLiteKit.search_vectors("images", [0.1] * 384, top_k=5, exact=False) # 旧量化口径(不提速)
# PostgreSQL(签名相同;可选 COSINE/L2/L1/DOT/SQUARED_L2)
from kit.db.pg_kit import PgKit
PgKit.create_vector_table("images", 384)
PgKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5, distance_type="COSINE")
# 大表 + 自建索引时才用 exact=False(否则索引不被使用,见上表)
PgKit.execute("CREATE INDEX ON images USING hnsw (embedding vector_cosine_ops)")
rows = PgKit.search_vectors("images", [0.1] * 384, top_k=5,
distance_type="COSINE", exact=False)
# MySQL(仅存储;search_vectors 抛 VectorNotSupportedError)
from kit.db.mysql_kit import MySqlKit
MySqlKit.create_vector_table("images", 384)
MySqlKit.insert_vectors("images", [[0.1] * 384, [0.2] * 384])
# 能力自省(无需 try/except)
PgKit.VECTOR_CAPABILITIES
# {'store': True, 'search': True, 'exact': True,
# 'distances': ('COSINE', 'L2', 'L1', 'DOT', 'SQUARED_L2'),
# 'distance_kind': 'true', 'fast_distance_kind': 'true'}
# 自建 DDL 的表:用 id_col / column 接入
PgKit.execute("CREATE TABLE my_vec (vid BIGINT PRIMARY KEY, vec vector(384))")
PgKit.insert_vectors("my_vec", [[0.1] * 384], ids=[1], id_col="vid", column="vec")
PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
0.2.2 行为变更(升级须知)
| 编号 | 变更 | 影响 |
|---|---|---|
| B1 | search_vectors 新增 exact: bool = True,默认精确 |
SQLite 返回的 distance 由量化值(≈L2×255)变为真值 L2;需旧口径请显式 exact=False |
| B2 | filter_sql 语义统一为先过滤、再取 top_k |
传 filter_sql 的调用返回值变多/变全(旧结果 ⊆ 新结果);曾自行放大 top_k 兜底的调用可收紧 |
| B3 | 默认 distance_type 由 "COSINE" 改为 "L2" |
依赖默认值的调用方请显式传 distance_type="COSINE" |
| B4 | create_vector_table 的 dims 必填,参数顺序为 (table_name, dims, id_col, column, extra_cols) |
create_vector_table("t") 类调用需补维度;位置传 id_col 的调用改为关键字 |
| B5 | MySQL 的 search_vectors 异常由 NotImplementedError 改为 VectorNotSupportedError(继承 DBError) |
except NotImplementedError 需改为 except VectorNotSupportedError(或 except DBError) |
新增(非破坏性):
VECTOR_CAPABILITIES能力声明、id_col/column逃生口参数、insert_vectors支持二维 numpy 数组与批量提交。
0.2.4 变更
| 编号 | 变更 | 影响 |
|---|---|---|
| B4 | 入参自洽性校验(extra_data 的键):保留绑定名 id / vec、与 id_col / column 重复、不是合法标识符、id_col == column → 抛可操作的 ValueError |
唯一行为变更:此前 SQLite 会静默写坏数据(如 id 列被覆盖)、PG/MySQL 报误导性错误;正常调用零影响 |
| — | 新增共享方法 build_insert_vectors(只拼装不执行,由 BaseDbKit 收敛 → 共享方法 23 → 24) |
纯新增:可在自有事务内写向量行(见下) |
| — | create_vector_table 的 extra_cols 支持 {列名: 类型串}(与 create_raw_table 同形) |
纯新增:既有 [{"name","type"}] 形态保留,两者产出 DDL 逐字一致 |
| — | 后端差异收口:向量列占位符由 VECTOR_PLACEHOLDER 驱动(MySQL = STRING_TO_VECTOR(:vec)) |
内部整理:公开签名与行为不变 |
| — | SQLiteKit.insert_vectors 的事务获取统一为 transaction()(与另两库同构) |
内部一致:行为等价(自成一事务并提交) |
0.2.3 变更
| 编号 | 变更 | 影响 |
|---|---|---|
| B6 | PgKit.get_table_info 按数据库解析语义定位表(search_path 解析到的那张表) |
多 schema 下同名表不再混行;单 schema / public 场景无影响;只接受非限定表名 |
| — | query / execute 支持列表参数(IN :ids,三库通用) |
纯新增;set/frozenset 自动归一化为 list |
| — | 新增 SQLiteKit.encode_vector / decode_vector(小端 float32) |
纯新增:可在自有事务内写向量表,见下 |
| — | 导出面统一:from kit.db import PgKit, MySqlKit, SQLiteKit, build_pg_url, … |
纯新增;既有导出未删除/未改名 |
两段查询范式(推荐用法):search_vectors 只返回 [{"id", "distance"}],载荷二次取回:
hits = SQLiteKit.search_vectors("item_vectors", query_vector, top_k=5)
rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
自有事务内写向量(推荐:build_insert_vectors + transaction()):
from sqlalchemy import text
with SQLiteKit.transaction() as session: # 换成 PgKit / MySqlKit 写法相同
session.execute(text("INSERT INTO docs_item (id, title) VALUES (:id, :t)"),
{"id": doc_id, "t": title})
for sql, params in SQLiteKit.build_insert_vectors("docs_doc_vectors", vectors,
ids=row_ids, extra_data=metas,
id_col="rowid"):
session.execute(text(sql), params)
# 正常退出统一提交;异常自动回滚(多处写入保持同一事务)
SQLite 的 DBAPI 变体(raw_connection() 支持 :name 命名参数):
conn = SQLiteKit.raw_connection() # 调用方负责 commit / close
try:
for sql, params in SQLiteKit.build_insert_vectors("item_vectors", vectors,
ids=row_ids, extra_data=metas):
conn.executemany(sql, params)
conn.commit()
finally:
conn.close()
⚠ 两点约束:①
build_insert_vectors输出为:name风格,PG / MySQL 的原生 DBAPI 不支持 (psycopg2 / pymysql 需%(name)s),只能经transaction()的 session 执行; ② SQLite 上持有未提交写事务时再调 kit 的自提交方法(如insert_vectors)会因另一个连接 拿不到写锁而失败(OperationalError: database is locked,约 6.4s 后才报错)—— 把写入并入同一个transaction()(如上)即可避免。
相似度换算建议:SQLite 仅支持 L2,若向量入库前已归一化,则
cos = 1 − L2²/2; PG 可直接用distance_type="COSINE"。
3. CRUDBase(配合 session_scope,不自动提交)
from kit.db import CRUDBase, session_scope
user_crud = CRUDBase(User)
with session_scope() as session: # 正常退出提交、异常回滚
user_crud.create(session, {"name": "张三"})
user_crud.update(session, user_crud.get(session, 1), {"name": "李四"})
| 方法 | 说明 |
|---|---|
CRUDBase(model) |
构造,传入 SQLModel 模型类 |
get(session, id) -> Model | None |
按主键取单条 |
get_or_raise(session, id) -> Model |
按主键取,缺失抛 NotFoundError |
get_multi(session, *, skip=0, limit=100, order_by=None, desc=False) -> list[Model] |
分页 + 排序 |
get_by_field(session, field_name, field_value) -> Model | None |
按字段等值取单条 |
get_multi_by_field(session, field_name, field_value, *, skip=0, limit=100) -> list[Model] |
按字段等值分页取多条 |
create(session, obj_in) -> Model |
插入单条(add + flush) |
create_multi(session, objs_in) -> list[Model] |
批量插入(add_all + flush) |
update(session, db_obj, obj_in) -> Model |
更新,入参为模型实例或字典 |
update_multi(session, ids, obj_in) -> int |
单条批量 UPDATE,返回影响行数 |
delete(session, id) -> Model | None |
按主键删除 |
delete_multi(session, ids) -> int |
单条批量 DELETE,返回影响行数 |
count(session) -> int |
记录总数 |
exists(session, id) -> bool |
主键是否存在 |
upsert(session, obj_in, conflict_fields) -> Model |
原生 INSERT ... ON CONFLICT / ON DUPLICATE KEY 原子语句 |
约定:
SQLiteKit / PgKit / MySqlKit方法自动提交;CRUDBase方法不提交,事务边界由外层session_scope或@transactional决定。
4. 引擎 / 会话 / 事务 / 异常
| 符号 | 签名 | 说明 |
|---|---|---|
get_engine |
(db_type=None, **kwargs) -> Engine |
线程安全引擎单例,**kwargs 首次创建时透传 create_engine |
create_tables |
(models, db_type=None) -> None |
按模型列表建表 |
list_tables |
(db_type=None) -> list[str] |
列出全部表 |
table_exists |
(table_name, db_type=None) -> bool |
判断表是否存在 |
close_engine |
(db_type) -> None |
关闭并移除指定类型引擎 |
close_all_engines |
() -> None |
关闭全部引擎 |
session_scope |
(db_type=None, *, autocommit=True) |
唯一会话入口:正常提交、异常回滚、最终关闭 |
get_session |
(db_type=None) -> Session |
裸 Session,调用方自管生命周期 |
transactional |
(db_type=None, *, retry_count=0, retry_delay=0.5) |
事务装饰器:自动注入 session 参数、失败退避重试 |
POSTGRESQL / MYSQL / SQLITE |
"postgresql" / "mysql" / "sqlite" |
数据库类型常量 |
DBError |
继承 SQLAlchemyError |
数据库异常基类 |
NotFoundError |
继承 DBError |
记录不存在 |
from kit.db import transactional
from sqlmodel import Session
@transactional(retry_count=3)
def transfer(session: Session, from_id: int, to_id: int, amount: float) -> None:
# 被装饰函数必须接受 session 参数;退出时自动提交,异常自动回滚并重试
...
5. 模型混入类与查询构建器
from sqlalchemy import BigInteger
from sqlmodel import Field, SQLModel
from kit.common_kit import CommonKit
from kit.db.models import TimestampMixin, SoftDeleteMixin, AuditMixin
class Article(TimestampMixin, SoftDeleteMixin, SQLModel, table=True):
# 主键:64 位整数 + 雪花 ID(应用侧生成,不用数据库自增)
id: int = Field(default_factory=CommonKit.generate_snowflake_id,
primary_key=True, sa_type=BigInteger)
title: str
| 混入类 | 字段 |
|---|---|
TimestampMixin |
created_at(UTC)、updated_at |
SoftDeleteMixin |
is_deleted、deleted_at |
AuditMixin |
时间戳字段 + created_by、updated_by |
查询构建器需显式从子模块导入(未进入 kit.db 顶层导出):
from kit.db.query import QueryBuilder, QueryCache, QueryOptimizer, cached_query
qb = QueryBuilder(User, session)
active = qb.filter(User.name == "张三").all() # 链式不可变,每步返回新实例
page = qb.paginate(page=1, per_page=20) # {"items", "total", ...}
| 类 | 方法 |
|---|---|
QueryBuilder(model, session) |
filter(*conditions)、filter_by(**kwargs)、join(target, onclause=None, isouter=False)、order_by(*columns)、limit(n)、offset(n)、distinct()、only(*field_names)、all()、first()、count()、exists()、paginate(page=1, per_page=20) |
QueryCache(default_ttl=300, maxsize=1000) |
get(key)、set(key, value, ttl=None)、delete(key)、clear()、clean_expired() |
QueryOptimizer(session) |
explain(statement)、suggest_indexes(model, query_fields)、measure(statement, params=None)、stats、clear_stats() |
cached_query(ttl=300, maxsize=1000) |
查询结果缓存装饰器 |
kit.ai —— AI 多模型服务池
安装:pip install "sdpy-kit[ai]"
档案:项目根 config/ai_models.json(见文末「AI / MCP 档案文件」)
1. 同步门面 AI(推荐)
from kit.ai.runner import AI, shutdown
text = AI.chat_complete("用一句话介绍 Python") # 单轮对话
hits = AI.rerank("水声通信", ["文档A", "文档B"], top_n=2) # 重排序
url = AI.generate_image("一只在写代码的猫") # 文生图
AI.use("chat", "glm") # 切换当前档案(进程内长期生效)
print(AI.available("chat")) # 查看各档案状态快照
shutdown() # 应用退出前释放资源(幂等)
| 分组 | 方法 | 说明 |
|---|---|---|
| chat | chat_complete(prompt, *, max_tokens=None) -> str |
单轮对话(仅 user 消息) |
| chat | chat_ask(messages, *, max_tokens=None) -> str |
多轮对话,messages 为 [{"role": "user", "content": "..."}] |
| chat | chat_stream(messages, *, max_tokens=None) -> Iterator[str] |
同步流式逐 token 迭代器(建议 with 使用) |
| embedding | embed_text(text) -> list[float] |
单文本向量化 |
| embedding | embed_texts(texts) -> list[list[float]] |
批量向量化 |
| rerank | rerank(query, documents, top_n=None) -> list[tuple[int, float]] |
返回 [(原索引, 相关分)] 降序列表 |
| image | generate_image(prompt, *, size=None, n=1) -> str |
文生图,返回图片 URL |
| 管理 | use(model_type, profile) -> None |
切换该类型当前档案(chat / embedding / rerank / image) |
| 管理 | available(model_type) -> list[ProfileStatus] |
各档案状态快照(含未配置与被剔除者) |
| 管理 | reload_config(*, model_type=None, reload_file=False, probe=True, immediate=False) -> ReloadResult |
配置热重载,改密钥 / 档案后无需重启 |
from kit.ai.runner import AI
with AI.chat_stream([{"role": "user", "content": "写一首短诗"}]) as stream:
for token in stream: # 逐 token 消费
print(token, end="", flush=True)
门面仅面向同步上下文;若调用点本身在 async 函数内,请直接
await原生异步服务,避免阻塞事件循环线程。 模块级另有kit.ai.runner.reload_config(...)、run_sync(coro_factory, *, timeout=120.0)、shutdown()。
2. 原生异步服务
from kit.ai import (
ChatService, EmbeddingService, RerankService, ImageService, # 服务类
ChatRequest, EmbeddingRequest, RerankRequest, ImageRequest, # 请求模型
RerankHit, ModelBuilder, # 结果类型 / 构建器
)
service = ModelBuilder.build_chat() # 老按类型构建:build_chat / build_embedding / build_rerank / build_image
answer = await service.ask(messages) # 或 await service.complete(prompt)
await service.close()
| 类 | 方法 |
|---|---|
ChatService |
ask(messages, *, temperature=None, max_tokens=None, deadline=None) -> str、complete(prompt, *, ...) -> str、stream(messages, *, ...) -> AsyncIterator[str] |
EmbeddingService |
embed_text(text) -> list[float]、embed_texts(texts, *, batch_size=None, concurrency=4) -> list[list[float]]、dimension 属性 |
RerankService |
rerank(query, documents, *, top_n=None) -> list[RerankHit](已按 score 降序) |
ImageService |
generate(prompt, *, size=None, n=1, return_base64=False, ratio=None) -> str |
ModelBuilder |
build(model_type, model=None, *, api_key=None) 及各类型专用 build_chat / build_embedding / build_rerank / build_image |
BaseAIService |
profile 属性、await close()、支持 async with |
3. 异常
from kit.ai.exceptions import (
AIError, AIConfigError, AIRequestError, AITimeoutError,
AIAggregateError, AIResponseError,
)
| 异常 | 触发场景 / 可用属性 |
|---|---|
AIError |
基类 |
AIConfigError |
档案缺失或非法、档案不存在、密钥未配置、必填字段缺失 |
AIRequestError |
单次请求失败;status_code、retry_after(429 时解析 Retry-After) |
AITimeoutError |
请求超时(继承 AIRequestError) |
AIAggregateError |
全部候选失败或整链预算耗尽;tried、statuses、last_error、budget_exhausted |
AIResponseError |
响应结构解析失败、流式数据无法解析 |
kit.mcp_kit —— 通用 MCP 客户端
安装:pip install "sdpy-kit[mcp]"
档案:项目根 config/mcp_servers.json;token 值放 .env(档案里只写环境变量名)
from kit.mcp_kit import McpKit
tools = McpKit.list_tools("aminer") # [{name, description}]
result = McpKit.call_tool("search_paper_by_title", {"title": "水声通信", "size": 10})
print(result.data, result.elapsed_ms)
# 直连临时服务器(不经档案)
r = McpKit.call_tool_url("https://example.com/mcp", "query", {}, token="...")
# 异步环境 / 一次握手复用多次调用
async with McpKit.session("aminer") as call:
r1 = await call("search_paper_by_title", {"title": "A"})
r2 = await call("get_papers_info", {"id": "1"})
| 方法 | 说明 |
|---|---|
list_tools(server=None) -> list[dict] |
工具发现;server=None 用档案 default_server |
call_tool(name, arguments, *, server=None) -> McpToolResult |
同步短连接调用(主路径) |
call_tool_async(name, arguments, *, server=None) -> McpToolResult |
异步版;FastAPI 等已有事件循环的进程内必须用 |
session(server=None) |
async with 长连接复用,产出 call(name, arguments) 协程函数 |
call_tool_url(url, name, arguments, *, token=None, transport="streamable-http", timeout=None) -> McpToolResult |
直连临时服务器,不经档案 |
| 类型 | 说明 |
|---|---|
McpToolResult(server, tool, elapsed_ms, data) |
调用结果;data 为 JSON 解析后对象,非 JSON 文本降级为字符串 |
McpKitError(message, *, detail=None) |
业务失败时 detail 为服务端原始响应;连接失败时为最后一次传输层错误 |
传输方式仅
streamable-http(默认)与sse;档案配fallback_url时主端点传输层失败会自动回退(业务失败不回退)。
kit.redis_kit —— Redis 工具
安装:pip install "sdpy-kit[redis]"
from kit.redis_kit import RedisKit
RedisKit.set("user:1", "clark", expire=60)
RedisKit.get("user:1") # -> "clark"
RedisKit.zadd("rank", {"alice": 1.0, "bob": 2.0})
RedisKit.zrange("rank", 0, -1, withscores=True)
RedisKit.close()
| 分组 | 方法 |
|---|---|
| 通用 | get_client()(上下文管理器)、set(key, value, expire=None) -> bool、get(key) -> RedisValue | None、delete(key) -> bool、exists(key) -> bool、keys(pattern) -> list[str]、close() -> None |
| 批量 | batch_set(key_value_pairs, expire=None) -> bool、batch_get(keys) -> dict、batch_delete(keys) -> bool |
| Hash | hset(key, field, value) -> bool、hget(key, field)、hgetall(key) -> dict、hdel(key, *fields) -> bool |
| List | lpush(key, *values) -> bool、rpush(key, *values) -> bool、lpop(key)、rpop(key)、lrange(key, start, stop) -> list |
| Set | sadd(key, *members) -> bool、srem(key, *members) -> bool、smembers(key) -> list、sismember(key, member) -> bool |
| Sorted Set | zadd(key, mapping) -> bool、zrange(key, start, stop, withscores=False) |
RedisValue = str | int | float | bytes;连接池decode_responses=True,内置指数退避重试。
kit.file_kit —— 文件工具
相对路径均相对于 FILE_STORAGE_ROOT(默认 ./data)。
from kit.file_kit import FileKit
FileKit.write_file("docs/a.txt", "hello") # mode="overwrite"(默认)/ "append"
text = FileKit.read_file("docs/a.txt")
path = FileKit.get_full_path("docs/a.txt") # -> Path 绝对路径
for root, dirs, files in FileKit.walk_dir("docs"):
print(root, files)
FileKit.open_with_default_app(path) # 系统默认程序打开:-> "opened" / "revealed"
| 分组 | 方法 |
|---|---|
| 配置 | configure(root_path) -> None、get_root_path() -> str、get_full_path(relative_path) -> Path |
| 文件 | read_file(relative_path, encoding="utf-8") -> str、write_file(relative_path, content, mode="overwrite", encoding="utf-8") -> bool、move_file(src, dest) -> bool、copy_file(src, dest) -> bool、delete_file(relative_path) -> bool、file_exists(relative_path) -> bool |
| 目录 | move_dir(src, dest) -> bool、copy_dir(src, dest) -> bool、delete_dir(relative_path) -> bool、walk_dir(relative_path="", top_down=True, on_error=None) |
| 命名 | generate_unique_id() -> str、generate_filename(prefix="", extension="") -> str |
| 系统 | open_with_default_app(path) -> OpenResult("opened" / "revealed",失败降级为文件管理器定位) |
kit.common_kit —— 通用工具
from kit.common_kit import CommonKit
CommonKit.configure(datacenter_id=1, machine_id=1) # 各限 1–31
CommonKit.generate_snowflake_id() # 63 位雪花 ID(int)
CommonKit.generate_snow_id() # 雪花 ID(str)
CommonKit.generate_timestamp_id() # 20 位时间戳 ID
CommonKit.format_current_time("yyyy-MM-dd")
| 方法 | 说明 |
|---|---|
configure(datacenter_id, machine_id) -> None |
配置雪花算法数据中心 / 机器 ID(越界抛 ValueError) |
generate_snowflake_id(datacenter_id=None, machine_id=None) -> int |
生成雪花 ID |
generate_snow_id(datacenter_id=None, machine_id=None) -> str |
生成雪花 ID 字符串 |
generate_timestamp_id() -> str |
YYYYMMDDHHMMSSfff + 3 位随机 |
format_current_time(format_pattern="yyyyMMdd") -> str |
支持 yyyyMMdd、yyyyMMddhhmm、yyyyMMddhhmmss、yyyy-MM-dd、yyyy-MM-dd hh:mm、yyyy-MM-dd hh:mm:ss |
reset() -> None |
重置 ID 生成器状态 |
kit.anydoc_kit —— 文档转 Markdown
安装:pip install "sdpy-kit[anydoc]"(支持 .doc .docx .ppt .pptx .xls .xlsx .csv .pdf)
from kit.anydoc_kit import AnyDocKit
markdown = AnyDocKit.convert_to_md("report.docx") # 直接转换,失败抛异常
result = AnyDocKit.convert("report.pdf") # 安全转换,异常封装为结果对象
if result.success:
print(result.markdown)
AnyDocKit.convert_to_file("report.docx", "out/report.md")
md = AnyDocKit.convert_bytes_to_md(file_bytes, "csv") # 字节内容直接转,无需落盘
md = AnyDocKit.convert_to_md("scan.pdf", ocr="hosted") # 需 FIRECRAWL_API_KEY
| 方法 | 说明 |
|---|---|
is_supported(file_path) -> bool |
按扩展名判断是否受支持 |
detect_format(file_path) -> str | None |
由扩展名推断规范格式名 |
convert_to_md(file_path, *, ocr="reject", api_key=None, api_url=None) -> str |
文件 → Markdown(非标 PDF 自动修复重试) |
convert_bytes_to_md(data, fmt=None, *, ocr="reject", api_key=None, api_url=None) -> str |
字节内容 → Markdown |
convert(file_path, *, ocr="reject", api_key=None, api_url=None) -> ConvertResult |
安全转换 |
convert_to_file(src_path, dest_path=None, *, ocr="reject", ...) -> str |
转换并写入 .md,返回目标路径 |
ConvertResult(success, markdown, file_path, source_format, error)
kit.docx_kit —— Word 模板动态填充
安装:pip install "sdpy-kit[docx]"
from kit.docx_kit import DocxKit, DocxReport, DocxSection, DocxParagraph, DocxTable
DocxKit.create_template("templates/report.docx", title="{{ title }}") # 生成模板骨架
DocxKit.unify_font("templates/report.docx", font_name="宋体")
DocxKit.unify_heading_color("templates/report.docx")
report = DocxReport(
sections=(
DocxSection(
title="第一章 概述",
level=1,
blocks=(
DocxParagraph("本章介绍项目背景。"),
DocxTable(headers=("名称", "数值"), rows=(("甲", "1"),)),
),
),
),
variables={"project": "SDPY"},
)
DocxKit.render("templates/report.docx", "out/report.docx", report)
| 方法 | 说明 |
|---|---|
create_template(dest_path, *, title=None, leading_paragraphs=(), anchor_style=None) -> str |
生成标准模板骨架(含章节循环锚点) |
unify_font(docx_path, *, font_name="宋体") -> str |
统一文档默认与全部样式的字体(原地,幂等) |
unify_heading_color(docx_path, *, color="000000") -> str |
统一 Heading 1..9 字体颜色 |
inspect_template(template_path, *, context=None) -> tuple[str, ...] |
列出模板中未被上下文满足的变量 |
render(template_path, dest_path, report, *, anchor_style=None, strict=True) -> str |
按结构化数据渲染模板并落盘(核心方法) |
数据结构:DocxReport(sections, variables)、DocxSection(title, level=1, blocks=())、DocxParagraph(text)、DocxListItem(text, level=1, ordered=False)、DocxTable(headers, rows=(), style=None)
kit.md2doc_kit —— Markdown 转 Word / PDF / HTML
安装:pip install "sdpy-kit[md2doc]"(PDF 需系统已安装 mainfont 指定字体,默认 SimSun)
from kit.md2doc_kit import Md2DocKit
Md2DocKit.convert_md_to_docx("report.md", "out.docx", generate_toc=True)
Md2DocKit.convert_md_to_html("report.md", title="项目报告")
Md2DocKit.convert_md_to_pdf("report.md", mainfont="KaiTi")
| 方法 | 说明 |
|---|---|
convert_md_to_docx(src_path, dest_path=None, *, generate_toc=False, mainfont="宋体", reference_doc=None) -> str |
Markdown → Word(可插目录、统一字体) |
convert_md_to_html(src_path, dest_path=None, *, title=None) -> str |
Markdown → 独立 HTML5 文件 |
convert_md_to_pdf(src_path, dest_path=None, *, mainfont="SimSun") -> str |
Markdown → PDF(实验性,内部使用) |
dest_path=None时输出到源文件同目录;同一源文件不支持并发转换。
kit.mineru_kit —— MinerU 文档解析
安装:pip install "sdpy-kit[mineru]"(精准解析需 MINERU_API_KEY,Agent 轻量解析免登录)
from kit.mineru_kit import MinerUKit
# 精准解析:公网 URL / 本地文件
result = MinerUKit.parse_url("https://cdn-mineru.openxlab.org.cn/demo/example.pdf", model_version="vlm")
if result.state == "done":
zip_path = MinerUKit.download_zip(result.full_zip_url, "./output")
results = MinerUKit.parse_files(["report1.pdf", "report2.docx"])
# Agent 轻量解析(免登录、单文件 ≤10MB、≤20 页)
r = MinerUKit.agent_parse_url(url, page_range="1-10")
if r.state == "done":
md_path = MinerUKit.download_markdown(r.markdown_url, "./output")
| 分组 | 方法 |
|---|---|
| 精准解析(高层) | parse_url(url, **opts) -> TaskResult、parse_files(file_paths, **opts) -> list[BatchTaskResult]、wait_for_task(task_id, *, poll_interval=None, timeout=None)、wait_for_batch(batch_id, *, expected_count=None, ...)、download_zip(zip_url, dest_dir) -> str |
| 精准解析(低层) | create_task(url, **opts) -> str、get_task_result(task_id) -> TaskResult、create_batch(files, **opts) -> BatchUploadInfo、upload_files(file_paths, upload_urls) -> list[bool]、get_batch_results(batch_id) -> list[BatchTaskResult] |
| Agent 轻量 | agent_parse_url(url, **opts) -> AgentParseResult、agent_parse_file(file_path, **opts) -> AgentParseResult、agent_create_url_task(url, **opts) -> str、agent_create_file_task(file_name, **opts) -> AgentUploadInfo、agent_upload_file(file_path, file_url) -> None、agent_get_result(task_id)、agent_wait_for_result(task_id, *, poll_interval=None, timeout=None)、download_markdown(markdown_url, dest_dir, filename=None) -> str |
| 校验 | is_supported(file_path) -> bool、is_agent_supported(file_path) -> bool |
可选参数(
model_version/is_ocr/enable_formula/enable_table/language/page_ranges/extra_formats/no_cache等)详见各方法 docstring。 结果类型:TaskResult、BatchTaskResult、BatchFile、BatchUploadInfo、AgentParseResult、AgentUploadInfo。
kit.marker_kit —— 本地文档解析(Markdown + Images)
安装:pip install "sdpy-kit[marker]"(依赖 marker-pdf[full],含 torch 等 80+ 包,体积较大)
from kit.marker_kit import MarkerKit
# 落盘 md + images(推荐):源文件同目录下建同名子目录
md_path = MarkerKit.convert_to_dir("report.pdf") # -> report/report.md + 图片
md_path = MarkerKit.convert_to_dir("report.pdf", "out/doc") # 指定产物目录
# 只取 Markdown 文本(图片不落盘)
md = MarkerKit.convert_to_md("report.pdf")
# 安全转换(不抛异常,返回结果对象)
result = MarkerKit.convert("report.pdf", "out/doc")
if result.success:
print(result.md_path, len(result.image_paths), result.page_count)
# 跳过 VLM(无需 llama.cpp)
md = MarkerKit.convert_to_md("scan.pdf", disable_ocr=True)
MarkerKit.shutdown() # 释放模型与推理服务(幂等)
| 方法 | 说明 |
|---|---|
is_supported(file_path) -> bool |
扩展名白名单(pdf / 图片 / docx / pptx / xlsx / html / epub) |
convert_to_md(file_path, *, mode=None, force_ocr=None, disable_ocr=None, page_range=None) -> str |
解析并返回 Markdown 文本(图片不落盘) |
convert_to_dir(src_path, output_dir=None, *, mode=None, force_ocr=None, disable_ocr=None, page_range=None, extract_images=True) -> str |
解析并落盘 md + images,返回 md 绝对路径 |
convert(src_path, output_dir=None, *, ...) -> MarkerResult |
安全版:失败封装为 MarkerResult,不抛异常 |
shutdown() -> None |
释放模型与共享推理服务(幂等) |
产物:
<output_dir>/<源文件名>.md+ 图片文件(md 内相对路径引用),不生成_meta.json。 结果类型:MarkerResult(success/markdown/file_path/output_dir/md_path/image_paths/page_count/error);异常:MarkerError。mode缺省时不写入配置,交由 marker 按设备自动选择;首次使用会下载模型权重(默认走MARKER_HF_ENDPOINT镜像)。 非 NVIDIA 平台完整 VLM 能力(公式 / 扫描页 OCR)需 llama.cpp 的llama-server,否则用disable_ocr=True跳过。
kit.zvec_kit —— 本地向量库
安装:pip install "sdpy-kit[zvec]"
from kit.zvec_kit import ZvecKit
schema = ZvecKit.make_text_embedding_schema("docs", dim=768)
ZvecKit.create_collection("docs", schema)
ZvecKit.insert("docs", {"id": "d1", "vectors": {"embedding": [0.1] * 768}, "fields": {"year": 2024}})
results = ZvecKit.vector_query("docs", field="embedding", vector=[0.3] * 768, topk=10)
ZvecKit.flush("docs")
| 分组 | 方法 |
|---|---|
| 连接 | connect() -> bool、close() -> None、is_connected() -> bool |
| Collection | create_collection(name_or_path, schema) -> Collection、open_collection(name_or_path)、collection_exists(name_or_path) -> bool、list_collections() -> list[str]、destroy_collection(name_or_path) -> bool、get_schema(name_or_path)、get_stats(name_or_path) -> dict |
| 文档 CRUD | insert(name_or_path, data) -> str、insert_multi(name_or_path, data_list) -> list[str]、upsert(name_or_path, data) -> str、update(name_or_path, data) -> str、fetch(name_or_path, ids, *, include_vector=True) -> dict、delete(name_or_path, ids) -> bool、delete_by_filter(name_or_path, filter_expr) -> bool、count(name_or_path) -> int、flush(name_or_path) -> None |
| 检索 / 维护 | vector_query(name_or_path, *, field, vector, topk=10, filter_expr=None, include_vector=False) -> list[dict]、optimize(name_or_path) -> None |
| Schema | make_text_embedding_schema(name, dim, metric=None, extra_fields=None) -> CollectionSchema |
文档字典键:
id(必需)、vectors(可选)、fields(可选);filter_expr中相等用单等号=。 异常:ZvecError(独立于kit.db的DBError体系)。
kit.wx_article_kit —— 微信公众号文章抓取存储
零第三方依赖(标准库 urllib + 内置 HTML→Markdown 转换器),无需 extra,按需 import:
from kit.wx_article_kit import WxArticleKit
# 抓取解析(不落盘)
result = WxArticleKit.fetch("https://mp.weixin.qq.com/s/xxxx")
if result.ok:
print(result.info.title, result.info.author, result.info.publish_time)
# 抓取并存储:<output_dir>/<file_name>/ 下 <file_name>.md + images/
saved = WxArticleKit.save(url, output_dir="./output", file_name="12345")
print(saved.md_path, saved.images_saved)
# 需要结构化元信息时再产出 meta.json(默认不产出)
WxArticleKit.save(url, "./output", "12345", write_meta=True)
| 方法 | 说明 |
|---|---|
fetch(url, timeout=20.0) -> ArticleResult |
抓取并解析正文(不落盘);结果对象字段 ok / error / info(ArticleInfo)/ markdown |
save(url, output_dir, file_name, write_meta=False, timeout=20.0) -> SavedOutput |
抓取并存储到 <output_dir>/<file_name>/;结果对象字段 ok / error / output_dir / md_path / images_dir / title / images_saved |
落盘结构:
<output_dir>/<file_name>/
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
└── meta.json # 可选:结构化元信息(write_meta=True 时产出)
- 元信息默认随 Markdown 的 YAML 头落盘(头部
publish_time为中文日期,如2026年8月4日);ArticleInfo.publish_time与meta.json内仍为 ISO 8601(结构化数据)。 - 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回
ok=False+ 原因,不破解验证码; 抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。 - 同名子目录覆盖写(不追加序号);
file_name带.md后缀会自动去除,为空则抛ValueError。
AI / MCP 档案文件
AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
config/ai_models.json—— chat / embedding / rerank / image 四类型多厂商模型档案config/mcp_servers.json—— MCP 服务器档案
文件缺失时,首次使用相应功能会报错,报错信息中附带可直接复制的最小模板;
其中 api_key / token_env 字段填写环境变量名,真实密钥配置在 .env。
ai_models.json 可选字段
| 层级 | 字段 | 说明 |
|---|---|---|
| 档案级 | timeout |
请求超时秒数(默认 60) |
| 档案级 | max_tokens |
输出上限(chat 用);AI.chat_ask/chat_complete/chat_stream 也可按调用点传参,逐级回落:调用点 → 档案 → provider 默认 |
| 档案级 | retries |
5xx 重试次数(默认 2;超时不重试,候选链切换才是有效重试) |
| 类型级 | chain_timeout |
整链预算秒数(与 default_profile 同级);门面同步等待上限。未配置时按 sum(候选 timeout) + 10s 自适应,也可用环境变量 KIT_AI_CHAIN_TIMEOUT 全局覆盖 |
候选链切换语义(两类,勿混淆)
- 跨调用切换:
AI.use()与其它请求产生的失败冷却均只影响下一次请求——每次调用的候选计划在开始时定格; - 一次调用内降级:本次调用按开始时定格的计划串行尝试(首帧前失败 / 首帧超时切下一候选),中途的
use/ 冷却变化不影响它。
全部候选失败(或整链预算耗尽)抛 AIAggregateError(AIRequestError 子类):
全候选状态码一致时透传(如整链 429 可用 exc.status_code == 429 判定),
429 的 Retry-After 解析后挂于 exc.retry_after(冷却钳制 5–300s)。
mcp_servers.json 最小模板
{
"default_server": "aminer",
"servers": {
"aminer": {
"transport": "streamable-http",
"url": "https://mcp.aminer.cn/mcp",
"token_env": "AMINER_TOKEN",
"timeout": 60
}
}
}
License
MIT
Metadata
Release files for sdpy-kit 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sdpy_kit-0.2.4.tar.gz | 195.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sdpy_kit-0.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 412.8 kB
Release files / sdpy_kit-0.2.4.tar.gz
| Download URL | sdpy_kit-0.2.4.tar.gz |
|---|---|
| Size | 195.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
231b6412fe19a1b6f081d8d2642ef34427d50f715f7aed4c880c37fb9ac4a6b2
|
|
BLAKE2b-256 checksum How to use checksums |
61892d1b88f79c74d83fd8117dcea6790c3688db70e501d39bf595c6dda2babe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / sdpy_kit-0.2.4-py3-none-any.whl
| Download URL | sdpy_kit-0.2.4-py3-none-any.whl |
|---|---|
| Size | 217.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51edc4d26ef10e7cd77f16da9558c0f35ba2c9ffaa081c85e4271573073d63ea
|
|
BLAKE2b-256 checksum How to use checksums |
72c3cbcfe272e476fa29ba2e5263e41f05a6e004ad71512f51d0641e66029b5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|