feat: fold sampling parameters into the cache key
Without this, five seeds over identical messages all hit the first cached response and the reported standard deviation is silently always zero.
This commit is contained in:
@@ -20,6 +20,8 @@ from loguru import logger
|
||||
from polygateway.types import ChatRequest, LLMResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
|
||||
from polygateway.ports import CacheBackend, CallNext, StructuredOutputStrategy
|
||||
|
||||
_KEY_PREFIX = "pgw:cache:"
|
||||
@@ -50,9 +52,19 @@ def _digest_part(part: Any) -> Any:
|
||||
|
||||
|
||||
def build_cache_key(
|
||||
model_fingerprint: str, messages: list[dict[str, Any]], namespace: str, salt: str | None
|
||||
model_fingerprint: str,
|
||||
messages: list[dict[str, Any]],
|
||||
namespace: str,
|
||||
salt: str | None,
|
||||
*,
|
||||
sampling: Mapping[str, Any] | None = None,
|
||||
) -> str:
|
||||
"""缓存 key 公式;salt 仅非 None 时参与(VT 旧键语义: 不传 salt 键形不变)。"""
|
||||
"""缓存 key 公式;salt 仅非 None 时参与(VT 旧键语义: 不传 salt 键形不变)。
|
||||
|
||||
`sampling` 仅**非空**时参与(与 salt 的"仅非 None"不同——空串是有意义的
|
||||
salt,而空采样参数与不传无语义差别)。它必须进 key: 否则同 messages 跑 5 个
|
||||
seed 会全部命中第一次的响应,标准差恒为 0 且不报错(issue #4 决策 C)。
|
||||
"""
|
||||
key_obj: dict[str, Any] = {
|
||||
"model": model_fingerprint,
|
||||
"messages": digest_messages(messages),
|
||||
@@ -60,6 +72,8 @@ def build_cache_key(
|
||||
}
|
||||
if salt is not None:
|
||||
key_obj["salt"] = salt
|
||||
if sampling:
|
||||
key_obj["sampling"] = dict(sampling)
|
||||
payload = json.dumps(key_obj, sort_keys=True, ensure_ascii=False)
|
||||
return _KEY_PREFIX + hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
@@ -92,7 +106,15 @@ class CacheMW:
|
||||
|
||||
async def __call__(self, request: ChatRequest, call_next: CallNext) -> LLMResponse:
|
||||
namespace = request.cache_namespace or self._namespace
|
||||
key = build_cache_key(self._fingerprint, request.messages, namespace, request.cache_salt)
|
||||
# 读 sampling 而非 overlay: 语义明确,且不依赖"CacheMW 恰在 StructuredMW
|
||||
# 外侧"这一层序巧合——结构化注入不该改变缓存身份(设计决策 C)
|
||||
key = build_cache_key(
|
||||
self._fingerprint,
|
||||
request.messages,
|
||||
namespace,
|
||||
request.cache_salt,
|
||||
sampling=request.sampling,
|
||||
)
|
||||
cached = await self._safe_get(key)
|
||||
if cached is not None:
|
||||
hit = self._rehydrate(cached, request)
|
||||
|
||||
Reference in New Issue
Block a user