Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modeling exercises: Add preliminary AI feedback requests for students #9278

Merged
Merged
Show file tree
Hide file tree
Changes from 125 commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
f03e5e5
add UI options for AI Feedback and result history
Aug 4, 2024
1067ef1
Add Athena Feedback option for text exercises
Aug 16, 2024
80242a0
Add Athena AI Feedback button and history to Text Exercises
Aug 16, 2024
0245572
Add check for automatic Athena results in code editor so the student …
Aug 16, 2024
992a2bd
Update result components for Text Feedback
Aug 16, 2024
b49ca0f
Handle toggle of athena ai feedback request option
Aug 16, 2024
b79d325
Get the last submission for assessment for text exercises
Aug 21, 2024
c79b540
Preliminary Feedback Server
Aug 22, 2024
5405ae1
Fix Assessment tutor side issues
Aug 22, 2024
9ddc3d9
remove todo
Aug 22, 2024
01159dc
Merge branch 'feature/text-exercises/immediate-preliminary-feedback' …
Aug 22, 2024
79c4739
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
EneaGore Aug 22, 2024
a5b1c45
prettier
EneaGore Aug 22, 2024
da61ffa
Formattierung
Aug 22, 2024
95895b1
Merge branch 'feature/text-exercises/immediate-preliminary-feedback' …
Aug 22, 2024
f1cf351
Improve results for preliminary text AI Feedback
Aug 23, 2024
1c6ac3d
Fix result icon and color for Ai Feedback
Aug 23, 2024
52083fe
typo
Aug 23, 2024
6070cb6
ups
Aug 23, 2024
44f8cef
typo
Aug 23, 2024
ac360d4
improve result strings for text ai feedback
Aug 23, 2024
3de99f0
set ai feedback for text to graded
Aug 23, 2024
2f99541
add spinner for ai feedback
Aug 23, 2024
d1a30f7
small improvements
Aug 23, 2024
b192080
fix rate limit and editor sign when athena results
Aug 23, 2024
69a179e
clean up code
Aug 23, 2024
c8f5526
revert last change partially
Aug 23, 2024
f6e5aef
turn off automatic feedback requests if no module selected
Aug 23, 2024
753c050
update string for successful and failed athena feedback
Aug 27, 2024
a175663
refactor feedback request (Thanks Leon)
Aug 27, 2024
4c97671
show spinner, don't save or show empty or failed athena results
Aug 27, 2024
0c4f088
dont save emppty result, new method for new submission after athena f…
Aug 27, 2024
7269dcb
Improve error handling and better manage result templates
Aug 27, 2024
f73fad8
Add integration tests
Aug 28, 2024
464dfe8
no junit 4
Aug 28, 2024
560af55
no assert Throws
Aug 28, 2024
23dd234
save transient instance in test
Aug 28, 2024
db0e185
use text exercise id for text participation test
Aug 28, 2024
4067400
use text course
Aug 28, 2024
d60b372
separate programming and text athena tests
Aug 28, 2024
5dc5b6e
better tests
Aug 28, 2024
69ab55e
fix text case success
Aug 28, 2024
a9b2243
code clean up
Aug 28, 2024
a2c1ecd
more code clean up
Aug 28, 2024
fca28bc
turn athena created submission external and fix successful = false bugs
Aug 28, 2024
a684a48
address change Request (part 1)
Aug 28, 2024
ce056ff
optional chain
Aug 28, 2024
9de3a3f
disable feedback button when generating and address feedback (part 2)
Aug 28, 2024
d8f292d
bring back the spinner
Aug 28, 2024
8d5187d
better condition for spinner and some client tests
Aug 29, 2024
03c3bd1
fix result icon and color
Aug 29, 2024
4b72a4a
typo
Aug 29, 2024
b4dc9db
back to gray for Athena results
Aug 29, 2024
fc8c873
more result client tests
Aug 29, 2024
f59b2b3
result spec ts conflict
Aug 29, 2024
2099628
result spec ts conflict
Aug 29, 2024
c77e624
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
Aug 29, 2024
56982d0
add text athena result tests after conflict resolve
Aug 29, 2024
48d56c8
use notch instead of spinner
Aug 29, 2024
901eff2
blue notch, no endless spin in more results, error alert from client …
Aug 30, 2024
9010ae1
new submission only after student submits if athena results present, …
Aug 30, 2024
ce159f4
minor code improvements
Aug 30, 2024
4b0d29f
small bugfix
Aug 30, 2024
75f88ac
turn window alerts to alert warning and update tests
Aug 30, 2024
5b5554c
Add check if last submission already has results + test + getNonAthen…
Aug 31, 2024
6d2a594
Update server code to also handle AI Feedback for Modeling Exercises
LeonWehrhahn Sep 2, 2024
9a3f613
Add athenaEnabled check
LeonWehrhahn Sep 3, 2024
c1df8b0
test: Add automatic Athena result test for modeling exercises
LeonWehrhahn Sep 4, 2024
fe3d235
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
FelixTJDietrich Sep 4, 2024
287e20c
after develop merge entities -> entities/text
EneaGore Sep 4, 2024
f23244d
Fix integration tests. Also send null results with non athena results
Sep 6, 2024
cf1b90a
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
EneaGore Sep 7, 2024
86f7b6e
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 8, 2024
ac8cb0a
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
EneaGore Sep 8, 2024
ad97f5f
Revert Integration Tests
EneaGore Sep 9, 2024
1bd4f94
Try a signle import of AthenaRequestMockProvider
EneaGore Sep 9, 2024
3deaceb
Spy Bean -> Mock bean
EneaGore Sep 9, 2024
69bf43b
Add websocket spy bean declaration
EneaGore Sep 9, 2024
6684a2b
remvoe mock bean websocket from participipation integration test
EneaGore Sep 9, 2024
20d3811
java style
EneaGore Sep 9, 2024
9618c23
Merge branch 'develop' into feature/text-exercises/immediate-prelimin…
FelixTJDietrich Sep 10, 2024
45e72ca
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 10, 2024
6f5d47e
Merge branch 'feature/text-exercises/immediate-preliminary-feedback' …
LeonWehrhahn Sep 10, 2024
381f705
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 10, 2024
9c667b2
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 11, 2024
b3203a8
Refactor exercise feedback request handling in ParticipationResource
LeonWehrhahn Sep 11, 2024
02c80f9
Add ModelingAssessmentEditorComponent test for testing ngOnInit with …
LeonWehrhahn Sep 12, 2024
2f9e0aa
Add request modeling feedback participation integration test
LeonWehrhahn Sep 12, 2024
3f98504
Add request modeling feedback participation integration test
LeonWehrhahn Sep 12, 2024
2d59db8
Refactor ModelingAssessmentEditorComponent test to include AthenaService
LeonWehrhahn Sep 12, 2024
aee0d0b
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 12, 2024
51e0778
Refactor package and imports
LeonWehrhahn Sep 12, 2024
9bad5dc
Add
LeonWehrhahn Sep 12, 2024
4b10bfc
Update src/test/javascript/spec/component/modeling-assessment-editor/…
LeonWehrhahn Sep 12, 2024
dd29c9f
Show the ResultHistoryComponent for Modeling Exercises
LeonWehrhahn Sep 13, 2024
15f7f1d
Merge branch 'feature/modeling-exercises/immediate-preliminary-feedba…
LeonWehrhahn Sep 13, 2024
48b2d4a
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 13, 2024
c1a3263
Update ModelingSubmissionService to create new submissions for empty …
LeonWehrhahn Sep 13, 2024
a482732
Update ModelingSubmissionService to remove results present check
LeonWehrhahn Sep 13, 2024
9c910bd
Update ModelingSubmissionService add modelingSubmission.setResults
LeonWehrhahn Sep 13, 2024
3b2ac10
Update result retrieval for modeling exercises
LeonWehrhahn Sep 13, 2024
f523991
Add null check
LeonWehrhahn Sep 14, 2024
8e26c2f
Add null check
LeonWehrhahn Sep 14, 2024
7452923
Update getModelSubmissionForModelingEditor to ensure that exercise is…
LeonWehrhahn Sep 14, 2024
971d11f
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 14, 2024
6480f4b
Filter out null values, to correctly display assessments in Assessmen…
LeonWehrhahn Sep 14, 2024
15c6151
Merge remote-tracking branch 'origin/feature/modeling-exercises/immed…
LeonWehrhahn Sep 14, 2024
33e53a7
Only filter out null values, to correctly display assessments in Asse…
LeonWehrhahn Sep 14, 2024
9fa7e44
Update comment
LeonWehrhahn Sep 14, 2024
52bd827
Implement change requests
LeonWehrhahn Sep 15, 2024
74d60a7
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 15, 2024
9532cc3
Add FileUploadExercise to unsupported Feedback Generation Exercise types
LeonWehrhahn Sep 16, 2024
4e3527e
Merge remote-tracking branch 'origin/feature/modeling-exercises/immed…
LeonWehrhahn Sep 16, 2024
5085800
Refactor resultIsPreliminary function to use optional chaining for ex…
LeonWehrhahn Sep 16, 2024
cadab42
Add setSuccessful(true), as this indicates to the client that the gen…
LeonWehrhahn Sep 17, 2024
5c71554
Restructure ModelingExerciseFeedbackService
LeonWehrhahn Sep 17, 2024
d08d384
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 17, 2024
1240732
Refactor exam exercise check to use examMode var
LeonWehrhahn Sep 17, 2024
5736c1a
Merge branch 'feature/modeling-exercises/immediate-preliminary-feedba…
LeonWehrhahn Sep 17, 2024
40e9ca3
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 17, 2024
bb6e048
Adjust SubmissionService to filter out Athena results for complaints
LeonWehrhahn Sep 19, 2024
a756435
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 19, 2024
c7044a2
Adjust hasAthenaResultForlatestSubmission check
LeonWehrhahn Sep 19, 2024
0623a03
Merge branch 'feature/modeling-exercises/immediate-preliminary-feedba…
LeonWehrhahn Sep 19, 2024
d4a9dd3
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 20, 2024
b89206a
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 20, 2024
c4dabfb
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 20, 2024
1eb7085
Merge branch 'develop' into feature/modeling-exercises/immediate-prel…
LeonWehrhahn Sep 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ public Set<Result> findResultsFilteredForStudents(Participation participation) {
boolean isAssessmentOver = getAssessmentDueDate() == null || getAssessmentDueDate().isBefore(ZonedDateTime.now());
if (!isAssessmentOver) {
// This allows the showing of preliminary feedback in case the assessment due date is set before its over.
if (this instanceof TextExercise) {
if (this instanceof TextExercise || this instanceof ModelingExercise) {
return participation.getResults().stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).collect(Collectors.toSet());
}
return Set.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,11 @@ public List<Result> getManualResults() {
/**
* This method is necessary to ignore Athena results in the assessment view
*
* @return non athena automatic results including null results
* @return non athena automatic results excluding null results
*/
@JsonIgnore
public List<Result> getNonAthenaResults() {
return results.stream().filter(result -> result == null || !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new));
return results.stream().filter(result -> result != null && !result.isAthenaAutomatic()).collect(Collectors.toCollection(ArrayList::new));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -715,16 +715,16 @@ public List<StudentParticipation> findByExerciseAndStudentIdWithEagerSubmissions
}

/**
* Get the text exercise participation with the Latest Submissions and its results
* Retrieves a StudentParticipation with its latest Submission and associated Result.
*
* @param participationId the id of the participation
* @return the participation with latest submission and result
* @throws EntityNotFoundException
* @param participationId The unique identifier of the participation to retrieve.
* @return A StudentParticipation object containing the latest submission and result.
* @throws EntityNotFoundException If no StudentParticipation is found with the given ID.
*/
public StudentParticipation findTextExerciseParticipationWithLatestSubmissionAndResultElseThrow(Long participationId) throws EntityNotFoundException {
public StudentParticipation findExerciseParticipationWithLatestSubmissionAndResultElseThrow(Long participationId) throws EntityNotFoundException {
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
Optional<Participation> participation = participationRepository.findByIdWithLatestSubmissionAndResult(participationId);
if (participation.isEmpty() || !(participation.get() instanceof StudentParticipation studentParticipation)) {
throw new EntityNotFoundException("No text exercise participation found with id " + participationId);
throw new EntityNotFoundException("No exercise participation found with id " + participationId);
}
return studentParticipation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ private List<SubmissionWithComplaintDTO> getSubmissionsWithComplaintsFromComplai
// add each submission with its complaint to the DTO
submissions.stream().filter(submission -> submission.getResultWithComplaint() != null).forEach(submission -> {
// get the complaint which belongs to the submission
submission.setResults(submission.getNonAthenaResults());
Complaint complaintOfSubmission = complaintMap.get(submission.getResultWithComplaint().getId());
prepareComplaintAndSubmission(complaintOfSubmission, submission);
submissionWithComplaintDTOs.add(new SubmissionWithComplaintDTO(submission, complaintOfSubmission));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ protected ResponseEntity<List<Submission>> getAllSubmissions(Long exerciseId, bo
submission.getParticipation().setExercise(null);
}
// Important for exercises with Athena results
if (assessedByTutor) {
if (assessedByTutor && !examMode) {
submission.setResults(submission.getNonAthenaResults());
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@
import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService;
import de.tum.cit.aet.artemis.exercise.service.ParticipationAuthorizationCheckService;
import de.tum.cit.aet.artemis.exercise.service.ParticipationService;
import de.tum.cit.aet.artemis.fileupload.domain.FileUploadExercise;
import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise;
import de.tum.cit.aet.artemis.modeling.service.ModelingExerciseFeedbackService;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation;
Expand Down Expand Up @@ -167,6 +169,8 @@ public class ParticipationResource {

private final TextExerciseFeedbackService textExerciseFeedbackService;

private final ModelingExerciseFeedbackService modelingExerciseFeedbackService;

public ParticipationResource(ParticipationService participationService, ProgrammingExerciseParticipationService programmingExerciseParticipationService,
CourseRepository courseRepository, QuizExerciseRepository quizExerciseRepository, ExerciseRepository exerciseRepository,
ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService,
Expand All @@ -176,7 +180,8 @@ public ParticipationResource(ParticipationService participationService, Programm
ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, SubmissionRepository submissionRepository,
ResultRepository resultRepository, ExerciseDateService exerciseDateService, InstanceMessageSendService instanceMessageSendService, QuizBatchService quizBatchService,
SubmittedAnswerRepository submittedAnswerRepository, QuizSubmissionService quizSubmissionService, GradingScaleService gradingScaleService,
ProgrammingExerciseCodeReviewFeedbackService programmingExerciseCodeReviewFeedbackService, TextExerciseFeedbackService textExerciseFeedbackService) {
ProgrammingExerciseCodeReviewFeedbackService programmingExerciseCodeReviewFeedbackService, TextExerciseFeedbackService textExerciseFeedbackService,
ModelingExerciseFeedbackService modelingExerciseFeedbackService) {
this.participationService = participationService;
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
this.quizExerciseRepository = quizExerciseRepository;
Expand All @@ -203,6 +208,7 @@ public ParticipationResource(ParticipationService participationService, Programm
this.gradingScaleService = gradingScaleService;
this.programmingExerciseCodeReviewFeedbackService = programmingExerciseCodeReviewFeedbackService;
this.textExerciseFeedbackService = textExerciseFeedbackService;
this.modelingExerciseFeedbackService = modelingExerciseFeedbackService;
}

/**
Expand Down Expand Up @@ -363,7 +369,7 @@ public ResponseEntity<StudentParticipation> requestFeedback(@PathVariable Long e

Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId);

if (!(exercise instanceof TextExercise) && !(exercise instanceof ProgrammingExercise)) {
if (exercise instanceof QuizExercise || exercise instanceof FileUploadExercise) {
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
throw new BadRequestAlertException("Unsupported exercise type", "participation", "unsupported type");
}

Expand Down Expand Up @@ -393,7 +399,7 @@ private ResponseEntity<StudentParticipation> handleExerciseFeedbackRequest(Exerc
participation = studentParticipationRepository.findByIdWithResultsElseThrow(participation.getId());

// Check submission requirements
if (exercise instanceof TextExercise) {
if (exercise instanceof TextExercise || exercise instanceof ModelingExercise) {
if (submissionRepository.findAllByParticipationId(participation.getId()).isEmpty()) {
throw new BadRequestAlertException("You need to submit at least once", "participation", "preconditions not met");
}
Expand All @@ -416,6 +422,9 @@ else if (exercise instanceof ProgrammingExercise) {
if (exercise instanceof TextExercise) {
updatedParticipation = textExerciseFeedbackService.handleNonGradedFeedbackRequest(exercise.getId(), participation, (TextExercise) exercise);
}
else if (exercise instanceof ModelingExercise) {
updatedParticipation = modelingExerciseFeedbackService.handleNonGradedFeedbackRequest(participation, (ModelingExercise) exercise);
}
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
else {
updatedParticipation = programmingExerciseCodeReviewFeedbackService.handleNonGradedFeedbackRequest(exercise.getId(),
(ProgrammingExerciseStudentParticipation) participation, (ProgrammingExercise) exercise);
Expand Down
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package de.tum.cit.aet.artemis.modeling.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.assessment.domain.AssessmentType;
import de.tum.cit.aet.artemis.assessment.domain.Feedback;
import de.tum.cit.aet.artemis.assessment.domain.FeedbackType;
import de.tum.cit.aet.artemis.assessment.domain.Result;
import de.tum.cit.aet.artemis.assessment.repository.ResultRepository;
import de.tum.cit.aet.artemis.assessment.service.ResultService;
import de.tum.cit.aet.artemis.assessment.web.ResultWebsocketService;
import de.tum.cit.aet.artemis.athena.dto.ModelingFeedbackDTO;
import de.tum.cit.aet.artemis.athena.service.AthenaFeedbackSuggestionsService;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException;
import de.tum.cit.aet.artemis.core.exception.NetworkingException;
import de.tum.cit.aet.artemis.exercise.domain.Submission;
import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation;
import de.tum.cit.aet.artemis.exercise.service.ParticipationService;
import de.tum.cit.aet.artemis.exercise.service.SubmissionService;
import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise;
import de.tum.cit.aet.artemis.modeling.domain.ModelingSubmission;

@Profile(PROFILE_CORE)
@Service
public class ModelingExerciseFeedbackService {

private static final Logger log = LoggerFactory.getLogger(ModelingExerciseFeedbackService.class);

private final Optional<AthenaFeedbackSuggestionsService> athenaFeedbackSuggestionsService;

private final ResultWebsocketService resultWebsocketService;

private final SubmissionService submissionService;

private final ParticipationService participationService;

private final ResultService resultService;

private final ResultRepository resultRepository;

public ModelingExerciseFeedbackService(Optional<AthenaFeedbackSuggestionsService> athenaFeedbackSuggestionsService, SubmissionService submissionService,
ResultService resultService, ResultRepository resultRepository, ResultWebsocketService resultWebsocketService, ParticipationService participationService) {
this.athenaFeedbackSuggestionsService = athenaFeedbackSuggestionsService;
this.submissionService = submissionService;
this.resultService = resultService;
this.resultRepository = resultRepository;
this.resultWebsocketService = resultWebsocketService;
this.participationService = participationService;
}

private void checkRateLimitOrThrow(StudentParticipation participation) {

List<Result> athenaResults = participation.getResults().stream().filter(result -> result.getAssessmentType() == AssessmentType.AUTOMATIC_ATHENA).toList();

if (athenaResults.size() >= 10) {
throw new BadRequestAlertException("Maximum number of AI feedback requests reached.", "participation", "preconditions not met");
}
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Handles the request for generating feedback for a modeling exercise.
* Unlike programming exercises a tutor is not notified if Athena is not available.
*
* @param participation the student participation associated with the exercise.
* @param modelingExercise the modeling exercise object.
* @return StudentParticipation updated modeling exercise for an AI assessment
*/
public StudentParticipation handleNonGradedFeedbackRequest(StudentParticipation participation, ModelingExercise modelingExercise) {
if (this.athenaFeedbackSuggestionsService.isPresent()) {
this.checkRateLimitOrThrow(participation);
CompletableFuture.runAsync(() -> this.generateAutomaticNonGradedFeedback(participation, modelingExercise));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle exceptions in asynchronous execution.

When running generateAutomaticNonGradedFeedback asynchronously using CompletableFuture.runAsync, any exceptions thrown within the asynchronous task may not be properly handled, potentially leading to unobserved exceptions. Consider adding exception handling within the asynchronous block to ensure that errors are logged and managed appropriately.

You can modify the code as follows:

CompletableFuture.runAsync(() -> {
    try {
        this.generateAutomaticNonGradedFeedback(participation, modelingExercise);
    } catch (Exception e) {
        log.error("Error generating automatic non-graded feedback", e);
    }
});

}
return participation;
}

/**
* Generates automatic non-graded feedback for a modeling exercise submission.
* This method leverages the Athena service to generate feedback based on the latest submission.
*
* @param participation the student participation associated with the exercise.
* @param modelingExercise the modeling exercise object.
*/
public void generateAutomaticNonGradedFeedback(StudentParticipation participation, ModelingExercise modelingExercise) {
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
log.debug("Using athena to generate (modeling exercise) feedback request: {}", modelingExercise.getId());

Optional<Submission> submissionOptional = participationService.findExerciseParticipationWithLatestSubmissionAndResultElseThrow(participation.getId())
.findLatestSubmission();

if (submissionOptional.isEmpty()) {
throw new BadRequestAlertException("No legal submissions found", "submission", "noSubmission");
}
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved

Submission submission = submissionOptional.get();

Result automaticResult = createInitialResult(participation, submission);

try {
this.resultWebsocketService.broadcastNewResult(participation, automaticResult);
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved

log.debug("Submission id: {}", submission.getId());

List<Feedback> feedbacks = getAthenaFeedback(modelingExercise, (ModelingSubmission) submission);

double totalFeedbackScore = calculateTotalFeedbackScore(feedbacks, modelingExercise);

automaticResult.setCompletionDate(ZonedDateTime.now());
automaticResult.setScore(Math.max(0, Math.min(totalFeedbackScore, 100)));
automaticResult.setSuccessful(true);
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved

automaticResult = this.resultRepository.save(automaticResult);
resultService.storeFeedbackInResult(automaticResult, feedbacks, true);
submissionService.saveNewResult(submission, automaticResult);
this.resultWebsocketService.broadcastNewResult(participation, automaticResult);
}
catch (Exception e) {
log.error("Could not generate feedback for exercise ID: {} and participation ID: {}", modelingExercise.getId(), participation.getId(), e);
throw new InternalServerErrorException("Something went wrong... AI Feedback could not be generated");
}
}

/**
* Creates an initial Result object for the automatic feedback.
*
* @param participation the student participation
* @param submission the submission to which the result is associated
* @return the initial Result object
*/
private Result createInitialResult(StudentParticipation participation, Submission submission) {
Result result = new Result();
result.setAssessmentType(AssessmentType.AUTOMATIC_ATHENA);
result.setRated(true);
result.setScore(0.0);
result.setSuccessful(null);
result.setSubmission(submission);
result.setParticipation(participation);
return result;
}

/**
* Retrieves feedback from the Athena service.
*
* @param modelingExercise the modeling exercise
* @param submission the modeling submission
* @return a list of Feedback objects
* @throws NetworkingException if there's a problem communicating with Athena
*/
private List<Feedback> getAthenaFeedback(ModelingExercise modelingExercise, ModelingSubmission submission) throws NetworkingException {
return this.athenaFeedbackSuggestionsService.orElseThrow().getModelingFeedbackSuggestions(modelingExercise, submission, false).stream()
.filter(feedbackItem -> feedbackItem.description() != null).map(this::convertToFeedback).toList();
}

/**
* Converts an Athena feedback suggestion to a Feedback object.
*
* @param feedbackItem the Athena feedback suggestion
* @return the Feedback object
*/
private Feedback convertToFeedback(ModelingFeedbackDTO feedbackItem) {
Feedback feedback = new Feedback();
feedback.setText(feedbackItem.title());
feedback.setDetailText(feedbackItem.description());
feedback.setHasLongFeedbackText(false);
feedback.setType(FeedbackType.AUTOMATIC);
feedback.setCredits(feedbackItem.credits());
return feedback;
}

/**
* Calculates the total feedback score based on the list of feedbacks and the exercise's max points.
*
* @param feedbacks the list of feedbacks
* @param modelingExercise the modeling exercise
* @return the total feedback score
*/
private double calculateTotalFeedbackScore(List<Feedback> feedbacks, ModelingExercise modelingExercise) {
double totalCredits = feedbacks.stream().mapToDouble(Feedback::getCredits).sum();
Double maxPoints = modelingExercise.getMaxPoints();

if (maxPoints == null || maxPoints == 0) {
throw new IllegalArgumentException("Maximum points must be greater than zero.");
}

return (totalCredits / maxPoints) * 100;
}
LeonWehrhahn marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,19 @@ public ModelingSubmission handleModelingSubmission(ModelingSubmission modelingSu
throw new AccessForbiddenException();
}

// remove result from submission (in the unlikely case it is passed here), so that students cannot inject a result
modelingSubmission.setResults(new ArrayList<>());

maximiliansoelch marked this conversation as resolved.
Show resolved Hide resolved
// update submission properties
// NOTE: from now on we always set submitted to true to prevent problems here! Except for late submissions of course exercises to prevent issues in auto-save
if (exercise.isExamExercise() || exerciseDateService.isBeforeDueDate(participation)) {
modelingSubmission.setSubmitted(true);
}

// if athena results are present, then create a new submission on submit
if (modelingSubmission.getParticipation() != null && modelingSubmission.getParticipation().getResults() != null
&& !modelingSubmission.getParticipation().getResults().isEmpty()) {
log.debug("Creating a new submission due to Athena results for user: {}", user.getLogin());
modelingSubmission.setId(null);
}

modelingSubmission = save(modelingSubmission, exercise, user, participation);
return modelingSubmission;
}
Expand Down
Loading
Loading