1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
//! Lightweight, zero-cost conversational mood detection.
//!
//! `SOUL.md` defines the bot's *stable* personality; this adds the *fluid*
//! layer — a coarse read of the user's current emotional state from their
//! latest message — so the bot can adapt tone (empathize when they're
//! frustrated, be crisp when they're in a hurry) without any extra LLM call.
//!
//! Detection is deliberately conservative: it only fires on clear signals and
//! otherwise returns `None` (no injection), since a wrong mood read is worse
//! than none. Cues cover both English and Chinese.
/// A coarse read of the user's current emotional tone.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mood {
Frustrated,
Urgent,
Sad,
Confused,
Grateful,
Excited,
Playful,
}
impl Mood {
/// Short label shown inside the `<conversation_mood>` block.
fn label(self) -> &'static str {
match self {
Mood::Frustrated => "frustrated",
Mood::Urgent => "in a hurry",
Mood::Sad => "down",
Mood::Confused => "confused",
Mood::Grateful => "appreciative",
Mood::Excited => "excited",
Mood::Playful => "playful",
}
}
/// One-line tone guidance for the model.
fn guidance(self) -> &'static str {
match self {
Mood::Frustrated => {
"Acknowledge the frustration briefly, skip the cheer, and get straight to fixing the problem."
}
Mood::Urgent => {
"Be fast and direct — give the answer first and trim everything optional."
}
Mood::Sad => {
"Lead with a little genuine warmth before the practical part; don't be chirpy."
}
Mood::Confused => {
"Slow down, use plainer words, and give one clear step at a time."
}
Mood::Grateful => {
"A brief, warm acknowledgement is enough — don't overdo it."
}
Mood::Excited => "Match their energy a little, then keep things moving.",
Mood::Playful => {
"A light, casual tone fits here, and a touch of humor is welcome."
}
}
}
}
fn contains_any(haystack: &str, needles: &[&str]) -> bool {
needles.iter().any(|n| haystack.contains(n))
}
/// Detect a coarse mood from the user's latest message. Returns `None` when the
/// signal is weak or neutral. Negative states are checked first since handling
/// them well matters most.
pub fn detect_mood(text: &str) -> Option<Mood> {
let t = text.trim();
if t.is_empty() {
return None;
}
let lower = t.to_lowercase();
// Frustrated / angry.
let strong_punct = t.contains("?!") || t.contains("!!!") || t.contains("?!");
if contains_any(
&lower,
&[
"frustrat",
"annoying",
"annoyed",
"this is ridiculous",
"not working",
"still broken",
"still not working",
"doesn't work",
"doesnt work",
"wtf",
" ugh",
"ugh ",
"useless",
"i hate",
"so stupid",
"again?!",
],
) || contains_any(
t,
&[
"烦",
"崩溃",
"无语",
"搞什么",
"又不行",
"还是不行",
"气死",
"什么破",
"废物",
"垃圾",
"受不了",
"服了",
],
) || (strong_punct
&& (contains_any(&lower, &["why", "still", "again"])
|| contains_any(t, &["为什么", "怎么", "又"])))
{
return Some(Mood::Frustrated);
}
// Urgent.
if contains_any(
&lower,
&[
"asap",
"urgent",
"right now",
"immediately",
"hurry",
"deadline",
"emergency",
"as fast as",
"quickly please",
"need it now",
],
) || contains_any(
t,
&[
"很急",
"急",
"马上",
"立刻",
"赶紧",
"尽快",
"快点",
"现在就要",
"等不及",
],
) {
return Some(Mood::Urgent);
}
// Sad / down / overwhelmed.
if contains_any(
&lower,
&[
"depressed",
"burned out",
"burnt out",
"overwhelmed",
"exhausted",
"feeling down",
"so tired",
"i'm sad",
"im sad",
"can't cope",
"cant cope",
],
) || contains_any(
t,
&[
"难过",
"伤心",
"好累",
"累了",
"压力好大",
"撑不住",
"焦虑",
"心累",
"失落",
],
) {
return Some(Mood::Sad);
}
// Confused.
if contains_any(
&lower,
&[
"i'm confused",
"im confused",
"i don't understand",
"i dont understand",
"doesn't make sense",
"what do you mean",
"i'm lost",
"im lost",
"no idea what",
],
) || contains_any(
t,
&[
"看不懂",
"不明白",
"搞不懂",
"什么意思",
"没看懂",
"懵",
"不懂",
],
) {
return Some(Mood::Confused);
}
// Grateful.
if contains_any(
&lower,
&[
"thank you",
"thanks",
"appreciate it",
"you're the best",
"lifesaver",
"much appreciated",
],
) || contains_any(t, &["谢谢", "感谢", "太感谢", "辛苦了", "多谢", "感激"])
{
return Some(Mood::Grateful);
}
// Excited.
if contains_any(
&lower,
&[
"awesome",
"amazing",
"let's go",
"lets go",
"can't wait",
"cant wait",
"so excited",
"this is great",
"love it",
"incredible",
"🎉",
"🚀",
"🔥",
],
) || contains_any(
t,
&[
"太棒",
"太好了",
"厉害",
"牛",
"期待",
"激动",
"爱了",
"yyds",
],
) {
return Some(Mood::Excited);
}
// Playful.
if contains_any(&lower, &["lol", "lmao", "rofl", "😂", "🤣", "😜", "haha"])
|| contains_any(t, &["哈哈", "嘻嘻", "2333", "doge"])
{
return Some(Mood::Playful);
}
None
}
/// Build the `<conversation_mood>` body for the latest user message, or `None`.
pub fn mood_hint(text: &str) -> Option<String> {
detect_mood(text).map(|m| format!("The user sounds {}. {}", m.label(), m.guidance()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neutral_text_yields_no_mood() {
assert_eq!(detect_mood("what's the capital of France?"), None);
assert_eq!(detect_mood(""), None);
assert!(mood_hint("please summarize this file").is_none());
}
#[test]
fn detects_frustration_en_and_zh() {
assert_eq!(
detect_mood("this is still not working, ugh"),
Some(Mood::Frustrated)
);
assert_eq!(detect_mood("又不行,气死了"), Some(Mood::Frustrated));
}
#[test]
fn detects_urgency_and_sadness() {
assert_eq!(detect_mood("I need this ASAP"), Some(Mood::Urgent));
assert_eq!(detect_mood("赶紧帮我看下"), Some(Mood::Urgent));
assert_eq!(
detect_mood("I'm so exhausted and overwhelmed"),
Some(Mood::Sad)
);
}
#[test]
fn gratitude_and_excitement_and_hint_text() {
assert_eq!(detect_mood("thank you so much!"), Some(Mood::Grateful));
assert_eq!(detect_mood("太棒了,厉害"), Some(Mood::Excited));
let hint = mood_hint("this is still broken, wtf").unwrap();
assert!(hint.contains("frustrated"));
}
}