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