Files

117 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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,不回退占位向量或伪造空结果;关键词和管理元数据接口可继续使用。启用前按 [本地评估说明](LOCAL_EMBEDDING_EVALUATION.md) 安装独立 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 配置仍按认证说明设置):
```bash
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` 是向量版本,不能与文本发布历史混用。
公开检索请求示例:
```json
{"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 数学、空间隔离、并发与回滚,绝不代表实际模型质量。真实模型贯通用例显式启用,在独立模型环境运行:
```bash
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 精确查询与余弦算子](https://github.com/pgvector/pgvector#querying)、[混合维度存储约束](https://github.com/pgvector/pgvector#can-i-store-vectors-with-different-dimensions-in-the-same-column)、[SQLAlchemy 集成](https://github.com/pgvector/pgvector-python#sqlalchemy)。