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
+14
View File
@@ -319,6 +319,20 @@ class TestEmbedBatching:
assert resp.cost is None
class TestEmbedGenerationMs:
"""裸生成时间按批累加(1.3.7 H8): generation_ms = 各批 transport 耗时之和。"""
async def test_generation_ms_sums_batch_transports(self):
# 0.25s 为二进制可精确表示值: int 截断下非精确值会因浮点误差少 1ms
clock = FakeClock()
transport = _ClockAdvancingEmbedTransport([(0.25, "ok"), (0.25, "ok")], clock)
client, _ = _embed_client([_src()], [], transport=transport, now=clock)
resp = await client.embed(["a", "bb", "ccc", "dddd"])
assert resp.call_stats is not None
assert resp.call_stats.attempts == 2
assert resp.call_stats.generation_ms == 500
class TestEmbedPostProcess:
async def test_normalize_l2(self):
raw = EmbeddingTransportResult(