Skip to main content

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_CAPABILITIES feature-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)

Source distribution for sdpy-kit 0.2.4
File Size Uploaded
sdpy_kit-0.2.4.tar.gz 195.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sdpy-kit 0.2.4
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.2.4 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page