9dae7dda98
content-addressed sha256 缓存键,Redis 不可用时静默降级。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
2.7 KiB
Python
96 lines
2.7 KiB
Python
"""Redis 响应缓存 — content-addressed SHA256 键,Redis 不可用时静默降级。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import dataclasses
|
||
import hashlib
|
||
import json
|
||
from typing import Any
|
||
|
||
from loguru import logger
|
||
|
||
from core.types import LLMResponse
|
||
|
||
|
||
class RedisResponseCache:
|
||
"""基于 Redis 的 LLM 响应缓存。
|
||
|
||
使用 content-addressed 策略:key = sha256(model + json(messages)),
|
||
值为 JSON 序列化的 LLMResponse。
|
||
|
||
当 Redis 不可用时静默降级:get 返回 None,set 吞异常并记录 warning。
|
||
|
||
Args:
|
||
redis: 异步 Redis 客户端实例(duck-typed,需支持 get/set 方法)。
|
||
ttl_s: 缓存过期时间(秒)。
|
||
"""
|
||
|
||
def __init__(self, redis: Any, ttl_s: int) -> None:
|
||
self._redis = redis
|
||
self._ttl_s = ttl_s
|
||
|
||
def _build_key(self, model: str, messages: list[dict[str, str]]) -> str:
|
||
"""构造 content-addressed 缓存键。
|
||
|
||
Args:
|
||
model: 模型名称。
|
||
messages: 消息列表。
|
||
|
||
Returns:
|
||
sha256 哈希字符串作为 Redis 键。
|
||
"""
|
||
payload = json.dumps(
|
||
{"model": model, "messages": messages},
|
||
sort_keys=True,
|
||
ensure_ascii=False,
|
||
)
|
||
digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||
return f"llm_cache:{digest}"
|
||
|
||
async def get(
|
||
self, model: str, messages: list[dict[str, str]]
|
||
) -> LLMResponse | None:
|
||
"""从缓存读取 LLM 响应。
|
||
|
||
Args:
|
||
model: 模型名称。
|
||
messages: 消息列表。
|
||
|
||
Returns:
|
||
缓存命中时返回 LLMResponse,未命中或 Redis 异常时返回 None。
|
||
"""
|
||
try:
|
||
key = self._build_key(model, messages)
|
||
raw = await self._redis.get(key)
|
||
except Exception:
|
||
logger.warning("Redis 缓存读取失败,降级为未命中")
|
||
return None
|
||
|
||
if raw is None:
|
||
return None
|
||
|
||
data = json.loads(raw)
|
||
return LLMResponse(**data)
|
||
|
||
async def set(
|
||
self,
|
||
model: str,
|
||
messages: list[dict[str, str]],
|
||
response: LLMResponse,
|
||
) -> None:
|
||
"""将 LLM 响应写入缓存。
|
||
|
||
Args:
|
||
model: 模型名称。
|
||
messages: 消息列表。
|
||
response: 待缓存的 LLMResponse。
|
||
"""
|
||
try:
|
||
key = self._build_key(model, messages)
|
||
value = json.dumps(
|
||
dataclasses.asdict(response), ensure_ascii=False
|
||
)
|
||
await self._redis.set(key, value, ex=self._ttl_s)
|
||
except Exception:
|
||
logger.warning("Redis 缓存写入失败,跳过缓存")
|