feat(auto-router): recognise CJK complex keywords in model picker

The Flash-router fallback heuristic `auto_model_heuristic` only matched
English complexity keywords (`refactor`, `architecture`, `design`,
`debug`, `security`, `review`, `audit`, `migrate`, `optimize`,
`rewrite`, `implement`, `analyze`). A Chinese-speaking user typing
"帮我重构这个模块" or "审计安全漏洞" silently fell through to the
short/long-message length branches and usually landed on Flash for
work that obviously needs Pro-grade reasoning — the symmetric of the
companion gap in `auto_reasoning::select` (and the same root cause).

Extracts the array into a `COMPLEX_KEYWORDS` constant and adds the
Simplified and Traditional Chinese counterparts for each English
keyword:

* refactor → 重构 / 重構
* architecture → 架构 / 架構
* design → 设计 / 設計
* debug → 调试 / 調試
* security → 安全
* review → 审查 / 審查
* audit → 审计 / 審計
* migrate → 迁移 / 遷移
* optimize → 优化 / 優化
* rewrite → 重写 / 重寫
* implement → 实现 / 實現
* analyze → 分析

CJK matches the literal form because the existing `to_lowercase()`
is a no-op for those scripts. English keywords are byte-identical to
before, so English-only behaviour doesn't shift.

Three new tests cover Simplified and Traditional Chinese keyword
routing to Pro, plus a sanity test that short non-keyword Chinese
prose still gets the cost-saving Flash fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
LinQ
2026-05-11 00:37:48 +01:00
committed by Hunter Bown
parent 68cc5d19cc
commit 05fcb0df24
+104 -15
View File
@@ -714,21 +714,7 @@ fn expand_tilde(raw: &str) -> String {
pub fn auto_model_heuristic(input: &str, _current_model: &str) -> String {
let len = input.chars().count();
let lower = input.to_lowercase();
let complex_keywords = [
"refactor",
"architecture",
"design",
"debug",
"security",
"review",
"audit",
"migrate",
"optimize",
"rewrite",
"implement",
"analyze",
];
if complex_keywords.iter().any(|kw| lower.contains(kw)) {
if COMPLEX_KEYWORDS.iter().any(|kw| lower.contains(kw)) {
return "deepseek-v4-pro".to_string();
}
// Short messages → Flash
@@ -743,6 +729,55 @@ pub fn auto_model_heuristic(input: &str, _current_model: &str) -> String {
"deepseek-v4-flash".to_string()
}
/// Keywords that escalate `auto`-mode model selection to
/// `deepseek-v4-pro`. The Latin entries are lowercase (the caller
/// lowercases the message); CJK has no case so the literal form
/// matches as-is.
///
/// Without the CJK entries, a Chinese-speaking user typing
/// "帮我重构这个模块" or "审计安全漏洞" silently fell through to the
/// short/long-message threshold and usually landed on Flash even
/// for tasks that obviously need Pro-grade reasoning.
const COMPLEX_KEYWORDS: &[&str] = &[
// English (unchanged from the original list).
"refactor",
"architecture",
"design",
"debug",
"security",
"review",
"audit",
"migrate",
"optimize",
"rewrite",
"implement",
"analyze",
// Simplified Chinese.
"\u{91cd}\u{6784}", // 重构
"\u{67b6}\u{6784}", // 架构
"\u{8bbe}\u{8ba1}", // 设计
"\u{8c03}\u{8bd5}", // 调试
"\u{5b89}\u{5168}", // 安全
"\u{5ba1}\u{67e5}", // 审查
"\u{5ba1}\u{8ba1}", // 审计
"\u{8fc1}\u{79fb}", // 迁移
"\u{4f18}\u{5316}", // 优化
"\u{91cd}\u{5199}", // 重写
"\u{5b9e}\u{73b0}", // 实现
"\u{5206}\u{6790}", // 分析
// Traditional Chinese variants where they differ.
"\u{91cd}\u{69cb}", // 重構
"\u{67b6}\u{69cb}", // 架構
"\u{8a2d}\u{8a08}", // 設計
"\u{8abf}\u{8a66}", // 調試
"\u{5be9}\u{67e5}", // 審查
"\u{5be9}\u{8a08}", // 審計
"\u{9077}\u{79fb}", // 遷移
"\u{512a}\u{5316}", // 優化
"\u{91cd}\u{5beb}", // 重寫
"\u{5be6}\u{73fe}", // 實現
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AutoRouteRecommendation {
pub model: String,
@@ -1258,6 +1293,60 @@ mod tests {
assert_eq!(app.model, "deepseek-v4-flash");
}
#[test]
fn auto_model_heuristic_chinese_keywords_route_to_pro() {
// Without these keywords, a Chinese user typing
// "帮我重构这个模块" (37 chars in chars().count() terms after
// the leading helper text) fell through to the short-message
// Flash branch even though the intent is obviously Pro-tier.
for msg in [
"\u{5e2e}\u{6211}\u{91cd}\u{6784}\u{8fd9}\u{4e2a}\u{6a21}\u{5757}", // 帮我重构这个模块
"\u{8bbe}\u{8ba1}\u{6570}\u{636e}\u{5e93}\u{67b6}\u{6784}", // 设计数据库架构
"\u{8c03}\u{8bd5}\u{5d29}\u{6e83}\u{95ee}\u{9898}", // 调试崩溃问题
"\u{5ba1}\u{8ba1}\u{5b89}\u{5168}\u{6f0f}\u{6d1e}", // 审计安全漏洞
"\u{8fc1}\u{79fb}\u{5230}\u{65b0}\u{6846}\u{67b6}", // 迁移到新框架
"\u{4f18}\u{5316}\u{6027}\u{80fd}\u{74f6}\u{9888}", // 优化性能瓶颈
"\u{5206}\u{6790}\u{8fd9}\u{6bb5}\u{4ee3}\u{7801}", // 分析这段代码
] {
assert_eq!(
auto_model_heuristic(msg, "auto"),
"deepseek-v4-pro",
"expected Pro for `{msg}`",
);
}
}
#[test]
fn auto_model_heuristic_traditional_chinese_keywords_route_to_pro() {
for msg in [
"\u{8acb}\u{91cd}\u{69cb}\u{6b64}\u{6a21}\u{7d44}", // 請重構此模組
"\u{67b6}\u{69cb}\u{8a2d}\u{8a08}", // 架構設計
"\u{4ee3}\u{78bc}\u{8abf}\u{8a66}", // 代碼調試
"\u{5be9}\u{8a08}\u{6f0f}\u{6d1e}", // 審計漏洞
"\u{9077}\u{79fb}\u{5230}\u{65b0}\u{67b6}\u{69cb}", // 遷移到新架構
"\u{512a}\u{5316}\u{6027}\u{80fd}", // 優化性能
"\u{91cd}\u{5beb}\u{4ee3}\u{78bc}", // 重寫代碼
"\u{5be6}\u{73fe}\u{65b0}\u{529f}\u{80fd}", // 實現新功能
] {
assert_eq!(
auto_model_heuristic(msg, "auto"),
"deepseek-v4-pro",
"expected Pro for `{msg}`",
);
}
}
#[test]
fn auto_model_heuristic_short_chinese_chat_stays_on_flash() {
// Sanity: a short non-keyword Chinese message still falls
// through to the cost-saving Flash branch.
// "你好" (2 chars) — well under the 100-char Flash floor.
assert_eq!(
auto_model_heuristic("\u{4f60}\u{597d}", "auto"),
"deepseek-v4-flash",
);
}
#[test]
fn auto_route_recommendation_parses_strict_json() {
let rec =