Make the governed RAG evidence path executable end to end
Some checks failed
verify / verify (push) Has been cancelled
Some checks failed
verify / verify (push) Has been cancelled
Separate local parsing from model indexing, bind review decisions to immutable manifests, persist vectors behind active profiles, and expose retrieval, chat, evaluation, and document workflows through the React workbench. Constraint: Live Bailian authentication currently fails for all three configured capabilities Rejected: Direct upload-to-embedding flow | bypasses local review and manifest binding Confidence: high Scope-risk: broad Directive: Keep private-data deployment blocked until authentication, RBAC, and separate database roles land Tested: make verify; fresh and replay Docker document smoke; worker recovery smoke; frozen synthetic evaluation; migration 0003-0004 roundtrip Not-tested: Successful live Bailian calls, OCR, real multi-user authorization
This commit is contained in:
112
docs/07-retrieval-worker-evaluation-runbook.md
Normal file
112
docs/07-retrieval-worker-evaluation-runbook.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 正式检索、Worker 与评测运行手册
|
||||
|
||||
本文验证已落地的可运行切片:正式 pgvector 检索与重排接口、PostgreSQL 租约栅栏,
|
||||
以及可复现的 synthetic 检索评测。它不把 synthetic 指标等同于真实地质语料效果,
|
||||
也不表示当前百炼凭据已经通过线上验收。
|
||||
|
||||
## 1. 启动并准备合成知识库
|
||||
|
||||
```bash
|
||||
bash scripts/init-local-secrets.sh
|
||||
docker compose up -d --build web
|
||||
docker compose --profile tools run --rm seed-demo-offline
|
||||
docker compose ps --all
|
||||
```
|
||||
|
||||
`migrate` 是一次性任务,显示 `Exited (0)` 表示 Alembic 已成功升级后正常退出。持续运行的
|
||||
`db/model-gateway/api/gateway/web` 应为 `healthy`,且只有 Web 发布
|
||||
`127.0.0.1:8000`。
|
||||
|
||||
## 2. 验证正式 Retrieval API
|
||||
|
||||
```bash
|
||||
curl -sS -X POST http://127.0.0.1:8000/api/v1/retrieval/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"knowledge_base_id":"3acd3785-970b-55f7-a669-9eb4695e27eb",
|
||||
"query":"花岗斑岩铜矿化特征",
|
||||
"vector_top_k":50,
|
||||
"rerank_top_n":10
|
||||
}'
|
||||
```
|
||||
|
||||
成功响应应包含:
|
||||
|
||||
- 服务端生效的 embedding profile hash、模型和 1024 维契约;
|
||||
- `vector_rank/vector_score` 与 `rank/rerank_score`;
|
||||
- 稳定且不透明的 `citation_id`、文档 ID、章节、页码和安全片段;
|
||||
- embedding、数据库、rerank 和总耗时,以及请求 trace ID;
|
||||
- `rerank_status=applied`;重排不可用时则保留初召回并明确标记 `degraded`。
|
||||
|
||||
请求体不能提交 access scope。当前 synthetic 身份由服务器固定映射到 synthetic 知识库;候选
|
||||
SQL 在 `LIMIT` 前同时过滤知识库、授权 scope、当前激活版本、云审批、READY assignment、
|
||||
profile 和 searchable 状态。
|
||||
|
||||
浏览器访问 <http://127.0.0.1:8000/retrieval> 可查看同一正式接口的 React 检索实验室。
|
||||
|
||||
## 3. 运行冻结配置评测
|
||||
|
||||
```bash
|
||||
docker compose --profile tools run --rm seed-demo-offline \
|
||||
python -m app.tools.evaluate_demo \
|
||||
/demo/demo_documents.jsonl /demo/demo_queries.jsonl
|
||||
```
|
||||
|
||||
输出是单行 JSON 工件,至少包含:
|
||||
|
||||
- corpus/query set SHA-256;
|
||||
- active embedding profile hash;
|
||||
- vector/rerank/cutoff 参数和 bootstrap seed;
|
||||
- 完整冻结配置 SHA-256;
|
||||
- 每题排序及 Hit、Recall、MRR、nDCG、CompleteHit、EvidenceGroupRecall;
|
||||
- 聚合指标和固定 seed 的 95% bootstrap 区间。
|
||||
|
||||
公开样例的 9 个可回答问题当前应达到 `Hit@3=1.0`。这是为了验证流水线和指标实现的
|
||||
synthetic 基线,不是论文最终质量结论;正式结论必须使用冻结的开发集/盲测集和真实合法语料。
|
||||
任何 top-k 未判定候选都会使正式评分失败,不能静默按 0 处理。
|
||||
|
||||
## 4. 验证 Worker 并发租约与 fencing
|
||||
|
||||
```bash
|
||||
docker compose --profile tools run --rm worker-smoke
|
||||
```
|
||||
|
||||
预期输出:
|
||||
|
||||
```json
|
||||
{
|
||||
"claim_winners": 1,
|
||||
"expired_lease_rejected": true,
|
||||
"recovery_claim_winners": 1,
|
||||
"recovery_token_rotated": true,
|
||||
"stale_fence_rejected": true,
|
||||
"status": "ok",
|
||||
"terminal_status": "SUCCEEDED"
|
||||
}
|
||||
```
|
||||
|
||||
工具在真实 PostgreSQL 中创建一条随机 capability 的 synthetic 任务,让两个领取者并发竞争,
|
||||
验证只有一个领取成功、伪造 token 和已过期租约均无法心跳、过期任务只能由一个新 Worker
|
||||
使用旋转后的 token 重领并完成,最后删除该验证行。Worker
|
||||
心跳配置被强制限制为不超过租约的三分之一。该工具证明队列运行时和数据库 fence;具体文档
|
||||
解析/向量化 handler 仍需按各自业务验收,不可据此推断完整入库链已经完成。
|
||||
|
||||
## 5. 真实百炼边界
|
||||
|
||||
正式非 synthetic profile 会由 API/Worker 通过内部 token 调用 `model-gateway`,只有后者持有
|
||||
百炼 Key 和公网出口。当前本机凭据对 `text-embedding-v4`、`qwen3-rerank` 和
|
||||
`deepseek-v4-flash` 均返回供应商鉴权失败;离线检索成功不代表线上三模型成功。更换为有效、
|
||||
同一北京工作空间且具备模型权限的新 Key 后,先按
|
||||
[Stage 1 运行手册](05-stage1-runbook.md)执行 `provider-smoke`,三项均成功后才运行真实 seed。
|
||||
|
||||
## 6. 提交前门禁
|
||||
|
||||
```bash
|
||||
make verify
|
||||
docker compose --profile tools run --rm worker-smoke
|
||||
docker compose --profile tools run --rm seed-demo-offline \
|
||||
python -m app.tools.evaluate_demo \
|
||||
/demo/demo_documents.jsonl /demo/demo_queries.jsonl
|
||||
```
|
||||
|
||||
任何输出都不得包含 API Key、内部 token、数据库密码、DSN 或私有文档正文。
|
||||
74
docs/08-grounded-chat-runbook.md
Normal file
74
docs/08-grounded-chat-runbook.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Grounded Chat 运行与引用验证手册
|
||||
|
||||
本手册验证“正式检索 → 证据约束回答 → 引用事件”的单轮问答闭环。默认 synthetic profile
|
||||
不会调用百炼;非 synthetic profile 才经内部 `model-gateway` 调用 `deepseek-v4-flash`。
|
||||
|
||||
## 1. 前置服务
|
||||
|
||||
```bash
|
||||
docker compose up -d --build web
|
||||
docker compose --profile tools run --rm seed-demo-offline
|
||||
curl -sS http://127.0.0.1:8000/health/ready
|
||||
```
|
||||
|
||||
浏览器入口为 <http://127.0.0.1:8000/chat>,接口文档位于
|
||||
<http://127.0.0.1:8000/docs>。
|
||||
|
||||
## 2. 直接验证 SSE
|
||||
|
||||
```bash
|
||||
curl -sS -N -X POST http://127.0.0.1:8000/api/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"knowledge_base_id":"3acd3785-970b-55f7-a669-9eb4695e27eb",
|
||||
"question":"花岗斑岩铜矿化有哪些典型蚀变特征?",
|
||||
"vector_top_k":20,
|
||||
"rerank_top_n":5,
|
||||
"max_tokens":512
|
||||
}'
|
||||
```
|
||||
|
||||
成功流必须严格满足:
|
||||
|
||||
```text
|
||||
meta(seq=1)
|
||||
retrieval(seq=2)
|
||||
delta(seq=3..n)
|
||||
citations
|
||||
usage
|
||||
done
|
||||
```
|
||||
|
||||
`done` 或 `error` 必须且只能出现一个。每个事件都是 JSON,`seq` 单调递增。`retrieval`
|
||||
先返回本次授权范围内的证据和检索耗时;`citations` 只允许引用同一轮 source map 中的
|
||||
`[S1]...[Sn]`。页面把回答、文件名和片段全部按纯文本显示,不执行 HTML。
|
||||
|
||||
## 3. 回答模式
|
||||
|
||||
| 模式 | 含义 | 是否调用云模型 |
|
||||
|---|---|---|
|
||||
| `grounded` + `synthetic_extractive` | 用已批准 synthetic 证据生成确定性摘录答案 | 否 |
|
||||
| `grounded` + `cloud_grounded` | 模型回答包含通过校验的本轮引用 | 是 |
|
||||
| `retrieval_only` | 模型没有给出合法引用,服务端退回安全证据摘录 | 云调用可能失败或输出不合格 |
|
||||
| `refused` | 当前授权检索没有足以支持回答的证据 | 否 |
|
||||
| `error` | 生成供应商或流契约失败;事件中不回显供应商正文 | 视失败阶段而定 |
|
||||
|
||||
文档片段在 Prompt 中被序列化为“不可信证据数据”,其中出现的命令、Prompt injection 或
|
||||
凭证索取指令均不能成为系统指令。模型输出会在公开前检查引用;越界、畸形和大小写伪造标签
|
||||
会被删除。引用 ID 合法只证明它来自本轮授权 source map,不自动证明自然语言声明在语义上
|
||||
完全受到该证据支持;后者必须进入人工标注和引用精确率评测。
|
||||
|
||||
## 4. 取消与失败
|
||||
|
||||
React 页面的“停止生成”会中止当前 fetch,后端取消会继续传播到内部模型流。当前接口是单次
|
||||
POST SSE,不保存会话,也不支持 `Last-Event-ID` 重放;持久会话、断线重放和 durable
|
||||
completion 是后续扩展,不得把本切片描述成完整多轮聊天系统。
|
||||
|
||||
生成前的检索/授权错误仍返回 `application/problem+json`。流开始后的错误只能用终态
|
||||
`event: error` 表达,不能再修改 HTTP 状态码。
|
||||
|
||||
## 5. 真实百炼验证
|
||||
|
||||
先运行 `provider-smoke` 并确认 Embedding、Rerank、Chat 三项均成功,再对百炼验证知识库发起
|
||||
问答。synthetic 离线成功不能代替真实模型验收。API 和前端永远不持有百炼 Key;只有隔离的
|
||||
`model-gateway` 持 Key 和公网出口。
|
||||
198
docs/09-document-ingestion-indexing-runbook.md
Normal file
198
docs/09-document-ingestion-indexing-runbook.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 文档上传、审核、向量化与检索运行手册
|
||||
|
||||
本手册用于验证当前已经落地的 synthetic 产品主链:浏览器上传公开虚构文档,经本地安全解析、
|
||||
人工绑定 outbound manifest 审批、异步向量化和原子激活后,能够被正式 Retrieval API 检索。
|
||||
这条链路已经在 Docker 中连续两次端到端通过;它证明工程状态机、幂等性和向量写库契约可运行,
|
||||
但不代替真实百炼授权、真实地质语料质量、PDF/OCR 或最终论文盲测验收。
|
||||
|
||||
## 1. 最短启动与验证
|
||||
|
||||
首次克隆后初始化仅存在于本机、已被 Git 忽略的 Secret 文件:
|
||||
|
||||
```bash
|
||||
make setup-hooks
|
||||
bash scripts/init-local-secrets.sh
|
||||
make up
|
||||
make status
|
||||
make seed-offline
|
||||
make smoke-document
|
||||
```
|
||||
|
||||
`make up` 等价于 `docker compose up -d --build`,会启动数据库、两个 Worker、模型隔离网关、
|
||||
API、入口 Gateway 和 Web,并等待迁移与上传卷初始化成功。`make seed-offline` 创建 synthetic
|
||||
profile 和公开虚构检索基线;`make smoke-document` 使用
|
||||
`data/samples/public/upload_demo.md` 加入本次运行 nonce 后,自动完成一次全新的上传、解析、审批、
|
||||
向量化、激活和检索;随后在同一次运行中重放相同声明与内容,验证 ID 不变且不会重复建任务。
|
||||
smoke 中的 `SYNTHETIC_REVIEW_APPROVED` 是只针对公开 fixture 的自动契约检查;真实资料必须由
|
||||
有权限的审核人通过 `/documents` 检查 exact cloud text 与 manifest,不能自动批准。
|
||||
|
||||
成功的 smoke 输出只包含不透明 ID、状态、rank、citation、模型名和重排状态,不输出正文或
|
||||
Secret。历史上曾用固定 fixture 连续两次验证相同 document/document-version ID,数据库计数均保持:
|
||||
|
||||
| 对象 | 幂等计数 |
|
||||
|---|---:|
|
||||
| document version | 1 |
|
||||
| chunk | 1 |
|
||||
| vector assignment | 1 |
|
||||
| background job | 2(解析 1 + 向量化 1) |
|
||||
| model invocation | 1 |
|
||||
|
||||
当前 smoke 每次命令都会创建带随机 nonce 的新文档,避免历史 READY 数据让当前故障误通过;
|
||||
同一次命令末尾会重放同一幂等键与内容,并要求 upload、document、parse job、active version 身份
|
||||
保持不变。输出中的 `replay_confirmed=true` 才表示该双重检查完成。该结果只适用于提交的
|
||||
synthetic fixture 和当前冻结配置;换文件、切分 profile 或 embedding profile 会生成不同身份。
|
||||
|
||||
## 2. `Exited (0)` 不是服务暂停
|
||||
|
||||
`docker compose ps -a` 中以下两个容器应当成功运行一次后退出:
|
||||
|
||||
- `migrate`:执行 `alembic upgrade head`;`Exited (0)` 表示迁移已经应用成功。
|
||||
- `upload-init`:以最小临时权限初始化上传卷属主和目录;`Exited (0)` 表示初始化成功。
|
||||
|
||||
它们不是长期服务,不应保持 `Up`,也不应配置 `restart: always`。只有非零退出码才代表启动
|
||||
失败。长期容器的期望状态如下:
|
||||
|
||||
| 服务 | 期望状态 | 说明 |
|
||||
|---|---|---|
|
||||
| `db` | `Up (healthy)` | PostgreSQL + pgvector |
|
||||
| `model-gateway` | `Up (healthy)` | 唯一持百炼 Key 与公网出口 |
|
||||
| `api` / `gateway` / `web` | `Up (healthy)` | API、固定上游入口、浏览器页面 |
|
||||
| `worker-local` | `Up` | 只处理 `document_parse`,挂载上传卷,无模型网络/Token |
|
||||
| `worker-model` | `Up` | 只处理 `embedding`,可访问模型网,不挂载上传卷 |
|
||||
|
||||
两个 Worker 的信任边界、Secret、网络和卷约束见
|
||||
[ADR-0007](adr/0007-split-local-and-model-workers.md)。
|
||||
|
||||
排查时使用:
|
||||
|
||||
```bash
|
||||
make status
|
||||
docker compose logs --tail=200 migrate upload-init api worker-local worker-model
|
||||
curl -sS http://127.0.0.1:8000/health/ready
|
||||
```
|
||||
|
||||
不要因为 Alembic 日志最后停在 `Will assume transactional DDL` 就判断服务挂起;先查看容器
|
||||
退出码、API readiness 和依赖服务状态。
|
||||
|
||||
## 3. 浏览器操作
|
||||
|
||||
访问 <http://127.0.0.1:8000/documents>。页面按以下顺序工作:
|
||||
|
||||
1. 在浏览器本地校验扩展名、MIME 和 100 MiB 上限,并计算 SHA-256。
|
||||
2. 创建带随机 `Idempotency-Key` 的上传声明,随后流式写入隔离上传卷。
|
||||
3. 完成上传并轮询 `PARSE_DOCUMENT` 任务;本地 Worker 不调用任何云模型。
|
||||
4. 展示 `display_text`、拟出域的 `cloud_text`、来源锚点和 outbound manifest。
|
||||
5. 审核人确认 exact manifest 后批准,或选择稳定原因码拒绝。
|
||||
6. 批准后轮询 `EMBED_DOCUMENT`;向量完整性通过后文档才显示 `READY`。
|
||||
7. 转到 <http://127.0.0.1:8000/retrieval> 检索刚刚激活的内容,或到
|
||||
<http://127.0.0.1:8000/chat> 验证带引用的单轮问答。
|
||||
|
||||
当前页面的“停止”只会停止浏览器上传请求或任务轮询。后台任务一旦提交,不会因关闭页面而
|
||||
被强制取消;Worker 依靠租约、心跳、fencing token 和 reaper 恢复。
|
||||
|
||||
## 4. 状态机与不可绕过的门禁
|
||||
|
||||
```text
|
||||
upload CREATED
|
||||
-> STORED
|
||||
-> COMPLETED
|
||||
-> document QUARANTINED_LOCAL_REVIEW
|
||||
-> PARSE_DOCUMENT / worker-local
|
||||
-> LOCAL_PARSED_PENDING_CLOUD_REVIEW
|
||||
-> OCR_REQUIRED 或 FAILED(禁止继续)
|
||||
-> 人工审核 exact outbound manifest
|
||||
-> REJECTED(终止)
|
||||
-> CLOUD_APPROVED
|
||||
-> EMBED_DOCUMENT / worker-model
|
||||
-> PENDING -> EMBEDDING -> READY
|
||||
-> version READY + document.active_version_id 原子切换
|
||||
-> document READY + chunks searchable
|
||||
-> 正式 Retrieval 候选
|
||||
```
|
||||
|
||||
上传成功、解析成功和审批通过都不等于“已可检索”。正式 Retrieval 的 SQL 在 `LIMIT` 前要求
|
||||
知识库、服务端授权 scope、active version、`CLOUD_APPROVED`、正确 profile、READY assignment
|
||||
和 `searchable=true` 同时成立。审批使用 `review_revision` 乐观并发控制,并绑定 exact outbound
|
||||
manifest hash;旧页面、变化后的正文或变化后的 profile 不能复用审批。
|
||||
|
||||
当前 TXT、Markdown、DOCX 走确定性本地解析;PDF 会 fail closed 为 `OCR_REQUIRED`。这表示系统
|
||||
没有把 PDF/OCR 或地质图空间理解伪装成已完成能力。
|
||||
|
||||
## 5. API 级手工检查
|
||||
|
||||
Swagger 位于 <http://127.0.0.1:8000/docs>。入库主链使用以下接口:
|
||||
|
||||
| 方法与路径 | 用途 |
|
||||
|---|---|
|
||||
| `POST /api/v1/document-uploads` | 声明文件名、MIME、大小和 SHA-256;要求 `Idempotency-Key` |
|
||||
| `PUT /api/v1/document-uploads/{id}/content` | 上传原始字节,入口上限 100 MiB |
|
||||
| `POST /api/v1/document-uploads/{id}/complete` | 校验摘要并创建文档/解析任务 |
|
||||
| `GET /api/v1/document-jobs/{id}` | 轮询解析或向量化任务 |
|
||||
| `GET /api/v1/documents` | 查看文档状态 |
|
||||
| `GET /api/v1/documents/{id}/review-bundle` | 分页查看解析结果与 manifest |
|
||||
| `POST /api/v1/documents/{id}/review-decisions` | 使用 revision + manifest 批准或拒绝 |
|
||||
| `POST /api/v1/retrieval/search` | 验证 READY 文档可检索 |
|
||||
|
||||
推荐用 `make smoke-document` 作为可重复验收;手工拼装请求时不要把文件正文、数据库 DSN、
|
||||
内部 Token 或百炼 Key 写进 shell 历史、截图或工单。
|
||||
|
||||
## 6. 常见错误排查
|
||||
|
||||
| 现象/错误 | 含义 | 检查与处理 |
|
||||
|---|---|---|
|
||||
| `migrate` 或 `upload-init` 为 `Exited (0)` | 正常一次性任务终态 | 检查长期服务是否 Up/healthy,不要反复重启一次性任务 |
|
||||
| `UPLOAD_*`、摘要/大小不匹配 | 声明与实际字节不一致或上传状态冲突 | 重新选择原文件;不要修改声明后复用 upload ID |
|
||||
| `OCR_REQUIRED` | PDF 当前不在可靠解析范围 | 换用 TXT/Markdown/DOCX 验证;等待 OCR/PDF adapter |
|
||||
| `REVIEW_REVISION_CONFLICT` | revision 或 manifest 已变化 | 刷新 review bundle,重新人工复核后提交 |
|
||||
| 文档停在待审核 | 安全门禁正常工作 | 在 `/documents` 检查 cloud text 和 manifest,再批准或拒绝 |
|
||||
| embedding job `FAILED` | profile、维度、模型调用或完整性校验失败 | 查看脱敏 error code 和 `worker-model` 日志;不要直接改 `searchable` |
|
||||
| 文档 READY 但检索不到 | scope/profile/active version 或 seed 基线不一致 | 先运行 `make seed-offline`,再检查 retrieval 的生效 profile 与 trace |
|
||||
| 百炼三能力返回 401 | Key、北京工作空间、端点或模型权限不匹配 | 按第 7 节重新做 provider smoke;401 不自动重试 |
|
||||
|
||||
日志和 API Problem 必须只包含稳定错误码、trace 和不透明 ID。若发现任何 Secret 或受限正文,
|
||||
立即停止真实调用、轮换凭证并运行 `make check-secrets`。
|
||||
|
||||
## 7. 切换到真实阿里云百炼
|
||||
|
||||
synthetic E2E 使用 fake embedding/rerank,不需要百炼。切换真实模式前必须完成:
|
||||
|
||||
1. 在百炼北京地域控制台撤销任何曾出现在聊天、日志或截图中的旧 Key。
|
||||
2. 创建具备 `text-embedding-v4`、`qwen3-rerank`、`deepseek-v4-flash` 权限的新 Key。
|
||||
3. 只把新 Key 写入已忽略的 `secrets/bailian_api_key`;真实工作空间 URL 只写本地部署配置。
|
||||
4. 确认 Key、专属工作空间域名、北京地域和计费方案属于同一空间。
|
||||
5. 重启 `model-gateway` 及调用方,运行:
|
||||
|
||||
```bash
|
||||
docker compose restart model-gateway api worker-model
|
||||
docker compose --profile tools run --rm provider-smoke
|
||||
```
|
||||
|
||||
只有 Embedding、Rerank、Chat 三项最小实际调用全部成功,才能运行真实 `seed-demo` 和真实文档
|
||||
向量化。当前已验证事实仍是三项请求到达供应商但均返回 401,因此真实百炼未验收;不能用
|
||||
synthetic smoke 的成功替代这项外部门禁。
|
||||
|
||||
三项探测成功后,先运行 `docker compose --profile tools run --rm seed-demo` 创建独立的百炼
|
||||
synthetic 知识库及 active profile,再在未提交的 `.env` 中设置
|
||||
`DOCUMENT_NAMESPACE_MODE=bailian`,重建 API 和模型 Worker,最后运行 fresh + replay smoke:
|
||||
|
||||
```bash
|
||||
docker compose --profile tools run --rm seed-demo
|
||||
docker compose up -d --force-recreate api worker-model
|
||||
make smoke-document
|
||||
```
|
||||
|
||||
此时上传声明的知识库/scope 仍由服务端固定选择,请求不能自行越权指定;输出中的
|
||||
`knowledge_base_id` 应为百炼 synthetic 命名空间,向量由 `text-embedding-v4` 生成并写入
|
||||
pgvector,检索经 `qwen3-rerank`。切回离线回归时把该配置恢复为 `fake` 并重建 API。
|
||||
在认证、RBAC 和资料出域审批完成前,这个开关只允许公开虚构资料,不能用于私有或真实报告。
|
||||
|
||||
API、浏览器、`worker-local`、seed/smoke 工具都不持百炼 Key。`worker-model` 只有内部 Worker
|
||||
Token,真正的百炼 Key 始终只存在于 `model-gateway`。任何真实资料还必须先完成权利、涉密和
|
||||
云处理审批;有效 Key 不等于有权把资料发送到云端。
|
||||
|
||||
## 8. 当前验证基线与剩余范围
|
||||
|
||||
截至 2026-07-13,提交前全量门禁记录为 296 项后端测试、53 项前端测试,并完成固定身份重放与
|
||||
fresh + replay 两类 Docker synthetic 产品链 E2E。正式毕业验收仍需完成真实百炼认证、PDF/OCR、真实
|
||||
授权语料、多租户/RBAC、至少 300 题正式双审盲测、备份恢复、性能/并发压测、论文定稿和答辩
|
||||
彩排。这些未完成项不能由本手册的 smoke 结果替代。
|
||||
56
docs/adr/0006-deterministic-local-ingestion.md
Normal file
56
docs/adr/0006-deterministic-local-ingestion.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# ADR-0006:采用确定性本地解析、切分与出域清单
|
||||
|
||||
- **状态:** accepted
|
||||
- **日期:** 2026-07-13
|
||||
|
||||
## 背景
|
||||
|
||||
文档进入百炼向量模型前,系统必须能证明“发送了哪段文本、来自哪个版本和页面、由什么配置
|
||||
产生、是否经过明确审批”。如果解析和切分结果随运行变化,embedding cache、引用锚点、审批
|
||||
manifest 和实验结果都会失去可复现性。PDF、DOCX 和扫描图件的能力也不能被模糊成同一种
|
||||
“已解析”状态。
|
||||
|
||||
## 决策
|
||||
|
||||
第一版建立纯本地、无模型调用的确定性入库核心:
|
||||
|
||||
- 严格验证文件大小、扩展名、声明 MIME 与内容签名;用户文件名永不作为存储路径。
|
||||
- TXT 支持严格 UTF-8/UTF-16;Markdown 恢复标题层级;DOCX 仅在 ZIP/XML 安全上限内提取
|
||||
标题、段落和表格行。
|
||||
- 不手写 PDF 文本或空间解析。没有可靠 parser/OCR adapter 时,PDF 明确进入
|
||||
`OCR_REQUIRED`,不产生切片或出域清单。
|
||||
- 规范化文本按结构块优先切分,默认 target 512、hard max 800、overlap 64。Tokenizer
|
||||
将 `ZK1203` 等字母数字地质标识符视为一个 token;块边界只能把窗口扩展到 hard max 内。
|
||||
- `display_text`、`cloud_text`、`embedding_text` 分离;embedding 输入固定为版本化前缀加
|
||||
已审批 cloud text。
|
||||
- parser、normalization、chunk、cloud policy 和 embedding 配置均计算 profile hash;规则变化
|
||||
生成新版本或 cache epoch,不能错误复用旧向量和审批。
|
||||
- chunk ID、source anchor 和 outbound manifest 由输入 hash、配置 hash、ordinal 与源范围
|
||||
确定性派生。每个锚点保留文档版本、页、块、行和字符范围;DOCX 无渲染引擎时物理页为
|
||||
`null`,不能伪造页码。
|
||||
- 任何外发必须在 manifest hash 与审核请求完全匹配后发生。上传/解析阶段无百炼调用。
|
||||
|
||||
## 安全限制
|
||||
|
||||
DOCX adapter 拒绝路径穿越、符号链接、重复条目、加密标志、宏、ActiveX、嵌入对象、DTD、
|
||||
XML entity、超大条目、过多条目和异常压缩比。错误只返回稳定 code,不包含文件名、正文、
|
||||
路径、密钥形态或底层异常。
|
||||
|
||||
凭证形态出现在上传文本时默认 fail closed,以防误把配置文件或日志当知识文档外发。未来如果
|
||||
确有合法语料包含类似字符串,必须新增受审计的本地脱敏策略,不能简单关闭该门禁。
|
||||
|
||||
## 被否决方案
|
||||
|
||||
1. **按固定字符数随意切片:** 中文、地质编号、表格和章节边界不可复现,无法稳定评测。
|
||||
2. **直接把原文同时用于展示、向量和生成:** 无法证明脱敏和出域审批覆盖的准确文本。
|
||||
3. **用标准库或正则手写 PDF 解析:** 不能可靠处理字体映射、多栏顺序、扫描页和加密状态。
|
||||
4. **把 OCR 图例视为空间理解:** OCR 只能识别局部文字,不恢复地图拓扑、比例和几何关系。
|
||||
|
||||
## 后续约束
|
||||
|
||||
- 调整 token 规则、target/max/overlap、前缀、脱敏策略或 parser 行为时,必须修改 profile hash
|
||||
版本并重跑 golden fixture、manifest、向量缓存和引用回归。
|
||||
- 引入 PyMuPDF、OCR 或多模态模型需要独立 adapter、依赖/镜像评审和 ADR;不能替换 raw
|
||||
artifact,必须保留新 revision。
|
||||
- 解析成功不等于可出域;只有 `CLOUD_APPROVED` 且 manifest/profile 绑定一致的 chunk 才能
|
||||
进入 Embedding、Rerank 或 Chat。
|
||||
78
docs/adr/0007-split-local-and-model-workers.md
Normal file
78
docs/adr/0007-split-local-and-model-workers.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# ADR-0007:拆分本地解析 Worker 与模型 Worker 的信任边界
|
||||
|
||||
- **状态:** accepted
|
||||
- **日期:** 2026-07-13
|
||||
|
||||
## 背景
|
||||
|
||||
文档入库同时涉及两类高风险资源:原始上传文件,以及访问云模型的能力。若一个通用 Worker
|
||||
同时挂载上传卷、内部模型 Token 和模型网络,那么解析器漏洞、恶意 DOCX/PDF 或任务 payload
|
||||
缺陷可能把未经审批的原文直接发送到云端。仅靠业务代码中的状态判断,无法形成可独立验证的
|
||||
最小权限边界。
|
||||
|
||||
本项目仍采用模块化单体和同一个后端镜像;拆分的是运行身份、capability、网络、卷和 Secret,
|
||||
不是拆成两套业务服务或数据库。两个 Worker 当前仍共用同一个 PostgreSQL 应用角色,因此该拆分
|
||||
只隔离上传卷、模型 Token 和网络能力,不构成数据库授权边界。
|
||||
|
||||
## 决策
|
||||
|
||||
Compose 运行两个互斥能力的长期 Worker:
|
||||
|
||||
| 边界 | `worker-local` | `worker-model` |
|
||||
|---|---|---|
|
||||
| capability | `document_parse` | `embedding` |
|
||||
| 数据库网络 | 有 | 有 |
|
||||
| 内部模型网络 | 无 | 有 |
|
||||
| 公网 egress | 无 | 无;只能访问 internal `model-gateway` |
|
||||
| 上传卷 | 有 | 无 |
|
||||
| 数据库 app Secret | 有 | 有 |
|
||||
| model-gateway Token | 无 | Worker 身份 Token |
|
||||
| 百炼 API Key | 无 | 无 |
|
||||
|
||||
`model-gateway` 是唯一持有百炼 API Key 和普通 egress 网络的进程;它不连接数据库、不挂载上传
|
||||
卷,也不发布宿主机端口。`worker-model` 只能发送数据库中已通过 manifest 审批的 `cloud_text`,
|
||||
不能读取原始文件。`worker-local` 能读取隔离上传卷,但没有模型网络与 Token,即使解析器被恶意
|
||||
文件影响,也缺少直接调用模型的凭据和路由。
|
||||
|
||||
两个 Worker 均以非 root 后端用户运行,根文件系统只读,使用临时 `/tmp`、`no-new-privileges`
|
||||
和 `cap_drop: ALL`,不暴露端口。它们共用 PostgreSQL 任务队列,但领取 SQL 只匹配各自
|
||||
`required_capability`。心跳、失败回写、阶段提交和最终激活必须匹配 `job_id + lease_owner +
|
||||
lease_token` 且租约仍有效,避免旧进程覆盖已重领任务。
|
||||
|
||||
上传卷初始化由一次性 `upload-init` 完成。该容器临时以 root 运行,但 `network_mode: none`、根
|
||||
文件系统只读,只保留初始化目录所需的最小文件能力,成功后正常 `Exited (0)`;长期 API 和
|
||||
`worker-local` 不获得这些额外 capability。
|
||||
|
||||
## 被否决方案
|
||||
|
||||
1. **单一通用 Worker 同时挂卷和模型 Token:** 部署简单,但把未审核原文与云出口放在同一
|
||||
攻击面,违背 manifest 审批门禁。
|
||||
2. **只靠 Python `if review_state == CLOUD_APPROVED`:** 状态判断仍然需要,但不能替代网络、
|
||||
Secret 和文件系统的纵深隔离。
|
||||
3. **每种任务拆成独立代码仓库/数据库:** 当前单机规模没有对应收益,会引入分布式事务、双写、
|
||||
部署和备份复杂度。
|
||||
4. **让 `worker-model` 直接访问百炼公网:** 会把供应商协议、Key 和公网出口扩散到业务进程,
|
||||
无法集中轮换、限流和脱敏错误。
|
||||
|
||||
## 影响
|
||||
|
||||
- 优点:原始文件与云调用能力不能在单个长期 Worker 中汇合;Compose 契约可直接验证卷、网络
|
||||
和 Secret;不同任务可独立扩缩容。
|
||||
- 代价:需要维护两个 Worker 服务和 capability 路由;跨阶段任务只能通过数据库中的已审批
|
||||
工件交接,不能依赖本地临时文件。
|
||||
- 限制:Docker internal network 是重要纵深防线,但不是形式化沙箱;API 与两个 Worker 当前共享
|
||||
数据库应用角色,文档 Actor 也仍是服务器固定的 synthetic 身份。真实多用户/私有数据部署必须
|
||||
先落地认证、对象级 RBAC、分离数据库角色或受控存储过程,并结合主机防火墙、出口 allowlist、
|
||||
集中 Secret Manager、容器运行时策略和审计。
|
||||
|
||||
## 后续约束
|
||||
|
||||
- 新增 OCR Worker 时默认归入本地高风险解析边界,除非独立 ADR 证明它需要模型出口;OCR
|
||||
结果仍须重新生成并审批 outbound manifest。
|
||||
- 任何服务若要同时获得上传卷与模型网络/Token,必须先做威胁建模、更新 ADR 并增加 Compose
|
||||
契约测试,不能通过临时排障静默扩大权限。
|
||||
- 新任务类型必须声明唯一 `required_capability`,并验证错误能力的 Worker 无法领取。
|
||||
- `DOCUMENT_NAMESPACE_MODE` 只能由服务端部署配置选择 `fake` 或 `bailian` synthetic 命名空间;
|
||||
请求不得携带任意 scope 绕过授权。真实多租户上线前必须用认证/RBAC 替代固定 Actor。
|
||||
- 变更 Worker 网络、卷、Secret 或部署拓扑时,必须重跑 document pipeline Docker E2E、租约
|
||||
fencing smoke、Secret 扫描和 Compose 安全契约。
|
||||
@@ -9,3 +9,5 @@ ADR 用于记录会长期影响系统的技术决策。状态使用 `proposed`
|
||||
- [0003-text-first-scope.md](0003-text-first-scope.md):第一版采用文本优先边界,不宣称地质图空间理解。
|
||||
- [0004-secretless-web-ingress.md](0004-secretless-web-ingress.md):用无 Secret 的 Nginx Web 与固定上游 gateway 隔离浏览器、API 和数据库网络。
|
||||
- [0005-isolate-model-egress.md](0005-isolate-model-egress.md):用独立 Model Gateway 隔离百炼 Key、模型出口与数据库感知服务。
|
||||
- [0006-deterministic-local-ingestion.md](0006-deterministic-local-ingestion.md):冻结本地解析、512/800/64 切分、文本分离和 outbound manifest 契约。
|
||||
- [0007-split-local-and-model-workers.md](0007-split-local-and-model-workers.md):拆分本地解析与模型 Worker 的网络、卷、Secret 和 capability 信任边界。
|
||||
|
||||
Reference in New Issue
Block a user