"""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 缓存写入失败,跳过缓存")