Skip to main content

codewhale_config/
auto_model.rs

1//! Legacy DeepSeek-scoped prompt complexity classifier.
2//!
3//! This pure scorer is retained for API compatibility, but the consolidated
4//! CLI dispatcher must not use it to resolve provider-neutral `model = "auto"`:
5//! doing so fabricates DeepSeek model ids for every active provider. The TUI's
6//! provider-aware router owns runtime auto selection. Callers may use this
7//! helper only when their candidate pair is explicitly the DeepSeek pair:
8//!
9//! - **`deepseek-v4-pro`** — complex tasks (debugging, refactoring, design,
10//!   security review, multi-file changes, code generation, …).
11//! - **`deepseek-v4-flash`** — simple tasks (lookups, formatting, small edits,
12//!   translation, Q&A, …).
13//!
14//! This is a pure rule-based classifier. It lives in the config crate because
15//! the resolved model name is a config-level concern; the route resolver never
16//! sees the `"auto"` sentinel or the prompt text.
17
18/// The resolved model name for the pro tier.
19pub const PRO_MODEL: &str = "deepseek-v4-pro";
20
21/// The resolved model name for the flash tier.
22pub const FLASH_MODEL: &str = "deepseek-v4-flash";
23
24/// The threshold score above which a task is classified as complex (pro).
25/// Score ≥ 2 → pro, else → flash.
26const PRO_THRESHOLD: i32 = 2;
27
28/// Strong indicators of a complex task. Each match adds +3.
29const COMPLEX_STRONG: &[&str] = &[
30    // Debugging & fixing
31    "debug",
32    "bug",
33    "fix",
34    "error",
35    "crash",
36    "异常",
37    "错误",
38    "调试",
39    "故障",
40    "排查",
41    "root cause",
42    // Architecture & design
43    "refactor",
44    "重构",
45    "architecture",
46    "架构",
47    "design pattern",
48    "系统设计",
49    "高并发",
50    "分布式",
51    "microservice",
52    // Security
53    "security",
54    "安全",
55    "vulnerability",
56    "漏洞",
57    "渗透",
58    "exploit",
59    // Code generation
60    "implement",
61    "实现",
62    "generate",
63    "生成",
64    "create",
65    "创建",
66    "build",
67    "构建",
68    "开发",
69    "prototype",
70    // Complex analysis
71    "analyze",
72    "分析",
73    "review",
74    "审查",
75    "audit",
76    "审计",
77    "optimize",
78    "优化",
79    "migrate",
80    "迁移",
81    // Multi-file / large scale
82    "multi-file",
83    "multiple files",
84    "多个文件",
85    "整个项目",
86    "full project",
87    "重构整个",
88    "large scale",
89    // Testing
90    "unit test",
91    "integration test",
92    "e2e test",
93    "测试用例",
94    "test suite",
95    "coverage",
96    // Complex logic
97    "algorithm",
98    "算法",
99    "状态机",
100    "state machine",
101    "concurrent",
102    "并行",
103    "异步",
104    "async",
105    // Documentation / PRD
106    "architecture document",
107    "设计文档",
108    "技术方案",
109    "prd",
110];
111
112/// Medium-strength indicators. Each match adds +1.
113const COMPLEX_MEDIUM: &[&str] = &[
114    "change",
115    "修改",
116    "update",
117    "更新",
118    "add",
119    "添加",
120    "新增",
121    "feature",
122    "功能",
123    "improve",
124    "改进",
125    "enhance",
126    "config",
127    "配置",
128    "setup",
129    "设置",
130    "deploy",
131    "部署",
132    "ci/cd",
133    "pipeline",
134    "script",
135    "脚本",
136    "tool",
137    "工具",
138    "api",
139    "interface",
140    "接口",
141    "endpoint",
142    "database",
143    "数据库",
144    "schema",
145    "query",
146    "document",
147    "文档",
148    "readme",
149];
150
151/// Simple-task indicators. Each match subtracts -1.
152const SIMPLE: &[&str] = &[
153    "find",
154    "查找",
155    "search",
156    "搜索",
157    "look up",
158    "查询",
159    "what is",
160    "什么是",
161    "explain",
162    "解释",
163    "tell me",
164    "告诉我",
165    "how to",
166    "如何",
167    "format",
168    "格式化",
169    "pretty",
170    "list",
171    "列出",
172    "show",
173    "显示",
174    "print",
175    "rename",
176    "重命名",
177    "move",
178    "移动",
179    "copy",
180    "复制",
181    "delete",
182    "删除",
183    "remove",
184    "typo",
185    "拼写",
186    "spelling",
187    "grammar",
188    "quick",
189    "快速",
190    "simple",
191    "简单",
192    "hello world",
193    "demo",
194    "example",
195    "示例",
196    "translate",
197    "翻译",
198    "convert",
199    "转换",
200    "short",
201    "简短",
202    "brief",
203    "简要",
204];
205
206/// Classify a prompt for the legacy DeepSeek candidate pair.
207///
208/// Uses a simple scoring system:
209/// - Strong complex keyword: +3
210/// - Medium complex keyword: +1
211/// - Simple keyword: -1
212/// - Prompt length > 500 chars: +2, > 200 chars: +1
213/// - Contains code fence or backtick: +1
214/// - Contains a file path: +1
215/// - Multi-line (> 5 newlines): +1
216///
217/// Total ≥ 2 → `PRO_MODEL`, else → `FLASH_MODEL`.
218#[must_use]
219pub fn classify(prompt: &str) -> &'static str {
220    if score(prompt) >= PRO_THRESHOLD {
221        PRO_MODEL
222    } else {
223        FLASH_MODEL
224    }
225}
226
227/// Compute the raw complexity score for a prompt.
228#[must_use]
229pub fn score(prompt: &str) -> i32 {
230    let lower = prompt.to_ascii_lowercase();
231    let mut score = 0i32;
232
233    // Strong complex keywords: +3 (first match only to avoid overcounting)
234    if COMPLEX_STRONG.iter().any(|kw| lower.contains(kw)) {
235        score += 3;
236    }
237
238    // Medium complex keywords: +1 each
239    for kw in COMPLEX_MEDIUM {
240        if lower.contains(kw) {
241            score += 1;
242        }
243    }
244
245    // Simple keywords: -1 each
246    for kw in SIMPLE {
247        if lower.contains(kw) {
248            score -= 1;
249        }
250    }
251
252    // Length factor: long prompts tend to be more complex
253    let len = prompt.len();
254    if len > 500 {
255        score += 2;
256    } else if len > 200 {
257        score += 1;
258    }
259
260    // Code fence or backtick: actual coding task
261    if prompt.contains("```") || prompt.contains('`') {
262        score += 1;
263    }
264
265    // File path pattern: e.g. /path/to/file.rs or C:\path
266    // Simple heuristic: path-like sequences contain / or \ and .
267    if (prompt.contains('/') || prompt.contains('\\')) && prompt.contains('.') {
268        score += 1;
269    }
270
271    // Multi-line: more lines = more context
272    if prompt.chars().filter(|&c| c == '\n').count() > 5 {
273        score += 1;
274    }
275
276    score
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_debug_task_uses_pro() {
285        assert_eq!(classify("帮我调试这个bug,程序崩溃了"), PRO_MODEL);
286    }
287
288    #[test]
289    fn test_refactor_task_uses_pro() {
290        assert_eq!(
291            classify("refactor the user module with a new architecture"),
292            PRO_MODEL
293        );
294    }
295
296    #[test]
297    fn test_security_review_uses_pro() {
298        assert_eq!(
299            classify("review this code for security vulnerabilities"),
300            PRO_MODEL
301        );
302    }
303
304    #[test]
305    fn test_simple_lookup_uses_flash() {
306        assert_eq!(classify("查找昨天的日志文件"), FLASH_MODEL);
307    }
308
309    #[test]
310    fn test_translation_uses_flash() {
311        assert_eq!(classify("translate this to Chinese"), FLASH_MODEL);
312    }
313
314    #[test]
315    fn test_formatting_uses_flash() {
316        assert_eq!(classify("format this code"), FLASH_MODEL);
317    }
318
319    #[test]
320    fn test_long_prompt_gets_bonus() {
321        let long = "a".repeat(300);
322        // No keywords, long prompt gives +1, total = 1 < 2 → flash
323        assert_eq!(classify(&long), FLASH_MODEL);
324    }
325
326    #[test]
327    fn test_very_long_prompt_gets_more_bonus() {
328        let long = "a".repeat(600);
329        // No keywords, very long prompt gives +2, total = 2 → pro
330        assert_eq!(classify(&long), PRO_MODEL);
331    }
332
333    #[test]
334    fn test_code_block_gets_bonus() {
335        // Code block without keywords, +1, total = 1 < 2 → flash
336        assert_eq!(classify("```\nhello\n```"), FLASH_MODEL);
337    }
338
339    #[test]
340    fn test_mixed_keywords_pro_wins() {
341        // "refactor" is strong (+3), "explain" is simple (-1), total = 2 → pro
342        assert_eq!(classify("refactor and explain the code"), PRO_MODEL);
343    }
344
345    #[test]
346    fn test_implement_task_uses_pro() {
347        assert_eq!(
348            classify("implement a new feature for the user module"),
349            PRO_MODEL
350        );
351    }
352
353    #[test]
354    fn test_quick_question_uses_flash() {
355        assert_eq!(classify("what is the capital of France?"), FLASH_MODEL);
356    }
357
358    #[test]
359    fn test_score_never_negative() {
360        // Even for very simple queries, score should be predictable
361        let s = score("hello world");
362        assert!(s >= -10); // sanity check
363    }
364}