"""LLM Cache Key Generation — Deterministic SHA-256 cache key from LLM request parameters.""" import hashlib import json def generate_cache_key( model: str, messages: list[dict[str, str]], temperature: float, tools: list[dict[str, object]] | None = None, tool_choice: str = "auto", max_tokens: int = 2000, user_id: str | None = None, kb_acl_hash: str | None = None, ) -> str: """Generate a deterministic SHA-256 cache key from LLM request parameters. The key captures ALL inputs that deterministically affect LLM output: model, system_prompt (extracted from messages), messages content, temperature, tools, tool_choice, and max_tokens. U17 安全扩展:``user_id`` 和 ``kb_acl_hash`` 非 None 时加入 hash 组件, 实现 per-user namespace 和 ACL-scope 隔离(安全要求 a, b)。为 None 时 行为与旧版完全一致(向后兼容)。 Args: model: Model identifier (e.g. "openai/gpt-4o"). messages: Chat messages list (may include system prompt as first message). temperature: Sampling temperature. tools: Optional list of tool definitions. tool_choice: Tool selection mode ("auto", "none", etc.). max_tokens: Maximum response tokens. user_id: U17 — 用户 ID,用于 per-user cache namespace 隔离。 kb_acl_hash: U17 — KB ACL-scope hash,用于 ACL 隔离缓存键。 Returns: 64-character hex SHA-256 hash string. """ system_prompt = _extract_system_prompt(messages) # 单次 SHA-256:用分隔符拼接所有组件,避免逐组件 hash 再 hash 的冗余计算。 # 分隔符使用长度前缀防止歧义(如 "ab" + "cd" vs "a" + "bcd")。 parts = [ f"m:{model}", f"s:{system_prompt}", f"msg:{json.dumps(messages, sort_keys=True, ensure_ascii=False)}", f"t:{temperature:.2f}", f"tools:{json.dumps(tools, sort_keys=True, ensure_ascii=False) if tools is not None else 'null'}", f"tc:{tool_choice}", f"mt:{max_tokens}", ] # U17 — per-user namespace + ACL scope hash(安全要求 a, b, e) if user_id is not None: parts.append(f"u:{user_id}") if kb_acl_hash is not None: parts.append(f"a:{kb_acl_hash}") combined = "\x1f".join(parts) # US (Unit Separator) 防止组件内容注入分隔符 return hashlib.sha256(combined.encode()).hexdigest() def _extract_system_prompt(messages: list[dict[str, str]]) -> str: """Extract system prompt from messages list.""" for msg in messages: if msg.get("role") == "system": return msg.get("content", "") return ""