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

Text exercises: Send feedback to Athena for continous in-context-learning #10134

Draft
wants to merge 23 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
687a6b5
Allow instructors to send feedback to athena for continuous in contex…
Jan 12, 2025
5103e8e
Allow instructors to send feedback to athena for continuous in contex…
Jan 12, 2025
57069a9
Allow instructors to send feedback to athena for continuous in contex…
Jan 12, 2025
740bd4b
Merge branch 'develop' into feature/text-exercises/send-feedback-to-a…
EneaGore Jan 12, 2025
90121ac
Add Translations and refactor
Jan 12, 2025
0666c8c
Refactor assessment submission.
Jan 12, 2025
52feceb
optional param after required
Jan 12, 2025
506b59c
SubmitWithAthena not And
EneaGore Jan 12, 2025
b614197
set Send Feedback to Athena Flag to false by default
Jan 13, 2025
4be22d9
Adjust tests
Jan 13, 2025
3c7ab55
Refactor text submission assessment
Jan 13, 2025
ce90cef
Add flag for whether feedback should be used by Athena to learn or on…
Jan 14, 2025
d58678a
Adapt DTO
Jan 14, 2025
e8006bc
Improve Java Docs
Jan 14, 2025
6d22be8
Merge branch 'develop' into feature/text-exercises/send-feedback-to-a…
EneaGore Jan 14, 2025
d9b7ed8
Adjust athena endpoints selection
Jan 15, 2025
31883ac
Merge branch 'feature/text-exercises/send-feedback-to-athena' of http…
Jan 15, 2025
96fbbf7
Try new approach
Jan 15, 2025
8fb5fc3
Refactor
Jan 15, 2025
2cec193
set isGraded to false for student requests
Jan 15, 2025
47c4be7
Merge branch 'develop' into feature/text-exercises/send-feedback-to-a…
EneaGore Jan 19, 2025
9f1fa93
Merge branch 'develop' into feature/text-exercises/send-feedback-to-a…
EneaGore Jan 19, 2025
2aea6c7
Merge branch 'develop' into feature/text-exercises/send-feedback-to-a…
EneaGore Jan 23, 2025
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 @@ -66,7 +66,20 @@ private record ResponseDTO(String data) {
*/
@Async
public void sendFeedback(Exercise exercise, Submission submission, List<Feedback> feedbacks) {
sendFeedback(exercise, submission, feedbacks, 1);
sendFeedback(exercise, submission, feedbacks, 1, "/feedbacks");
}

/**
* Calls the remote Athena service to submit feedback given by a tutor
* We send the feedback asynchronously because it's not important for the user => quicker response
*
* @param exercise the exercise the feedback is given for
* @param submission the submission the feedback is given for
* @param feedbacks the feedback given by the tutor
*/
@Async
public void sendFeedbackWithICL(Exercise exercise, Submission submission, List<Feedback> feedbacks) {
sendFeedback(exercise, submission, feedbacks, 1, "/feed_feedbacks");
}

/**
Expand All @@ -77,9 +90,10 @@ public void sendFeedback(Exercise exercise, Submission submission, List<Feedback
* @param submission the submission the feedback is given for
* @param feedbacks the feedback given by the tutor
* @param maxRetries number of retries before the request will be canceled
* @param endpoint the endpoint to send to
*/
@Async
public void sendFeedback(Exercise exercise, Submission submission, List<Feedback> feedbacks, int maxRetries) {
public void sendFeedback(Exercise exercise, Submission submission, List<Feedback> feedbacks, int maxRetries, String endpoint) {
if (!exercise.areFeedbackSuggestionsEnabled()) {
throw new IllegalArgumentException("The exercise does not have feedback suggestions enabled.");
}
Expand All @@ -97,7 +111,7 @@ public void sendFeedback(Exercise exercise, Submission submission, List<Feedback
// Only send manual feedback from tutors to Athena
final RequestDTO request = new RequestDTO(athenaDTOConverterService.ofExercise(exercise), athenaDTOConverterService.ofSubmission(exercise.getId(), submission),
feedbacks.stream().filter(Feedback::isManualFeedback).map((feedback) -> athenaDTOConverterService.ofFeedback(exercise, submission.getId(), feedback)).toList());
ResponseDTO response = connector.invokeWithRetry(athenaModuleService.getAthenaModuleUrl(exercise) + "/feedbacks", request, maxRetries);
ResponseDTO response = connector.invokeWithRetry(athenaModuleService.getAthenaModuleUrl(exercise) + endpoint, request, maxRetries);
log.info("Athena responded to feedback: {}", response.data);
}
catch (NetworkingException networkingException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,16 @@ public ResponseEntity<Void> deleteTextExampleAssessment(@PathVariable long exerc
* POST participations/:participationId/results/:resultId/submit-text-assessment : Submits manual textAssessments for a given result
* and notify the user if it's before the Assessment Due Date
*
* @param participationId the participationId of the participation whose assessment shall be saved
* @param resultId the resultId the assessment belongs to
* @param textAssessment the assessments which should be submitted
* @param participationId the participationId of the participation whose assessment shall be saved
* @param resultId the resultId the assessment belongs to
* @param textAssessment the assessments which should be submitted
* @param useForContinuousLearning whether the feedback should be used by the llm to learn
* @return 200 Ok if successful with the corresponding result as a body, but sensitive information are filtered out
*/
@PostMapping("participations/{participationId}/results/{resultId}/submit-text-assessment")
@EnforceAtLeastTutor
public ResponseEntity<Result> submitTextAssessment(@PathVariable Long participationId, @PathVariable Long resultId, @RequestBody TextAssessmentDTO textAssessment) {
public ResponseEntity<Result> submitTextAssessment(@PathVariable Long participationId, @PathVariable Long resultId, @RequestBody TextAssessmentDTO textAssessment,
@RequestParam(defaultValue = "false") boolean sendFeedback) {
final boolean hasAssessmentWithTooLongReference = textAssessment.getFeedbacks().stream().filter(Feedback::hasReference)
.anyMatch(feedback -> feedback.getReference().length() > Feedback.MAX_REFERENCE_LENGTH);
if (hasAssessmentWithTooLongReference) {
Expand All @@ -262,9 +264,13 @@ else if (!result.getParticipation().getId().equals(participationId)) {
if (response.getStatusCode().is2xxSuccessful()) {
final var feedbacksWithIds = response.getBody().getFeedbacks();
saveTextBlocks(textAssessment.getTextBlocks(), textSubmission, feedbacksWithIds);
sendFeedbackToAthena(exercise, textSubmission, feedbacksWithIds);
if (sendFeedback) {
sendFeedbackToAthenaWithICL(exercise, textSubmission, feedbacksWithIds);
}
else {
sendFeedbackToAthena(exercise, textSubmission, feedbacksWithIds);
}
}

return response;
}

Expand Down Expand Up @@ -522,6 +528,17 @@ private void saveTextBlocks(final Set<TextBlock> textBlocks, final TextSubmissio
private void sendFeedbackToAthena(final TextExercise exercise, final TextSubmission textSubmission, final List<Feedback> feedbacks) {
if (athenaFeedbackSendingService.isPresent() && exercise.areFeedbackSuggestionsEnabled()) {
athenaFeedbackSendingService.get().sendFeedback(exercise, textSubmission, feedbacks);

}
}

/**
* Send feedback to Athena with ICL (if enabled for both the Artemis instance and the exercise).
*/
private void sendFeedbackToAthenaWithICL(final TextExercise exercise, final TextSubmission textSubmission, final List<Feedback> feedbacks) {
if (athenaFeedbackSendingService.isPresent() && exercise.areFeedbackSuggestionsEnabled()) {
athenaFeedbackSendingService.get().sendFeedbackWithICL(exercise, textSubmission, feedbacks);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ <h3 class="top-container flex-wrap flex-lg-nowrap">
}
<span jhiTranslate="entity.action.submit"></span>
</button>
@if (exercise && exercise.type === ExerciseType.TEXT && exercise.allowFeedbackRequests) {
<button class="btn m-1 btn-secondary" (click)="sendToAthena.emit()" [disabled]="submitDisabled">
@if (submitBusy) {
<fa-icon [icon]="faSpinner" animation="spin" />
}
<span jhiTranslate="entity.action.submitWithAthena"></span>
</button>
}
@if (!isTestRun) {
<button
class="btn m-1 btn-danger"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class AssessmentHeaderComponent {
@Output() nextSubmission = new EventEmitter<void>();
@Output() highlightDifferencesChange = new EventEmitter<boolean>();
@Output() useAsExampleSubmission = new EventEmitter<void>();
@Output() sendToAthena = new EventEmitter<void>();

private _highlightDifferences: boolean;
readonly ExerciseType = ExerciseType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
(save)="save.emit()"
(onSubmit)="onSubmit.emit()"
(onCancel)="onCancel.emit()"
(sendToAthena)="sendToAthena.emit()"
[(highlightDifferences)]="highlightDifferences"
(nextSubmission)="nextSubmission.emit()"
[hasComplaint]="!!complaint"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ export class AssessmentLayoutComponent {
@Output() updateAssessmentAfterComplaint = new EventEmitter<AssessmentAfterComplaint>();
@Output() highlightDifferencesChange = new EventEmitter<boolean>();
@Output() useAsExampleSubmission = new EventEmitter<void>();
@Output() sendToAthena = new EventEmitter<void>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,34 @@ export class TextAssessmentService {
* @param assessmentNote of the result
*/
public submit(participationId: number, resultId: number, feedbacks: Feedback[], textBlocks: TextBlock[], assessmentNote?: string): Observable<EntityResponseType> {
return this.submitFeedback(participationId, resultId, feedbacks, textBlocks, false, assessmentNote);
}

/**
* Submits the passed feedback items of the assessment and sends feedback to Athena.
* @param participationId the assessed submission was made to of type {number}
* @param resultId of the corresponding result of type {number}
* @param feedbacks made during assessment of type {Feedback[]}
* @param textBlocks of type {TextBlock[]}
* @param assessmentNote of the result
*/
public submitWithAthena(participationId: number, resultId: number, feedbacks: Feedback[], textBlocks: TextBlock[], assessmentNote?: string): Observable<EntityResponseType> {
return this.submitFeedback(participationId, resultId, feedbacks, textBlocks, true, assessmentNote);
}

private submitFeedback(
participationId: number,
resultId: number,
feedbacks: Feedback[],
textBlocks: TextBlock[],
sendFeedback: boolean,
assessmentNote?: string,
): Observable<EntityResponseType> {
const body = TextAssessmentService.prepareFeedbacksAndTextblocksForRequest(feedbacks, textBlocks, assessmentNote);
return this.http
.post<Result>(`${this.RESOURCE_URL}/participations/${participationId}/results/${resultId}/submit-text-assessment`, body, { observe: 'response' })
.post<Result>(`${this.RESOURCE_URL}/participations/${participationId}/results/${resultId}/submit-text-assessment?sendFeedback=${sendFeedback}`, body, {
observe: 'response',
})
.pipe(map((res: EntityResponseType) => this.convertResultEntityResponseTypeFromServer(res)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
(save)="save()"
(onSubmit)="submit()"
(onCancel)="cancel()"
(sendToAthena)="submitAndSendToAthena()"
(nextSubmission)="nextSubmission()"
[(highlightDifferences)]="highlightDifferences"
(updateAssessmentAfterComplaint)="updateAssessmentAfterComplaint($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export class TextSubmissionAssessmentComponent extends TextAssessmentBaseCompone
*/
submit(): void {
if (!this.result?.id) {
return; // We need to have saved the result before
return;
}

if (!this.assessmentsAreValid) {
Expand All @@ -394,6 +394,28 @@ export class TextSubmissionAssessmentComponent extends TextAssessmentBaseCompone
});
}

/**
* Submit the assessment and Send to Athena for llm learning
*/
submitAndSendToAthena(): void {
if (!this.result?.id) {
return;
}

if (!this.assessmentsAreValid) {
this.alertService.error('artemisApp.textAssessment.error.invalidAssessments');
return;
}

this.submitBusy = true;
this.assessmentsService
.submitWithAthena(this.participation!.id!, this.result!.id!, this.assessments, this.textBlocksWithFeedback, this.result!.assessmentNote?.note)
.subscribe({
next: (response) => this.handleSaveOrSubmitSuccessWithAlert(response, 'artemisApp.textAssessment.submitSuccessful'),
error: (error: HttpErrorResponse) => this.handleError(error),
});
}

protected handleSaveOrSubmitSuccessWithAlert(response: HttpResponse<Result>, translationKey: string): void {
super.handleSaveOrSubmitSuccessWithAlert(response, translationKey);
this.result = response.body!;
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
"purge": "Löschen",
"archive": "Archivieren",
"submit": "Absenden",
"submitWithAthena": "Absenden Und An Athena Schicken",
"continueSubmission": "Weitermachen",
"viewTimeline": "Verlauf Anzeigen",
"hideTimeline": "Verlauf Ausblenden",
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
"purge": "Purge",
"archive": "Archive",
"submit": "Submit",
"submitWithAthena": "Submit And Send To Athena",
"continueSubmission": "Continue",
"viewTimeline": "View Timeline",
"hideTimeline": "Hide Timeline",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('TextAssessment Service', () => {
.pipe(take(1))
.subscribe((resp) => (actualResponse = resp.body));
const req = httpMock.expectOne({
url: `api/participations/${1}/results/${6}/submit-text-assessment`,
url: `api/participations/${1}/results/${6}/submit-text-assessment?sendFeedback=false`,
method: 'POST',
});
req.flush(result);
Expand Down
Binary file not shown.
Loading