mofa_plugins/tools/
response_optimizer.rs1use super::*;
2use serde_json::json;
3use std::sync::{Arc, Mutex};
4
5#[derive(Debug, Default)]
7struct FeedbackStats {
8 consecutive_confused: u32,
10 current_mode: String,
12 total_feedback: u32,
14}
15
16pub struct ResponseOptimizerTool {
18 definition: ToolDefinition,
19 feedback_stats: Arc<Mutex<FeedbackStats>>,
20}
21
22impl Default for ResponseOptimizerTool {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl ResponseOptimizerTool {
29 pub fn new() -> Self {
30 Self {
31 definition: ToolDefinition {
32 name: "response_optimizer".to_string(),
33 description: "Educational response optimization: tracks student feedback and adjusts teaching strategies based on rules.".to_string(),
34 parameters: json!({
35 "type": "object",
36 "properties": {
37 "action": {
38 "type": "string",
39 "enum": ["record_feedback", "check_status", "reset_stats"],
40 "description": "Action to perform"
41 },
42 "feedback_type": {
43 "type": "string",
44 "enum": ["understand", "confused", "too_fast", "too_slow", "other"],
45 "description": "Type of student feedback"
46 },
47 "feedback_content": {
48 "type": "string",
49 "description": "Detailed feedback content"
50 }
51 },
52 "required": ["action"]
53 }),
54 requires_confirmation: false,
55 },
56 feedback_stats: Arc::new(Mutex::new(FeedbackStats::default())),
57 }
58 }
59
60 fn update_feedback_stats(&self, feedback_type: &str) -> String {
62 let mut stats = self.feedback_stats.lock().unwrap();
63 if stats.current_mode.is_empty() {
65 stats.current_mode = "normal".to_string();
66 }
67
68 let old_mode = stats.current_mode.clone();
69
70 match feedback_type {
71 "confused" => {
72 stats.consecutive_confused += 1;
73 }
74 _ => {
75 stats.consecutive_confused = 0;
77 }
78 }
79
80 stats.total_feedback += 1;
81
82 if stats.consecutive_confused >= 3 && stats.current_mode != "basic" {
84 stats.current_mode = "basic".to_string();
85 return format!(
86 "Mode switched from {} to basic because of {} consecutive 'confused' feedbacks",
87 old_mode, stats.consecutive_confused
88 );
89 }
90 else if feedback_type == "understand" && stats.current_mode == "basic" {
92 stats.current_mode = "normal".to_string();
93 return "Mode switched from basic to normal because student reported understanding"
94 .to_string();
95 }
96
97 "No mode change".to_string()
98 }
99}
100
101#[async_trait::async_trait]
102impl ToolExecutor for ResponseOptimizerTool {
103 fn definition(&self) -> &ToolDefinition {
104 &self.definition
105 }
106
107 async fn execute(&self, arguments: serde_json::Value) -> PluginResult<serde_json::Value> {
108 let action = arguments["action"]
109 .as_str()
110 .ok_or_else(|| anyhow::anyhow!("Action is required"))?;
111
112 match action {
113 "record_feedback" => {
114 let feedback_type = arguments["feedback_type"].as_str().ok_or_else(|| {
115 anyhow::anyhow!("feedback_type is required for record_feedback")
116 })?;
117
118 let feedback_content = arguments["feedback_content"].as_str().unwrap_or("");
119
120 let mode_change = self.update_feedback_stats(feedback_type);
122
123 let stats = self.feedback_stats.lock().unwrap();
124
125 Ok(json!({
126 "status": "success",
127 "feedback_type": feedback_type,
128 "feedback_content": feedback_content,
129 "mode_change": mode_change,
130 "current_mode": stats.current_mode,
131 "consecutive_confused": stats.consecutive_confused,
132 "total_feedback": stats.total_feedback
133 }))
134 }
135 "check_status" => {
136 let mut stats = self.feedback_stats.lock().unwrap();
137 if stats.current_mode.is_empty() {
139 stats.current_mode = "normal".to_string();
140 }
141
142 Ok(json!({
143 "status": "success",
144 "current_mode": stats.current_mode,
145 "consecutive_confused": stats.consecutive_confused,
146 "total_feedback": stats.total_feedback
147 }))
148 }
149 "reset_stats" => {
150 let mut stats = self.feedback_stats.lock().unwrap();
151
152 *stats = FeedbackStats::default();
153
154 Ok(json!({
155 "status": "success",
156 "message": "Feedback stats reset successfully"
157 }))
158 }
159 _ => Err(anyhow::anyhow!("Unknown action: {}", action)),
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[tokio::test]
169 async fn test_response_optimizer_mode_switch() {
170 let optimizer = ResponseOptimizerTool::new();
171
172 let status = optimizer
174 .execute(json!({
175 "action": "check_status"
176 }))
177 .await
178 .unwrap();
179 assert_eq!(status["current_mode"], "normal");
180 assert_eq!(status["consecutive_confused"], 0);
181
182 let result = optimizer
184 .execute(json!({
185 "action": "record_feedback",
186 "feedback_type": "confused",
187 "feedback_content": "不明白"
188 }))
189 .await
190 .unwrap();
191 assert_eq!(result["consecutive_confused"], 1);
192 assert_eq!(result["current_mode"], "normal");
193
194 let result = optimizer
196 .execute(json!({
197 "action": "record_feedback",
198 "feedback_type": "confused",
199 "feedback_content": "还是不明白"
200 }))
201 .await
202 .unwrap();
203 assert_eq!(result["consecutive_confused"], 2);
204 assert_eq!(result["current_mode"], "normal");
205
206 let result = optimizer
208 .execute(json!({
209 "action": "record_feedback",
210 "feedback_type": "confused",
211 "feedback_content": "完全不明白"
212 }))
213 .await
214 .unwrap();
215 assert_eq!(result["consecutive_confused"], 3);
216 assert_eq!(result["current_mode"], "basic");
217 assert!(
218 result["mode_change"]
219 .as_str()
220 .unwrap()
221 .contains("Mode switched")
222 );
223
224 let result = optimizer
226 .execute(json!({
227 "action": "record_feedback",
228 "feedback_type": "understand",
229 "feedback_content": "现在明白了"
230 }))
231 .await
232 .unwrap();
233 assert_eq!(result["consecutive_confused"], 0);
234 assert_eq!(result["current_mode"], "normal");
235
236 println!("✓ 所有测试用例通过!");
237 }
238}