"""语义缓存路由签名分桶(T-X10,采纳 cortiq 路由签名设计)。""" from gateway.proxy.config import build_proxy_config from gateway.proxy.normalizer import canonical_hash from gateway.proxy.routes import _route_sig _BODY = {"model": "m1", "messages": [{"role": "user", "content": "什么是递归"}]} def test_canonical_hash_legacy_format_without_sig(): """不带签名保持旧三段格式(旧调用/旧条目兼容)。""" h = canonical_hash("default", 1, _BODY) assert h.count("|") == 2 h2 = canonical_hash("default", 1, _BODY, "") assert h == h2 def test_canonical_hash_with_sig_divides_keyspace(): """签名不同 -> 键不同:vision 请求与纯文本请求不再共用缓存条目。""" plain = canonical_hash("default", 1, _BODY) vision = canonical_hash("default", 1, _BODY, "v1t0") tools = canonical_hash("default", 1, _BODY, "v0t1") assert len({plain, vision, tools}) == 3 def test_route_sig_scopes(): """三种 scope 的签名形态:capabilities 默认 / model 精确 / none 旧格式。""" cfg = build_proxy_config({"proxy": {"enabled": True}}) needs = {"vision": True, "tools": False, "min_context_tokens": 10} assert _route_sig(needs, "m1", cfg) == "v1t0" cfg_model = build_proxy_config({"proxy": {"semcache": {"route_sig_scope": "model"}}}) assert _route_sig(needs, "m1", cfg_model) == "m:m1" cfg_none = build_proxy_config({"proxy": {"semcache": {"route_sig_scope": "none"}}}) assert _route_sig(needs, "m1", cfg_none) == "" # 非法 scope 回落 capabilities cfg_bad = build_proxy_config({"proxy": {"semcache": {"route_sig_scope": "bogus"}}}) assert _route_sig(needs, "m1", cfg_bad) == "v1t0" def test_sig_isolation_end_to_end_via_semcache(tmp_path): """经 SemanticCache 验证:签名分区 L1 精确层与 L2 语义扫描两层。""" from gateway.proxy.ledger import Ledger from gateway.proxy.semcache import SemanticCache led = Ledger.init_db(tmp_path / "l.sqlite3") cache = SemanticCache(led, max_entries=100, sim_threshold=0.5, promote_frequency=5) k_text = canonical_hash("default", 1, _BODY, "v0t0") k_vision = canonical_hash("default", 1, _BODY, "v1t0") cache.put(k_text, "什么是递归", "纯文本答案", "m1", doc_version=1, route_sig="v0t0") # 相同问题、vision 签名:L1 键不同必不中;L2 语义扫描按签名分区也不中 assert cache.lookup(k_vision, "什么是递归", doc_version=1, route_sig="v1t0") is None # 同签名:L1 精确命中 hit = cache.lookup(k_text, "什么是递归", doc_version=1, route_sig="v0t0") assert hit is not None and hit["level"] == "exact" def test_sig_partition_survives_rebuild(tmp_path): """重启重建(semcache 表无签名列):签名从缓存键解析恢复,分区仍有效。""" from gateway.proxy.ledger import Ledger from gateway.proxy.semcache import SemanticCache led = Ledger.init_db(tmp_path / "l.sqlite3") c1 = SemanticCache(led, max_entries=100, sim_threshold=0.5, promote_frequency=5) k = canonical_hash("default", 1, _BODY, "v1t0") c1.put(k, "什么是递归", "多模态答案", "m1", doc_version=1, route_sig="v1t0") c2 = SemanticCache(led, max_entries=100, sim_threshold=0.5, promote_frequency=5) assert c2.lookup(k, "什么是递归", doc_version=1, route_sig="v1t0") is not None assert c2.lookup(canonical_hash("default", 1, _BODY, "v0t0"), "什么是递归", doc_version=1, route_sig="v0t0") is None def test_legacy_calls_without_sig_unchanged(tmp_path): """不传签名(route_sig=None)的旧调用零过滤:既有测试行为不变。""" from gateway.proxy.ledger import Ledger from gateway.proxy.semcache import SemanticCache led = Ledger.init_db(tmp_path / "l.sqlite3") cache = SemanticCache(led, max_entries=100, sim_threshold=0.5, promote_frequency=5) cache.put("k1", "请解释一下递归函数的概念", "答案", "m1", doc_version=1) assert cache.lookup("k1", "请解释一下递归函数的概念", doc_version=1) is not None # 语义路径同样不受影响(近似文本跨键命中) assert cache.lookup("k2", "请解释一下递归函数的概念呢", doc_version=1) is not None