- difficulty:标记表编译拆分——英文标记改词边界正则(修真实误判:'int' 子串 命中 'print'/'point'、'log' 命中 'logic'、'list' 命中 'listen'),中文标记 保持子串语义;命中行为对合法用例不变(整词出现照常计数) - classifier:HuggingFaceClassifier 推理期异常回落内置 RuleClassifier(单次 推理异常不打垮路由);build_classifier 的 hf 分支构造失败(ML 依赖缺失/ 模型加载失败)打印提示并回落规则分类器(外置规则照常合并) - 新增 tests/test_failsafe.py 5 项;全量 43 passed(38+5)
84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
"""查询难度估计器(启发式,纯标准库)。
|
||
|
||
依据:RouterArena 用 Bloom 分类法把问题分为 easy/medium/hard。
|
||
这里用查询长度、指令动词、数学/推理标记做轻量估计。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Tuple
|
||
|
||
# 触发 hard 的指令动词 / 推理标记
|
||
_HARD_MARKERS = [
|
||
"证明", "推导", "为什么", "如何", "对比", "比较", "分析", "评估", "设计", "优化",
|
||
"复杂度", "时间复杂度", "空间复杂度", "原理", "机制", "优缺点", "区别", "推论", "定理",
|
||
"proof", "prove", "derive", "explain why", "why", "how", "compare", "contrast",
|
||
"analy", "evaluate", "design", "optimize", "refactor", "architect",
|
||
"implement", "debug", "review", "plan", "synthesize",
|
||
"int", "sum", "sqrt", "lim", "log", "derivative", "integral",
|
||
]
|
||
# 触发 medium 的标记
|
||
_MEDIUM_MARKERS = [
|
||
"用", "写", "计算", "求解", "生成", "翻译", "总结", "解释",
|
||
"注意", "建议", "是否", "实现", "步骤",
|
||
"write", "code", "function", "script", "calculate", "solve", "summarize",
|
||
"translate", "fix", "explain", "describe", "list",
|
||
]
|
||
|
||
|
||
def _compile_markers(markers):
|
||
"""标记表编译:英文标记用词边界正则,中文标记保持子串匹配。
|
||
|
||
T-R3(采纳 llmrouter 词边界思想,super_hard 先于 hard 的同理):
|
||
纯子串匹配会让 "int" 误命中 "print"/"point"、"log" 误命中 "logic"、
|
||
"list" 误命中 "listen"——英文必须整词命中才计数。
|
||
"""
|
||
substr, words = [], []
|
||
for m in markers:
|
||
(words if m.isascii() else substr).append(m)
|
||
pattern = re.compile(r"\b(?:%s)\b" % "|".join(re.escape(w) for w in words)) \
|
||
if words else None
|
||
return substr, pattern
|
||
|
||
|
||
_HARD_SUBSTR, _HARD_RE = _compile_markers(_HARD_MARKERS)
|
||
_MEDIUM_SUBSTR, _MEDIUM_RE = _compile_markers(_MEDIUM_MARKERS)
|
||
|
||
# 预编译正则(模块级一次,避免每次调用走 re 内部缓存查找)
|
||
_RE_CODE_EXPR = re.compile(r"\b(def|class|function|import)\b")
|
||
_RE_ARITH_EXPR = re.compile(r"[0-9]+\s*[+\-*/^=]\s*[0-9xya-z]")
|
||
|
||
|
||
def _hit_count(substr, pattern, q: str) -> int:
|
||
"""中文子串命中数 + 英文整词命中数。"""
|
||
n = sum(1 for m in substr if m in q)
|
||
if pattern is not None:
|
||
n += len(pattern.findall(q))
|
||
return n
|
||
|
||
|
||
def estimate_difficulty(query: str) -> Tuple[str, float]:
|
||
"""返回 (difficulty, score),score 属于 [0,1]。"""
|
||
q = query.lower()
|
||
hard_hits = _hit_count(_HARD_SUBSTR, _HARD_RE, q)
|
||
medium_hits = _hit_count(_MEDIUM_SUBSTR, _MEDIUM_RE, q)
|
||
length = len(query)
|
||
|
||
score = 0.0
|
||
score += min(0.30, length / 600.0) # 长度贡献
|
||
score += min(0.55, hard_hits * 0.25) # 推理标记贡献
|
||
score += min(0.30, medium_hits * 0.08) # 一般指令贡献
|
||
|
||
# 额外:代码 / 数学表达式(多步骤信号)
|
||
if "```" in query or _RE_CODE_EXPR.search(q):
|
||
score += 0.15
|
||
if _RE_ARITH_EXPR.search(q):
|
||
score += 0.15
|
||
|
||
score = max(0.0, min(1.0, score))
|
||
if score >= 0.55:
|
||
return "hard", score
|
||
if score >= 0.25:
|
||
return "medium", score
|
||
return "easy", score
|