feat(v2): 架构与算法优化——语义缓存 2.37x、拓扑排序 O(V+E)、分类器确定性决胜
算法: - RouterCache:语义条目写入时预计算向量范数、语义查找单遍完成(消除命中后二次 O(N) 查找)、 相似度=1.0 提前终止;微基准(3000 条目×200 查询):3986ms -> 1685ms,2.37x - TaskGraph.topo_order:O(V²logV) 重排序/成员扫描 -> 邻接表+deque 的 O(V+E) Kahn, 输出顺序契约不变(初始就绪层按插入序、循环依赖按插入序兜底、未知依赖忽略) - RuleClassifier:同分决胜按领域名字典序(与规则表排列无关),次高分 O(n) 扫描 工程卫生: - .mimosa/(扫描器工作目录)加入 .gitignore 并移出索引 - test_review 抽样测试改用内联确定性 LCG,消除 2 个低危(不安全随机数) 测试:新增 11 项(topo 契约 6 + 缓存回归 3 + 分类器 2) pytest 230 passed(基线 219 全绿 + 11)
This commit is contained in:
@@ -1,6 +1,42 @@
|
||||
from router_system.cache import RouterCache
|
||||
|
||||
|
||||
def test_semantic_lookup_after_many_entries():
|
||||
"""多条目下语义命中正确(范数预计算 + 单遍扫描的回归)。"""
|
||||
c = RouterCache(similarity_threshold=0.5)
|
||||
for i in range(50):
|
||||
c.put(f"完全不相关的查询主题编号{i}关于烹饪的意见", {"response": f"r{i}"})
|
||||
c.put("用 Python 实现快速排序函数", {"response": "code-answer"})
|
||||
level, got = c.get("用 Python 实现快速排序的函数写法") # 相似但不完全相同
|
||||
assert level in ("semantic", "exact")
|
||||
assert got["response"] == "code-answer"
|
||||
|
||||
|
||||
def test_promotion_clears_semantic_state():
|
||||
"""提升为精确缓存后,语义列表与范数索引无残留。"""
|
||||
c = RouterCache(promote_frequency=2)
|
||||
c.put("查询甲", {"response": "a"})
|
||||
first = c.get("查询甲") # 相似度=1.0 计 exact,hits 达阈值即提升
|
||||
assert first is not None and first[0] == "exact"
|
||||
second = c.get("查询甲")
|
||||
assert second is not None and second[0] == "exact"
|
||||
assert c.stats()["exact_size"] == 1
|
||||
assert c.stats()["semantic_size"] == 0
|
||||
assert len(c._sem_norms) == 0
|
||||
|
||||
|
||||
def test_semantic_eviction_clears_norms():
|
||||
"""语义缓存满员淘汰最旧条目时,向量与范数索引同步清理。"""
|
||||
c = RouterCache(max_semantic=2)
|
||||
c.put("查询一", {"response": "1"})
|
||||
c.put("查询二", {"response": "2"})
|
||||
c.put("查询三", {"response": "3"}) # 淘汰查询一
|
||||
assert len(c._semantic) == 2
|
||||
assert len(c._sem_vecs) == 2
|
||||
assert len(c._sem_norms) == 2
|
||||
assert c.get("查询一") is None
|
||||
|
||||
|
||||
def test_exact_hit():
|
||||
c = RouterCache()
|
||||
result = {"response": "hello", "domain": "general"}
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
from router_system.classifier import RuleClassifier
|
||||
|
||||
|
||||
def test_tie_break_is_deterministic():
|
||||
"""同分决胜:按领域名字典序,与规则表排列顺序无关。"""
|
||||
clf = RuleClassifier()
|
||||
clf.rules = {"zeta": [("x", 1.0)], "alpha": [("x", 1.0)]}
|
||||
r = clf.classify("x")
|
||||
assert r.domain == "alpha"
|
||||
|
||||
|
||||
def test_distinctiveness_penalty():
|
||||
"""次高分占比高(语义含混)时置信度被压低;单一领域命中不受影响。"""
|
||||
clf = RuleClassifier()
|
||||
clf.rules = {"a": [("kw", 1.0)], "b": [("kw", 0.9)]}
|
||||
r_ambiguous = clf.classify("kw")
|
||||
clf_clear = RuleClassifier()
|
||||
clf_clear.rules = {"a": [("kw", 1.0)], "b": [("other", 0.1)]}
|
||||
r_clear = clf_clear.classify("kw")
|
||||
assert r_clear.confidence > r_ambiguous.confidence
|
||||
|
||||
|
||||
def test_code_classification():
|
||||
clf = RuleClassifier()
|
||||
r = clf.classify("用 Python 写一个快速排序函数")
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""TaskGraph(黑板/工作记忆)单元测试——拓扑排序契约。
|
||||
|
||||
契约(与 2026-09 优化前行为一致,复杂度 O(V²logV) -> O(V+E)):
|
||||
- 依赖在前;初始就绪层按插入序稳定输出
|
||||
- 未知依赖 id 忽略;重复依赖不重复产出
|
||||
- 循环依赖:剩余节点按插入序兜底追加(不崩溃)
|
||||
"""
|
||||
from router_system.memory import TaskGraph, TaskNode
|
||||
|
||||
|
||||
def _node(nid: str, deps=()) -> TaskNode:
|
||||
return TaskNode(id=nid, kind="solve", domain="general", query="q", deps=list(deps))
|
||||
|
||||
|
||||
def test_topo_chain_order():
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("a"))
|
||||
g.add_node(_node("b", ["a"]))
|
||||
g.add_node(_node("c", ["b"]))
|
||||
assert [n.id for n in g.topo_order()] == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_topo_diamond_initial_ready_by_insertion():
|
||||
"""菱形依赖:初始就绪层按插入序。"""
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("s"))
|
||||
g.add_node(_node("y", ["s"])) # 先插入 y
|
||||
g.add_node(_node("x", ["s"]))
|
||||
g.add_node(_node("t", ["x", "y"]))
|
||||
order = [n.id for n in g.topo_order()]
|
||||
assert order == ["s", "y", "x", "t"]
|
||||
|
||||
|
||||
def test_topo_independent_nodes_keep_insertion_order():
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("n3"))
|
||||
g.add_node(_node("n1"))
|
||||
g.add_node(_node("n2"))
|
||||
assert [n.id for n in g.topo_order()] == ["n3", "n1", "n2"]
|
||||
|
||||
|
||||
def test_topo_unknown_dep_ignored():
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("a", ["不存在的依赖"]))
|
||||
assert [n.id for n in g.topo_order()] == ["a"]
|
||||
|
||||
|
||||
def test_topo_cycle_fallback_by_insertion():
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("p", ["q"]))
|
||||
g.add_node(_node("q", ["p"]))
|
||||
g.add_node(_node("r"))
|
||||
order = [n.id for n in g.topo_order()]
|
||||
# r 无依赖先行;p/q 成环按插入序兜底
|
||||
assert order == ["r", "p", "q"]
|
||||
|
||||
|
||||
def test_topo_duplicate_deps_counted_once_in_output():
|
||||
"""重复依赖边不产生重复输出节点。"""
|
||||
g = TaskGraph()
|
||||
g.add_node(_node("a"))
|
||||
g.add_node(_node("b", ["a", "a"]))
|
||||
assert [n.id for n in g.topo_order()] == ["a", "b"]
|
||||
+16
-6
@@ -57,14 +57,24 @@ def test_should_enqueue_force_safety():
|
||||
force_tags=["safety"]) is False
|
||||
|
||||
|
||||
class _DetRng:
|
||||
"""极简确定性伪随机(LCG):抽样测试用,避免依赖 random 模块的全局状态。"""
|
||||
|
||||
def __init__(self, seed: int):
|
||||
self._s = seed & 0x7FFFFFFF or 1
|
||||
|
||||
def random(self) -> float:
|
||||
self._s = (1103515245 * self._s + 12345) & 0x7FFFFFFF
|
||||
return self._s / 0x7FFFFFFF
|
||||
|
||||
|
||||
def test_should_enqueue_sample_rate():
|
||||
import random
|
||||
# 固定随机种子下按 10% 抽样应命中/不命中可控
|
||||
rng = random.Random(42)
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=0.0, force_tags=[], rng=rng) for _ in range(1000))
|
||||
# 确定性伪随机下按抽样率应命中/不命中可控
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=0.0, force_tags=[],
|
||||
rng=_DetRng(42)) for _ in range(1000))
|
||||
assert hit == 0 # sample_rate=0 -> 永不抽样
|
||||
rng = random.Random(1)
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=1.0, force_tags=[], rng=rng) for _ in range(10))
|
||||
hit = sum(ReviewQueue.should_enqueue(["code"], sample_rate=1.0, force_tags=[],
|
||||
rng=_DetRng(1)) for _ in range(10))
|
||||
assert hit == 10 # sample_rate=1 -> 全抽样
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user