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