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