Files
border-red-post-nalati/docs/VECTOR_API.md
T

11 KiB
Raw Blame History

T07 向量快照、发布与精确检索

本分支 feat/backend-vector-retrieval 组合待审 #8 的文本版本与 #9 的固定版本 CPU Provider,继承 #7/#6。合并到 dev 前须按依赖审阅;不自行批准或合并。双方阶段文档均保留,独立阶段差异从组合基线 bd0b738 起查看。

交付 pgvector 存储、空间约束、独立发布、精确余弦检索与历史。关键词 POST /api/retrieval 保持原请求/分数/条目粒度;向量使用新的 POST /api/retrieval/vector,粒度为 chunk。本阶段尚未实现 RRF、ANN、RAG、持久化后台任务或正式领域评测。

配置与迁移

执行 alembic upgrade head 到 0006_vectors。迁移增加 embedding_spaces、vector_versions、chunk_vectors、vector_state 和 vector_publications,以及 chunks 的复合唯一约束。保留账号、目录、原文版本及既有检索日志,不预置模型或向量。

扩展类型/函数/距离算子显式引用 public.vector,业务表仍放在连接的当前 Schema,不把 public 加入测试搜索路径。已有 vector 扩展须位于 public;其他安装位置会明确报错,迁移不会移动共享扩展。新数据库可先由有权限的数据库管理员执行 CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public。

默认 EMBEDDING_ENABLED=false,普通 API 启动不导入 PyTorch 或加载权重。模型相关请求返回 503,不回退占位向量或伪造空结果;关键词和管理元数据接口可继续使用。启用前按 本地评估说明 安装独立 CPU 环境、缓存固定修订;该运行环境还需安装 backend/requirements.txt 的 API 依赖。

变量 默认值 作用
EMBEDDING_ENABLED false 显式启用本地编码
EMBEDDING_MODEL small 仅 small/base;完整修订、维度等由固定 Spec 决定
EMBEDDING_CACHE_DIR 未设置 固定模型缓存目录;空值使用库默认缓存
EMBEDDING_BATCH_SIZE 8 1–32
EMBEDDING_THREADS 2 1–4 个 CPU 线程

开发资源优先暂用 small,正式模型仍待已核验领域资料/问题集评测。API 始终只从本机固定修订缓存读取,禁止在请求中下载权重,不接收模型路径、任意仓库名或用户向量。CPU Provider 首次使用才加载,单进程同时只允许一个编码调用,繁忙立即返回 503;当前建议单 worker 本机运行,不能据此宣称已实现跨进程队列或生产并发容量。

Linux 仓库根目录示例(先完成模型准备及数据库迁移;本机 Cookie/Origin 配置仍按认证说明设置):

task_vector_root=$(git rev-parse --absolute-git-dir)
uv pip install --python "$task_vector_root/embedding-eval-venv/bin/python" \
  -r backend/requirements.txt
export EMBEDDING_ENABLED=true EMBEDDING_MODEL=small
export EMBEDDING_CACHE_DIR="$task_vector_root/embedding-model-cache"
export HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1 HF_HUB_DISABLE_IMPLICIT_TOKEN=1
export TOKENIZERS_PARALLELISM=false
cd backend
"$task_vector_root/embedding-eval-venv/bin/python" -m uvicorn app.main:app --host 127.0.0.1

环境、模型权重和实际日志只留本机,不提交。普通 API 环境不自动增加 PyTorch 依赖。

构建与发布协议

  1. 通过 /api/indexes 构建并发布当前有效审核语料的文本版本。建议开发先用 chunk_size=200, overlap=20;实际 tokenizer 仍须检查,字符数不保证 token 数。
  2. 管理员向 POST /api/vector-indexes 提交 {"text_index_version_id":"<已发布文本版本 UUID>"}。服务仅接受当前有效的文本发布版本;整份快照最多 2000 chunks,超限返回 503,不截断语料。
  3. 服务短暂锁住目录/文本状态,复核实际审核语料指纹、原文窗口、偏移、哈希、出处和完整数量;复制输入后提交并释放事务。模型加载与正文编码全部在锁外运行。
  4. 编码成功且向量数量/维度/有限数/L2 范数通过后,再取得同一状态锁,重新校验文本代数、目录修订、当前指针、语料指纹及实际 chunks。变化则 409,整批不入库。成功只保存 ready 向量版本,不自动发布。
  5. 管理员向 POST /api/vector-indexes/{id}/publish 提交 {"expected_generation":0}。向量代数独立于文本代数;原子切换当前指针、前后版本状态和发布历史。旧代数、过期文本或不完整向量集均拒绝。相同当前版本和当前代数为幂等读取。

上述构建同步执行,没有后台任务 ID 或自动重试。超过模型 token 上限返回 422,应重新切分并发布文本,再重建;不会静默截断。编码不可用/繁忙为 503;数据库约束或版本竞争为 409。失败不会改写旧指针或留下部分向量版本。空的有效审核语料允许生成/发布 0 chunks 的版本;随后检索如实返回 no_evidence。

目录变化会使既有文本和向量版本过期;文本切换即使内容相同也要求对应新文本版本的向量。旧指针和日志保留为历史,公开检索对过期状态返回 409,不继续使用旧审核依据。切换模型配置后,若当前空间不一致,也须重新构建/发布相应空间或恢复匹配的运行配置。

数据库空间隔离

space_id 为固定 Spec 的 SHA-256,包含模型名、完整 revision、维度、token 上限、查询指令、编码实现(CLS/L2/CPU FP32)及权重格式。同维度的不同修订/指令/配置仍是不同空间。

数据库同时约束:

  • 版本的 (space_id, dimension) 必须引用同一 EmbeddingSpace。
  • 每条向量的 (version_id, text_index_version_id, space_id, dimension) 必须引用同一向量版本。
  • (chunk_id, text_index_version_id) 必须引用对应文本版本中的 chunk。
  • public.vector_dims 必须等于声明维度;public.vector_norm 须在 0.9999–1.0001,pgvector 拒绝非有限元素。

使用无固定维度的 vector 列容纳独立空间,查询显式限定版本、空间、文本版本和维度,然后使用余弦距离。当前为有限语料精确扫描,没有 HNSW/IVFFlat;模型空间和向量发布均不由客户端参数任意指定。

接口与字段交接

/api/vector-indexes 下所有读写要求 admin;写入沿用当前 Cookie、CSRF 和明确 Origin。元数据不要求加载模型。分页使用 {items,total,page,page_size},page_size 最大 100。

方法与路径 请求/用途 响应
POST /api/vector-indexes text_index_version_id 201,VectorVersion
GET /api/vector-indexes page/page_size 版本分页
GET /api/vector-indexes/{id} UUID VectorVersion,缺失 404
GET /api/vector-indexes/active 当前元数据 generation/text_generation/catalog_revision/is_current/active_version
GET /api/vector-indexes/spaces page/page_size 空间 id/dimension/spec
POST /api/vector-indexes/{id}/publish expected_generation 当前向量元数据
GET /api/vector-indexes/publications page/page_size 代数、前后向量版本、actor_id、UTC 时间
POST /api/retrieval/vector 下方请求 VectorRetrievalResponse

VectorVersion 字段:id/text_index_version_id/space_id/dimension/chunk_count/status/created_by/created_at/published_at,状态为 ready/published/superseded;时间统一 UTC ISO 8601。发布历史的 version_id 是向量版本,不能与文本发布历史混用。

公开检索请求示例:

{"query":"待检索问题","mode":"vector","top_k":5,"category":"交通"}

mode 可省略,固定为 vector;query 1–500 字符,top_k 1–20,category 可省略。未知字段拒绝。流程在查询编码前后均复核当前发布/审核状态和完整向量集;编码时不持有目录发布锁,期间任何发布代数变化都会返回 409,重试使用新快照。

响应记录:run_id/query/mode/status/top_k/category/vector_version_id/text_index_version_id/generation/text_generation/corpus_fingerprint/corpus_size/total_hits/results/elapsed_ms/created_at/config。config 固定 scorer=pgvector-cosine-exact-v1、ranking_unit=chunk,并保存完整空间 Spec。

每个 result 沿用关键词出处字段,再加 chunk_id/chunk_ordinal;snippet 为完整原文 chunk,snippet_start/end 是原正文的 Unicode 字符偏移(左闭右开),matched_terms 为空数组。score 为 1 - cosine_distance,不是正确率或置信度,也未设领域无依据阈值。按实际距离、knowledge_id、chunk_ordinal、chunk_id 排序;同一知识可出现多个 chunks,不能把结果数量当作命中的知识数。

corpus_size/total_hits 为分类筛选后的候选 chunk 数,不是超过置信阈值的数量;corpus_fingerprint 是整个已发布文本语料指纹,分类另行保存。耗时包括两次审核/快照检查、查询编码(首次可含模型加载)、SQL 距离排序与片段生成,不含最终日志提交及网络。不同于关键词基线的条目粒度,正式对照前必须统一评测单位。

成功和零候选均保存 retrieval_runs 不可变快照。既有历史接口现在返回 keyword/vector 判别联合,仍限 admin;不改写过去结果,也不因历史可读取就代表资料仍有效。前端调用层已同步 services/vectors.ts 与历史联合类型,页面和浏览器仍待接入验收。

回滚与验证

备份后 alembic downgrade 0005_chunk_indexes 删除本阶段空间、向量、向量状态和发布历史,保留文本 chunks/发布、账号、知识及所有检索结果快照。切回对应旧版后端前须处理其历史接口只认识 keyword 响应的兼容性;重升后向量指针为空,需要重新构建,不会复原被删权重结果。当前 API 启动会拒绝旧 Schema。

默认测试用明确的三维坐标 fixture 验证 SQL 数学、空间隔离、并发与回滚,绝不代表实际模型质量。真实模型贯通用例显式启用,在独立模型环境运行:

task_vector_root=$(git rev-parse --absolute-git-dir)
uv pip install --python "$task_vector_root/embedding-eval-venv/bin/python" \
  -r backend/requirements-dev.txt pgserver
RUN_LOCAL_EMBEDDING_TEST=1 EMBEDDING_CACHE_DIR="$task_vector_root/embedding-model-cache" \
  "$task_vector_root/embedding-eval-venv/bin/python" -m pytest -q \
  backend/tests/test_vector_runtime.py

该用例用合成测试文字,验证真实 small 的 512 维编码、HTTP 构建/发布/查询、pgvector 持久化及新进程重启后的空间、发布、命中和历史一致;不是领域分数或生产性能实验。Docker 容器、前端浏览器、RRF 和领域验收保持待办。

实现依据:pgvector 精确查询与余弦算子、混合维度存储约束、SQLAlchemy 集成。