Files
projectAIpopular/router_system/difficulty.py
T
tzt 06700a5aa3 feat(v1): T-R3 采纳 llmrouter 词边界与失败安全——难度英文标记整词命中 + HF 分类器两级回落
- difficulty:标记表编译拆分——英文标记改词边界正则(修真实误判:'int' 子串
  命中 'print'/'point'、'log' 命中 'logic'、'list' 命中 'listen'),中文标记
  保持子串语义;命中行为对合法用例不变(整词出现照常计数)
- classifier:HuggingFaceClassifier 推理期异常回落内置 RuleClassifier(单次
  推理异常不打垮路由);build_classifier 的 hf 分支构造失败(ML 依赖缺失/
  模型加载失败)打印提示并回落规则分类器(外置规则照常合并)
- 新增 tests/test_failsafe.py 5 项;全量 43 passed(38+5)
2026-09-19 09:27:15 +08:00

84 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""查询难度估计器(启发式,纯标准库)。
依据: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