docs: document the optional call deadline and cancellation settlement
The deadline governs waiting, not the moment a call returns: cleanup still runs in finally, so the return time is the deadline plus the cleanup cost (5-7x the deadline in the measured fixture). An expiry therefore does not mean nothing was produced or nothing was billed. Leaving the key unset keeps 1.3.5 semantics verbatim, which also keeps its two long waits: a pure 429 sequence can still wait for a long time, and a large finite Retry-After is still slept in full because the library deliberately does not clamp the hint with backoff_max_s. - CHANGELOG unreleased section states those three sentences, records the cancellation settlement change (an attempt cancelled after the port started but before the settlement is known keeps its reservation at the source estimate: over-charge rather than refund something the upstream may already have billed; known settlements and unclassified escapes are untouched) and warns that except GatewayUnavailableError does not catch CallDeadlineExceeded - README in four places: the capability table, the exception handling example, the "which exceptions reach the caller" table and the error model section, which now spells out the remaining large-but-finite Retry-After wait - .env.example documents LLM__CALL_DEADLINE_S as commented out - new findings file indexes the red/green evidence, the commands and their exit codes, what was not run and who covers it, and repeats the three residual risks No version bump, no tag, no release: those belong to the release checklist.
This commit is contained in:
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## 未发布
|
||||
|
||||
给一次逻辑调用加了一条**可选**墙钟硬边界(issue #22),并修好取消路径的 TPM 结算与 `Retry-After` 非有限值防御。
|
||||
|
||||
### 关于调用期限,请先读这三句
|
||||
|
||||
| # | 承诺 | 展开 |
|
||||
| --- | --- | --- |
|
||||
| 1 | **期限治理的是「等待」,不是「返回时刻」** | 到期后取消在飞的尝试,但清理(遥测写入、限流结算、缓存收尾)仍在 `finally` 里跑完,**允许超出期限**。实测构造(两次慢遥测写入)里返回时刻达期限的 **5–7 倍**;库只对「等待被切断」给承诺,对「多久返回」不给上界 |
|
||||
| 2 | **到期 ≠ 未产出、≠ 未计费** | 上游可能已经算完并计费,只是结果在返回路上被丢弃(缓存写入慢于期限就是一例)。把 `CallDeadlineExceeded` 当成「这次没花钱」会低估成本 |
|
||||
| 3 | **不配置就是 1.3.5 语义,逐字不变** | `call_deadline_s` 缺省 `None` 时根本不进 `asyncio.timeout` 上下文。故 1.3.5 的两条长等仍在: 纯 429 序列(429 不消耗重试预算)仍可能长时间等待;**有限大的 `Retry-After`(如 3600s)仍照睡**——库有意不用 `backoff_max_s` 去夹它,唯一制约手段就是本版这条期限 |
|
||||
|
||||
### 公共面新增(三项,全为纯新增)
|
||||
|
||||
| # | 位置 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `polygateway.CallDeadlineExceeded` | 新异常;带 `scope` / `deadline_s`,**无 `retry_after_s`**(到期不含「何时可再试」,给 `0.0` 会指示下游立刻重打饱和渠道) |
|
||||
| 2 | 配置键 `{SCOPE}__CALL_DEADLINE_S` | 缺省不设 = 不启用;非法值(0/负/`nan`/`inf`/非数)在**装配期**当场 `ValueError` |
|
||||
| 3 | 三个 client 构造参数 + 四个公开方法的 keyword-only 参数 | `GatewayClient` / `EmbeddingClient` / `OcrClient` 的 `call_deadline_s`;`chat` / `embed` / `recognize_text` / `parse_layout` 可 per-call 覆盖(`None` = 继承装配值,**不提供「本次关闭」**)。一次 `embed` 的 N 个批次共享同一份期限,不随批数放大 |
|
||||
|
||||
> [!WARNING]
|
||||
> **`except GatewayUnavailableError` 接不住 `CallDeadlineExceeded`。** 新异常直接继承 `PolyGatewayError`,既不属四分类,也不在 `GatewayUnavailableError` 族内——期限到期是**调用方自己设的边界**,不是网关不可用。只有显式配了期限的调用方才会遇到它,需要处理就单列一条 `except`。遥测侧无需改动: 三个边界既有的 `except PolyGatewayError` 会接住它并照常写一条 `terminal_failure` 行(`error_type='CallDeadlineExceeded'`),**零新增列**。
|
||||
|
||||
### 行为变更:取消路径的 TPM 结算口径
|
||||
|
||||
| 情形 | 1.3.5 | 本版 |
|
||||
| --- | --- | --- |
|
||||
| 取消发生在**端口已开始、结算尚未确定**时 | `settle(0)`,入场预扣整笔退还 | 按 `est` **保留预扣**(方向是宁多扣不空退: 上游可能已计费) |
|
||||
| 结算已确定(含真实 usage 恰为 0 的成功、已判 `SourceDead` 的 `0`) | 按已算出的值 | **一字不变**,取消不覆写 |
|
||||
| 未被四分类接住的异常逃逸(`RuntimeError` 等) | `0` | **仍按 `0`**,本版不扩大语义(已登记为残留) |
|
||||
|
||||
启用期限后库自身会常规性触发取消路径,故这条记账修复与期限同版交付。OCR 的 `settle(0)` 不变——无 token 是事实而非「未知」。非取消路径的最终结算值与 1.3.5 逐字相同,只是算得更早(失败分支的结算决定前移到其第一个 `await` 之前)。
|
||||
|
||||
### 其他
|
||||
|
||||
- `Retry-After: inf` / `1e999`(`float()` 会把它舍成 `inf`)此前会原样进入退避并让该次尝试睡到天荒地老;现按「无提示」处理,退回纯指数退避,并发**一条**带源名与判据词的 warning(不回显原始头,429 风暴下会淹掉真信号)。`nan`、空串、负数、HTTP-date 的既有值语义一字未动。
|
||||
- 限流 Lua、`Permit` 端口签名、遥测 schema、缓存 key 公式、重试预算与退避算法、熔断语义**均未改动**。
|
||||
|
||||
## 1.3.5(2026-09-09)
|
||||
|
||||
把治理单位从「一次尝试」补齐到「一次逻辑调用」(issue #19、#23)。此前重试、换源、结构化重问、embedding 分批都各自独立可见,而「这一次调用总共打了几次、总共花了多久、最后为什么失败」在库外拼不出来;结构化耗尽、embedding/OCR 的无源与准入拒绝更是**一条遥测行都没有**。
|
||||
|
||||
Reference in New Issue
Block a user