feat(v1): 架构与算法优化二轮——共享 LLM 客户端去重、语义缓存 O(1) 提升/淘汰、分类器热路径清理

- 新增 router_system/llm_client.py:OpenAICompatClient 统一 experts/judge/fallback
  三处复制的懒建 AsyncClient + /chat/completions + choices/usage 解析(~60 行去重);
  密钥解析统一走 config.get_api_key(激活原死代码,顺带消除 experts 默认环境名不一致)
- 语义缓存 L2:条目容器 list→OrderedDict(提升/淘汰 O(n)→O(1)),按 query 天然去重;
  n-gram 向量 lru_cache 复用(同一次 miss 的 get/put 免重复分词);
  A/B:淘汰路径 0.040→0.034s,miss→put 往返 9.41→8.57s(-9%)
- RuleJudge 覆盖度:response.lower() 提出逐词循环(原 O(terms×len) 重复复制)
- extract_content_terms 纯函数 lru_cache 化(专家与 Judge 对同一查询免重复分词),返回 tuple
- RuleClassifier:_score 去掉败者领域白建的命中词 list(胜出后单独收集);
  修复 code 规则 ("api",0.7) 重复登记(原命中计 1.4 分)
- difficulty:正则模块级预编译
- tests:恢复上一轮引入的乱码中文 docstring;网关测试输入串恢复为可判 code 的中文查询
This commit is contained in:
tzt
2026-09-18 23:27:26 +08:00
parent 81b0bc9b9b
commit c072a1a237
10 changed files with 696 additions and 631 deletions
+36 -19
View File
@@ -12,11 +12,17 @@
- 每条语义缓存条目在写入时预计算并缓存向量范数,查询时免重复计算(原来每对比较都重算)
- 语义查找单遍完成:扫描即跟踪最优条目与命中计数,命中后不再二次线性查找
- 相似度达到 1.0(完全相同查询)时提前终止扫描(余弦相似度上界,不可能更优)
- 语义条目容器为 OrderedDict:提升/淘汰均为 O(1)(原 list pop 是 O(n) 搬移),
且按 query 天然去重(原实现同一 query 可重复追加,重复条目白白参与扫描)
- n-gram 向量经 lru_cache 复用:同一次未命中请求里 get 与紧随的 put
不再重复做两遍分词+计数
"""
from __future__ import annotations
import re
from collections import OrderedDict
from dataclasses import dataclass
from functools import lru_cache
from typing import Any, Dict, List, Optional, Tuple
@@ -52,6 +58,17 @@ def _dot(vec_a: Dict[str, float], vec_b: Dict[str, float]) -> float:
return sum(v * vec_b.get(k, 0.0) for k, v in vec_a.items())
@lru_cache(maxsize=1024)
def _embed(text: str) -> Tuple[Dict[str, float], float]:
"""n-gram 向量 + 范数(带缓存)。
纯函数且同一次未命中请求会在 get/put 各用一次,缓存可省一遍分词。
契约:返回的 dict 仅供只读(内部无任何修改点),调用方不得改写。
"""
vec = _tf_vector(_ngrams(text))
return vec, _norm(vec)
class RouterCache:
"""L1 精确缓存 + L2 语义缓存。"""
@@ -63,7 +80,8 @@ class RouterCache:
self.max_exact = max_exact
self.max_semantic = max_semantic
self._exact: Dict[str, CacheEntry] = {}
self._semantic: List[Tuple[str, CacheEntry]] = [] # (query, entry)
# (query -> entry),插入序即淘汰序;len 兼容原 list 断言
self._semantic: "OrderedDict[str, CacheEntry]" = OrderedDict()
self._sem_vecs: Dict[str, Dict[str, float]] = {}
self._sem_norms: Dict[str, float] = {} # 预计算范数,避免查询期重算
self.hits = {"exact": 0, "semantic": 0}
@@ -78,57 +96,56 @@ class RouterCache:
return ("exact", entry.result)
if self.semantic_enabled:
q_vec = _tf_vector(_ngrams(query))
q_norm = _norm(q_vec)
q_vec, q_norm = _embed(query)
best_sim = 0.0
best_idx = -1
best_q = ""
if q_norm > 0.0:
# 单遍扫描:同时跟踪最优相似度与条目位置
for i, (q, _e) in enumerate(self._semantic):
# 单遍扫描(插入序):同时跟踪最优条目,sim=1.0 提前终止
for q, _e in self._semantic.items():
n_q = self._sem_norms.get(q, 0.0)
if n_q <= 0.0:
continue
sim = _dot(q_vec, self._sem_vecs.get(q, {})) / (q_norm * n_q)
if sim > best_sim:
best_sim = sim
best_idx = i
best_q = q
if sim >= 1.0:
break # 余弦相似度上界:完全相同查询,提前终止
if best_idx >= 0 and best_sim >= self.similarity_threshold:
best_q, best_entry = self._semantic[best_idx]
if best_q and best_sim >= self.similarity_threshold:
best_entry = self._semantic[best_q]
# 完全相同查询(相似度=1.0)计为 exact 命中
is_exact = best_sim >= 0.999
level = "exact" if is_exact else "semantic"
self.hits[level] += 1
self._bump_semantic(best_idx, best_q, best_entry)
self._bump_semantic(best_q, best_entry)
return (level, best_entry.result)
self.misses += 1
return None
def _bump_semantic(self, idx: int, query: str, entry: CacheEntry):
"""语义命中:累计命中次数,达到阈值提升为精确缓存(O(1),无需二次查找)。"""
def _bump_semantic(self, query: str, entry: CacheEntry):
"""语义命中:累计命中次数,达到阈值提升为精确缓存(OrderedDict 删除 O(1))。"""
entry.hits += 1
if entry.hits >= self.promote_frequency:
self._exact[query] = entry
self._semantic.pop(idx)
del self._semantic[query]
self._sem_vecs.pop(query, None)
self._sem_norms.pop(query, None)
# ---- 写入 ----
def put(self, query: str, result: Dict[str, Any]):
if query in self._exact:
return
if query in self._exact or query in self._semantic:
return # 已缓存(语义区按 query 天然去重,避免重复条目参与扫描)
entry = CacheEntry(result=result)
if self.semantic_enabled:
if len(self._semantic) >= self.max_semantic:
old_q, _ = self._semantic.pop(0)
old_q, _old = self._semantic.popitem(last=False) # O(1) 淘汰最旧
self._sem_vecs.pop(old_q, None)
self._sem_norms.pop(old_q, None)
self._semantic.append((query, entry))
vec = _tf_vector(_ngrams(query))
self._semantic[query] = entry
vec, norm = _embed(query) # 与 get miss 共享同一份缓存向量
self._sem_vecs[query] = vec
self._sem_norms[query] = _norm(vec)
self._sem_norms[query] = norm
else:
self._exact[query] = entry
if len(self._exact) > self.max_exact: