fix: keep a low-tier answer out of the cache slot a max-tier one filled
The per-call reasoning tier never reached the cache key, and the model fingerprint could not stand in for it: the fingerprint is computed once at assembly time, so two calls on the same client asking for low and max looked identical to it. Same messages, different tiers, one shared entry -- the verbatim replay of issue #4's five seeds all hitting the same response. Source-level tiers join the fingerprint under the same rule enable_thinking already follows (appended only when the source takes a position), and the filter that decides which sources enter the mark set is widened to match -- without that, a source configured with nothing but REASONING_EFFORT would never reach _fingerprint_mark at all. None (no opinion) and Effort.NONE (asked not to reason) stay distinct keys. Sources that opine on neither keep byte-identical keys and fingerprints, so nothing existing cold-starts.
This commit is contained in:
@@ -11,7 +11,13 @@ from polygateway.backends.memory.cache import InMemoryCache
|
||||
from polygateway.errors import ResultInvalidError, TransientError
|
||||
from polygateway.middleware.cache import CacheMW, build_cache_key, digest_messages
|
||||
from polygateway.middleware.telemetry import TelemetryEmitter
|
||||
from polygateway.types import ChatRequest, LLMResponse, SourceConfig, ThinkingObservation
|
||||
from polygateway.types import (
|
||||
ChatRequest,
|
||||
Effort,
|
||||
LLMResponse,
|
||||
SourceConfig,
|
||||
ThinkingObservation,
|
||||
)
|
||||
|
||||
_MSGS = [{"role": "user", "content": "hi"}]
|
||||
|
||||
@@ -114,6 +120,40 @@ class TestKeyFormula:
|
||||
"m", messages2, "p", None
|
||||
)
|
||||
|
||||
def test_request_tier_changes_key(self):
|
||||
"""同 messages 跑 low 与 max 不得互相命中(issue #20;issue #4 的逐字翻版)。
|
||||
|
||||
请求级档位必须**独立于** `model_fingerprint` 进 key: 后者是装配期算出的
|
||||
集合级指纹,一次调用改档位不会让它变一个字节。
|
||||
"""
|
||||
k_low = build_cache_key("m", _MSGS, "proj", None, reasoning_effort=Effort.LOW)
|
||||
k_max = build_cache_key("m", _MSGS, "proj", None, reasoning_effort=Effort.MAX)
|
||||
assert k_low != k_max
|
||||
|
||||
def test_explicit_none_tier_is_not_the_absent_tier(self):
|
||||
"""`None`(不表态)与 `Effort.NONE`(要求不推理)是两个 key。
|
||||
|
||||
二者合并即毒化: "没写档位"的调用会读到"明确关掉推理"那次的响应,
|
||||
而后者的内容恰恰是缺推理过程的。
|
||||
"""
|
||||
assert build_cache_key("m", _MSGS, "proj", None) != build_cache_key(
|
||||
"m", _MSGS, "proj", None, reasoning_effort=Effort.NONE
|
||||
)
|
||||
|
||||
def test_absent_tier_keeps_legacy_key(self):
|
||||
"""不表态档位时键形逐字不变,存量缓存不被本次升级全量作废。
|
||||
|
||||
golden 值与 `test_empty_sampling_keeps_legacy_key` 同源,取自加
|
||||
`reasoning_effort` 维度之前的实现,不得随实现漂移。
|
||||
"""
|
||||
assert build_cache_key(
|
||||
"qwen-max",
|
||||
[{"role": "user", "content": "hi"}],
|
||||
"proj",
|
||||
None,
|
||||
reasoning_effort=None,
|
||||
) == ("pgw:cache:c54544e8672f4c91373b4a72716a88497445b440b89445aa5379b356b228f58b")
|
||||
|
||||
|
||||
class _Terminal:
|
||||
def __init__(self, response):
|
||||
@@ -163,6 +203,21 @@ class TestCacheFlow:
|
||||
third = await mw(ChatRequest(messages=_MSGS, sampling={"seed": 1}), terminal)
|
||||
assert third.cache_hit is True and terminal.calls == 2
|
||||
|
||||
async def test_differing_reasoning_effort_does_not_hit(self):
|
||||
"""接线门: `CacheMW` 必须把 `request.reasoning_effort` 传进 key 公式。
|
||||
|
||||
只测 `build_cache_key` 不够——参数加了却没人传是本改动最可能的落地方式,
|
||||
那种缺口在公式层的用例里完全看不见。
|
||||
"""
|
||||
backend = InMemoryCache()
|
||||
mw = _mw(backend)
|
||||
terminal = _Terminal(_resp())
|
||||
await mw(ChatRequest(messages=_MSGS, reasoning_effort=Effort.LOW), terminal)
|
||||
await mw(ChatRequest(messages=_MSGS, reasoning_effort=Effort.MAX), terminal)
|
||||
assert terminal.calls == 2 # 两档各自回源
|
||||
third = await mw(ChatRequest(messages=_MSGS, reasoning_effort=Effort.LOW), terminal)
|
||||
assert third.cache_hit is True and terminal.calls == 2 # 同档才命中
|
||||
|
||||
async def test_structured_injection_does_not_pollute_key(self):
|
||||
"""CacheMW 读 sampling 而非 overlay: 结构化注入不该改变缓存身份。"""
|
||||
backend = InMemoryCache()
|
||||
|
||||
Reference in New Issue
Block a user