feat: let tpm be configured without an est_tokens companion
The gate check forced operators to guess a per-call token size before they could enable the TPM gate at all; est_tokens is now an optional tuning override and effective_est_tokens() derives the reservation from the provider quota. Reservation and settlement already read the same derived value, so the deposit still nets to zero on both the success path and the non-dead transient failure path. The rest of _validate_gates is untouched, and the est_tokens field plus its EST_TOKENS env key stay put for migration compatibility.
This commit is contained in:
@@ -171,6 +171,28 @@ class TestSuccessPath:
|
||||
# 预扣 400,实测 40+60 → settle 后窗口记 100(而非派生兜底的 400)
|
||||
assert (await limiter.source_stats("a")).tpm_used == 100
|
||||
|
||||
async def test_settle_keeps_derived_deposit_when_usage_unavailable(self):
|
||||
"""未填 est_tokens + usage 帧缺失的**成功**调用: 押金留存而非整笔退回。
|
||||
|
||||
入场预扣与结算须同取 `effective_est_tokens()`(delta==0),否则对
|
||||
"从不返回 usage 帧"的源等于 TPM 闸进门即放行、出门即清账(设计 §3.2 #9)。
|
||||
"""
|
||||
src = _src("a", tpm=1000, est_tokens=0) # 派生预扣量 = max(1, 1000 // 60) = 16
|
||||
result = TransportResult(
|
||||
content="ok",
|
||||
thinking="",
|
||||
prompt_tokens=0,
|
||||
completion_tokens=0,
|
||||
usage_source="unavailable",
|
||||
ttft_ms=12.0,
|
||||
max_inter_token_ms=3.0,
|
||||
raw={},
|
||||
)
|
||||
mw, limiter, *_ = _harness([src], [result])
|
||||
await mw(_REQ)
|
||||
assert src.effective_est_tokens() == 16
|
||||
assert (await limiter.source_stats("a")).tpm_used == 16
|
||||
|
||||
|
||||
class TestRetryAndFailover:
|
||||
async def test_transient_switches_source_then_succeeds(self):
|
||||
@@ -221,6 +243,18 @@ class TestRetryAndFailover:
|
||||
assert sleep.delays == [] # 源死亡不退避
|
||||
assert not (await gate.try_enter("a", "w")).allowed # a 已 force_open
|
||||
|
||||
async def test_transient_failure_keeps_derived_deposit(self):
|
||||
"""未填 est_tokens 的**非 dead 瞬时失败**同样按派生预扣量保守结算。
|
||||
|
||||
失败请求可能已被网关计费,退掉押金会低估用量(设计 §3.2 #8);
|
||||
max_attempts=1 保证恰一次尝试,窗口残留量即单次预扣量。
|
||||
"""
|
||||
src = _src("a", tpm=1000, est_tokens=0) # 派生预扣量 = 16
|
||||
mw, limiter, *_ = _harness([src], [TransientError("boom")], max_attempts=1)
|
||||
with pytest.raises(AllSourcesExhausted):
|
||||
await mw(_REQ)
|
||||
assert (await limiter.source_stats("a")).tpm_used == 16
|
||||
|
||||
|
||||
class TestNonRetryableOutcomes:
|
||||
async def test_request_rejected_propagates_without_retry(self):
|
||||
|
||||
Reference in New Issue
Block a user