feat: expose bare generation time and hedge flags in CallStats

CallStats gains hedges/generation_ms/hedge_won (all defaulted, appended
after total_latency_ms); _CallContext counts them via record_generation
(overwrite for chat/OCR, accumulate for embedding batches) and
register_hedge, and snapshot carries them out. All three _attempt
implementations time only the transport call itself on the same injected
clock as total_latency_ms; the chat sink is recorded by the orchestrator
so hedge winner attribution stays with T3. Hedge counters stay 0/False
until the T3 orchestration lands.

Red-green evidence: tests/outputs/137/t2/ (10 new tests AttributeError
red, then green; full unit+contracts 1621 passed).
This commit is contained in:
2026-09-10 13:28:51 -04:00
parent 0a6d6225db
commit 463eca380d
9 changed files with 283 additions and 6 deletions
+15
View File
@@ -196,6 +196,21 @@ class TestSuccessPaths:
await client.parse_layout(b"")
class TestOcrGenerationMs:
"""OCR 裸生成时间单次覆盖(1.3.7 H8): 计时只包 transport 调用本身。"""
async def test_generation_ms_single_transport_call(self):
# 0.5s 为二进制可精确表示值: int 截断下非精确值会因浮点误差少 1ms
clock = FakeClock()
transport = ClockAdvancingOcrTransport([(0.5, "text")], clock)
client, _, _ = _client([_src()], [], now=clock, transport=transport)
r = await client.recognize_text(b"jpg")
assert r.text == "LINE-1"
assert r.call_stats is not None
assert r.call_stats.attempts == 1
assert r.call_stats.generation_ms == 500
class TestFailover:
async def test_transient_retries_with_backoff(self):
sleeps = []