Isolate cloud model access before enabling product RAG workflows
Some checks failed
verify / verify (push) Has been cancelled
Some checks failed
verify / verify (push) Has been cancelled
The API and ingestion tools now use a fixed internal model gateway while governed profiles, embedding cache assignments, traceable citations, and stable API errors establish the boundaries required by later workflows. Constraint: The current Alibaba Cloud workspace rejects all three live model calls with authentication failures Rejected: Give the API or seed tools the Bailian key and direct egress | combines database access, cloud credentials, and public network access Rejected: Mix offline and Bailian vectors in one demo namespace | makes profile activation and retrieval ambiguous Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep Bailian credentials and egress exclusive to model-gateway and create a new immutable profile hash for any embedding identity change Tested: make verify; 121 backend tests; 14 frontend tests; fresh and populated Alembic upgrade-downgrade-upgrade; two idempotent offline seeds; Docker health and HTTP retrieval; isolated provider smoke Not-tested: Successful live Bailian responses because the supplied workspace credential currently fails authentication
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
|---|---|
|
||||
| 课题 | 基于 RAG 的地质找矿知识问答系统构建与应用 |
|
||||
| 学科方向 | 大数据分析 |
|
||||
| 文档版本 | v1.0-design |
|
||||
| 状态 | 设计基线,待实现验证 |
|
||||
| 更新日期 | 2026-07-11 |
|
||||
| 文档版本 | v1.1-implementation-sync |
|
||||
| 状态 | 设计基线;安全运行骨架已部分实现,产品主链路未完成 |
|
||||
| 更新日期 | 2026-07-13 |
|
||||
| 后端 | Python + FastAPI |
|
||||
| 前端 | React + TypeScript |
|
||||
| 向量存储 | PostgreSQL + pgvector |
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
1. 仓库、提交历史、镜像层、前端包、测试数据和日志都不能出现真实 Key。
|
||||
2. 开发环境从未提交的 Docker Secret 文件读取;生产环境从 Secret Manager 或编排平台读取。
|
||||
3. 前端永远不接触 Key,所有百炼请求由后端发起。
|
||||
3. 前端、入口 Gateway、业务 API、Worker、seed 和 smoke 工具永远不接触百炼 Key;所有百炼请求只由独立 `model-gateway` 发起。
|
||||
4. 日志不得记录 `Authorization` 请求头或完整模型请求体。
|
||||
5. 工作空间真实域名也只进入本地部署配置;文档统一使用 `<workspace-id>`。
|
||||
6. 每次提交前执行 `make verify`;仓库已配置本地钩子和 Gitea Actions 双重检查,Actions 需以远端 runner 首次成功记录作为生效证据。
|
||||
@@ -33,11 +33,15 @@
|
||||
|
||||
```text
|
||||
React/Nginx
|
||||
-> 无 Secret 入口 Gateway
|
||||
-> FastAPI API
|
||||
-> PostgreSQL + pgvector
|
||||
-> 阿里云百炼 Embedding / Rerank / Chat
|
||||
-> FastAPI Worker(与 API 共用同一 Python 代码镜像)
|
||||
-> 内部 model-gateway client
|
||||
-> Python Worker(与 API 共用同一 Python 代码镜像)
|
||||
-> 文档解析、分块、向量化和持久化任务
|
||||
-> 内部 model-gateway client
|
||||
model-gateway(唯一持有百炼 Key 和公网出口)
|
||||
-> 阿里云百炼 Embedding / Rerank / Chat
|
||||
```
|
||||
|
||||
默认模型链:
|
||||
@@ -51,7 +55,7 @@ deepseek-v4-flash
|
||||
-> 只依据证据回答并给出页码引用
|
||||
```
|
||||
|
||||
选择 PostgreSQL + pgvector 的原因是当前预计 1 万至 30 万切片,单机 PostgreSQL 已足够,并能把业务状态、元数据、向量和后台任务放在一个事务边界中。这样核心长期运行组件只有 Web、API、Worker、数据库,一条 `docker compose up -d --build` 即可启动。达到百万级切片或高吞吐边界后再评估 Qdrant,而不是在第一版提前承担双写一致性和第二套备份系统。
|
||||
选择 PostgreSQL + pgvector 的原因是当前预计 1 万至 30 万切片,单机 PostgreSQL 已足够,并能把业务状态、元数据、向量和后台任务放在一个事务边界中。为把数据库权限与云凭证/公网出口分开,运行拓扑额外保留一个很小的 `model-gateway` 安全边界;它复用后端镜像和领域端口,不把业务拆成多套数据库或分布式事务。达到百万级切片或高吞吐边界后再评估 Qdrant,而不是在第一版提前承担双写一致性和第二套备份系统。
|
||||
|
||||
## 2. 项目目标和边界
|
||||
|
||||
@@ -136,13 +140,14 @@ flowchart LR
|
||||
G -->|"internal data"| A["FastAPI API"]
|
||||
A --> D[("PostgreSQL + pgvector")]
|
||||
A --> F[("文件卷 / OSS 适配器")]
|
||||
A --> E["text-embedding-v4"]
|
||||
A --> R["qwen3-rerank"]
|
||||
A --> L["deepseek-v4-flash"]
|
||||
A -->|"Bearer + X-RAG-Caller: api"| M["内部 model-gateway"]
|
||||
M --> E["text-embedding-v4"]
|
||||
M --> R["qwen3-rerank"]
|
||||
M --> L["deepseek-v4-flash"]
|
||||
J["DB 持久化任务"] --> K["Python Worker"]
|
||||
K --> D
|
||||
K --> F
|
||||
K --> E
|
||||
K -->|"Bearer + X-RAG-Caller: worker"| M
|
||||
A --> J
|
||||
```
|
||||
|
||||
@@ -154,13 +159,14 @@ flowchart LR
|
||||
| `gateway` | 固定 API 上游、请求大小/头边界、脱敏错误和流式转发 | 数据库凭证、模型凭证、业务权限判断 |
|
||||
| `api` | 认证、知识库、检索、问答、评测 API | 长时间 OCR/批量入库 |
|
||||
| `worker` | 解析、OCR、分块、向量化、索引、评测批任务 | 对外开放端口 |
|
||||
| `model-gateway` | 唯一读取百炼 Key、统一模型协议/限流/脱敏错误;校验 API/Worker 内部身份 | 数据库、上传卷、外部端口、业务授权 |
|
||||
| `db` | 元数据、向量、任务、会话、评测事实来源 | 原始大文件长期存储 |
|
||||
| `storage` | 开发期 Docker volume,生产可切换 OSS | 访问控制的最终判定 |
|
||||
| 百炼适配器 | 统一超时、重试、计量、错误映射 | 记录密钥或正文日志 |
|
||||
| 百炼适配器 | 仅在 `model-gateway` 进程内统一超时、重试、计量、错误映射 | 记录密钥或正文日志 |
|
||||
|
||||
### 4.3 为什么不先拆微服务
|
||||
|
||||
解析、检索、生成和评测共享同一领域模型、配置和数据库。拆分微服务会提前引入服务发现、分布式追踪、数据一致性和接口版本问题,却没有对应吞吐收益。第一版使用一个 Python 包,由 `api` 和 `worker` 两个进程加载;当某个模块具有独立扩容证据时再拆分。
|
||||
解析、检索、生成和评测共享同一领域模型、配置和数据库。拆分业务微服务会提前引入服务发现、分布式追踪、数据一致性和接口版本问题,却没有对应吞吐收益。第一版使用一个 Python 包,由 `api`、`worker` 和 `model-gateway` 三种进程加载;其中 `model-gateway` 不是独立业务服务,而是最小权限的凭证/出口隔离边界。详细决策见 [ADR-0005](adr/0005-isolate-model-egress.md)。
|
||||
|
||||
## 5. 技术选型
|
||||
|
||||
@@ -209,7 +215,7 @@ ADR 见 [ADR-0001](adr/0001-use-pgvector.md)。
|
||||
|
||||
### 6.1 端点必须分开
|
||||
|
||||
业务空间专属域名的 Key、地域和 Base URL 必须匹配;官方推荐生产使用专属域名,见[Base URL 总览](https://help.aliyun.com/zh/model-studio/base-url)。配置只保存以下形态:
|
||||
业务空间专属域名的 Key、地域和 Base URL 必须匹配;官方推荐生产使用专属域名,见[Base URL 总览](https://help.aliyun.com/zh/model-studio/base-url)。这些配置和 Key 只注入 `model-gateway`:
|
||||
|
||||
```dotenv
|
||||
BAILIAN_OPENAI_BASE_URL=https://<workspace-id>.cn-beijing.maas.aliyuncs.com/compatible-mode/v1
|
||||
@@ -227,6 +233,17 @@ DASHSCOPE_API_KEY_FILE=/run/secrets/bailian_api_key
|
||||
|
||||
`/apps/anthropic` 是 Anthropic 协议入口,本项目不使用。尤其不能把 `/reranks` 拼到 `/compatible-mode/v1` 后面;官方的 Rerank 路径是独立的 `/compatible-api/v1/reranks`。
|
||||
|
||||
业务进程不接受任意模型名、供应商 URL 或向量维度,而只调用固定内部接口:
|
||||
|
||||
```text
|
||||
POST /internal/v1/embeddings
|
||||
POST /internal/v1/rerank
|
||||
POST /internal/v1/chat/completions
|
||||
POST /internal/v1/chat/stream
|
||||
```
|
||||
|
||||
调用必须同时携带 `Authorization: Bearer <internal-token>` 和 `X-RAG-Caller: api|worker`。服务端以常量时间比较 token 并要求 token 身份与 caller 一致;`api` 只允许查询向量、重排和聊天,文档向量化只允许 `worker` 身份。两个内部 token 相互独立、只通过 Docker Secret 注入。API、seed 和 smoke 工具均经内部客户端访问,既不挂载百炼 Key,也不加入公网 `egress` 网络。
|
||||
|
||||
### 6.2 Embedding 契约
|
||||
|
||||
基线请求:
|
||||
@@ -365,6 +382,10 @@ UPLOADED
|
||||
| `evaluation_sets/cases` | 版本化问题、证据和标签 |
|
||||
| `evaluation_runs/results` | 参数快照、指标、延迟、成本和错误 |
|
||||
| `audit_logs` | 管理操作和安全审计 |
|
||||
| `model_profiles` | 模型种类、模型名、API 模式、1024 维约束、端点身份哈希和无凭证配置快照;受控 cache epoch 必须进入不可变 profile hash |
|
||||
| `embedding_cache` | 以 `profile_hash + embedding_text_sha256` 唯一缓存 1024 维向量和脱敏调用元数据 |
|
||||
| `chunk_embedding_assignments` | chunk/profile 到缓存项的状态化绑定,保证 READY 只指向同文本同 profile 向量 |
|
||||
| `model_invocations` | 只记录 trace、caller、模型、用量、耗时、request ID 和脱敏错误,不保存请求/响应正文 |
|
||||
|
||||
### 8.2 核心向量表(示意)
|
||||
|
||||
@@ -441,6 +462,8 @@ COMMIT;
|
||||
- 删除先清空 `active_version_id` 并令投影不可检索,再异步物理删除文件和向量;
|
||||
- 任何回答保留当时的 chunk ID、模型和 Prompt 版本,保证复现。
|
||||
|
||||
Alembic `0002_model_profiles` 已实现上述 profile、缓存、assignment 和调用审计表,为 `knowledge_bases` 增加激活 Embedding profile,并为 `chunks` 增加稳定唯一的 `citation_id`。迁移只会为可明确识别的单一 synthetic fake profile 做安全回填,绝不从模型别名或 URL 猜测真实供应商身份;同一知识库存在多个候选 profile 时保持未激活,等待显式治理。已在独立全新 volume 验证 `空库 -> 0001 -> 0002 -> 0001 -> 0002`,并在已有 20 条合成向量的数据卷完成相同升降级;重复 seed 后 profile/active KB 为 1/1,cache/READY assignment/citation 均为 20,且 citation 无重复。
|
||||
|
||||
## 9. 在线 RAG 流程
|
||||
|
||||
### 9.1 主流程
|
||||
@@ -527,6 +550,8 @@ Rerank 只能重新排序已召回候选,无法找回初召回遗漏的证据
|
||||
|
||||
## 10. 后端 API 设计
|
||||
|
||||
FastAPI 入口通过 `create_app()` 应用工厂构建,导入阶段不打开数据库、不读取百炼凭证。HTTP 请求由统一中间件生成或透传安全的 trace ID;业务错误使用稳定的 Problem JSON 响应并回传 trace,未知异常不得泄漏连接串、Secret 路径或供应商正文。`/health/live` 只证明进程存活,`/health/ready` 只验证本地数据库依赖;百炼能力状态由独立模型探测展示,避免供应商短时故障触发 API 重启风暴。
|
||||
|
||||
统一前缀 `/api/v1`:
|
||||
|
||||
```text
|
||||
@@ -703,6 +728,10 @@ pgvector 官方给出的单精度 `vector` 存储约为 `4 * dimensions + 8` 字
|
||||
|
||||
详细指标与阶段完成定义分别见 [01-data-and-evaluation.md](01-data-and-evaluation.md) 和 [03-implementation-plan.md](03-implementation-plan.md)。
|
||||
|
||||
### 15.1 2026-07-13 实现边界
|
||||
|
||||
当前已实现安全运行骨架、内部 `model-gateway` 及 token 身份、模型内部客户端、应用工厂/Problem/trace 契约和 `0002_model_profiles` 迁移代码;`provider-smoke`、真实 `seed-demo` 和 API 进程不再直接持有百炼 Key。当前工作空间对 `text-embedding-v4`、`qwen3-rerank`、`deepseek-v4-flash` 的实际请求仍返回 401,因此三能力真实可用性尚未验收。数字文档上传/解析/审核、正式检索 API、grounded chat、Worker 租约恢复、评测运行器和完整产品 UI 仍是后续实现项;不得把本节理解为“整个项目完成”。
|
||||
|
||||
## 16. 参考资料
|
||||
|
||||
1. [阿里云百炼 Base URL 总览](https://help.aliyun.com/zh/model-studio/base-url)
|
||||
|
||||
@@ -443,7 +443,7 @@ embedding_cache_key = SHA256(
|
||||
chunk_embedding_assignment = UNIQUE(chunk_id, embedding_profile_hash)
|
||||
```
|
||||
|
||||
相同文本可以安全复用向量缓存,但每个 chunk 都必须建立独立 assignment,不能用缓存键替代任务/行唯一键。模型别名无法解析到稳定 revision 时,每次冻结实验显式设置 `cache_epoch`,避免别名漂移后复用旧向量。激活前再次核对当前 `embedding_text_sha256`、完整 profile 与 assignment;任何一项变化都生成新索引版本。
|
||||
相同文本可以安全复用向量缓存,但每个 chunk 都必须建立独立 assignment,不能用缓存键替代任务/行唯一键。模型别名无法解析到稳定 revision 时,每次冻结实验显式设置 `cache_epoch`,并把它纳入不可变的 `embedding_profile_hash`;epoch 变化必须创建新 profile,禁止原地修改 profile 后继续复用旧缓存。激活前再次核对当前 `embedding_text_sha256`、完整 profile 与 assignment;任何一项变化都生成新索引版本。
|
||||
|
||||
`endpoint_identity_hash` 由规范化后的工作空间/部署端点身份计算,仅保存哈希而不在实验导出中暴露真实工作空间。这样同地域、同模型别名但不同 MaaS 工作空间的向量不会错误共用缓存。
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
| 项目 | 设计值 |
|
||||
|---|---|
|
||||
| 部署方式 | Docker Compose 单机 |
|
||||
| 长期服务 | `web`、`api`、`worker`、`db` |
|
||||
| 长期服务 | 当前 `web`、`gateway`、`api`、`model-gateway`、`db`;后续加入 `worker` |
|
||||
| 一次性服务 | `migrate` |
|
||||
| 对外端口 | 仅 Nginx/Web |
|
||||
| 密钥 | Docker Secret / Secret Manager |
|
||||
@@ -17,7 +17,7 @@
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
这条命令应完成数据库健康等待、迁移、Web/API/Worker 启动。它不等于“所有数据和模型凭证自动生成”,首次部署仍必须完成密钥轮换、Secret 创建和三模型能力探测。
|
||||
这条命令当前完成数据库健康等待、一次性迁移、`model-gateway`、API、入口 Gateway 和 Web 启动;Worker 落地后也遵循同一依赖链。它不等于“所有数据和模型凭证自动生成”,首次部署仍必须完成密钥轮换、Secret 创建和三模型能力探测。
|
||||
|
||||
架构定位是适合毕设、演示和单机内部使用的生产式单节点,不宣称跨主机高可用。互联网生产环境应替换托管数据库、对象存储和集中密钥管理。
|
||||
|
||||
@@ -29,8 +29,9 @@ docker compose up -d --build
|
||||
|---|---|---|---|
|
||||
| `db` | 固定版本 pgvector 镜像 | 元数据、向量、任务、会话、评测 | 仅内部网络 |
|
||||
| `migrate` | 与后端同镜像 | 运行 Alembic,一次成功退出 | 无 |
|
||||
| `api` | `backend/Dockerfile` | FastAPI、检索、问答、管理 API | 仅 internal data 网络 |
|
||||
| `api` | `backend/Dockerfile` | FastAPI、检索、问答、管理 API | 仅 internal data/model 网络 |
|
||||
| `gateway` | 与后端同镜像 | 固定上游、请求边界、脱敏错误和流式转发 | 仅 internal ingress/data 网络 |
|
||||
| `model-gateway` | 与后端同镜像 | 唯一读取百炼 Key,代理 Embedding/Rerank/Chat | 仅 internal model + egress 网络,无宿主机端口 |
|
||||
| `worker` | 同一后端镜像 | 解析、向量化、评测后台任务 | 无 |
|
||||
| `web` | `frontend/Dockerfile` | React 静态资源、Nginx 同源入口和 SSE 代理 | `127.0.0.1:8000`;生产部署 HTTPS |
|
||||
| `ocr-worker` | 可选 profile | PaddleOCR 重型任务 | 无 |
|
||||
@@ -72,6 +73,11 @@ x-rag-config: &rag-config
|
||||
MODEL_MAX_RETRIES: "${MODEL_MAX_RETRIES:-3}"
|
||||
MODEL_MAX_CONCURRENCY: "${MODEL_MAX_CONCURRENCY:-4}"
|
||||
|
||||
x-model-client-config: &model-client-config
|
||||
MODEL_GATEWAY_BASE_URL: http://model-gateway:8000
|
||||
MODEL_GATEWAY_TOKEN_FILE: /run/secrets/model_gateway_api_token
|
||||
MODEL_GATEWAY_CALLER: api
|
||||
|
||||
services:
|
||||
db:
|
||||
image: pgvector/pgvector:0.8.2-pg17
|
||||
@@ -115,15 +121,35 @@ services:
|
||||
networks: [data]
|
||||
restart: "no"
|
||||
|
||||
model-gateway:
|
||||
build: ./backend
|
||||
command: ["uvicorn", "app.model_gateway:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
environment:
|
||||
<<: *rag-config
|
||||
MODEL_GATEWAY_ALLOWED_TOKEN_FILES: >-
|
||||
api=/run/secrets/model_gateway_api_token,worker=/run/secrets/model_gateway_worker_token
|
||||
secrets:
|
||||
- bailian_api_key
|
||||
- model_gateway_api_token
|
||||
- model_gateway_worker_token
|
||||
networks: [model, egress]
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health/ready', timeout=2)"]
|
||||
restart: unless-stopped
|
||||
|
||||
api:
|
||||
build: ./backend
|
||||
command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
depends_on:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
environment: *runtime-config
|
||||
model-gateway:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
<<: [*runtime-config, *model-client-config]
|
||||
secrets:
|
||||
- postgres_app_password
|
||||
- model_gateway_api_token
|
||||
volumes:
|
||||
- uploads:/data/uploads
|
||||
healthcheck:
|
||||
@@ -132,7 +158,7 @@ services:
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
networks: [data]
|
||||
networks: [data, model]
|
||||
restart: unless-stopped
|
||||
|
||||
gateway:
|
||||
@@ -151,24 +177,29 @@ services:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
<<: [*runtime-config, *rag-config]
|
||||
<<: [*runtime-config, *model-client-config]
|
||||
MODEL_GATEWAY_TOKEN_FILE: /run/secrets/model_gateway_worker_token
|
||||
MODEL_GATEWAY_CALLER: worker
|
||||
WORKER_CAPABILITIES: ${WORKER_CAPABILITIES:-document_parse,embedding,rerank,evaluation}
|
||||
secrets:
|
||||
- postgres_app_password
|
||||
- bailian_api_key
|
||||
- model_gateway_worker_token
|
||||
volumes:
|
||||
- uploads:/data/uploads
|
||||
networks: [data, egress]
|
||||
networks: [data, model]
|
||||
restart: unless-stopped
|
||||
|
||||
provider-smoke:
|
||||
build: ./backend
|
||||
command: ["python", "-m", "app.tools.provider_smoke"]
|
||||
profiles: ["tools"]
|
||||
environment: *rag-config
|
||||
depends_on:
|
||||
model-gateway:
|
||||
condition: service_healthy
|
||||
environment: *model-client-config
|
||||
secrets:
|
||||
- bailian_api_key
|
||||
networks: [egress]
|
||||
- model_gateway_api_token
|
||||
networks: [model]
|
||||
restart: "no"
|
||||
|
||||
seed-demo:
|
||||
@@ -179,11 +210,13 @@ services:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
<<: [*runtime-config, *rag-config]
|
||||
<<: [*runtime-config, *model-client-config]
|
||||
MODEL_GATEWAY_TOKEN_FILE: /run/secrets/model_gateway_worker_token
|
||||
MODEL_GATEWAY_CALLER: worker
|
||||
secrets:
|
||||
- postgres_app_password
|
||||
- bailian_api_key
|
||||
networks: [data, egress]
|
||||
- model_gateway_worker_token
|
||||
networks: [data, model]
|
||||
restart: "no"
|
||||
|
||||
web:
|
||||
@@ -209,6 +242,9 @@ networks:
|
||||
data:
|
||||
driver: bridge
|
||||
internal: true
|
||||
model:
|
||||
driver: bridge
|
||||
internal: true
|
||||
egress:
|
||||
driver: bridge
|
||||
|
||||
@@ -219,13 +255,17 @@ secrets:
|
||||
file: ./secrets/postgres_migrator_password
|
||||
postgres_app_password:
|
||||
file: ./secrets/postgres_app_password
|
||||
model_gateway_api_token:
|
||||
file: ./secrets/model_gateway_api_token
|
||||
model_gateway_worker_token:
|
||||
file: ./secrets/model_gateway_worker_token
|
||||
bailian_api_key:
|
||||
file: ./secrets/bailian_api_key
|
||||
```
|
||||
|
||||
最终实现固定镜像 digest,示意版本只说明可行结构。`migrate` 只有一个实例执行;API 和 Worker 不在启动时并发自动迁移。首次初始化脚本只在 `db` 容器内使用 bootstrap 凭据创建 `vector` 扩展、无超级用户权限的 migrator/app 登录角色和专属 schema;migrator 拥有 schema/DDL,app 仅获运行期所需的 DML/sequence 权限及默认权限,API/Worker 永远不挂载 bootstrap 或 migrator Secret。最后一个初始化脚本只有在全部 SQL 事务和授权成功后,才在 `PGDATA` 内用同文件系统 rename 原子写入 `.rag-bootstrap-complete`;healthcheck 同时检查 `pg_isready` 与该哨兵,防止临时初始化 server 提前放行迁移。备份再使用独立只读角色。Compose 的 `.env` 仅做变量插值,不会自动注入容器,因此可配置的非敏感项和 `*_FILE` 路径通过 YAML anchor 在 `environment` 中完整声明。后端从各自的 `POSTGRES_PASSWORD_FILE` 读取密码后在内存中组装 DSN,不把明文密码放进 `.env` 或 `DATABASE_URL`。
|
||||
实际实现固定数据库镜像 digest。`migrate` 只有一个实例执行;成功应用到 head 后退出码为 0,随后在 `docker compose ps -a` 显示 `Exited (0)` 是一次性任务的正常终态,不是服务暂停。API 和 Worker 不在启动时并发自动迁移。首次初始化脚本只在 `db` 容器内使用 bootstrap 凭据创建 `vector` 扩展、无超级用户权限的 migrator/app 登录角色和专属 schema;migrator 拥有 schema/DDL,app 仅获运行期所需的 DML/sequence 权限及默认权限,API/Worker 永远不挂载 bootstrap 或 migrator Secret。最后一个初始化脚本只有在全部 SQL 事务和授权成功后,才在 `PGDATA` 内用同文件系统 rename 原子写入 `.rag-bootstrap-complete`;healthcheck 同时检查 `pg_isready` 与该哨兵,防止临时初始化 server 提前放行迁移。Compose 的 `.env` 仅做变量插值,不会自动注入容器;后端从 `*_FILE` 读取 Secret,不把明文密码放进 `.env` 或 `DATABASE_URL`。
|
||||
|
||||
网络分为四层:`web` 位于普通 `edge` 和 internal `ingress`;`gateway` 位于 `ingress + data`;API、迁移、Worker 和数据库位于 internal `data`;只有明确需要云调用的一次性工具或未来 Worker 才能加入命名的 `egress`。因此浏览器入口不能横向连接数据库,数据库感知 API 也没有公网默认出口。只有无 Secret 的 Web 容器发布回环端口;gateway 固定上游并继续执行请求边界和脱敏错误契约。详细理由和被否决方案见 [ADR-0004](adr/0004-secretless-web-ingress.md)。必须注意:普通 `edge` bridge 为 Web 提供宿主机端口发布时,也给 Web 留有技术上的默认公网出口;当前接受这一点的前提是 Web 无 Secret、无数据网络、无挂载且代理上游固定。若部署基线要求入口容器也完全禁网,必须在宿主机防火墙/出口代理层阻断,不能把“未加入名为 egress 的网络”误写成“没有外网出口”。普通 bridge 本身也不构成域名白名单,正式联网 Worker 仍需主机防火墙或出口代理只放行百炼和对象存储域名。
|
||||
网络分为五层:`web` 位于普通 `edge` 和 internal `ingress`;入口 `gateway` 位于 `ingress + data`;API 位于 internal `data + model`;数据库/迁移位于 internal `data`;只有 `model-gateway` 位于 internal `model + egress`。`provider-smoke` 只持 API 内部 token,真实 `seed-demo` 只持数据库 Secret 和 Worker 内部 token;二者都不挂载百炼 Key、不加入 `egress`。未来 Worker 沿用相同边界。`model-gateway` 不连接数据库、不挂载上传卷、不发布端口。入口隔离见 [ADR-0004](adr/0004-secretless-web-ingress.md),模型出口隔离见 [ADR-0005](adr/0005-isolate-model-egress.md)。必须注意:普通 `edge` bridge 为 Web 提供宿主机端口发布时,也给 Web 留有技术上的默认公网出口;生产仍需主机防火墙或出口代理只允许 `model-gateway` 到百炼域名。
|
||||
|
||||
### 2.3 后台任务为何不用 Redis
|
||||
|
||||
@@ -289,7 +329,9 @@ def read_secret(file_env: str, value_env: str) -> str:
|
||||
return os.environ[value_env]
|
||||
```
|
||||
|
||||
生产只允许文件/Secret Manager;本地值环境变量仅作为临时兼容。应用启动后不打印 Settings 的 Secret 字段,异常对象不得包含请求头。
|
||||
生产只允许文件/Secret Manager;本地值环境变量仅作为临时兼容。`bailian_api_key` 只挂载到 `model-gateway`。API 与 Worker 分别挂载 `model_gateway_api_token`、`model_gateway_worker_token`;调用同时使用 Bearer token 和 `X-RAG-Caller`,服务端以常量时间比较并拒绝 token/身份不匹配。应用启动后不打印 Settings 的 Secret 字段,异常对象不得包含请求头。
|
||||
|
||||
Secret 或模型端点配置在运行中发生变化时,`model-gateway` 会原子清空旧 provider 与内部 token,并进入 `restart_required`;旧、新 token 都不能在旧进程继续调用。Docker 的 `restart: unless-stopped` 不会仅因 unhealthy 自动重启,因此轮换操作必须协调执行 `docker compose restart model-gateway api`(Worker 落地后同时重启 Worker),待 readiness 恢复后再运行 provider smoke。禁止只替换文件而让旧进程长期携带旧凭据。
|
||||
|
||||
### 3.3 Git 防泄漏
|
||||
|
||||
@@ -326,6 +368,10 @@ EMBEDDING_MODEL=text-embedding-v4
|
||||
EMBEDDING_DIMENSION=1024
|
||||
RERANK_MODEL=qwen3-rerank
|
||||
LLM_MODEL=deepseek-v4-flash
|
||||
|
||||
MODEL_GATEWAY_BASE_URL=http://model-gateway:8000
|
||||
MODEL_GATEWAY_TOKEN_FILE=/run/secrets/model_gateway_api_token
|
||||
MODEL_GATEWAY_CALLER=api
|
||||
```
|
||||
|
||||
启动时 fail fast:
|
||||
@@ -336,6 +382,8 @@ LLM_MODEL=deepseek-v4-flash
|
||||
- Embedding 维度为 schema 支持的 1024;
|
||||
- 生产没有默认密码和通配 CORS;
|
||||
- Secret 文件存在、非空且权限合理;
|
||||
- 内部模型地址必须精确为 `http://model-gateway:8000`,不能通过配置改成公网或用户控制的 URL;
|
||||
- API/Worker 内部 token 必须不同,caller 必须与 token 身份一致;
|
||||
- `APP_ENV=production` 时关闭开发文档或增加管理员保护。
|
||||
|
||||
### 4.2 地域与计费方案
|
||||
@@ -373,13 +421,15 @@ LLM_MODEL=deepseek-v4-flash
|
||||
|
||||
`GET {OPENAI_BASE}/models` 可作为辅助清单,但不能证明 Rerank 路径和三个模型权限都正常,最小实际调用才是最终验证。
|
||||
|
||||
截至 2026-07-13,最近一次真实探测已到达百炼端点,但 Embedding、Rerank、Chat 三项均返回 401。改由内部 `model-gateway` 路由后必须重新运行同一探测;已有 401 证明不了模型授权可用。必须核对 Key 所属工作空间、北京地域、端点和计费/模型权限后重新验收。401 不进入自动重试风暴,也不能把 Stage 1 标记完成。
|
||||
|
||||
## 6. 网络与接口安全
|
||||
|
||||
### 6.1 网络边界
|
||||
|
||||
- 只发布 Nginx 端口;数据库、API、Worker 不直接暴露公网;
|
||||
- Nginx 到 API 使用内部网络;
|
||||
- `data` 网络启用 Docker internal 隔离,Web 不加入;API/Worker 通过独立 `egress` 网络访问云服务;
|
||||
- `data` 和 `model` 网络启用 Docker internal 隔离,Web 不加入;API/Worker 只经 `model-gateway` 访问云服务,自身不加入 `egress`;
|
||||
- 生产入口启用 TLS、HSTS 和安全响应头;
|
||||
- 出站只允许百炼/OSS 等必要域名;
|
||||
- 数据库账号按迁移、应用、备份职责分权;
|
||||
@@ -442,11 +492,12 @@ LLM_MODEL=deepseek-v4-flash
|
||||
### 8.1 健康端点
|
||||
|
||||
- `/health/live`:进程事件循环可用,不检查外部模型;
|
||||
- `/health/ready`:数据库、迁移版本、存储路径和配置可用;
|
||||
- API `/health/ready`:当前验证数据库可用;后续再纳入迁移版本和存储路径对账;
|
||||
- `model-gateway /health/ready`:只验证本地 Key/token/端点配置可加载且未漂移,不产生计费云调用;
|
||||
- `/admin/providers/health`:按需调用三个模型,结果短期缓存;
|
||||
- `/admin/index/health`:`documents.active_version_id`、searchable 切片投影、非空向量、文本哈希和 embedding profile 一致性。
|
||||
|
||||
百炼短时波动不应让编排器不断重启健康 API 容器。
|
||||
百炼短时波动不应让编排器不断重启健康 API 容器。真实三能力是否可用只能由显式 provider smoke 证明,不能从 `model-gateway` readiness 推断。
|
||||
|
||||
### 8.2 日志脱敏
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
| 项目 | 当前值 |
|
||||
|---|---|
|
||||
| 基线日期 | 2026-07-12 |
|
||||
| 基线日期 | 2026-07-13 |
|
||||
| 当前阶段 | Stage 1:模型与数据库 PoC,`IN_PROGRESS` |
|
||||
| 已完成阶段 | Stage 0:仓库、安全和设计基线 |
|
||||
| 整体完成度 | 约 12%,合理区间 10%–15% |
|
||||
| 设计完成度 | 设计基线已完成;实现中发现新约束时继续通过 ADR 和文档修订维护 |
|
||||
| 业务代码完成度 | 约 18%;Stage 1 的 Docker API、离线检索、数据库 PoC,以及 React 离线演示、Nginx 单入口和四网络隔离前置均已验收;真实百炼 smoke 仍待轮换后的新 Key |
|
||||
| 业务代码完成度 | 不以新增文件提前抬高里程碑;已增加独立 `model-gateway`、内部 token 身份、应用工厂/Problem/trace 和 `0002` profile/cache 迁移,完整产品主链路仍未完成 |
|
||||
| 当前预计剩余工期 | 约 9–13 周,含 300 题正式标注、盲测、论文和答辩缓冲 |
|
||||
| 进度权威来源 | 本文的阶段状态、验收证据和已推送提交 |
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
| Stage 9 答辩、发布与归档 | 3% | `TODO` | 0% |
|
||||
| **合计** | **100%** | — | **12%** |
|
||||
|
||||
Stage 1 已完成可离线验收的数据库、适配器契约、synthetic seed、只读 API 和 Web 演示子闭环,安全离线子阶段内部约完成 95%;但真实三模型 smoke 仍依赖轮换后的新 Key,因此 Stage 1 不提前计入整体里程碑。Stage 2 中不依赖云密钥的 React、Nginx、网络隔离和质量门禁前置已经完成,但 Worker 租约、完整产品工作流等必选项尚未完成,Stage 2 仍为 `TODO`。本文复选框表示“已通过验收”,不表示“文件是否已经出现”。当前可诚实表述为:**设计基线完成,整体里程碑约 12%,业务代码约 18%,Stage 1 安全离线链路可运行,真实百炼验证尚未完成。**
|
||||
Stage 1 已完成可离线验收的数据库、适配器契约、synthetic seed、只读 API 和 Web 演示子闭环,并已把百炼 Key/公网出口收敛到独立 `model-gateway`;但真实三模型请求当前均返回 401,因此 Stage 1 不提前计入整体里程碑。Stage 2 中应用工厂、Problem/trace、React、Nginx、网络隔离和质量门禁已作为前置落地,`0002_model_profiles` 迁移代码也已建立;Worker 租约、上传入库、正式检索/聊天和评测等必选项尚未完成,Stage 2 仍为 `TODO`。本文复选框表示“已通过对应层级的验收”,不表示“文件是否已经出现”。当前可诚实表述为:**设计基线完成,整体里程碑仍约 12%,安全离线链路可运行,真实百炼验证和产品主链路尚未完成。**
|
||||
|
||||
## 2. 阶段依赖与关键路径
|
||||
|
||||
@@ -99,8 +99,8 @@ Stage 0 DONE
|
||||
|
||||
- `git status --short --branch` 显示 `main...origin/main`,Stage 0 基线提交远端可见。
|
||||
- `make verify-design` 可检查 Secret、设计文档、相对链接和 diff。
|
||||
- [00-overall-design.md](00-overall-design.md) 明确状态为“设计基线,待实现验证”。
|
||||
- 当前不存在可运行应用,因此 Stage 0 的完成只代表设计和工程防线完成。
|
||||
- [00-overall-design.md](00-overall-design.md) 现已同步“设计基线;安全运行骨架部分实现,产品主链路未完成”。
|
||||
- Stage 0 完成时尚无可运行应用;后续运行骨架不改变“Stage 0 只代表设计和工程防线”的历史验收口径。
|
||||
|
||||
### 已完成提交/推送节点
|
||||
|
||||
@@ -133,11 +133,13 @@ Stage 0 DONE
|
||||
|
||||
- [x] 创建 `backend/pyproject.toml` 和依赖锁文件,不引入未说明依赖。
|
||||
- [x] 创建最小 `backend/Dockerfile`,使用非 root 用户和固定基础镜像 digest。
|
||||
- [x] 创建最小 `compose.yaml`,包含 `db + migrate + provider-smoke + seed-demo/offline`。
|
||||
- [x] 创建 Compose,包含 `db + migrate + model-gateway + api + gateway + web + provider-smoke + seed-demo/offline`;Worker 仍待实现。
|
||||
- [x] 启动最小 FastAPI `api + gateway`,内部 API 无 egress,gateway 无 Secret 并仅发布回环端口;提供真实数据库 readiness、Swagger 和只读 synthetic demo 检索。
|
||||
- [x] 创建 PostgreSQL bootstrap、migrator 和 app 分权角色初始化脚本;备份只读角色在备份功能落地时单独创建。
|
||||
- [x] 创建 Alembic 基线迁移,启用 pgvector 并建立 1024 维向量表/HNSW 基线。
|
||||
- [x] 确保运行期服务不挂载 bootstrap 或 migrator Secret。
|
||||
- [x] 将百炼 Key 和公网出口隔离到独立 `model-gateway`;API/seed/smoke 只持对应内部 token,不直持 Key。
|
||||
- [x] 以 `Authorization: Bearer` + `X-RAG-Caller: api|worker` 建立内部身份,常量时间比较并限制 API 身份不得执行文档向量化。
|
||||
|
||||
#### 4.3 三模型适配器
|
||||
|
||||
@@ -157,6 +159,7 @@ Stage 0 DONE
|
||||
- [x] 建立并执行迁移空 volume、角色权限和 Docker 重启持久化测试。
|
||||
- [x] 明确文档族 split、盲测保管人和“盲测用于调参即退役”规则。
|
||||
- [x] 扩展 `make verify`,纳入 Stage 1 后端格式、类型和测试门禁。
|
||||
- [x] 实现并实跑 `0002_model_profiles`:profile、embedding cache、chunk assignment、model invocation 与稳定 `citation_id`;空卷和已有数据均完成 upgrade/downgrade/upgrade。
|
||||
|
||||
### 验收证据
|
||||
|
||||
@@ -165,12 +168,13 @@ Stage 0 DONE
|
||||
- [x] `docker compose run --rm seed-demo-offline` 完成 20 条虚构数据写入、检索和重排;真实模式待新 Key。
|
||||
- [x] `api + gateway` 镜像构建并达到 healthy;live/ready/meta/demo status/demo search 均通过真实 HTTP 验收。
|
||||
- [x] 第二次运行 seed 后数据库计数与第一次相同,均为 chunks/vectors/searchable = 20/20/20。
|
||||
- [x] 65 项后端测试证明模型契约、健康检查、固定上游 gateway、SSE 代理分块与早断连释放契约、离线 demo、数据治理和失败路径正确。
|
||||
- [x] 已验证测试集证明模型契约、健康检查、固定上游 gateway、SSE 代理分块与早断连释放契约、离线 demo、数据治理和失败路径正确;本轮新增 model-gateway/0002 契约须随阶段提交重新运行全量门禁。
|
||||
- [x] `make verify`、Secret 扫描、固定镜像构建和 `git diff --check` 通过。
|
||||
|
||||
### 2026-07-12 已验证运行证据
|
||||
|
||||
- 全新 volume 上 `db` 达到 healthy,`migrate` 以非超级用户退出码 0,版本为 `0001_initial_schema`。
|
||||
- `0002_model_profiles` 已在独立全新 volume 完成 `空库 -> 0001 -> 0002 -> 0001 -> 0002`,并在已有 20 条合成数据的 volume 完成 downgrade/upgrade;重复 seed 后 profile/active KB=1/1、cache/READY assignment/citation=20/20/20,citation 无重复。
|
||||
- `vector` 扩展与 HNSW 基线存在;app/migrator 均非超级用户,app 无 `rag` schema DDL 权限但具有所需 DML。
|
||||
- 两次离线 seed 均输出 20/20/20,9 个可回答虚构问题 Hit@3 = 9/9。
|
||||
- FastAPI 容器使用 app 最小权限角色,根文件系统只读且无 egress;无 Secret gateway 仅提供回环入口;Swagger 可见,demo search 返回合成片段与不透明 citation ID。
|
||||
@@ -179,9 +183,9 @@ Stage 0 DONE
|
||||
- app 尝试修改已 `CLOUD_APPROVED` 的 `cloud_text` 被审批不可变触发器拒绝;尝试建表被权限拒绝。
|
||||
- React + TypeScript strict 离线演示已实现工作台与系统状态页;14 项前端测试、ESLint、类型检查和生产构建通过。
|
||||
- Nginx 成为唯一回环入口,Compose 仅发布 `127.0.0.1:8000`;`web -> gateway -> api` 同源链路、Swagger、OpenAPI、健康检查和 synthetic search 均通过真实 HTTP 验收。
|
||||
- `edge / ingress / data / egress` 四网络边界已落地;Web 不可解析数据库,API 与 gateway 无公网 TCP 出口,Web 与 gateway 均不持有 Secret。
|
||||
- `edge / ingress / data / model / egress` 五网络边界已落地;API、seed、smoke 不挂载百炼 Key,只有不连数据库/上传卷的 `model-gateway` 同时连接 internal model 与 egress。
|
||||
- gateway 与 Nginx 已关闭代理缓冲,自动测试证明两个 SSE 事件保持为独立下游分块,且响应头阶段断连也会释放上游流;真实聊天生成与取消传播仍属于 Stage 5,尚未实现。
|
||||
- 唯一未验收项是真实 `text-embedding-v4` / `qwen3-rerank` / `deepseek-v4-flash` smoke 与真实模式 seed。
|
||||
- 当前真实 `text-embedding-v4` / `qwen3-rerank` / `deepseek-v4-flash` 请求均到达供应商但返回 401;三能力与真实模式 seed 未验收。上传、正式检索、grounded chat、Worker 和评测仍属于后续阶段,不能称为“唯一剩余项”。
|
||||
|
||||
### 提交/推送节点
|
||||
|
||||
@@ -206,16 +210,16 @@ Stage 0 DONE
|
||||
- **状态:** `TODO`
|
||||
- **预计工期:** 3–4 个工作日
|
||||
- **依赖:** Stage 1 `DONE`
|
||||
- **已完成前置:** 不依赖云密钥的 React 离线演示、OpenAPI 类型、Nginx 单入口、四网络隔离和当前质量门禁已验收;这不代表 Stage 2 已启动或完成。
|
||||
- **已完成前置:** 不依赖云密钥的 React 离线演示、OpenAPI 类型、Nginx 单入口和质量门禁已验收;模型隔离网络加入后当前为五层网络。这不代表 Stage 2 已启动或完成。
|
||||
|
||||
### 具体实现任务
|
||||
|
||||
- [ ] 建立 FastAPI 应用工厂、配置加载、稳定错误码和 OpenAPI。
|
||||
- [x] 建立 FastAPI 应用工厂、配置加载、稳定 Problem JSON、trace middleware 和 OpenAPI;导入阶段不连库、不读模型凭证。
|
||||
- [ ] 按 `domain -> ports -> services -> adapters/persistence` 落实后端依赖边界。
|
||||
- [ ] 建立 SQLAlchemy 仓储、Alembic 迁移运行器和事务边界。
|
||||
- [ ] 建立完整 SQLAlchemy 仓储和业务事务边界;Alembic 已推进到 `0002_model_profiles`,且空卷/已有数据升降级已验证。
|
||||
- [ ] 建立 PostgreSQL `background_jobs`、租约、lease token、fencing 和重试状态机。
|
||||
- [ ] 建立 Worker maintenance loop、advisory-lock reaper 和强杀恢复测试。
|
||||
- [ ] 建立 `/health/live`、`/health/ready` 和管理员模型能力探测端点。
|
||||
- [ ] `/health/live`、数据库 `/health/ready` 及 model-gateway 本地 readiness 已有;管理员模型能力探测 API 仍待实现。
|
||||
- [x] 建立 React + TypeScript strict、路由、TanStack Query 和 OpenAPI 客户端生成。
|
||||
- [x] 建立 Nginx,仅发布 Web 端口;当前 API、DB 只在内部网络,Worker 落地后必须沿用同一边界。
|
||||
- [ ] 建立完整 Compose:`web/api/worker/db/migrate`,OCR profile 默认关闭。
|
||||
@@ -229,11 +233,11 @@ Stage 0 DONE
|
||||
- [x] 当前 `docker compose ps` 只暴露 Nginx/Web 回环端口;加入 Worker 后须重新验收。
|
||||
- [ ] Docker 重启后数据库数据保留。
|
||||
- [ ] 强杀 Worker 后验证可重领、耗尽进入 `FAILED`、旧 token 不能提交。
|
||||
- [x] 当前后端 65 项、前端 14 项测试及两端 lint、类型和构建门禁均通过;Stage 2 新功能仍需增量验证。
|
||||
- [x] 已提交基线的两端 lint、类型、测试和构建门禁通过;本轮新增 model-gateway、应用工厂和 `0002` 仍须在提交前以 `make verify` 重新确认。
|
||||
|
||||
### 提交/推送节点
|
||||
|
||||
- [x] `S2-PREP`:React 离线演示、Nginx 单入口、四网络隔离、SSE 代理安全修复与质量门禁已随 `c3bad0f` 验证并推送;该前置提交不代表 Stage 2 已启动或完成。
|
||||
- [x] `S2-PREP`:React 离线演示、Nginx 单入口、当时的四网络隔离、SSE 代理安全修复与质量门禁已随 `c3bad0f` 验证并推送;后续 `model` 网络把当前拓扑扩为五层,该前置提交不代表 Stage 2 已启动或完成。
|
||||
- [ ] `S2-A`:FastAPI、持久化、健康端点和错误契约。
|
||||
- [ ] `S2-B`:任务租约、reaper、fencing 和恢复测试。
|
||||
- [ ] `S2-C`:React、Nginx、完整 Compose、CI 和文档;其中 React/Nginx/CI 前置已完成,缺 Worker 与完整 Compose 验收,节点不得提前关闭。
|
||||
@@ -561,12 +565,12 @@ Stage 0 DONE
|
||||
|
||||
## 15. 下一步执行顺序
|
||||
|
||||
当前主线仍是关闭 Stage 1 的真实供应商门禁;已完成的 React/Nginx 工作仅作为 Stage 2 安全前置保留。建议按以下顺序继续:
|
||||
当前主线仍是关闭 Stage 1 的真实供应商门禁,同时继续不依赖云授权的安全骨架。建议按以下顺序继续:
|
||||
|
||||
1. 在百炼控制台撤销聊天中暴露的旧 Key,创建新 Key,并仅通过未提交的 Docker Secret 注入。
|
||||
2. 分别运行 `text-embedding-v4`、`qwen3-rerank`、`deepseek-v4-flash` 受控 live smoke,记录脱敏 provider profile、能力指纹、维度、流式协议和错误映射。
|
||||
3. 用现有 20 条 synthetic 数据运行真实 Embedding/Rerank/Chat 链路和幂等 seed;真实地质资料仍不得出域。
|
||||
4. 关闭 S1-B 并把 Stage 1 标记为 `DONE` 后,再实现 Stage 2 的 Worker 租约、fencing、reaper、恢复测试和完整 Compose。
|
||||
1. 在百炼控制台确认旧 Key 已撤销,并核对当前本地 Secret 对应的工作空间、北京地域、端点、计费方案和模型权限;Key 只保存在未提交 Secret 中。
|
||||
2. 通过独立 `model-gateway` 重跑三能力 smoke;当前三项 401 未消除前保持 S1-B 未完成。
|
||||
3. 三能力成功后,用现有 20 条 synthetic 数据运行真实 Embedding/Rerank/Chat 链路和幂等 seed;真实地质资料仍不得出域。
|
||||
4. 继续实现 Stage 2 的 Worker 租约、fencing、reaper、恢复测试和完整 Compose,不让外部 401 阻塞可离线验证的工程工作。
|
||||
5. Stage 3 实现数字 PDF/DOCX/TXT/Markdown 的隔离解析、页码/章节恢复、审批状态机、切分、向量化写库和可追溯版本激活。
|
||||
6. Stage 4 完成真实向量召回、重排与验证集评测;Stage 5 再实现 grounded chat SSE、取消传播、引用解析和聊天 UI,不把当前代理分块测试误称为聊天功能完成。
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ make backend-sync
|
||||
bash scripts/init-local-secrets.sh
|
||||
```
|
||||
|
||||
脚本只生成三组彼此不同的随机 PostgreSQL 密码,文件权限为 `0600`。不带 `--with-bailian` 时不会创建模型 Key。
|
||||
脚本生成三组彼此不同的 PostgreSQL 密码,以及互不相同的 `model_gateway_api_token`、`model_gateway_worker_token`,文件权限为 `0600`。不带 `--with-bailian` 时不会创建或修改百炼 Key;若本地已存在 Key,脚本会明确提示保留原文件。
|
||||
|
||||
## 2. 离线数据库与向量 PoC
|
||||
|
||||
@@ -46,7 +46,7 @@ Seed 的实际流程是:
|
||||
|
||||
### 2.1 查看后端运行效果
|
||||
|
||||
内部 API 只连接 `internal` data 网络,只挂载 app 数据库 Secret,不连接外网,也不挂载百炼 Key。无 Secret、无数据库凭证的 gateway 连接 internal ingress/data;React/Nginx Web 连接 edge/ingress,并且是唯一发布到本机回环地址 `127.0.0.1:8000` 的容器。三个运行容器均使用非 root 用户、只读根文件系统并移除 Linux capabilities。
|
||||
内部 API 只连接 internal `data + model`,挂载 app 数据库 Secret 和 API 内部 token,不连接公网,也不挂载百炼 Key。独立 `model-gateway` 只连接 internal `model + egress`,是唯一挂载百炼 Key 的服务;它不连接数据库、不挂载上传卷、不发布端口。真实 `provider-smoke` 和 `seed-demo` 也只通过内部 token 调用该服务,不直持 Key。无数据库/模型 Secret 的入口 gateway 连接 internal ingress/data;React/Nginx Web 连接 edge/ingress,并且是唯一发布到本机回环地址 `127.0.0.1:8000` 的容器。
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8000/health/live
|
||||
@@ -82,7 +82,7 @@ bash scripts/init-local-secrets.sh --with-bailian
|
||||
docker compose --profile tools run --rm provider-smoke
|
||||
```
|
||||
|
||||
`provider-smoke` 依次验证:
|
||||
`provider-smoke` 以 API 内部身份调用 `model-gateway`,再由后者依次验证:
|
||||
|
||||
- `text-embedding-v4` 返回 1 个 1024 维有限非零向量;
|
||||
- `qwen3-rerank` 返回可映射到本地候选的下标和分数;
|
||||
@@ -97,7 +97,9 @@ docker compose --profile tools run --rm seed-demo
|
||||
docker compose --profile tools run --rm seed-demo
|
||||
```
|
||||
|
||||
真实模式仍只允许本仓库的虚构样例;任何真实地质报告必须等 Stage 3 的许可、涉密和 outbound manifest 审核链完成。
|
||||
真实模式使用 Worker 内部身份,仍只允许本仓库的虚构样例;任何真实地质报告必须等 Stage 3 的许可、涉密和 outbound manifest 审核链完成。
|
||||
|
||||
截至 2026-07-13,已通过独立 `model-gateway` 重跑三项真实调用:三项都到达供应商,但均返回安全脱敏的 `authentication` 类别(内部非流式调用由 Gateway 以 502 封装,Chat SSE 以终态错误事件返回;根因仍是供应商鉴权失败)。此时应核对 Key 所属工作空间、北京地域、专属端点、计费方案和模型权限,不要反复自动重试。在三项成功前不得声明百炼接入验收通过。
|
||||
|
||||
## 4. 质量门禁与排障
|
||||
|
||||
@@ -113,6 +115,7 @@ docker compose logs --no-log-prefix migrate
|
||||
|---|---|
|
||||
| `invalid_local_configuration` | 检查是否仍是占位 URL、两个 URL 是否同一北京工作空间、新 Key 文件是否存在 |
|
||||
| migrate 等不到 DB | 检查三个数据库 Secret 是否存在且互不相同;查看 bootstrap 日志 |
|
||||
| `migrate` 显示 `Exited (0)` | 正常:它是一次性 Alembic 服务,成功升级到 head 后就应退出;只有非 0 或反复重启才是故障 |
|
||||
| 401/403 | 不重试风暴;检查新 Key、工作空间和模型授权 |
|
||||
| 429/5xx/timeout | 适配器执行有界指数退避;持续失败时保留脱敏 request ID 后停止 |
|
||||
| seed 计数不是 20 | 不进入下一阶段;检查迁移、manifest 约束和事务日志 |
|
||||
@@ -127,4 +130,4 @@ docker compose down
|
||||
|
||||
## 5. 当前完成边界
|
||||
|
||||
可在没有百炼 Key 时验收:配置/Secret 安全、MockTransport 契约、离线向量/重排、Compose 渲染、空卷迁移、20 条写入和幂等。只有轮换后的新 Key 完成三模型真实 smoke 与真实 seed 后,Stage 1 才能从 `IN_PROGRESS` 改为 `DONE`。
|
||||
可在没有可用百炼权限时验收:配置/Secret 安全、内部 token 身份、MockTransport 契约、离线向量/重排、Compose 渲染、迁移、20 条写入和幂等。当前代码还包含 FastAPI 应用工厂、稳定 Problem/trace 契约,以及 `0002_model_profiles` 的 profile/cache/assignment/invocation/citation 迁移;这些不代表上传、正式检索、grounded chat、Worker 和评测已完成。只有有效 Key 完成三模型真实 smoke 与真实 seed 后,Stage 1 才能从 `IN_PROGRESS` 改为 `DONE`。
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
- Stage 0“仓库、安全和设计基线”已完成并推送。
|
||||
- Stage 1“模型与数据库 PoC”正在进行。
|
||||
- 当前整体完成度约 12%,合理区间为 10%–15%。
|
||||
- 00–03 的设计基线已完成;业务代码完成度约 18%,Stage 1 安全离线子阶段约 95%。
|
||||
- 后端离线链路、20 条 synthetic 数据、React 离线演示、Nginx 单入口和四网络隔离均已验收;真实百炼三模型 smoke 仍待旧 Key 撤销并轮换。
|
||||
- Stage 2 仍为 `TODO`:当前只完成了不依赖云密钥的前置,Worker 租约、完整 Compose 和产品工作流尚未完成。
|
||||
- 当前整体里程碑仍约 12%,合理区间为 10%–15%;新增代码不在阶段验收前提前计入完成度。
|
||||
- 00–03 的设计基线已完成;后端离线链路、20 条 synthetic 数据、React 离线演示和 Nginx 单入口已验收。
|
||||
- 五层网络与独立 `model-gateway` 已实现:只有该服务持百炼 Key/egress,API、seed、smoke 使用区分 API/Worker 身份的内部 token。
|
||||
- FastAPI 应用工厂、稳定 Problem/trace 契约,以及 `0002_model_profiles` 的 profile/cache/assignment/invocation/citation 迁移代码已落地。
|
||||
- 真实 `text-embedding-v4`、`qwen3-rerank`、`deepseek-v4-flash` 当前仍全部返回 401,真实百炼能力尚未验收。
|
||||
- Stage 2 仍为 `TODO`:Worker 租约/fencing/reaper、上传入库、正式检索、grounded chat、评测和完整产品 UI 尚未完成。
|
||||
- 后续状态、依赖、验收证据、工期和提交节点以 [项目全生命周期 TODO](04-project-todo.md) 为准。
|
||||
|
||||
## 文档列表
|
||||
|
||||
68
docs/adr/0005-isolate-model-egress.md
Normal file
68
docs/adr/0005-isolate-model-egress.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# ADR-0005:使用独立 Model Gateway 隔离百炼出口
|
||||
|
||||
- **状态:** accepted
|
||||
- **日期:** 2026-07-13
|
||||
|
||||
## 背景
|
||||
|
||||
在线 RAG 必须同步完成 Query Embedding、Rerank 和 Chat SSE,但 ADR-0004 又要求数据库感知 API 不持有百炼 Key、没有公网默认出口。让 API 或 Worker 直接调用百炼,会把业务数据、数据库凭据、模型凭据和公网出口集中到同一信任边界;只保留一次性 smoke/seed 工具则无法支持在线问答。
|
||||
|
||||
## 决策
|
||||
|
||||
新增与 API 共用 backend 镜像的长期 `model-gateway` 服务:
|
||||
|
||||
```text
|
||||
browser
|
||||
-> web (edge + ingress)
|
||||
-> gateway (ingress + data)
|
||||
-> api (data + model)
|
||||
-> PostgreSQL/pgvector (data)
|
||||
-> model-gateway (model + egress)
|
||||
-> Alibaba Cloud Model Studio
|
||||
|
||||
worker (data + model)
|
||||
-> PostgreSQL/pgvector
|
||||
-> model-gateway
|
||||
```
|
||||
|
||||
- 只有 `model-gateway` 挂载 `bailian_api_key` 并连接 `egress`;它不连接 `data`、不挂数据库 Secret、上传卷或宿主端口。
|
||||
- API 与 Worker 不挂百炼 Key、不连接 `egress`,只通过 internal `model` 网络访问固定的 `http://model-gateway:8000`。
|
||||
- API 与 Worker 使用不同的内部令牌。调用必须同时携带 `Authorization: Bearer ...` 与 `X-RAG-Caller`;Gateway 用常量时间比较把令牌映射到调用者,不能只信任 header 或 Docker 来源地址。
|
||||
- API 允许 Query Embedding、Rerank、Chat 和受控 provider health;Worker 额外允许 Document Embedding。任意越权调用失败关闭。
|
||||
- Model Gateway 只接受固定 profile、固定模型和固定端点的 vendor-neutral DTO,不接受客户端提供 URL、模型名、维度或任意转发目标。
|
||||
- Embedding、Rerank 和 Chat 继续复用现有 provider ports 与百炼 adapter,不引入 SDK、Redis、消息队列、服务网格或第二套实现。
|
||||
- 日志只允许 trace、调用者、操作、profile、输入数量、usage、耗时、request ID 和脱敏错误码;禁止记录 Key、Authorization、URL、query、messages、候选正文或响应正文。
|
||||
- 容器 readiness 只验证本地配置、Secret 和客户端初始化,不产生模型费用;真实探测使用受控内部端点和显式命令。
|
||||
|
||||
## 内部接口
|
||||
|
||||
- `POST /internal/v1/embeddings`:接收 `texts` 和 `input_type=query|document`;Query 恰好一条,Document 为 1–10 条;返回 1024 维向量、resolved model、usage、request ID 和耗时。
|
||||
- `POST /internal/v1/rerank`:接收 query、候选正文和 Top N;返回原始下标、score、受校验正文与 provider 元数据。
|
||||
- `POST /internal/v1/chat/completions`:非流式内部调用,用于需要先完成引用校验的场景。
|
||||
- `POST /internal/v1/chat/stream`:vendor-neutral SSE;流开始后的 provider 错误转换为终态 `error` 事件,取消必须关闭上游流。
|
||||
- `GET /health/live` 与 `GET /health/ready`:不调用百炼。
|
||||
|
||||
公共 API 不把 Model Gateway 的内部 401/403 原样返回浏览器;这类错误统一映射为可观测的 503 配置故障。429、timeout、5xx 和非法响应分别映射为稳定、脱敏的错误码。首个输出之后不得自动重试 Chat,避免重复文本。
|
||||
|
||||
## 被否决方案
|
||||
|
||||
1. **API 直接挂 Key 与 egress:** 数据库感知进程同时拥有数据、模型凭据和公网出口。
|
||||
2. **Worker 直接调用百炼:** 会形成两个供应商调用实现和两个 Key 边界。
|
||||
3. **Model Gateway 连接数据库验证审批:** 重新形成“数据 + Key + egress”的高风险组合。
|
||||
4. **只依赖 Docker DNS 或来源 IP:** 网络成员不是应用层身份,不能替代内部令牌。
|
||||
5. **引入通用 API Gateway、Redis、Celery 或服务网格:** 当前单机毕设规模没有足够收益,并增加部署与恢复面。
|
||||
|
||||
## 后续约束
|
||||
|
||||
- `provider-smoke` 与真实 seed 必须使用 Model Gateway 客户端,不允许恢复直连百炼旁路。
|
||||
- 普通在线查询只能使用知识库已激活的 embedding profile;Fake 与 Bailian profile 永不混查。
|
||||
- Docker bridge 不是域名白名单。生产环境仍需主机防火墙或出口代理只放行获批的百炼域名。
|
||||
- 改变模型调用者、内部鉴权、网络成员、Key 边界或 SSE 协议时,必须更新本 ADR 并重跑容器网络、Secret、取消和错误脱敏验收。
|
||||
|
||||
## 验证要求
|
||||
|
||||
- Compose 合同证明百炼 Key 与 `egress` 只存在于 `model-gateway`。
|
||||
- Model Gateway 无数据库 Secret、`data` 网络、上传卷和宿主端口;API/Worker 无百炼 Key和 `egress`。
|
||||
- 缺失/错误令牌、调用者冒充、scope 越权、URL/模型/维度注入均有失败测试。
|
||||
- Embedding、Rerank、Chat 的成功、401、429、timeout、5xx、非法响应、SSE 取消均由 hermetic contract test 覆盖。
|
||||
- `make verify`、镜像 Secret 扫描、Docker 健康检查、网络隔离与真实三模型 smoke 全部通过后,才能宣称在线百炼模型边界可用。
|
||||
@@ -8,3 +8,4 @@ ADR 用于记录会长期影响系统的技术决策。状态使用 `proposed`
|
||||
- [0002-separate-bailian-protocols.md](0002-separate-bailian-protocols.md):分离百炼 Chat/Embedding 与 Rerank 协议适配器。
|
||||
- [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、模型出口与数据库感知服务。
|
||||
|
||||
Reference in New Issue
Block a user