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

103 lines
7.9 KiB
Markdown
Raw 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.
# 检索与上下文历史导出
本接口为后续实验保留当时的检索证据与配置,不重新检索、不加载 Embedding 或调用生成模型,也不生成质量分数。四种模式仍保持各自证据粒度,不能把条目与 chunk 混为同一评测样本。
## 请求与权限
`POST /api/retrieval/exports`,管理员会话、允许的 Origin 和 `X-CSRF-Token` 均为必需。普通用户 403,匿名/失效会话 401;错误 Origin 或 CSRF 为 403。继承 [认证契约](AUTH_API.md),不会返回用户、会话或凭据字段。
```json
{
"run_ids": ["00000000-0000-4000-8000-000000000001"],
"include_contexts": false
}
```
编号仅为格式示例,实际应先从管理员 `GET /api/retrieval/runs` 选择已存在的记录。`run_ids` 必须为 1–100 个不重复 UUID,大小写规范化后仍重复也拒绝;`include_contexts` 为严格布尔值,默认 false。额外字段、空列表和非法编号为 422。
明确选择避免导出无边界的全库记录。任一检索编号不存在即 404,不生成部分文件。可选上下文是这些检索记录关联的**全部已保存准备记录**,不含其他检索的上下文。
## 下载与 JSONL 契约
- 成功为 200,`Content-Type: application/x-ndjson; charset=utf-8`,`Content-Disposition: attachment; filename="nalati-retrieval-<export UUID>.jsonl"`。
- 响应固定 `Cache-Control: no-store` 和 `X-Content-Type-Options: nosniff`;`X-Records-SHA256` 等于文件首行的 `records_sha256`。
- UTF-8 编码,每行一个 JSON 对象,每行(包括末行)以 LF 结束;内容中的换行由 JSON 转义。不是 CSV,也不是一个 JSON 数组。
- 首行为 `record_type=manifest`。其余先为检索记录,再为上下文记录,各组按数据库 `created_at,id` 升序。请求 UUID 列表的顺序不影响记录区顺序。
首行字段:
| 字段 | 含义 |
|---|---|
| `schema_version` | 固定 `nalati-retrieval-export-v1` |
| `export_id`、`exported_at` | 本次导出 UUID 与带时区 UTC 时间 |
| `selection` | 排序后的检索 UUID 及是否包含上下文 |
| `counts` | 实际 `retrieval_runs` 与 `rag_contexts` 条数,不含首行 |
| `records_sha256` | **首行之后全部原始 UTF-8 字节**的 SHA-256,含每条末尾 LF |
| `snapshot_semantics` | 固定 `stored_history_not_current_corpus` |
| `generation_measurements` | `status=not_called`,`usage/cost/currency=null`;没有生成调用或费用测量,不能解释为零成本 |
检索行有 `record_type=retrieval_run`、`id`、`ranking_unit` 和 `payload`。`payload` 保留原已保存响应,不重新计算或修改分数、耗时、文本、来源及版本:
| `payload.mode` | `ranking_unit` | 保留的配置与证据 |
|---|---|---|
| `keyword` | `knowledge_item` | 条目 BM25、中文分词/标题权重、片段与来源版本 |
| `keyword_chunks` | `chunk` | 文本发布/切分/语料版本、正文 BM25、chunk 原文出处 |
| `vector` | `chunk` | 文本/向量发布、模型完整 revision、空间/维度与精确余弦配置 |
| `hybrid` | `chunk` | 同一 chunk 粒度、两路完整候选/排名/原始分数/贡献、RRF 与模型空间配置 |
上下文行有 `record_type=rag_context`、`id` 和原保存的 `payload`;保留准备参数、证据、排除原因、提示词版本、请求体及请求摘要。`model_called=false`,即使 `status=prepared` 也不是模型回答。原字段详见 [RAG_CONTEXT_API.md](RAG_CONTEXT_API.md)。不导出数据库 `created_by` 或认证表数据。
## 快照、容量与失败
- 检索与可选上下文通过**一个 PostgreSQL SQL 语句快照**读取,没有跨页读取造成的漂移;执行后新增的上下文留到下一次导出。
- 已修改、删除或重新发布的知识不会改写历史。导出成功只说明当时记录可读取,不说明它仍是当前有效发布或来源仍受支持。
- 每次最多 100 条检索、100 条关联上下文,完整 UTF-8 文件(含 manifest)最多 8 MiB。超量为 413,不静默截断;某个检索自身关联超过 100 条准备记录时,本版可先不含上下文导出,不能借此声称已导出全部准备记录。
- 输出前完成结构、编号/关联、数据库元数据和准备请求摘要校验;未知/缺失字段、非有限数值或关联异常为 409。它是历史格式一致性检查,不是来源真实性或事实正确性证明。
- 服务端先完整验证并组装文件,再返回下载响应;校验、容量或数据库失败不会先发出部分 JSONL。此接口不写入新的检索/上下文记录,不重建索引。
- 同一历史选择的记录区字节和摘要可重复;manifest 的导出 UUID/时间每次变化,因此不要比较整个文件的摘要。若新增上下文后再次启用 `include_contexts`,记录区也会变化。SHA-256 仅供字节完整性核对,不是审阅签名或质量指标。
`elapsed_ms` 沿用各模式原始计时范围,不是生成模型或完整浏览器交互耗时。模型、语料与参数不一致的记录不能只凭分数直接做实验对比。
## 调用与核对
已有管理员登录并按认证契约准备 Cookie 文件和当前 CSRF 后,可在仓库根目录调用。Cookie 文件、真实请求、JSONL 和实际日志均留在 Git 元数据目录或仓库外;下面的编号仍需替换成真实已保存的检索编号。
```bash
mkdir -p .git/exports
curl --fail-with-body --request POST http://127.0.0.1:8000/api/retrieval/exports \
--cookie "$COOKIE_JAR" \
--header 'Origin: http://localhost:5173' \
--header "X-CSRF-Token: $CSRF_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"run_ids":["00000000-0000-4000-8000-000000000001"],"include_contexts":true}' \
--output .git/exports/history.jsonl
```
仅在 HTTP 200 后按 JSONL 读取文件。核对摘要时不应重新序列化记录:
```python
import hashlib
import json
from pathlib import Path
header, records = Path(".git/exports/history.jsonl").read_bytes().split(b"\n", 1)
manifest = json.loads(header)
assert manifest["schema_version"] == "nalati-retrieval-export-v1"
assert hashlib.sha256(records).hexdigest() == manifest["records_sha256"]
rows = [json.loads(line) for line in records.rstrip(b"\n").split(b"\n")]
assert len(rows) == sum(manifest["counts"].values())
```
前端调用层 `services/exports.ts` 的 `exportRetrievalHistory` 返回 Blob、受限文件名和记录摘要,沿用同一 Cookie/CSRF、401 撤销及错误处理,不放入 localStorage。此阶段没有新增下载页面,页面入口由后续前端集成;调用层测试是模拟网络的契约测试,不冒充浏览器下载验收。
## 验证与撤回
新增专项覆盖混合模式原始快照、可选准备记录、无命中、软删除后历史、摘要稳定性、权限/CSRF、未知字段/关联损坏拒绝、字节/条数上限、并发新增上下文以及真实 HTTP 后端重启后的同一记录区。坐标 fixture 仅用于导出协议,不构成真实领域模型实验。专项命令 `pytest -q backend/tests/test_exports.py`:**30 passed**;完整 `pytest -q backend/tests`:**254 passed, 1 skipped**,skip 为显式启用的真实 Embedding 用例,本阶段没有重跑模型。`ruff check backend`、`ruff format --check backend` 通过;前端在 `frontend/` 执行 `npm run test:session`:**3 passed**,`npm run build` 通过。
数据库不新增迁移,继续为 `0007_rag_contexts`。撤回导出路由、服务、调用层即可停止新下载;原检索/上下文历史与已保存文件不变,不为回滚删除数据库记录。
### Responses 与旧准备快照
新准备记录的 `completion.protocol=responses`,导出按 Responses 重建请求体并核对哈希;旧记录没有 protocol,继续按原 Chat Completions 配置重建。读取与导出不补写旧字段,不改变旧 payload 或记录字节;同一文件可包含两种协议。该变化仍不代表生成模型已调用,manifest 的生成用量和费用继续为未采集。