1use std::collections::HashMap;
2
3use crate::{
4 azure_chatbot::ChatbotUserContext,
5 chatbot_error::chatbot_err,
6 chatbot_tools::{
7 AzureLLMFunctionToolDefinition, ChatbotTool, LLMToolParamType, LLMToolParams, LLMToolType,
8 ToolProperties,
9 },
10 prelude::{ChatbotError, ChatbotErrorType, ChatbotResult},
11};
12use headless_lms_base::prelude_base_and_re_exports::BackendError;
13use headless_lms_models::{
14 course_modules::{CompletionPolicy, CourseModule},
15 user_exercise_states::UserCourseProgress,
16};
17use sqlx::PgConnection;
18
19pub type CourseProgressTool = ToolProperties<CourseProgressState, CourseProgressArguments>;
20
21impl ChatbotTool for CourseProgressTool {
22 type State = CourseProgressState;
23 type Arguments = CourseProgressArguments;
24
25 fn parse_arguments(_args_string: String) -> ChatbotResult<Self::Arguments> {
26 Ok(CourseProgressArguments {})
27 }
28
29 async fn from_db_and_arguments(
31 conn: &mut PgConnection,
32 arguments: Self::Arguments,
33 user_context: &ChatbotUserContext,
34 ) -> ChatbotResult<Self> {
35 let Some(user_id) = user_context.user_id else {
36 return Err(chatbot_err!(
37 ToolUseError,
38 "User id is missing.".to_string()
39 ));
40 };
41 let Some(course_id) = user_context.course_id else {
42 return Err(chatbot_err!(
43 ToolUseError,
44 "Course id is missing.".to_string()
45 ));
46 };
47 let Some(course_name) = &user_context.course_name else {
48 return Err(chatbot_err!(
49 ToolUseError,
50 "Course name is missing.".to_string()
51 ));
52 };
53 let user_progress = headless_lms_models::user_exercise_states::get_user_course_progress(
54 conn, course_id, user_id, true,
55 )
56 .await?;
57 let modules =
58 headless_lms_models::course_modules::get_by_course_id(conn, course_id).await?;
59 let progress = progress_info(user_progress, modules, course_name)?;
60 Result::Ok(CourseProgressTool {
61 state: CourseProgressState {
62 course_name: course_name.clone(),
63 progress,
64 },
65 arguments,
66 })
67 }
68
69 fn output(&self) -> String {
71 let mut progress = self.state.progress.to_owned();
72 let course_name = &self.state.course_name;
73 let mut res = format!("The user is completing a course called {course_name}. ");
74
75 if progress.len() == 1 {
77 let progress_info = &progress[0];
78 let module_progress = &progress_info.progress;
79
80 res += "Their progress on this course is the following:";
81
82 res += &push_exercises_scores_progress(
83 module_progress,
84 progress_info.automatic_completion,
85 progress_info.requires_exam,
86 "course",
87 );
88 } else {
89 progress.sort_by_key(|m| m.order_number);
92 let first_mod = progress.first();
93
94 let s = if let Some(progress_info) = first_mod {
96 let module_progress = &progress_info.progress;
97 let m_name = &module_progress.course_module_name;
98 format!(
99 "The course has one base module, and additional modules. The user's progress on the base course module called {m_name} is the following:"
100 ) + &push_exercises_scores_progress(
101 module_progress,
102 progress_info.automatic_completion,
103 progress_info.requires_exam,
104 "module",
105 ) + "To pass the course, it's required to pass the base module. The following modules are additional to the course and to complete them, it's required to first complete the base module.\n"
106 } else {
107 "There is no progress information for this user on this course. ".to_string()
109 };
110 res += &s;
111
112 for progress_info in progress.iter().skip(1) {
115 let module_progress = &progress_info.progress;
116 let m_name = &module_progress.course_module_name;
117 res.push_str(&format!(
118 "The user's progress on the course module called {m_name} is the following:"
119 ));
120 res += &push_exercises_scores_progress(
121 module_progress,
122 progress_info.automatic_completion,
123 progress_info.requires_exam,
124 "module",
125 );
126 }
127 }
128 res
129 }
130
131 fn output_description_instructions(&self) -> Option<String> {
132 if self.state.progress.len() > 1 {
133 Some(
134 "Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. If the user asks something like 'how to pass the course', describe the passing criteria and requirements of the base module. Encourage the user to ask further questions about other modules if needed.".to_string(),
135 )
136 } else {
137 Some(
138 "Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.".to_string(),
139 )
140 }
141 }
142
143 fn get_arguments(&self) -> &Self::Arguments {
144 &self.arguments
145 }
146
147 fn get_tool_definition() -> AzureLLMFunctionToolDefinition {
148 AzureLLMFunctionToolDefinition {
149 tool_type: LLMToolType::Function,
150 name: "course_progress".to_string(),
151 description: "Get the user's progress on this course, including information about exercises attempted, points gained, the passing criteria for the course and if the user meets the criteria.".to_string(),
152 parameters: LLMToolParams {tool_type: LLMToolParamType::Object, properties: HashMap::new(), required: vec![], additional_properties: false},
153 strict: true
154 }
155 }
156}
157
158#[derive(serde::Serialize, serde::Deserialize)]
159pub struct CourseProgressArguments {}
160
161pub struct CourseProgressState {
162 course_name: String,
163 progress: Vec<CourseProgressInfo>,
164}
165
166#[derive(Debug, PartialEq, Clone)]
168pub struct CourseProgressInfo {
169 order_number: i32,
170 progress: UserCourseProgress,
171 automatic_completion: bool,
172 requires_exam: bool,
173}
174
175fn push_exercises_scores_progress(
176 module_progress: &UserCourseProgress,
177 automatic_completion: bool,
178 requires_exam: bool,
179 course_or_module: &str,
180) -> String {
181 let attempted_exercises = module_progress.attempted_exercises;
182 let total_exercises = module_progress.total_exercises;
183 let attempted_exercises_required = module_progress.attempted_exercises_required;
184 let score_given = module_progress.score_given;
185 let score_maximum = module_progress.score_maximum;
186 let score_required = module_progress.score_required;
187
188 let mut res = "".to_string();
189 if total_exercises.is_some() || score_maximum.is_some() {
190 if let (Some(a), Some(b)) = (total_exercises, score_maximum)
191 && a == 0
192 && b == 0
193 {
194 res += &format!(
195 " This {course_or_module} has no exercises and no points. It cannot be completed by doing exercises."
196 );
197 if requires_exam {
198 res += " Passing an exam is required for completion.";
199 } else {
200 res += &format!(
201 " The user should look for information about completing the {course_or_module} in the course material or contact the teacher.\n"
202 );
203 }
204 return res;
205 }
206 res += &format!(" On this {course_or_module}, there are available a total of ");
207
208 if let Some(a) = total_exercises {
209 res += &format!("{a} exercises");
210 }
211 if let Some(b) = score_maximum {
212 if total_exercises.is_some() {
213 res += " and ";
214 }
215 res += &format!("{b} exercise points");
216 }
217 res += ".";
218 }
219 if automatic_completion && score_required.is_none() && attempted_exercises_required.is_none() {
220 res += &format!(
221 " It's not required to attempt exercises or gain points to pass this {course_or_module}."
222 );
223 }
224
225 if requires_exam {
226 res += &format!(" To pass this {course_or_module}, it's required to complete an exam.");
227 }
228 if !automatic_completion {
229 res += &format!(
230 " This {course_or_module} is graded by a teacher and can't be automatically passed by completing exercises. The user should look for information about completing the {course_or_module} in the course material or contact the teacher."
231 );
232 }
233
234 if attempted_exercises_required.is_some() || score_required.is_some() {
235 if requires_exam {
236 res += " To be qualified to take the exam, it's required to ";
237 } else {
238 res += &format!(" To pass this {course_or_module}, it's required to ");
239 }
240
241 if let Some(a) = attempted_exercises_required {
242 res += &format!("attempt {a} exercises");
243 }
244 if let Some(b) = score_required {
245 if attempted_exercises_required.is_some() {
246 res += " and ";
247 }
248 res += &format!("gain {b} exercise points");
249 }
250 res += ".";
251 } else if requires_exam {
252 res += " The user can attempt the exam regardless of their progress on the course."
253 }
254
255 if let Some(b) = attempted_exercises {
256 res += &format!(" The user has attempted {b} exercises.");
257 } else {
258 res += " The user has not attempted any exercises.";
259 }
260 let attempted_exercises_n = attempted_exercises.unwrap_or(0);
261
262 let pass = if requires_exam {
263 "be qualified to take the exam".to_string()
264 } else {
265 format!("pass this {course_or_module}")
266 };
267
268 if let Some(c) = attempted_exercises_required {
269 let ex_left = c - attempted_exercises_n;
270 if ex_left <= 0 {
271 res += &format!(
272 " They meet the criteria to {pass} if they have also received enough points."
273 );
274 } else {
275 res += &format!(" To {pass}, they need to attempt {ex_left} more exercises.");
276 }
277 }
278
279 let score = (score_given * 10.0).floor() / 10.0;
281 res += &format!(" The user has gained {:.1} points.", score);
282 if let Some(e) = score_required {
283 let pts_left = e as f32 - score;
284 if pts_left <= 0 as f32 {
285 res += &format!(" The user has gained enough points to {pass}.")
286 } else {
287 res += &format!(
288 " To {pass}, the user needs to gain {:.1} more points.",
289 pts_left
290 )
291 }
292 }
293 res + "\n"
294}
295
296fn progress_info(
298 user_progress: Vec<UserCourseProgress>,
299 modules: Vec<CourseModule>,
300 course_name: &str,
301) -> ChatbotResult<Vec<CourseProgressInfo>> {
302 user_progress
303 .into_iter()
304 .map(|u| {
305 let module = modules
306 .iter()
307 .find(|x| x.order_number == u.course_module_order_number);
308 if let Some(m) = module {
309 let (automatic_completion, requires_exam) = match &m.completion_policy {
310 CompletionPolicy::Automatic(policy) => (true, policy.requires_exam),
311 CompletionPolicy::Manual => (false, false),
312 };
313 Ok(CourseProgressInfo {
314 order_number: u.course_module_order_number,
315 progress: u,
316 automatic_completion,
317 requires_exam,
318 })
319 } else {
320 Err(chatbot_err!(Other, format!("There was an error fetching the user's course progress information. Couldn't find course module {} of course {}.", u.course_module_name, course_name)))
321 }
322 })
323 .collect::<ChatbotResult<Vec<CourseProgressInfo>>>()
324}
325
326#[cfg(test)]
327mod tests {
328 use uuid::Uuid;
329
330 use super::*;
331
332 impl CourseProgressTool {
333 fn new_mock(course_name: String, progress: Vec<CourseProgressInfo>) -> Self {
334 CourseProgressTool {
335 state: CourseProgressState {
336 course_name,
337 progress,
338 },
339 arguments: CourseProgressArguments {},
340 }
341 }
342 }
343
344 #[test]
345 fn test_course_progress_output_only_base_module() {
346 let progress = vec![CourseProgressInfo {
347 order_number: 1,
348 progress: UserCourseProgress {
349 course_module_id: Uuid::nil(),
350 course_module_name: "Example base module".to_string(),
351 course_module_order_number: 1,
352 score_given: 3.3,
353 score_required: Some(4),
354 score_maximum: Some(5),
355 total_exercises: Some(11),
356 attempted_exercises: Some(4),
357 attempted_exercises_required: Some(10),
358 },
359 automatic_completion: true,
360 requires_exam: false,
361 }];
362 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
363 let output = tool.get_tool_output();
364
365 let expected_output =
366"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 11 exercises and 5 exercise points. To pass this course, it's required to attempt 10 exercises and gain 4 exercise points. The user has attempted 4 exercises. To pass this course, they need to attempt 6 more exercises. The user has gained 3.3 points. To pass this course, the user needs to gain 0.7 more points.\n[/output]
367
368Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
369
370 assert_eq!(output, expected_output);
371 }
372
373 #[test]
374 fn test_course_progress_output_many_modules() {
375 let progress = vec![
376 CourseProgressInfo {
377 order_number: 3,
378 progress: UserCourseProgress {
379 course_module_id: Uuid::nil(),
380 course_module_name: "Second extra module".to_string(),
381 course_module_order_number: 3,
382 score_given: 0.0,
383 score_required: Some(4),
384 score_maximum: Some(5),
385 total_exercises: Some(6),
386 attempted_exercises: None,
387 attempted_exercises_required: Some(5),
388 },
389 automatic_completion: true,
390 requires_exam: false,
391 },
392 CourseProgressInfo {
393 order_number: 1,
394 progress: UserCourseProgress {
395 course_module_id: Uuid::nil(),
396 course_module_name: "Advanced Chatbot Course".to_string(),
397 course_module_order_number: 1,
398 score_given: 8.056,
399 score_required: Some(8),
400 score_maximum: Some(10),
401 total_exercises: Some(5),
402 attempted_exercises: Some(5),
403 attempted_exercises_required: Some(5),
404 },
405 automatic_completion: true,
406 requires_exam: false,
407 },
408 CourseProgressInfo {
409 order_number: 2,
410 progress: UserCourseProgress {
411 course_module_id: Uuid::nil(),
412 course_module_name: "First extra module".to_string(),
413 course_module_order_number: 2,
414 score_given: 3.94,
415 score_required: Some(5),
416 score_maximum: Some(6),
417 total_exercises: Some(6),
418 attempted_exercises: Some(4),
419 attempted_exercises_required: Some(5),
420 },
421 automatic_completion: true,
422 requires_exam: false,
423 },
424 CourseProgressInfo {
425 order_number: 4,
426 progress: UserCourseProgress {
427 course_module_id: Uuid::nil(),
428 course_module_name: "Chatbot advanced topics".to_string(),
429 course_module_order_number: 4,
430 score_given: 2.0,
431 score_required: None,
432 score_maximum: None,
433 total_exercises: Some(2),
434 attempted_exercises: Some(2),
435 attempted_exercises_required: None,
436 },
437 automatic_completion: true,
438 requires_exam: false,
439 },
440 ];
441
442 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
443 let output = tool.get_tool_output();
444
445 let expected_output =
446"Result: [output]The user is completing a course called Advanced Chatbot Course. The course has one base module, and additional modules. The user's progress on the base course module called Advanced Chatbot Course is the following: On this module, there are available a total of 5 exercises and 10 exercise points. To pass this module, it's required to attempt 5 exercises and gain 8 exercise points. The user has attempted 5 exercises. They meet the criteria to pass this module if they have also received enough points. The user has gained 8.0 points. The user has gained enough points to pass this module.
447To pass the course, it's required to pass the base module. The following modules are additional to the course and to complete them, it's required to first complete the base module.
448The user's progress on the course module called First extra module is the following: On this module, there are available a total of 6 exercises and 6 exercise points. To pass this module, it's required to attempt 5 exercises and gain 5 exercise points. The user has attempted 4 exercises. To pass this module, they need to attempt 1 more exercises. The user has gained 3.9 points. To pass this module, the user needs to gain 1.1 more points.
449The user's progress on the course module called Second extra module is the following: On this module, there are available a total of 6 exercises and 5 exercise points. To pass this module, it's required to attempt 5 exercises and gain 4 exercise points. The user has not attempted any exercises. To pass this module, they need to attempt 5 more exercises. The user has gained 0.0 points. To pass this module, the user needs to gain 4.0 more points.
450The user's progress on the course module called Chatbot advanced topics is the following: On this module, there are available a total of 2 exercises. It's not required to attempt exercises or gain points to pass this module. The user has attempted 2 exercises. The user has gained 2.0 points.\n[/output]
451
452Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. If the user asks something like 'how to pass the course', describe the passing criteria and requirements of the base module. Encourage the user to ask further questions about other modules if needed.[/instructions]".to_string();
453
454 assert_eq!(output, expected_output);
455 }
456
457 #[test]
458 fn test_course_progress_output_no_progress() {
459 let progress: Vec<CourseProgressInfo> = vec![];
460
461 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
462 let output = tool.get_tool_output();
463
464 let expected_output =
465"Result: [output]The user is completing a course called Advanced Chatbot Course. There is no progress information for this user on this course. [/output]
466
467Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
468
469 assert_eq!(output, expected_output);
470 }
471
472 #[test]
473 fn test_course_progress_output_no_course_points_exercises() {
474 let progress = vec![CourseProgressInfo {
475 order_number: 1,
476 progress: UserCourseProgress {
477 course_module_id: Uuid::nil(),
478 course_module_name: "Example base module".to_string(),
479 course_module_order_number: 1,
480 score_given: 0.0,
481 score_required: None,
482 score_maximum: Some(0),
483 total_exercises: Some(0),
484 attempted_exercises: None,
485 attempted_exercises_required: None,
486 },
487 automatic_completion: true,
488 requires_exam: false,
489 }];
490 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
491 let output = tool.get_tool_output();
492
493 let expected_output =
494"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: This course has no exercises and no points. It cannot be completed by doing exercises. The user should look for information about completing the course in the course material or contact the teacher.\n[/output]
495
496Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
497
498 assert_eq!(output, expected_output);
499 }
500
501 #[test]
502 fn test_course_progress_output_cant_be_completed() {
503 let progress = vec![CourseProgressInfo {
504 order_number: 1,
505 progress: UserCourseProgress {
506 course_module_id: Uuid::nil(),
507 course_module_name: "Example base module".to_string(),
508 course_module_order_number: 1,
509 score_given: 0.0,
510 score_required: Some(9),
511 score_maximum: Some(10),
512 total_exercises: Some(10),
513 attempted_exercises: None,
514 attempted_exercises_required: Some(10),
515 },
516 automatic_completion: false,
518 requires_exam: false,
519 }];
520 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
521 let output = tool.get_tool_output();
522
523 let expected_output =
524"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. This course is graded by a teacher and can't be automatically passed by completing exercises. The user should look for information about completing the course in the course material or contact the teacher. To pass this course, it's required to attempt 10 exercises and gain 9 exercise points. The user has not attempted any exercises. To pass this course, they need to attempt 10 more exercises. The user has gained 0.0 points. To pass this course, the user needs to gain 9.0 more points.\n[/output]
525
526Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
527
528 assert_eq!(output, expected_output);
529 }
530
531 #[test]
532 fn test_course_progress_output_exercises_required_none() {
533 let progress = vec![CourseProgressInfo {
534 order_number: 1,
535 progress: UserCourseProgress {
536 course_module_id: Uuid::nil(),
537 course_module_name: "Example base module".to_string(),
538 course_module_order_number: 1,
539 score_given: 0.0,
540 score_required: Some(9),
541 score_maximum: Some(10),
542 total_exercises: Some(10),
543 attempted_exercises: None,
544 attempted_exercises_required: None,
545 },
546 automatic_completion: true,
547 requires_exam: false,
548 }];
549 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
550 let output = tool.get_tool_output();
551
552 let expected_output =
553"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. To pass this course, it's required to gain 9 exercise points. The user has not attempted any exercises. The user has gained 0.0 points. To pass this course, the user needs to gain 9.0 more points.\n[/output]
554
555Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
556
557 assert_eq!(output, expected_output);
558 }
559
560 #[test]
561 fn test_course_progress_output_pts_required_none() {
562 let progress = vec![CourseProgressInfo {
563 order_number: 1,
564 progress: UserCourseProgress {
565 course_module_id: Uuid::nil(),
566 course_module_name: "Example base module".to_string(),
567 course_module_order_number: 1,
568 score_given: 0.0,
569 score_required: None,
570 score_maximum: Some(10),
571 total_exercises: Some(10),
572 attempted_exercises: None,
573 attempted_exercises_required: Some(10),
574 },
575 automatic_completion: true,
576 requires_exam: false,
577 }];
578 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
579 let output = tool.get_tool_output();
580
581 let expected_output =
582"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. To pass this course, it's required to attempt 10 exercises. The user has not attempted any exercises. To pass this course, they need to attempt 10 more exercises. The user has gained 0.0 points.\n[/output]
583
584Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
585
586 assert_eq!(output, expected_output);
587 }
588
589 #[test]
590 fn test_course_progress_output_exam_required() {
591 let progress = vec![CourseProgressInfo {
592 order_number: 1,
593 progress: UserCourseProgress {
594 course_module_id: Uuid::nil(),
595 course_module_name: "Example base module".to_string(),
596 course_module_order_number: 1,
597 score_given: 0.0780006,
598 score_required: Some(9),
599 score_maximum: Some(10),
600 total_exercises: Some(10),
601 attempted_exercises: None,
602 attempted_exercises_required: Some(10),
603 },
604 automatic_completion: true,
605 requires_exam: true,
606 }];
607 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
608 let output = tool.get_tool_output();
609
610 let expected_output =
611"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. To pass this course, it's required to complete an exam. To be qualified to take the exam, it's required to attempt 10 exercises and gain 9 exercise points. The user has not attempted any exercises. To be qualified to take the exam, they need to attempt 10 more exercises. The user has gained 0.0 points. To be qualified to take the exam, the user needs to gain 9.0 more points.\n[/output]
612
613Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
614
615 assert_eq!(output, expected_output);
616 }
617
618 #[test]
619 fn test_course_progress_output_exam_required_can_do_exam() {
620 let progress = vec![CourseProgressInfo {
621 order_number: 1,
622 progress: UserCourseProgress {
623 course_module_id: Uuid::nil(),
624 course_module_name: "Example base module".to_string(),
625 course_module_order_number: 1,
626 score_given: 9.00006,
627 score_required: Some(9),
628 score_maximum: Some(10),
629 total_exercises: Some(10),
630 attempted_exercises: Some(10),
631 attempted_exercises_required: Some(10),
632 },
633 automatic_completion: true,
634 requires_exam: true,
635 }];
636 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
637 let output = tool.get_tool_output();
638
639 let expected_output =
640"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. To pass this course, it's required to complete an exam. To be qualified to take the exam, it's required to attempt 10 exercises and gain 9 exercise points. The user has attempted 10 exercises. They meet the criteria to be qualified to take the exam if they have also received enough points. The user has gained 9.0 points. The user has gained enough points to be qualified to take the exam.\n[/output]
641
642Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
643
644 assert_eq!(output, expected_output);
645 }
646
647 #[test]
648 fn test_course_progress_output_exam_only_required() {
649 let progress = vec![CourseProgressInfo {
650 order_number: 1,
651 progress: UserCourseProgress {
652 course_module_id: Uuid::nil(),
653 course_module_name: "Example base module".to_string(),
654 course_module_order_number: 1,
655 score_given: 9.0,
656 score_required: None,
657 score_maximum: Some(10),
658 total_exercises: Some(10),
659 attempted_exercises: Some(10),
660 attempted_exercises_required: None,
661 },
662 automatic_completion: true,
663 requires_exam: true,
664 }];
665 let tool = CourseProgressTool::new_mock("Advanced Chatbot Course".to_string(), progress);
666 let output = tool.get_tool_output();
667
668 let expected_output =
669"Result: [output]The user is completing a course called Advanced Chatbot Course. Their progress on this course is the following: On this course, there are available a total of 10 exercises and 10 exercise points. It's not required to attempt exercises or gain points to pass this course. To pass this course, it's required to complete an exam. The user can attempt the exam regardless of their progress on the course. The user has attempted 10 exercises. The user has gained 9.0 points.\n[/output]
670
671Instructions for describing the output: [instructions]Describe this information in a short, clear way with no or minimal bullet points. Only give information that is relevant to the user's question. Encourage the user to ask further questions if needed.[/instructions]".to_string();
672
673 assert_eq!(output, expected_output);
674 }
675}