sdpy-kit 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/PKG-INFO +41 -8
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/README.md +40 -7
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/__init__.py +1 -1
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/base_db_kit.py +170 -4
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/mysql_kit.py +31 -63
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/pg_kit.py +27 -55
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/sqlite_kit.py +33 -55
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/.gitignore +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/ai/types.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/common_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/crud.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/engine.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/query.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/session.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/file_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/wx_article_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/kit/zvec_kit.py +0 -0
- {sdpy_kit-0.2.3 → sdpy_kit-0.2.4}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -336,7 +336,7 @@ from kit.db import session_scope, transactional, DBError # 推荐
|
|
|
336
336
|
from kit import session_scope, transactional, DBError # 等价
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
-
### 1. 三个数据库 Kit(
|
|
339
|
+
### 1. 三个数据库 Kit(24 个共享方法 API 完全一致)
|
|
340
340
|
|
|
341
341
|
| 类 | 导入 | 数据库 | extra |
|
|
342
342
|
|----|------|--------|-------|
|
|
@@ -424,8 +424,9 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
424
424
|
|
|
425
425
|
| 方法 | 说明 |
|
|
426
426
|
|------|------|
|
|
427
|
-
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL
|
|
428
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` |
|
|
427
|
+
| `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"}]` 两种形态 |
|
|
428
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(**自成一事务并提交**);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
429
|
+
| `build_insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> list[tuple[str, list[dict]]]` | **只拼装、不执行、不管事务**:返回按列集分组的 `(SQL, 参数列表)`,供调用方在**自有事务**内写向量行(见「自有事务内写向量」) |
|
|
429
430
|
| `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`** |
|
|
430
431
|
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
431
432
|
|
|
@@ -510,6 +511,16 @@ PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
|
510
511
|
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
511
512
|
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
512
513
|
|
|
514
|
+
#### 0.2.4 变更
|
|
515
|
+
|
|
516
|
+
| 编号 | 变更 | 影响 |
|
|
517
|
+
|------|------|------|
|
|
518
|
+
| B4 | **入参自洽性校验**(`extra_data` 的键):保留绑定名 `id` / `vec`、与 `id_col` / `column` 重复、不是合法标识符、`id_col == column` → 抛可操作的 `ValueError` | **唯一行为变更**:此前 SQLite 会**静默写坏数据**(如 `id` 列被覆盖)、PG/MySQL 报误导性错误;正常调用**零影响** |
|
|
519
|
+
| — | 新增共享方法 `build_insert_vectors`(只拼装不执行,由 `BaseDbKit` 收敛 → 共享方法 23 → **24**) | 纯新增:可在**自有事务**内写向量行(见下) |
|
|
520
|
+
| — | `create_vector_table` 的 `extra_cols` 支持 `{列名: 类型串}`(与 `create_raw_table` 同形) | 纯新增:既有 `[{"name","type"}]` 形态保留,两者产出 DDL 逐字一致 |
|
|
521
|
+
| — | 后端差异收口:向量列占位符由 `VECTOR_PLACEHOLDER` 驱动(MySQL = `STRING_TO_VECTOR(:vec)`) | 内部整理:公开签名与行为不变 |
|
|
522
|
+
| — | `SQLiteKit.insert_vectors` 的事务获取统一为 `transaction()`(与另两库同构) | 内部一致:行为等价(自成一事务并提交) |
|
|
523
|
+
|
|
513
524
|
#### 0.2.3 变更
|
|
514
525
|
|
|
515
526
|
| 编号 | 变更 | 影响 |
|
|
@@ -527,18 +538,40 @@ rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
|
527
538
|
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
528
539
|
```
|
|
529
540
|
|
|
530
|
-
|
|
541
|
+
**自有事务内写向量(推荐:`build_insert_vectors` + `transaction()`)**:
|
|
531
542
|
|
|
532
543
|
```python
|
|
533
|
-
|
|
544
|
+
from sqlalchemy import text
|
|
545
|
+
|
|
546
|
+
with SQLiteKit.transaction() as session: # 换成 PgKit / MySqlKit 写法相同
|
|
547
|
+
session.execute(text("INSERT INTO docs_item (id, title) VALUES (:id, :t)"),
|
|
548
|
+
{"id": doc_id, "t": title})
|
|
549
|
+
for sql, params in SQLiteKit.build_insert_vectors("docs_doc_vectors", vectors,
|
|
550
|
+
ids=row_ids, extra_data=metas,
|
|
551
|
+
id_col="rowid"):
|
|
552
|
+
session.execute(text(sql), params)
|
|
553
|
+
# 正常退出统一提交;异常自动回滚(多处写入保持同一事务)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**SQLite 的 DBAPI 变体**(`raw_connection()` 支持 `:name` 命名参数):
|
|
557
|
+
|
|
558
|
+
```python
|
|
559
|
+
conn = SQLiteKit.raw_connection() # 调用方负责 commit / close
|
|
534
560
|
try:
|
|
535
|
-
|
|
536
|
-
|
|
561
|
+
for sql, params in SQLiteKit.build_insert_vectors("item_vectors", vectors,
|
|
562
|
+
ids=row_ids, extra_data=metas):
|
|
563
|
+
conn.executemany(sql, params)
|
|
537
564
|
conn.commit()
|
|
538
565
|
finally:
|
|
539
566
|
conn.close()
|
|
540
567
|
```
|
|
541
568
|
|
|
569
|
+
> ⚠ 两点约束:① `build_insert_vectors` 输出为 `:name` 风格,**PG / MySQL 的原生 DBAPI 不支持**
|
|
570
|
+
> (psycopg2 / pymysql 需 `%(name)s`),只能经 `transaction()` 的 session 执行;
|
|
571
|
+
> ② SQLite 上**持有未提交写事务**时再调 kit 的**自提交**方法(如 `insert_vectors`)会因另一个连接
|
|
572
|
+
> 拿不到写锁而失败(`OperationalError: database is locked`,约 **6.4s** 后才报错)——
|
|
573
|
+
> 把写入并入**同一个** `transaction()`(如上)即可避免。
|
|
574
|
+
|
|
542
575
|
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
543
576
|
> PG 可直接用 `distance_type="COSINE"`。
|
|
544
577
|
|
|
@@ -266,7 +266,7 @@ from kit.db import session_scope, transactional, DBError # 推荐
|
|
|
266
266
|
from kit import session_scope, transactional, DBError # 等价
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
### 1. 三个数据库 Kit(
|
|
269
|
+
### 1. 三个数据库 Kit(24 个共享方法 API 完全一致)
|
|
270
270
|
|
|
271
271
|
| 类 | 导入 | 数据库 | extra |
|
|
272
272
|
|----|------|--------|-------|
|
|
@@ -354,8 +354,9 @@ with SQLiteKit.transaction() as session: # 多操作原子提
|
|
|
354
354
|
|
|
355
355
|
| 方法 | 说明 |
|
|
356
356
|
|------|------|
|
|
357
|
-
| `create_vector_table(table_name, dims, id_col="id", column="embedding", extra_cols=None) -> None` | 建含向量列的表;**`dims` 必填**(SQLite 用 `CHECK` 落实为数据库层约束;PG 硬约束并回读校验已存在表;MySQL
|
|
358
|
-
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` |
|
|
357
|
+
| `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"}]` 两种形态 |
|
|
358
|
+
| `insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> int` | 批量插入向量(**自成一事务并提交**);`ids=None` 时由 Kit 生成雪花 ID;接受 `list[list[float]]` 或二维 numpy 数组 |
|
|
359
|
+
| `build_insert_vectors(table_name, vectors, ids=None, extra_data=None, id_col="id", column="embedding") -> list[tuple[str, list[dict]]]` | **只拼装、不执行、不管事务**:返回按列集分组的 `(SQL, 参数列表)`,供调用方在**自有事务**内写向量行(见「自有事务内写向量」) |
|
|
359
360
|
| `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`** |
|
|
360
361
|
| `vector_version() -> str` | 向量后端版本;不可用时返回 `"unavailable"`(MySQL 侧为服务端版本号) |
|
|
361
362
|
|
|
@@ -440,6 +441,16 @@ PgKit.search_vectors("my_vec", [0.1] * 384, top_k=5, id_col="vid", column="vec")
|
|
|
440
441
|
> 新增(非破坏性):`VECTOR_CAPABILITIES` 能力声明、`id_col` / `column` 逃生口参数、
|
|
441
442
|
> `insert_vectors` 支持二维 numpy 数组与批量提交。
|
|
442
443
|
|
|
444
|
+
#### 0.2.4 变更
|
|
445
|
+
|
|
446
|
+
| 编号 | 变更 | 影响 |
|
|
447
|
+
|------|------|------|
|
|
448
|
+
| B4 | **入参自洽性校验**(`extra_data` 的键):保留绑定名 `id` / `vec`、与 `id_col` / `column` 重复、不是合法标识符、`id_col == column` → 抛可操作的 `ValueError` | **唯一行为变更**:此前 SQLite 会**静默写坏数据**(如 `id` 列被覆盖)、PG/MySQL 报误导性错误;正常调用**零影响** |
|
|
449
|
+
| — | 新增共享方法 `build_insert_vectors`(只拼装不执行,由 `BaseDbKit` 收敛 → 共享方法 23 → **24**) | 纯新增:可在**自有事务**内写向量行(见下) |
|
|
450
|
+
| — | `create_vector_table` 的 `extra_cols` 支持 `{列名: 类型串}`(与 `create_raw_table` 同形) | 纯新增:既有 `[{"name","type"}]` 形态保留,两者产出 DDL 逐字一致 |
|
|
451
|
+
| — | 后端差异收口:向量列占位符由 `VECTOR_PLACEHOLDER` 驱动(MySQL = `STRING_TO_VECTOR(:vec)`) | 内部整理:公开签名与行为不变 |
|
|
452
|
+
| — | `SQLiteKit.insert_vectors` 的事务获取统一为 `transaction()`(与另两库同构) | 内部一致:行为等价(自成一事务并提交) |
|
|
453
|
+
|
|
443
454
|
#### 0.2.3 变更
|
|
444
455
|
|
|
445
456
|
| 编号 | 变更 | 影响 |
|
|
@@ -457,18 +468,40 @@ rows = SQLiteKit.query("SELECT id, title FROM items WHERE id IN :ids",
|
|
|
457
468
|
{"ids": [h["id"] for h in hits]}) # IN 不保证顺序,需按 hits 重排
|
|
458
469
|
```
|
|
459
470
|
|
|
460
|
-
|
|
471
|
+
**自有事务内写向量(推荐:`build_insert_vectors` + `transaction()`)**:
|
|
461
472
|
|
|
462
473
|
```python
|
|
463
|
-
|
|
474
|
+
from sqlalchemy import text
|
|
475
|
+
|
|
476
|
+
with SQLiteKit.transaction() as session: # 换成 PgKit / MySqlKit 写法相同
|
|
477
|
+
session.execute(text("INSERT INTO docs_item (id, title) VALUES (:id, :t)"),
|
|
478
|
+
{"id": doc_id, "t": title})
|
|
479
|
+
for sql, params in SQLiteKit.build_insert_vectors("docs_doc_vectors", vectors,
|
|
480
|
+
ids=row_ids, extra_data=metas,
|
|
481
|
+
id_col="rowid"):
|
|
482
|
+
session.execute(text(sql), params)
|
|
483
|
+
# 正常退出统一提交;异常自动回滚(多处写入保持同一事务)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**SQLite 的 DBAPI 变体**(`raw_connection()` 支持 `:name` 命名参数):
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
conn = SQLiteKit.raw_connection() # 调用方负责 commit / close
|
|
464
490
|
try:
|
|
465
|
-
|
|
466
|
-
|
|
491
|
+
for sql, params in SQLiteKit.build_insert_vectors("item_vectors", vectors,
|
|
492
|
+
ids=row_ids, extra_data=metas):
|
|
493
|
+
conn.executemany(sql, params)
|
|
467
494
|
conn.commit()
|
|
468
495
|
finally:
|
|
469
496
|
conn.close()
|
|
470
497
|
```
|
|
471
498
|
|
|
499
|
+
> ⚠ 两点约束:① `build_insert_vectors` 输出为 `:name` 风格,**PG / MySQL 的原生 DBAPI 不支持**
|
|
500
|
+
> (psycopg2 / pymysql 需 `%(name)s`),只能经 `transaction()` 的 session 执行;
|
|
501
|
+
> ② SQLite 上**持有未提交写事务**时再调 kit 的**自提交**方法(如 `insert_vectors`)会因另一个连接
|
|
502
|
+
> 拿不到写锁而失败(`OperationalError: database is locked`,约 **6.4s** 后才报错)——
|
|
503
|
+
> 把写入并入**同一个** `transaction()`(如上)即可避免。
|
|
504
|
+
|
|
472
505
|
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
473
506
|
> PG 可直接用 `distance_type="COSINE"`。
|
|
474
507
|
|
|
@@ -105,18 +105,30 @@ def to_vector_literal(vector: builtins.list[float]) -> str:
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def vector_insert_placeholders(
|
|
108
|
-
columns: builtins.list[str],
|
|
108
|
+
columns: builtins.list[str],
|
|
109
|
+
id_col: str,
|
|
110
|
+
column: str,
|
|
111
|
+
vector_placeholder: str = ":vec",
|
|
109
112
|
) -> str:
|
|
110
|
-
"""生成向量表 INSERT 的占位符串(供三个数据库 Kit 的
|
|
113
|
+
"""生成向量表 INSERT 的占位符串(供三个数据库 Kit 的 build_insert_vectors 共用)
|
|
111
114
|
|
|
112
|
-
绑定参数名与列名**解耦**:id 列固定为 ``:id``、向量列固定为
|
|
115
|
+
绑定参数名与列名**解耦**:id 列固定为 ``:id``、向量列固定为 ``vector_placeholder``、
|
|
113
116
|
其余列用 ``:列名``。这样调用方覆写 ``id_col`` / ``column``(自建 DDL 场景)时,
|
|
114
117
|
参数字典的键不受列名影响。
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
columns: 列名列表(按 INSERT 顺序)
|
|
121
|
+
id_col: id 列名 → ``:id``
|
|
122
|
+
column: 向量列名 → ``vector_placeholder``
|
|
123
|
+
vector_placeholder: 向量列的占位符文本(MySQL 传 ``"STRING_TO_VECTOR(:vec)"``)
|
|
124
|
+
|
|
125
|
+
⚠ 内部符号(不在 ``kit.db.__all__``):调用方请用
|
|
126
|
+
`BaseDbKit.build_insert_vectors`,无需关心绑定名。
|
|
115
127
|
"""
|
|
116
128
|
|
|
117
129
|
def _one(name: str) -> str:
|
|
118
130
|
if name == column:
|
|
119
|
-
return
|
|
131
|
+
return vector_placeholder
|
|
120
132
|
if name == id_col:
|
|
121
133
|
return ":id"
|
|
122
134
|
return f":{name}"
|
|
@@ -124,6 +136,54 @@ def vector_insert_placeholders(
|
|
|
124
136
|
return ", ".join(_one(name) for name in columns)
|
|
125
137
|
|
|
126
138
|
|
|
139
|
+
# 向量插入的内部保留绑定名(与 vector_insert_placeholders 的映射一一对应)
|
|
140
|
+
_RESERVED_BIND_NAMES = frozenset({"id", "vec"})
|
|
141
|
+
|
|
142
|
+
# 绑定参数名须为标识符:否则 SQLAlchemy 无法解析 `:name`(列名含空格等会得到
|
|
143
|
+
# "A value is required for bind parameter" 这类无法定位的报错)
|
|
144
|
+
_BIND_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _check_extra_key(key: str, id_col: str, column: str) -> None:
|
|
148
|
+
"""校验 ``extra_data`` 的键可作为绑定参数名(拼装前抛出可操作的 ``ValueError``)
|
|
149
|
+
|
|
150
|
+
三类问题(均实测):
|
|
151
|
+
① 保留绑定名 ``id`` / ``vec`` → ``params`` 被覆盖,SQLite 静默写坏数据;
|
|
152
|
+
② 与 ``id_col`` / ``column`` 重复 → SQLite 接受重复列名并丢弃调用方给的值;
|
|
153
|
+
③ 不是合法标识符 → 报错无法定位("A value is required for bind parameter")。
|
|
154
|
+
"""
|
|
155
|
+
if key in _RESERVED_BIND_NAMES:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"extra_data 含保留绑定名 {key!r}:向量插入的内部绑定名固定为 "
|
|
158
|
+
f"'id'(id 列)与 'vec'(向量列),同名业务列会覆盖它们;"
|
|
159
|
+
f"请重命名该键,或改用自定义 SQL(配合 Kit.transaction())"
|
|
160
|
+
)
|
|
161
|
+
if key in (id_col, column):
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"extra_data 含重复列 {key!r}:该列已由 id_col / column 覆盖"
|
|
164
|
+
)
|
|
165
|
+
if not _BIND_NAME_RE.match(key):
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"extra_data 的键 {key!r} 不是合法标识符,无法生成绑定参数名"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def normalize_extra_cols(
|
|
172
|
+
extra_cols: dict[str, str] | builtins.list[dict[str, str]] | None,
|
|
173
|
+
) -> builtins.list[tuple[str, str]]:
|
|
174
|
+
"""归一化 ``create_vector_table`` 的额外列定义(两种入参形态)
|
|
175
|
+
|
|
176
|
+
接受 ``{列名: 方言类型串}``(推荐,与 `create_raw_table` 同形,**键序即 DDL 列序**)
|
|
177
|
+
或既有 ``[{"name": ..., "type": ...}]``;空值返回空列表。
|
|
178
|
+
两种形态产出**逐字一致**的 DDL。
|
|
179
|
+
"""
|
|
180
|
+
if not extra_cols:
|
|
181
|
+
return []
|
|
182
|
+
if isinstance(extra_cols, dict):
|
|
183
|
+
return [(name, col_type) for name, col_type in extra_cols.items()]
|
|
184
|
+
return [(col["name"], col["type"]) for col in extra_cols]
|
|
185
|
+
|
|
186
|
+
|
|
127
187
|
def generate_snowflake_ids(count: int) -> builtins.list[int]:
|
|
128
188
|
"""生成 count 个雪花 ID(int64;当前 18 位,上限 19 位)
|
|
129
189
|
|
|
@@ -190,6 +250,112 @@ class BaseDbKit(ABC):
|
|
|
190
250
|
# 数据库类型标识(postgresql / mysql / sqlite),由子类覆写
|
|
191
251
|
DB_TYPE: str
|
|
192
252
|
|
|
253
|
+
# 向量列的占位符(后端覆写点):MySQL 需经 STRING_TO_VECTOR 转换
|
|
254
|
+
VECTOR_PLACEHOLDER: str = ":vec"
|
|
255
|
+
|
|
256
|
+
# ==================== 向量写入的共享构造(只拼装,不执行) ====================
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def _vector_param(cls, vector: builtins.list[float]) -> object:
|
|
260
|
+
"""向量 → 绑定参数值(**私有钩子**,子类覆写;基类=文本字面量)
|
|
261
|
+
|
|
262
|
+
PostgreSQL(pgvector)与 MySQL(``STRING_TO_VECTOR``)共用文本字面量;
|
|
263
|
+
`SQLiteKit` 覆写为 float32 小端字节。
|
|
264
|
+
"""
|
|
265
|
+
return to_vector_literal(vector)
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def build_insert_vectors(
|
|
269
|
+
cls,
|
|
270
|
+
table_name: str,
|
|
271
|
+
vectors: builtins.list[builtins.list[float]],
|
|
272
|
+
ids: builtins.list[int] | None = None,
|
|
273
|
+
extra_data: builtins.list[dict[str, object]] | None = None,
|
|
274
|
+
id_col: str = "id",
|
|
275
|
+
column: str = "embedding",
|
|
276
|
+
) -> builtins.list[tuple[str, builtins.list[dict[str, object]]]]:
|
|
277
|
+
"""构造向量表 INSERT 语句(**只拼装、不执行、不管事务**)
|
|
278
|
+
|
|
279
|
+
供调用方在**自己的事务**内写入向量行(与其它表行同事务提交)::
|
|
280
|
+
|
|
281
|
+
with SQLiteKit.transaction() as session:
|
|
282
|
+
session.execute(text("INSERT INTO docs_item (...) VALUES (...)"), {...})
|
|
283
|
+
for sql, params in SQLiteKit.build_insert_vectors("docs_doc_vectors",
|
|
284
|
+
vectors, ids=[...],
|
|
285
|
+
id_col="rowid"):
|
|
286
|
+
session.execute(text(sql), params)
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
table_name: 表名
|
|
290
|
+
vectors: 向量列表,每个向量为 float 列表(也接受二维 numpy 数组)
|
|
291
|
+
ids: 可选的 ID 列表(长度需与 vectors 一致);None 时由 Kit 生成雪花 ID
|
|
292
|
+
(生成值可从返回的参数列表读取)
|
|
293
|
+
extra_data: 额外列数据列表(长度需与 vectors 一致)
|
|
294
|
+
id_col: 主键列名(默认 ``id``,供自建 DDL 的表使用)
|
|
295
|
+
column: 向量列名(默认 ``embedding``,供自建 DDL 的表使用)
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
按**列集分组**的 ``[(SQL, 参数列表), ...]``:同列集的行合并为一条可
|
|
299
|
+
``executemany`` 的语句(常规同构 ``extra_data`` 只返回 1 条)。SQL 为
|
|
300
|
+
SQLAlchemy ``text()`` 风格(``:id`` / ``:vec`` / ``:列名``)。
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: 入参自洽性校验失败——长度/维度不一致、``extra_data`` 键为保留绑定名
|
|
304
|
+
(``id`` / ``vec``)或与 ``id_col`` / ``column`` 重复、键不是合法标识符、
|
|
305
|
+
``id_col == column``
|
|
306
|
+
|
|
307
|
+
Note:
|
|
308
|
+
- **无副作用**:不建连接、不接触数据库、不改任何状态;对**不存在的表**也正常返回。
|
|
309
|
+
- **执行载体**:SQLite 可直接在 ``raw_connection()``(DBAPI 支持 ``:name``)或
|
|
310
|
+
``transaction()`` 上执行;**PG / MySQL 的原生 DBAPI 不支持 ``:name``**
|
|
311
|
+
(psycopg2 / pymysql 需 ``%(name)s``),须经 ``Kit.transaction()`` 的 session 执行。
|
|
312
|
+
- ``insert_vectors`` 的执行语义即"本方法 + 自带事务",两者共用同一份拼装逻辑。
|
|
313
|
+
"""
|
|
314
|
+
if id_col == column:
|
|
315
|
+
raise ValueError(f"id_col 与 column 不能同名(当前均为 {id_col!r})")
|
|
316
|
+
# 用 len() 而非真值判断:入参可为 list 或二维 numpy 数组
|
|
317
|
+
if len(vectors) == 0:
|
|
318
|
+
return []
|
|
319
|
+
if ids is None:
|
|
320
|
+
ids = generate_snowflake_ids(len(vectors))
|
|
321
|
+
if len(ids) != len(vectors):
|
|
322
|
+
raise ValueError("ids 长度必须与 vectors 一致")
|
|
323
|
+
if extra_data is not None and len(extra_data) != len(vectors):
|
|
324
|
+
raise ValueError("extra_data 长度必须与 vectors 一致")
|
|
325
|
+
|
|
326
|
+
dims = len(vectors[0])
|
|
327
|
+
if any(len(vec) != dims for vec in vectors):
|
|
328
|
+
raise ValueError(
|
|
329
|
+
f"同一批次的向量长度必须一致(首个向量 {dims} 维,存在其它维度的向量)"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# 按「列集合」分组:executemany 要求同一语句的参数列表**键集一致**
|
|
333
|
+
groups: dict[tuple[str, ...], builtins.list[dict[str, object]]] = {}
|
|
334
|
+
for i, vector in enumerate(vectors):
|
|
335
|
+
columns: builtins.list[str] = [id_col, column]
|
|
336
|
+
params: dict[str, object] = {"id": ids[i], "vec": cls._vector_param(vector)}
|
|
337
|
+
if extra_data is not None:
|
|
338
|
+
for key, value in extra_data[i].items():
|
|
339
|
+
_check_extra_key(key, id_col, column)
|
|
340
|
+
columns.append(key)
|
|
341
|
+
params[key] = value
|
|
342
|
+
groups.setdefault(tuple(columns), []).append(params)
|
|
343
|
+
|
|
344
|
+
statements: builtins.list[tuple[str, builtins.list[dict[str, object]]]] = []
|
|
345
|
+
for columns_tuple, params_list in groups.items():
|
|
346
|
+
columns_list = list(columns_tuple)
|
|
347
|
+
placeholders = vector_insert_placeholders(
|
|
348
|
+
columns_list, id_col, column, cls.VECTOR_PLACEHOLDER
|
|
349
|
+
)
|
|
350
|
+
statements.append(
|
|
351
|
+
(
|
|
352
|
+
f"INSERT INTO {table_name} ({', '.join(columns_list)}) "
|
|
353
|
+
f"VALUES ({placeholders})",
|
|
354
|
+
params_list,
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
return statements
|
|
358
|
+
|
|
193
359
|
# ==================== 连接管理 ====================
|
|
194
360
|
|
|
195
361
|
@classmethod
|
|
@@ -65,8 +65,7 @@ from kit.config.log_config import get_default_logger
|
|
|
65
65
|
from kit.db.base_db_kit import (
|
|
66
66
|
DEFAULT_DISTANCE_TYPE,
|
|
67
67
|
BaseDbKit,
|
|
68
|
-
|
|
69
|
-
to_vector_literal,
|
|
68
|
+
normalize_extra_cols,
|
|
70
69
|
)
|
|
71
70
|
from kit.db.engine import MYSQL
|
|
72
71
|
from kit.db.exceptions import DBError, VectorNotSupportedError
|
|
@@ -86,6 +85,9 @@ class MySqlKit(BaseDbKit):
|
|
|
86
85
|
|
|
87
86
|
DB_TYPE = MYSQL
|
|
88
87
|
|
|
88
|
+
# 向量列占位符:本后端 VECTOR 列需经 STRING_TO_VECTOR 转换(覆写基类的 ":vec")
|
|
89
|
+
VECTOR_PLACEHOLDER = "STRING_TO_VECTOR(:vec)"
|
|
90
|
+
|
|
89
91
|
# 向量能力声明(供调用方 feature-detect,详见 docs/agents/modules/kit-db.md)
|
|
90
92
|
VECTOR_CAPABILITIES: dict[str, object] = {
|
|
91
93
|
"store": True, # 可建表 / 可写入
|
|
@@ -151,7 +153,7 @@ class MySqlKit(BaseDbKit):
|
|
|
151
153
|
dims: int,
|
|
152
154
|
id_col: str = "id",
|
|
153
155
|
column: str = "embedding",
|
|
154
|
-
extra_cols: list[dict[str, str]] | None = None,
|
|
156
|
+
extra_cols: dict[str, str] | list[dict[str, str]] | None = None,
|
|
155
157
|
) -> None:
|
|
156
158
|
"""创建包含向量列的表(MySQL 9.x 原生 VECTOR 类型)
|
|
157
159
|
|
|
@@ -161,8 +163,10 @@ class MySqlKit(BaseDbKit):
|
|
|
161
163
|
id_col: 主键列名(``BIGINT PRIMARY KEY``,**不自增**,id 由调用方提供
|
|
162
164
|
或由 `insert_vectors` 兜底生成雪花 ID)
|
|
163
165
|
column: 向量列名(默认 ``embedding``)
|
|
164
|
-
extra_cols:
|
|
165
|
-
|
|
166
|
+
extra_cols: 额外列定义,**两种形态**(产出 DDL 逐字一致):
|
|
167
|
+
``{列名: 方言类型串}``(**推荐**,与 `create_raw_table` 同形,键序即列序)
|
|
168
|
+
或既有 ``[{"name": str, "type": str}]``;
|
|
169
|
+
type 按 MySQL 方言书写,如 ``{"label": "VARCHAR(32)"}``
|
|
166
170
|
|
|
167
171
|
表结构示例::
|
|
168
172
|
|
|
@@ -187,11 +191,10 @@ class MySqlKit(BaseDbKit):
|
|
|
187
191
|
|
|
188
192
|
cls._ensure_vector_supported()
|
|
189
193
|
|
|
190
|
-
extra_defs = ""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
194
|
+
extra_defs = "".join(
|
|
195
|
+
f", {name} {col_type}"
|
|
196
|
+
for name, col_type in normalize_extra_cols(extra_cols)
|
|
197
|
+
)
|
|
195
198
|
|
|
196
199
|
sql = (
|
|
197
200
|
f"CREATE TABLE IF NOT EXISTS {table_name} ("
|
|
@@ -227,63 +230,28 @@ class MySqlKit(BaseDbKit):
|
|
|
227
230
|
插入的行数
|
|
228
231
|
|
|
229
232
|
Raises:
|
|
230
|
-
ValueError: ids / extra_data 长度与 vectors
|
|
233
|
+
ValueError: 入参自洽性校验失败——ids / extra_data 长度与 vectors 不一致、
|
|
234
|
+
批内向量长度不一致、``extra_data`` 键为保留绑定名(``id`` / ``vec``)、
|
|
235
|
+
键与 ``id_col`` / ``column`` 重复、键不是合法标识符、``id_col == column``
|
|
231
236
|
|
|
232
237
|
Note:
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
单次事务内批量写入(``cls.transaction()``,与 SQLiteKit / PgKit 同语义:整体提交);
|
|
239
|
+
语句由 `build_insert_vectors` 构造(**单一事实源**,本后端经 ``VECTOR_PLACEHOLDER``
|
|
240
|
+
输出 ``STRING_TO_VECTOR(:vec)``)。需要把向量行写入**调用方自己的事务**时,
|
|
241
|
+
请改用 `build_insert_vectors` + `transaction()`。
|
|
235
242
|
"""
|
|
236
|
-
# 用 len() 而非真值判断:入参可为 list 或二维 numpy 数组
|
|
237
|
-
if len(vectors) == 0:
|
|
238
|
-
return 0
|
|
239
|
-
if ids is None:
|
|
240
|
-
ids = generate_snowflake_ids(len(vectors))
|
|
241
|
-
if len(ids) != len(vectors):
|
|
242
|
-
raise ValueError("ids 长度必须与 vectors 一致")
|
|
243
|
-
if extra_data is not None and len(extra_data) != len(vectors):
|
|
244
|
-
raise ValueError("extra_data 长度必须与 vectors 一致")
|
|
245
|
-
|
|
246
|
-
dims = len(vectors[0])
|
|
247
|
-
if any(len(vec) != dims for vec in vectors):
|
|
248
|
-
raise ValueError(
|
|
249
|
-
f"同一批次的向量长度必须一致(首个向量 {dims} 维,存在其它维度的向量)"
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
def _placeholder(name: str) -> str:
|
|
253
|
-
"""列名 → 占位符:向量列需经 STRING_TO_VECTOR 转换(本后端特有)"""
|
|
254
|
-
if name == column:
|
|
255
|
-
return "STRING_TO_VECTOR(:vec)"
|
|
256
|
-
if name == id_col:
|
|
257
|
-
return ":id"
|
|
258
|
-
return f":{name}"
|
|
259
|
-
|
|
260
|
-
# 按「列集合」分组:键集相同的行才能共用同一条批量语句(executemany)
|
|
261
|
-
groups: dict[tuple[str, ...], list[dict[str, object]]] = {}
|
|
262
|
-
for i, vector in enumerate(vectors):
|
|
263
|
-
columns: list[str] = [id_col, column]
|
|
264
|
-
params: dict[str, object] = {
|
|
265
|
-
"id": ids[i],
|
|
266
|
-
"vec": to_vector_literal(vector),
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if extra_data is not None:
|
|
270
|
-
for key, value in extra_data[i].items():
|
|
271
|
-
columns.append(key)
|
|
272
|
-
params[key] = value
|
|
273
|
-
|
|
274
|
-
groups.setdefault(tuple(columns), []).append(params)
|
|
275
|
-
|
|
276
243
|
with cls.transaction() as session:
|
|
277
|
-
for
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
244
|
+
for sql, params in cls.build_insert_vectors(
|
|
245
|
+
table_name,
|
|
246
|
+
vectors,
|
|
247
|
+
ids=ids,
|
|
248
|
+
extra_data=extra_data,
|
|
249
|
+
id_col=id_col,
|
|
250
|
+
column=column,
|
|
251
|
+
):
|
|
252
|
+
session.execute(text(sql), params)
|
|
253
|
+
logger.info(f"✅ 插入 {len(vectors)} 条向量到 {table_name}")
|
|
254
|
+
return len(vectors)
|
|
287
255
|
|
|
288
256
|
@classmethod
|
|
289
257
|
def search_vectors(
|
|
@@ -67,9 +67,8 @@ from kit.config.log_config import get_default_logger
|
|
|
67
67
|
from kit.db.base_db_kit import (
|
|
68
68
|
DEFAULT_DISTANCE_TYPE,
|
|
69
69
|
BaseDbKit,
|
|
70
|
-
|
|
70
|
+
normalize_extra_cols,
|
|
71
71
|
to_vector_literal,
|
|
72
|
-
vector_insert_placeholders,
|
|
73
72
|
)
|
|
74
73
|
from kit.db.engine import POSTGRESQL, get_engine
|
|
75
74
|
from kit.db.exceptions import DBError
|
|
@@ -177,7 +176,7 @@ class PgKit(BaseDbKit):
|
|
|
177
176
|
dims: int,
|
|
178
177
|
id_col: str = "id",
|
|
179
178
|
column: str = "embedding",
|
|
180
|
-
extra_cols: list[dict[str, str]] | None = None,
|
|
179
|
+
extra_cols: dict[str, str] | list[dict[str, str]] | None = None,
|
|
181
180
|
) -> None:
|
|
182
181
|
"""创建包含向量列的表(pgvector)
|
|
183
182
|
|
|
@@ -187,8 +186,10 @@ class PgKit(BaseDbKit):
|
|
|
187
186
|
id_col: 主键列名(``BIGINT PRIMARY KEY``,**不自增**,id 由调用方提供
|
|
188
187
|
或由 `insert_vectors` 兜底生成雪花 ID)
|
|
189
188
|
column: 向量列名(默认 ``embedding``)
|
|
190
|
-
extra_cols:
|
|
191
|
-
|
|
189
|
+
extra_cols: 额外列定义,**两种形态**(产出 DDL 逐字一致):
|
|
190
|
+
``{列名: 方言类型串}``(**推荐**,与 `create_raw_table` 同形,键序即列序)
|
|
191
|
+
或既有 ``[{"name": str, "type": str}]``;
|
|
192
|
+
type 按 PostgreSQL 方言书写,如 ``{"label": "TEXT"}``
|
|
192
193
|
|
|
193
194
|
内部自动执行 ``CREATE EXTENSION IF NOT EXISTS vector``(需相应权限)。
|
|
194
195
|
|
|
@@ -214,11 +215,10 @@ class PgKit(BaseDbKit):
|
|
|
214
215
|
|
|
215
216
|
cls._ensure_vector_extension()
|
|
216
217
|
|
|
217
|
-
extra_defs = ""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
218
|
+
extra_defs = "".join(
|
|
219
|
+
f", {name} {col_type}"
|
|
220
|
+
for name, col_type in normalize_extra_cols(extra_cols)
|
|
221
|
+
)
|
|
222
222
|
|
|
223
223
|
sql = (
|
|
224
224
|
f"CREATE TABLE IF NOT EXISTS {table_name} ("
|
|
@@ -271,55 +271,27 @@ class PgKit(BaseDbKit):
|
|
|
271
271
|
插入的行数
|
|
272
272
|
|
|
273
273
|
Raises:
|
|
274
|
-
ValueError: ids / extra_data 长度与 vectors
|
|
274
|
+
ValueError: 入参自洽性校验失败——ids / extra_data 长度与 vectors 不一致、
|
|
275
|
+
批内向量长度不一致、``extra_data`` 键为保留绑定名(``id`` / ``vec``)、
|
|
276
|
+
键与 ``id_col`` / ``column`` 重复、键不是合法标识符、``id_col == column``
|
|
275
277
|
|
|
276
278
|
Note:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
单次事务内批量写入(``cls.transaction()``,与 SQLiteKit / MySqlKit 同语义:整体提交);
|
|
280
|
+
语句由 `build_insert_vectors` 构造(**单一事实源**)。需要把向量行写入**调用方自己的事务**时,
|
|
281
|
+
请改用 `build_insert_vectors` + `transaction()`。
|
|
279
282
|
"""
|
|
280
|
-
# 用 len() 而非真值判断:入参可为 list 或二维 numpy 数组
|
|
281
|
-
if len(vectors) == 0:
|
|
282
|
-
return 0
|
|
283
|
-
if ids is None:
|
|
284
|
-
ids = generate_snowflake_ids(len(vectors))
|
|
285
|
-
if len(ids) != len(vectors):
|
|
286
|
-
raise ValueError("ids 长度必须与 vectors 一致")
|
|
287
|
-
if extra_data is not None and len(extra_data) != len(vectors):
|
|
288
|
-
raise ValueError("extra_data 长度必须与 vectors 一致")
|
|
289
|
-
|
|
290
|
-
dims = len(vectors[0])
|
|
291
|
-
if any(len(vec) != dims for vec in vectors):
|
|
292
|
-
raise ValueError(
|
|
293
|
-
f"同一批次的向量长度必须一致(首个向量 {dims} 维,存在其它维度的向量)"
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
# 按「列集合」分组:键集相同的行才能共用同一条批量语句(executemany)
|
|
297
|
-
groups: dict[tuple[str, ...], list[dict[str, object]]] = {}
|
|
298
|
-
for i, vector in enumerate(vectors):
|
|
299
|
-
columns: list[str] = [id_col, column]
|
|
300
|
-
params: dict[str, object] = {
|
|
301
|
-
"id": ids[i],
|
|
302
|
-
"vec": to_vector_literal(vector),
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if extra_data is not None:
|
|
306
|
-
for key, value in extra_data[i].items():
|
|
307
|
-
columns.append(key)
|
|
308
|
-
params[key] = value
|
|
309
|
-
|
|
310
|
-
groups.setdefault(tuple(columns), []).append(params)
|
|
311
|
-
|
|
312
283
|
with cls.transaction() as session:
|
|
313
|
-
for
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
284
|
+
for sql, params in cls.build_insert_vectors(
|
|
285
|
+
table_name,
|
|
286
|
+
vectors,
|
|
287
|
+
ids=ids,
|
|
288
|
+
extra_data=extra_data,
|
|
289
|
+
id_col=id_col,
|
|
290
|
+
column=column,
|
|
291
|
+
):
|
|
292
|
+
session.execute(text(sql), params)
|
|
293
|
+
logger.info(f"✅ 插入 {len(vectors)} 条向量到 {table_name}")
|
|
294
|
+
return len(vectors)
|
|
323
295
|
|
|
324
296
|
@classmethod
|
|
325
297
|
def search_vectors(
|
|
@@ -59,8 +59,7 @@ from kit.config.log_config import get_default_logger
|
|
|
59
59
|
from kit.db.base_db_kit import (
|
|
60
60
|
DEFAULT_DISTANCE_TYPE,
|
|
61
61
|
BaseDbKit,
|
|
62
|
-
|
|
63
|
-
vector_insert_placeholders,
|
|
62
|
+
normalize_extra_cols,
|
|
64
63
|
)
|
|
65
64
|
from kit.db.engine import SQLITE, get_engine
|
|
66
65
|
from kit.db.exceptions import DBError
|
|
@@ -124,6 +123,11 @@ class SQLiteKit(BaseDbKit):
|
|
|
124
123
|
|
|
125
124
|
# ==================== 向量操作 ====================
|
|
126
125
|
|
|
126
|
+
@classmethod
|
|
127
|
+
def _vector_param(cls, vector: list[float]) -> object:
|
|
128
|
+
"""向量 → BLOB(小端 float32);覆写基类的文本字面量实现"""
|
|
129
|
+
return cls.encode_vector(vector)
|
|
130
|
+
|
|
127
131
|
@classmethod
|
|
128
132
|
def encode_vector(cls, vector: list[float]) -> bytes:
|
|
129
133
|
"""把向量编码为 sqlite-vector 使用的 float32 **小端** BLOB(4 字节/维)
|
|
@@ -179,7 +183,7 @@ class SQLiteKit(BaseDbKit):
|
|
|
179
183
|
dims: int,
|
|
180
184
|
id_col: str = "id",
|
|
181
185
|
column: str = "embedding",
|
|
182
|
-
extra_cols: list[dict[str, str]] | None = None,
|
|
186
|
+
extra_cols: dict[str, str] | list[dict[str, str]] | None = None,
|
|
183
187
|
) -> None:
|
|
184
188
|
"""创建包含向量列的表
|
|
185
189
|
|
|
@@ -190,8 +194,10 @@ class SQLiteKit(BaseDbKit):
|
|
|
190
194
|
等价于另两库的 ``BIGINT``;id 由调用方提供或由 `insert_vectors`
|
|
191
195
|
兜底生成雪花 ID)。须为 rowid 别名,见 `search_vectors` 的 Note
|
|
192
196
|
column: 向量列名(默认 ``embedding``)
|
|
193
|
-
extra_cols:
|
|
194
|
-
|
|
197
|
+
extra_cols: 额外列定义,**两种形态**(产出 DDL 逐字一致):
|
|
198
|
+
``{列名: 方言类型串}``(**推荐**,与 `create_raw_table` 同形,键序即列序)
|
|
199
|
+
或既有 ``[{"name": str, "type": str}]``;
|
|
200
|
+
如 ``{"label": "TEXT"}``
|
|
195
201
|
|
|
196
202
|
表结构示例::
|
|
197
203
|
|
|
@@ -212,11 +218,10 @@ class SQLiteKit(BaseDbKit):
|
|
|
212
218
|
if dims < 1:
|
|
213
219
|
raise ValueError(f"dims 必须为正整数,当前为 {dims}")
|
|
214
220
|
|
|
215
|
-
extra_defs = ""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
221
|
+
extra_defs = "".join(
|
|
222
|
+
f", {name} {col_type}"
|
|
223
|
+
for name, col_type in normalize_extra_cols(extra_cols)
|
|
224
|
+
)
|
|
220
225
|
|
|
221
226
|
sql = f"""
|
|
222
227
|
CREATE TABLE IF NOT EXISTS {table_name} (
|
|
@@ -253,56 +258,29 @@ class SQLiteKit(BaseDbKit):
|
|
|
253
258
|
插入的行数
|
|
254
259
|
|
|
255
260
|
Raises:
|
|
256
|
-
ValueError: ids / extra_data 长度与 vectors
|
|
261
|
+
ValueError: 入参自洽性校验失败——ids / extra_data 长度与 vectors 不一致、
|
|
262
|
+
批内向量长度不一致、``extra_data`` 键为保留绑定名(``id`` / ``vec``)、
|
|
263
|
+
键与 ``id_col`` / ``column`` 重复、键不是合法标识符、``id_col == column``
|
|
257
264
|
|
|
258
265
|
Note:
|
|
259
266
|
- `ids=None` 时**不使用 SQLite rowid 自增**,改由 Kit 生成雪花 ID
|
|
260
267
|
(64 位,18~19 位数字),与 PgKit / MySqlKit 语义一致。
|
|
261
|
-
-
|
|
268
|
+
- 单次事务内批量写入(``cls.transaction()``);语句由 `build_insert_vectors`
|
|
269
|
+
构造(**单一事实源**)。需要把向量行写入**调用方自己的事务**时,请改用
|
|
270
|
+
`build_insert_vectors` + `transaction()`,或 `raw_connection()`(见文档 cookbook)。
|
|
262
271
|
"""
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
raise ValueError(
|
|
276
|
-
f"同一批次的向量长度必须一致(首个向量 {dims} 维,存在其它维度的向量)"
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
engine = get_engine(cls.DB_TYPE)
|
|
280
|
-
|
|
281
|
-
# 按「列集合」分组:键集相同的行才能共用同一条批量语句(executemany)
|
|
282
|
-
groups: dict[tuple[str, ...], list[dict[str, object]]] = {}
|
|
283
|
-
for i, vec in enumerate(vectors):
|
|
284
|
-
vec_bytes = cls.encode_vector(vec)
|
|
285
|
-
columns: list[str] = [id_col, column]
|
|
286
|
-
params: dict[str, object] = {"id": ids[i], "vec": vec_bytes}
|
|
287
|
-
|
|
288
|
-
if extra_data is not None:
|
|
289
|
-
for key, value in extra_data[i].items():
|
|
290
|
-
columns.append(key)
|
|
291
|
-
params[key] = value
|
|
292
|
-
|
|
293
|
-
groups.setdefault(tuple(columns), []).append(params)
|
|
294
|
-
|
|
295
|
-
with engine.connect() as conn:
|
|
296
|
-
for columns, params_list in groups.items():
|
|
297
|
-
placeholders = vector_insert_placeholders(list(columns), id_col, column)
|
|
298
|
-
sql = (
|
|
299
|
-
f"INSERT INTO {table_name} ({', '.join(columns)}) "
|
|
300
|
-
f"VALUES ({placeholders})"
|
|
301
|
-
)
|
|
302
|
-
conn.execute(text(sql), params_list)
|
|
303
|
-
conn.commit()
|
|
304
|
-
logger.info(f"✅ 插入 {len(vectors)} 条向量到 {table_name}")
|
|
305
|
-
return len(vectors)
|
|
272
|
+
with cls.transaction() as session:
|
|
273
|
+
for sql, params in cls.build_insert_vectors(
|
|
274
|
+
table_name,
|
|
275
|
+
vectors,
|
|
276
|
+
ids=ids,
|
|
277
|
+
extra_data=extra_data,
|
|
278
|
+
id_col=id_col,
|
|
279
|
+
column=column,
|
|
280
|
+
):
|
|
281
|
+
session.execute(text(sql), params)
|
|
282
|
+
logger.info(f"✅ 插入 {len(vectors)} 条向量到 {table_name}")
|
|
283
|
+
return len(vectors)
|
|
306
284
|
|
|
307
285
|
@classmethod
|
|
308
286
|
def _vector_dims(cls, table_name: str, column: str, conn: Connection) -> int | None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|