fix(retrieval): 修复原子性、过滤漏召回、tags 语义与 rebuild 回滚
- 元数据 + 向量单事务提交,避免 PATCH 半提交(审阅 #2) - vectorstore upsert 改 delete-then-insert 幂等,支持共享 conn - FTS 取全量 + 过滤 oversample,修复 metadata 过滤漏召回(审阅 #4) - PATCH tags 区分 None/[]/非空:保留/清空/替换(审阅 #5) - rebuild 拒绝增量 scope/note_ids,扫描先行 + 失败回滚旧索引(审阅 #6) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ vec0 虚拟表返回的 distance 是欧氏距离(非平方)。入库前向
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from contextlib import nullcontext
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
@@ -38,30 +40,36 @@ class VectorStore(Protocol):
|
||||
class SqliteVecStore:
|
||||
"""sqlite-vec 默认实现。"""
|
||||
|
||||
async def upsert(self, records: list[VectorRecord]) -> None:
|
||||
async def upsert(self, records: list[VectorRecord], *, conn: sqlite3.Connection | None = None) -> None:
|
||||
if not records:
|
||||
return
|
||||
conn = connect()
|
||||
owns = conn is None
|
||||
conn = conn or connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
with transaction(conn) if owns else nullcontext():
|
||||
for record in records:
|
||||
# vec0 不支持 UPDATE,采用 delete-then-insert 实现幂等 upsert,避免主键冲突
|
||||
conn.execute("DELETE FROM vec_blocks WHERE block_id = ?", (record.id,))
|
||||
conn.execute(
|
||||
"INSERT INTO vec_blocks (block_id, embedding) VALUES (?, ?)",
|
||||
(record.id, sqlite_vec.serialize_float32(record.vector)),
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
if owns:
|
||||
conn.close()
|
||||
|
||||
async def delete(self, ids: list[str]) -> None:
|
||||
async def delete(self, ids: list[str], *, conn: sqlite3.Connection | None = None) -> None:
|
||||
if not ids:
|
||||
return
|
||||
conn = connect()
|
||||
owns = conn is None
|
||||
conn = conn or connect()
|
||||
try:
|
||||
with transaction(conn):
|
||||
with transaction(conn) if owns else nullcontext():
|
||||
for bid in ids:
|
||||
conn.execute("DELETE FROM vec_blocks WHERE block_id = ?", (bid,))
|
||||
finally:
|
||||
conn.close()
|
||||
if owns:
|
||||
conn.close()
|
||||
|
||||
async def search(self, vector: list[float], *, top_k: int) -> list[VectorHit]:
|
||||
conn = connect()
|
||||
|
||||
Reference in New Issue
Block a user