返回 CodeWhale
auto_reasoning.rs
根目录 / crates / tui / src / auto_reasoning.rs
1 //! Adaptive reasoning-effort tier selection for `Auto` mode (#663).
2 //!
3 //! When the user sets `reasoning_effort = "auto"`, the engine calls
4 //! [`select`] before each turn-level request to pick the actual tier
5 //! based on the current message.
6
7 use crate::tui::app::ReasoningEffort;
8
9 /// Choose a concrete `ReasoningEffort` tier for the next API request.
10 ///
11 /// Rules:
12 /// - Sub-agent contexts (`is_subagent == true`) → `Low`
13 /// - Last user message contains a high-effort keyword
14 /// (English: `debug`, `error`; Chinese: 调试 / 错误 / 报错 / 出错 /
15 /// 崩溃 / 調試 / 錯誤; Japanese: デバッグ / エラー / バグ) → `Max`
16 /// - Last user message contains a low-effort keyword
17 /// (English: `search`, `lookup`; Chinese: 搜索 / 查找 / 查询;
18 /// Japanese: 検索) → `Low`
19 /// - Everything else → `High`
20 #[must_use]
21 pub fn select(is_subagent: bool, last_msg: &str) -> ReasoningEffort {
22 if is_subagent {
23 return ReasoningEffort::Low;
24 }
25
26 let lower = last_msg.to_ascii_lowercase();
27
28 if HIGH_EFFORT_KEYWORDS.iter().any(|kw| lower.contains(kw)) {
29 return ReasoningEffort::Max;
30 }
31
32 if LOW_EFFORT_KEYWORDS.iter().any(|kw| lower.contains(kw)) {
33 return ReasoningEffort::Low;
34 }
35
36 ReasoningEffort::High
37 }
38
39 /// Keywords that bump `reasoning_effort` to `Max`. Latin terms are
40 /// lowercase because the caller lowercases the message; CJK has no
41 /// case so the literal form matches as-is. Covers the Chinese and
42 /// Japanese vocabulary a non-English user reaches for when reporting
43 /// the same kind of problem the original `"debug" | "error"` rule was
44 /// trying to catch — without those terms a Chinese-speaking user
45 /// paying for Auto mode silently got `High` even on hard debugging
46 /// tasks.
47 const HIGH_EFFORT_KEYWORDS: &[&str] = &[
48 // English (unchanged from the original keyword set).
49 "debug",
50 "error",
51 // Simplified / Traditional Chinese.
52 "\u{8c03}\u{8bd5}", // 调试
53 "\u{9519}\u{8bef}", // 错误
54 "\u{62a5}\u{9519}", // 报错
55 "\u{51fa}\u{9519}", // 出错
56 "\u{5d29}\u{6e83}", // 崩溃
57 "\u{8abf}\u{8a66}", // 調試
58 "\u{932f}\u{8aa4}", // 錯誤
59 // Japanese.
60 "\u{30c7}\u{30d0}\u{30c3}\u{30b0}", // デバッグ
61 "\u{30a8}\u{30e9}\u{30fc}", // エラー
62 "\u{30d0}\u{30b0}", // バグ
63 ];
64
65 /// Keywords that drop `reasoning_effort` to `Low`. Same locale coverage
66 /// as [`HIGH_EFFORT_KEYWORDS`].
67 const LOW_EFFORT_KEYWORDS: &[&str] = &[
68 "search",
69 "lookup",
70 "\u{641c}\u{7d22}", // 搜索
71 "\u{67e5}\u{627e}", // 查找
72 "\u{67e5}\u{8be2}", // 查询
73 "\u{691c}\u{7d22}", // 検索
74 ];
75
76 #[cfg(test)]
77 mod tests {
78 use super::*;
79
80 #[test]
81 fn subagent_returns_low() {
82 assert_eq!(select(true, "anything"), ReasoningEffort::Low);
83 assert_eq!(select(true, "debug this"), ReasoningEffort::Low);
84 assert_eq!(select(true, "search query"), ReasoningEffort::Low);
85 }
86
87 #[test]
88 fn debug_or_error_returns_max() {
89 assert_eq!(select(false, "find a bug"), ReasoningEffort::High);
90 assert_eq!(select(false, "debug crash"), ReasoningEffort::Max);
91 assert_eq!(select(false, "Error: timeout"), ReasoningEffort::Max);
92 assert_eq!(select(false, "fix this error"), ReasoningEffort::Max);
93 assert_eq!(select(false, "DEBUG output"), ReasoningEffort::Max);
94 }
95
96 #[test]
97 fn search_or_lookup_returns_low() {
98 assert_eq!(select(false, "search for the file"), ReasoningEffort::Low);
99 assert_eq!(select(false, "lookup docs"), ReasoningEffort::Low);
100 assert_eq!(select(false, "SearchQuery"), ReasoningEffort::Low);
101 assert_eq!(select(false, "lookup_user"), ReasoningEffort::Low);
102 }
103
104 #[test]
105 fn default_returns_high() {
106 assert_eq!(select(false, "hello"), ReasoningEffort::High);
107 assert_eq!(select(false, "write a test"), ReasoningEffort::High);
108 assert_eq!(select(false, "refactor this module"), ReasoningEffort::High);
109 assert_eq!(select(false, ""), ReasoningEffort::High);
110 }
111
112 #[test]
113 fn chinese_debug_keywords_return_max() {
114 // The original keyword set was English-only; Chinese-speaking
115 // Auto-mode users paid for `High` even on real debugging tasks.
116 for msg in [
117 "\u{5e2e}\u{6211}\u{8c03}\u{8bd5}\u{4ee3}\u{7801}", // 帮我调试代码
118 "\u{8fd9}\u{91cc}\u{6709}\u{4e2a}\u{9519}\u{8bef}", // 这里有个错误
119 "\u{4ee3}\u{7801}\u{62a5}\u{9519}\u{4e86}", // 代码报错了
120 "\u{7a0b}\u{5e8f}\u{51fa}\u{9519}", // 程序出错
121 "\u{7cfb}\u{7edf}\u{5d29}\u{6e83}", // 系统崩溃
122 "\u{4ee3}\u{78bc}\u{8abf}\u{8a66}", // 代碼調試 (zh-Hant)
123 "\u{6709}\u{500b}\u{932f}\u{8aa4}", // 有個錯誤 (zh-Hant)
124 ] {
125 assert_eq!(
126 select(false, msg),
127 ReasoningEffort::Max,
128 "expected Max for `{msg}`",
129 );
130 }
131 }
132
133 #[test]
134 fn japanese_debug_keywords_return_max() {
135 for msg in [
136 "\u{30b3}\u{30fc}\u{30c9}\u{3092}\u{30c7}\u{30d0}\u{30c3}\u{30b0}", // コードをデバッグ
137 "\u{30a8}\u{30e9}\u{30fc}\u{304c}\u{51fa}\u{305f}", // エラーが出た
138 "\u{30d0}\u{30b0}\u{3092}\u{4fee}\u{6b63}", // バグを修正
139 ] {
140 assert_eq!(
141 select(false, msg),
142 ReasoningEffort::Max,
143 "expected Max for `{msg}`",
144 );
145 }
146 }
147
148 #[test]
149 fn chinese_search_keywords_return_low() {
150 for msg in [
151 "\u{641c}\u{7d22}\u{4e00}\u{4e0b}\u{6587}\u{4ef6}", // 搜索一下文件
152 "\u{5e2e}\u{6211}\u{67e5}\u{627e}\u{5b9a}\u{4e49}", // 帮我查找定义
153 "\u{67e5}\u{8be2}\u{6587}\u{6863}", // 查询文档
154 ] {
155 assert_eq!(
156 select(false, msg),
157 ReasoningEffort::Low,
158 "expected Low for `{msg}`",
159 );
160 }
161 }
162
163 #[test]
164 fn japanese_search_keyword_returns_low() {
165 // 検索 → "search"
166 assert_eq!(
167 select(
168 false,
169 "\u{30c9}\u{30ad}\u{30e5}\u{30e1}\u{30f3}\u{30c8}\u{691c}\u{7d22}"
170 ),
171 ReasoningEffort::Low,
172 );
173 }
174
175 #[test]
176 fn cjk_default_still_returns_high() {
177 // No keyword hits — ordinary Chinese/Japanese prose stays on
178 // the `High` default like English does.
179 for msg in [
180 "\u{5e2e}\u{6211}\u{5199}\u{4e2a}\u{6d4b}\u{8bd5}", // 帮我写个测试
181 "\u{91cd}\u{6784}\u{8fd9}\u{4e2a}\u{6a21}\u{5757}", // 重构这个模块
182 "\u{30c6}\u{30b9}\u{30c8}\u{3092}\u{66f8}\u{304f}", // テストを書く
183 ] {
184 assert_eq!(
185 select(false, msg),
186 ReasoningEffort::High,
187 "expected High for `{msg}`",
188 );
189 }
190 }
191 }
192
192 lines RUST