feat: track logical call statistics across governed calls
This commit is contained in:
@@ -818,3 +818,75 @@ class TestExplicitCacheMigration:
|
||||
finally:
|
||||
for transport in transports:
|
||||
await transport.aclose()
|
||||
|
||||
|
||||
class TestCallStatsNotPoisoned:
|
||||
"""缓存不得回放历史统计(1.3.5 设计 §3)。
|
||||
|
||||
统计描述**本次**调用;把上次那条存进去再放出来,等于对调用方谎称这次
|
||||
重试了 N 次、耗了 M 毫秒。
|
||||
"""
|
||||
|
||||
async def test_serialized_payload_carries_no_call_stats_key(self):
|
||||
from polygateway.types import CallStats
|
||||
|
||||
backend = InMemoryCache()
|
||||
mw = _mw(backend)
|
||||
stats = CallStats(logical_call_id="lc-1", attempts=3, total_latency_ms=900)
|
||||
terminal = _Terminal(_resp(call_stats=stats))
|
||||
await mw(ChatRequest(messages=_MSGS), terminal)
|
||||
key = build_cache_key("m", _MSGS, "proj", None)
|
||||
stored = json.loads(await backend.get(key))
|
||||
assert "call_stats" not in stored # asdict 会把它摊成 dict,必须显式剔除
|
||||
|
||||
async def test_historic_dict_never_impersonates_call_stats(self):
|
||||
"""旧条目里的 `call_stats` dict 会被 `_RESPONSE_FIELDS` 放行,必须显式覆盖。
|
||||
|
||||
不覆盖就会有一个 dict 冒充 `CallStats` 从公共 API 漏给调用方,
|
||||
`resp.call_stats.attempts` 当场 `AttributeError`。
|
||||
"""
|
||||
backend = InMemoryCache()
|
||||
mw = _mw(backend)
|
||||
key = build_cache_key("m", _MSGS, "proj", None)
|
||||
poisoned = {
|
||||
"content": "legacy",
|
||||
"thinking": "",
|
||||
"model": "m",
|
||||
"provider": "p",
|
||||
"prompt_tokens": 1,
|
||||
"completion_tokens": 2,
|
||||
"latency_ms": 30,
|
||||
"ttft_ms": 5.0,
|
||||
"max_inter_token_ms": 2.0,
|
||||
"cache_hit": False,
|
||||
"call_id": "orig",
|
||||
"source_name": "s1",
|
||||
"usage_source": "measured",
|
||||
"call_stats": {
|
||||
"logical_call_id": "stale-lc",
|
||||
"attempts": 7,
|
||||
"total_latency_ms": 9999,
|
||||
},
|
||||
}
|
||||
await backend.set(key, json.dumps(poisoned), ttl_s=100)
|
||||
terminal = _Terminal(_resp())
|
||||
hit = await mw(ChatRequest(messages=_MSGS), terminal)
|
||||
assert hit.content == "legacy" and terminal.calls == 0 # 真的走了缓存
|
||||
assert hit.call_stats is None # dict 不得冒充 CallStats
|
||||
|
||||
async def test_cache_key_is_unchanged_by_the_new_field(self):
|
||||
"""新增内部字段不得扰动 key 公式,否则存量缓存全量冷启动(黄金值)。"""
|
||||
from polygateway.types import _CallContext
|
||||
|
||||
class _Clock:
|
||||
def __call__(self):
|
||||
return 1000.0
|
||||
|
||||
ctx = _CallContext(now=_Clock())
|
||||
bare = build_cache_key("m", _MSGS, "proj", None)
|
||||
assert bare == build_cache_key("m", _MSGS, "proj", None)
|
||||
# 带上下文的请求与不带的请求必须落在同一个 key 上
|
||||
with_ctx = ChatRequest(messages=_MSGS, call_context=ctx)
|
||||
without = ChatRequest(messages=_MSGS)
|
||||
assert with_ctx.cache_namespace == without.cache_namespace
|
||||
assert digest_messages(with_ctx.messages) == digest_messages(without.messages)
|
||||
|
||||
Reference in New Issue
Block a user