docs: add D13 retry decision and development roadmap
Record tenacity vs self-built evaluation as D13 in ARCHITECTURE.md (self-built wins: per-attempt orchestration, cancellation guarantees, supply-chain discipline). Add research-wiki/ROADMAP.md expanding the milestone table with ordering rationale and exit criteria.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
## 1. 项目元数据
|
||||
- **核心目标**: PolyGateway = 统一的大语言模型(LLM/VLM/OCR,音频预留)调度与中转库。治理单位是**一次模型调用**:请求封装、多源多账号、限流、错误分类与重试、熔断、Redis 响应缓存、流式看门狗、遥测(含成本)、结构化输出策略。全组件端口化可插拔。
|
||||
- **架构权威文档**: `research-wiki/ARCHITECTURE.md`(架构单一事实源,含 D1-D12 决策及讨论过程、子系统设计、三项目迁移验收标准;**不受 400 行设计文档限制**,以无歧义传达既有讨论为准绳)。`research-wiki/designs/` 仅存放每次实现具体功能的设计文档。
|
||||
- **架构权威文档**: `research-wiki/ARCHITECTURE.md`(架构单一事实源,含 D1-D13 决策及讨论过程、子系统设计、三项目迁移验收标准;**不受 400 行设计文档限制**,以无歧义传达既有讨论为准绳)。开发顺序见 `research-wiki/ROADMAP.md`;`research-wiki/designs/` 仅存放每次实现具体功能的设计文档。
|
||||
- **参考项目**: `reference/` 下三个项目是本库的需求来源与代码蓝本(**只读,勿改**);库必须能按 ARCHITECTURE.md §11 被它们迁移接入,否则即边界缺口。
|
||||
- **技术栈**: Python 3.11+,核心仅依赖 `httpx` + `pydantic`,其余(redis/sqlite/postgres/json_repair/openai)一律 optional extras。conda 环境 `PolyGateway`。
|
||||
|
||||
@@ -107,7 +107,8 @@ project_root/
|
||||
|
||||
| 需求 | 路径 |
|
||||
|---|---|
|
||||
| 架构全貌: 决策 D1-D12 及讨论过程、端口清单、错误分类、子系统设计、迁移验收 | `research-wiki/ARCHITECTURE.md`(单一事实源) |
|
||||
| 架构全貌: 决策 D1-D13 及讨论过程、端口清单、错误分类、子系统设计、迁移验收 | `research-wiki/ARCHITECTURE.md`(单一事实源) |
|
||||
| 开发顺序与里程碑状态 | `research-wiki/ROADMAP.md`(活文档,随进度更新) |
|
||||
| 功能设计文档(每次实现新功能时新增) | `research-wiki/designs/` |
|
||||
| 实现计划 | `research-wiki/plans/` |
|
||||
| 治理网关参考实现 | `reference/Video-Tree-TRM5/adapters/`(llm/breaker/streaming/redis_cache/telemetry) |
|
||||
|
||||
@@ -119,7 +119,7 @@ HTTP API → arq 队列 → worker 协程 脚本 → asyncio.gather 协
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构决策记录(D1–D12,含讨论过程与备选方案)
|
||||
## 3. 架构决策记录(D1–D13,含讨论过程与备选方案)
|
||||
|
||||
> 每条决策记录格式:**决策 / 背景与讨论 / 被否决的备选 / 影响**。这些决策已与人类逐条确认;推翻任何一条需要人类批准并修订本节。
|
||||
|
||||
@@ -223,6 +223,21 @@ HTTP API → arq 队列 → worker 协程 脚本 → asyncio.gather 协
|
||||
|
||||
**决策**: 库内禁止出现任何下游业务领域词汇(视频/文书/超声等)与业务 fixtures;扩展点一律 Protocol;import-linter 契约机械化执法(§8)。GovDoc 已证明这套纪律可执行(`pyproject.toml [tool.importlinter]`)。
|
||||
|
||||
### D13 重试自研,不引入 tenacity
|
||||
|
||||
**决策**: RetryMW 的重试循环自研(即移植三项目已实战验证的循环并收敛为单层),不引入 tenacity;下游业务项目中"重试一个幂等调用"的简单场景可自行使用 stamina,但不属于本库。
|
||||
|
||||
**背景与讨论**: 三个参考项目当年因不知道 tenacity 而自研。人类要求带着完整信息重新评估(2026-07-20 网络调研,含源码级查证)。tenacity 的客观优点:9.1.x 仍在维护、零传递依赖、wheel <30KB、月下载亿级、自定义 wait callable 可读取异常对象、sleep 可注入。**若需求只是"按指数退避重试一个幂等函数",应直接用它**。但对本库是净负担,理由:
|
||||
|
||||
1. **控制反转与逐次编排冲突(决定性)**: 我们每次尝试要改变下一次尝试做什么——换源、重新过限流闸、新 call_id、逐次遥测与熔断计数。查证确认 tenacity 的回调只能旁观 retry_state,**无任何 per-attempt argument mutation 机制**;唯一绕法是把全部编排塞进被重试的 callable,此时 tenacity 只剩循环骨架,而 Retry-After 取大者、按错误类型分支等待仍要写在自定义 wait callable 里(官方无按异常类型路由 wait 的组合子)。它能省下的只有约 15 行已被三项目验证过的退避公式。
|
||||
2. **两个已证实的坑打在要害**: ① statistics 用 thread-local 实现,不隔离同一事件循环内的并发协程——`AsyncRetrying` 实例不可跨并发协程共享(pydantic-ai issue #2661,2025-08,框架层被迫每次调用新建实例),而"共享 GatewayClient 被数百协程并发调用"正是本库标准形态;② CancelledError 默认不被吞,但谓词配成 BaseException 即复现 issue #186"被取消的协程在后台继续重试"——对"取消可穿透"铁律(§6.4)是靠约定而非结构维持的风险;自研循环中该保证是结构性的。
|
||||
3. **供应链与调试透明度**: 8.4.0(2024-06)发版事故一天击穿 langchain/llama-index/plotly 全生态;裸 `@retry` 默认无限次零间隔重试。基础库为省 15 行引入此类外部风险不划算;重试 bug 排查走自己 ~100 行循环远快于穿框架内部栈。
|
||||
4. **依赖纪律**(§8): 核心依赖极简,本案例恰是该铁律要拦的典型——收益小、面积大。
|
||||
|
||||
**被否决的备选**: tenacity(上述);stamina(hynek 封装,2026-04 仍活跃,安全默认值+仪表,适合业务侧简单重试,不适合需逐次编排的网关核心);backoff(仓库 2025-08 已 archive,不再考虑,实验室他处若在用应提醒迁移)。
|
||||
|
||||
**影响**: §7.2 的单层重试原则不变;RetryMW 循环保持结构性禁止 `except BaseException`;此结论基于 2026-07 的库现状,若 tenacity 未来提供逐次编排能力可重评。
|
||||
|
||||
---
|
||||
|
||||
## 4. 总体架构
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# PolyGateway 开发路线图
|
||||
|
||||
> **定位**: ARCHITECTURE.md §12 里程碑表的展开——论述开发**顺序及其理由**、每阶段的交付物与验收出口。本文档是**活文档**:里程碑动工、完成、变更时更新状态;与 ARCHITECTURE.md 冲突时以后者为准(或先修订后者)。
|
||||
> **流程约束**: 每个里程碑动工前,按 SOP 产出该里程碑的功能设计文档(`designs/`,≤400 行,人类门);本文档不替代设计文档,只定顺序与验收。
|
||||
|
||||
## 0. 排序的四条原则
|
||||
|
||||
1. **每个里程碑结束都有真实接入方**——排序服务于"最快让真项目用上",防止造没人用的空中楼阁(ARCHITECTURE §11 的验收哲学)。这就是 M1 先做内存后端(GovDoc/Video-Tree 可接)、M2 才做 Redis 后端(CHSAnalyzer 需要)的原因。
|
||||
2. **公共承诺最先冻结**——types/errors/ports 是三项目要消费的公共 API,返工代价最大,放在一切实现之前设计并过人类门;`LLMResponse` 字段从冻结起只增不删(ARCHITECTURE §5.1)。
|
||||
3. **单进程能力先于分布式**——内存版限流/熔断先行,语义契约先在单进程下被测试钉死,Redis 后端随后实现**同一套契约**(双后端 D3),避免两个后端语义漂移。
|
||||
4. **纯函数与叶子模块先行**——看门狗、SSE 解析、错误翻译这类零依赖组件先落地先测试,为上层提供已验证的地基。
|
||||
|
||||
## 1. 总览与状态
|
||||
|
||||
| 阶段 | 内容 | 接入方 | 状态 |
|
||||
|---|---|---|---|
|
||||
| P0 奠基 | 调研、ARCHITECTURE.md、CLAUDE.md、skill Fable 5 改造、hooks 硬边界、脚手架(git/pyproject/Makefile/conda 环境/CI 绿) | — | ✅ 完成(2026-07-20) |
|
||||
| M1 核心 | 内核类型 + httpx transport + 治理中间件(内存后端)+ 缓存 + SQLite 遥测 + 结构化输出 + from_env | GovDoc、Video-Tree | ⬜ 未开始(前置: M1 设计文档过人类门) |
|
||||
| M2 分布式 | Redis 限流/熔断后端、多源多账号、背压、Postgres 遥测、成本 | CHSAnalyzer(治理) | ⬜ 未开始 |
|
||||
| M3 OCR | OCR 端口族 + MonkeyOCR transport | CHSAnalyzer(全量)、Video-Tree(OCR 升级) | ⬜ 未开始 |
|
||||
| M4 迁移验证 | 三项目逐一按 ARCHITECTURE §11 验收,缺口回补,发 v1.0 | 全部 | ⬜ 未开始 |
|
||||
|
||||
## 2. M1 核心(目标: GovDoc / Video-Tree 可试点接入)
|
||||
|
||||
**内部开发顺序**(编号即依赖顺序,同号可并行):
|
||||
|
||||
| 步 | 内容 | 顺序理由 |
|
||||
|---|---|---|
|
||||
| 1 | `types.py` + `errors.py` + `ports.py` 全量设计与冻结 | 原则 2:公共承诺先行;这是 M1 设计文档(人类门)的主体 |
|
||||
| 2a | `streaming.py` 看门狗移植 | 原则 4:纯函数,零依赖,直接移植+补测 |
|
||||
| 2b | `providers.py` 注册表 | 叶子模块;transport 的前置(thinking 注入/思考流字段声明) |
|
||||
| 3 | `transports/openai_compat.py`(SSE 解析、非流式快路径、错误翻译 §6.2) | 依赖 1/2a/2b;错误翻译是中间件的语义地基 |
|
||||
| 4a | `middleware/retry.py`(D13 自研,单层原则) | 依赖错误分类;先于限流接入便于独立测试 |
|
||||
| 4b | `backends/memory/`(limiter + breaker)+ 对应中间件 | 语义契约(permit/settle、状态机)在内存版上钉死,契约测试同步交付 |
|
||||
| 4c | `backends/` 缓存(Redis 为主 + 内存)+ `middleware/cache.py` | 缓存独立于限流/熔断,可并行;key 公式 §7.5 |
|
||||
| 4d | `telemetry/sqlite.py` + `middleware/telemetry.py`(单一 helper 铁律) | 可并行;18 字段 §7.8,pricing 留 M2 |
|
||||
| 5 | `structured/`(json_repair + native_schema 双策略) | 依赖 transport 的响应形态 |
|
||||
| 6 | `client.py` 组装 + `from_env()`/`from_settings()` + `gather_bounded` | 组装层最后;.env 键名清单在此定稿并回填 `.env.example` |
|
||||
| 7 | 端到端验证: 对真实私有网关的冒烟(输出落 `tests/outputs/`)+ 全新上下文 verifier subagent 审查 | Phase 2 独立验证门 |
|
||||
|
||||
**验收出口(exit criteria)**: `make ci` 绿(含 import-linter 契约生效,门控解除);单元+集成覆盖 ≥80%;对真实网关冒烟通过;**GovDoc 或 Video-Tree 任一项目完成"最小接入冒烟"**——不要求全迁移,只要求用 `polygateway` 发起一次真实治理调用替代其 `GovernedLLMClient` 路径跑通。
|
||||
|
||||
**主要风险**: 端口签名一旦冻结返工代价大 → 设计文档阶段用三项目现有调用点反推签名(ARCHITECTURE §11 对接点)做纸面验证。
|
||||
|
||||
## 3. M2 分布式(目标: CHSAnalyzer 治理能力对标)
|
||||
|
||||
**内部顺序**: ① Redis 限流六道闸(移植 CHS Lua + **契约测试与内存版共用一套**,原则 3)→ ② Redis 熔断(epoch fencing)→ ③ 多源多账号 + 选源策略 + 源冷却备忘 → ④ 背压 stall 判定 → ⑤ `telemetry/postgres.py` + `pricing.py` 成本入遥测 → ⑥ (若 Q3 拍板纳入)Embedding 客户端复用治理栈。
|
||||
|
||||
**验收出口**: 双后端在同一契约测试套件下全绿;多 worker 压测下全局限额真实生效(RPM 不超配);CHSAnalyzer 的 `limiter/provider_gate/governance` 能力对标清单逐项打钩(ARCHITECTURE §11.3);Redis 掉线时降级方向符合铁律(限流/熔断报错、缓存静默)。
|
||||
|
||||
**主要风险**: Lua 脚本移植语义漂移 → 连同 CHS 的 `tests/contracts_limiter.py` 一起移植并扩充。
|
||||
|
||||
## 4. M3 OCR(目标: CHSAnalyzer 全量可迁、Video-Tree OCR 免费升级)
|
||||
|
||||
**内部顺序**: ① `OcrTextResult`/`OcrLayoutResult` 类型 + 两端口(设计文档人类门,因是公共 API)→ ② `transports/monkey_ocr.py`(两端点:multipart→JSON 与 multipart→ZIP→`_middle.json`,数值防御校验下沉)→ ③ 接入既有中间件栈(OCR 无 token 计费,Usage 置 0)→ ④ `ResultInvalidError` 语义联调(坏结果不熔断)。
|
||||
|
||||
**验收出口**: 对真实 MonkeyOCR 服务(LAN)双端点集成测试通过;CHSAnalyzer 的 `MonkeyOcrParseInvoker` 路径可替换;Video-Tree 的裸调 OCR 换库后获得重试/熔断。
|
||||
|
||||
## 5. M4 迁移验证(目标: 三项目验收,发 v1.0)
|
||||
|
||||
**顺序**(按难度递增,每个项目的缺口回补后再迁下一个): ① GovDoc-SaaS → ② Video-Tree-TRM5 → ③ CHSAnalyzer。每项目按 ARCHITECTURE §11 的删除清单+处置表执行,原测试全绿为过关;发现的边界缺口回补进库(可能触发小版本迭代)后重验。全部通过后打 `v1.0.0`,分发方式按 Q1 拍板结果执行。
|
||||
|
||||
**主要风险**: 迁移中发现隐性行为依赖(参考 Video-Tree CLAUDE.md 的"前序版本对照"教训)→ 每项目迁移前先做旧版行为审计(brainstorming skill 已内置该环节)。
|
||||
|
||||
## 6. 后续储备(非承诺,按需求触发)
|
||||
|
||||
音频端口实现(D10)、SDK transport(openai/anthropic 原生协议,D2 预留)、GLM OCR invoker(D9 预留)、内网 pip index(Q1)、多项目共用 Redis 的 namespace 治理、harness-eval 评估流水线激活。
|
||||
|
||||
## 7. 开放决策依赖
|
||||
|
||||
| 决策 | 阻塞点 | 需拍板时间 |
|
||||
|---|---|---|
|
||||
| Q1 打包分发(git+ssh vs 内网 index) | 不阻塞开发,阻塞 M4 发布 | M4 前 |
|
||||
| Q3 Embedding 是否纳入 | 影响 M2 范围 | M2 设计文档前 |
|
||||
Reference in New Issue
Block a user