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

Programming exercises: Add Artemis intelligence rewriting for problem statement #10156

Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
be1583a
Initial setup of new branch
cremertim Jan 16, 2025
8c467fa
initial setup finished
cremertim Jan 16, 2025
b06e720
alert Service
cremertim Jan 16, 2025
43e4e4e
add artemis intelligence dropdown for AI text editor actions
FelixTJDietrich Jan 16, 2025
8f45f69
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 16, 2025
e4c307a
remove currently test
cremertim Jan 16, 2025
a318d3f
Merge remote-tracking branch 'origin/feature/communication/rephrasing…
cremertim Jan 16, 2025
6875c4f
Added Felix Icon to resemble AI
cremertim Jan 16, 2025
ba2b5d1
Renamed everything to rewrite
cremertim Jan 16, 2025
c5b77a9
Updated comments and adressed changes from Felix
cremertim Jan 17, 2025
7e24015
fixed issues
cremertim Jan 17, 2025
fe6141e
fix some found issues directly
FelixTJDietrich Jan 17, 2025
e845b8d
fix too many arguments
FelixTJDietrich Jan 17, 2025
57b08c6
fix comment
FelixTJDietrich Jan 17, 2025
369e4b4
fix another comment
FelixTJDietrich Jan 17, 2025
e151990
Merge branch 'develop' into feature/communication/rephrasing-pipeline
FelixTJDietrich Jan 17, 2025
8ede345
reorganize translations and add artemis intelligence dropdown
FelixTJDietrich Jan 17, 2025
6ec2d01
changed to signal
cremertim Jan 17, 2025
06f5bd2
rename service and fix loading spinner and spamming
FelixTJDietrich Jan 17, 2025
f1c27a8
fix missed merge conflict
FelixTJDietrich Jan 17, 2025
446332f
merge faq rephrasing into current branch
FelixTJDietrich Jan 17, 2025
7418566
Fixed tests
cremertim Jan 17, 2025
23d0830
Prettier
cremertim Jan 17, 2025
3a7afa1
Merge remote-tracking branch 'origin/feature/communication/rephrasing…
FelixTJDietrich Jan 17, 2025
b745604
add artemis intelligence rewrite action to problem statement
FelixTJDietrich Jan 17, 2025
76fb2a1
add RewriteRequest
cremertim Jan 17, 2025
fbd97b2
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 20, 2025
d6ef320
fix double import
FelixTJDietrich Jan 20, 2025
4d562f6
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 20, 2025
7eb7409
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 21, 2025
aeeec63
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 21, 2025
9bd725b
Fixed client build
cremertim Jan 21, 2025
1fe0bac
Merge remote-tracking branch 'origin/feature/communication/rephrasing…
cremertim Jan 21, 2025
97a9f64
Rename TextRequestDTO
cremertim Jan 21, 2025
f4002b0
Include the @jsonInclude
cremertim Jan 21, 2025
10c89e8
Include the @jsonInclude for another one
cremertim Jan 21, 2025
ef89f62
Fix failing tests
cremertim Jan 21, 2025
cf5cae0
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 22, 2025
8a431b7
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim Jan 28, 2025
b3634eb
Merge branch 'feature/communication/rephrasing-pipeline' into feature…
FelixTJDietrich Jan 29, 2025
8484123
Rename RewritingResource
cremertim Jan 29, 2025
2daddff
Add random testcase for coverage
cremertim Jan 29, 2025
678b497
Merge branch 'feature/communication/rephrasing-pipeline' into feature…
FelixTJDietrich Jan 29, 2025
7c3251d
Merge branch 'develop' into feature/communication/rephrasing-pipeline
FelixTJDietrich Jan 29, 2025
ccb5fb6
remove unused translation
FelixTJDietrich Jan 29, 2025
f17f289
Merge branch 'feature/communication/rephrasing-pipeline' into feature…
FelixTJDietrich Jan 29, 2025
c76aa7c
Fix testcases
cremertim Jan 29, 2025
3cb8439
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
cremertim Jan 29, 2025
efaaa21
Fix testcases
cremertim Jan 29, 2025
5a5a9d8
Fix testcases
cremertim Jan 29, 2025
f8e9c67
Merge branch 'develop' into feature/programming-exercises/rephrase-ac…
FelixTJDietrich Jan 30, 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
@@ -0,0 +1,97 @@
package de.tum.cit.aet.artemis.iris.service;

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

import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.LLMServiceType;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.service.LLMTokenUsageService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisJobService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisPipelineService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingPipelineExecutionDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.RewritingVariant;
import de.tum.cit.aet.artemis.iris.service.pyris.job.RewritingJob;
import de.tum.cit.aet.artemis.iris.service.websocket.IrisWebsocketService;

/**
* Service to handle the rewriting subsystem of Iris.
*/
@Service
@Profile(PROFILE_IRIS)
public class IrisRewritingService {

private final PyrisPipelineService pyrisPipelineService;

private final LLMTokenUsageService llmTokenUsageService;

private final CourseRepository courseRepository;

private final IrisWebsocketService websocketService;

private final PyrisJobService pyrisJobService;

private final UserRepository userRepository;

public IrisRewritingService(PyrisPipelineService pyrisPipelineService, LLMTokenUsageService llmTokenUsageService, CourseRepository courseRepository,
IrisWebsocketService websocketService, PyrisJobService pyrisJobService, UserRepository userRepository) {
this.pyrisPipelineService = pyrisPipelineService;
this.llmTokenUsageService = llmTokenUsageService;
this.courseRepository = courseRepository;
this.websocketService = websocketService;
this.pyrisJobService = pyrisJobService;
this.userRepository = userRepository;
}

/**
* Executes the rewriting pipeline on Pyris
*
* @param user the user for which the pipeline should be executed
* @param course the course for which the pipeline should be executed
* @param variant the rewriting variant to be used
* @param toBeRewritten the text to be rewritten
*/
public void executeRewritingPipeline(User user, Course course, RewritingVariant variant, String toBeRewritten) {
// @formatter:off
pyrisPipelineService.executePipeline(
"rewriting",
variant.toString(),
Optional.empty(),
pyrisJobService.createTokenForJob(token -> new RewritingJob(token, course.getId(), user.getId())),
executionDto -> new PyrisRewritingPipelineExecutionDTO(executionDto, toBeRewritten),
stages -> websocketService.send(user.getLogin(), websocketTopic(course.getId()), new PyrisRewritingStatusUpdateDTO(stages, null, null))
);
// @formatter:on
}

/**
* Takes a status update from Pyris containing a new rewriting result and sends it to the client via websocket
*
* @param job Job related to the status update
* @param statusUpdate the status update containing text recommendations
* @return the same job that was passed in
*/
public RewritingJob handleStatusUpdate(RewritingJob job, PyrisRewritingStatusUpdateDTO statusUpdate) {
Course course = courseRepository.findByIdForUpdateElseThrow(job.courseId());
if (statusUpdate.tokens() != null && !statusUpdate.tokens().isEmpty()) {
llmTokenUsageService.saveLLMTokenUsage(statusUpdate.tokens(), LLMServiceType.IRIS, builder -> builder.withCourse(course.getId()).withUser(job.userId()));
}

var user = userRepository.findById(job.userId()).orElseThrow();
websocketService.send(user.getLogin(), websocketTopic(job.courseId()), statusUpdate);

return job;
}

private static String websocketTopic(long courseId) {
return "rewriting/" + courseId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public PyrisDTOService(GitService gitService, RepositoryService repositoryServic
public PyrisProgrammingExerciseDTO toPyrisProgrammingExerciseDTO(ProgrammingExercise exercise) {
var templateRepositoryContents = getFilteredRepositoryContents(exercise.getTemplateParticipation());
var solutionRepositoryContents = getFilteredRepositoryContents(exercise.getSolutionParticipation());

// var templateRepositoryContents = new HashMap<String, String>();
// var solutionRepositoryContents = new HashMap<String, String>();

Optional<Repository> testRepo = Optional.empty();
try {
testRepo = Optional.ofNullable(gitService.getOrCheckoutRepository(exercise.getVcsTestRepositoryUri(), true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
import org.springframework.stereotype.Service;

import de.tum.cit.aet.artemis.iris.service.IrisCompetencyGenerationService;
import de.tum.cit.aet.artemis.iris.service.IrisRewritingService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.chat.PyrisChatStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.chat.textexercise.PyrisTextExerciseChatStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.competency.PyrisCompetencyStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisLectureIngestionStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.status.PyrisStageDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.status.PyrisStageState;
import de.tum.cit.aet.artemis.iris.service.pyris.job.CompetencyExtractionJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.CourseChatJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.ExerciseChatJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.IngestionWebhookJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.PyrisJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.RewritingJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.TextExerciseChatJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.TrackedSessionBasedPyrisJob;
import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService;
Expand All @@ -41,16 +44,19 @@ public class PyrisStatusUpdateService {

private final IrisCompetencyGenerationService competencyGenerationService;

private final IrisRewritingService rewritingService;

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

public PyrisStatusUpdateService(PyrisJobService pyrisJobService, IrisExerciseChatSessionService irisExerciseChatSessionService,
IrisTextExerciseChatSessionService irisTextExerciseChatSessionService, IrisCourseChatSessionService courseChatSessionService,
IrisCompetencyGenerationService competencyGenerationService) {
IrisCompetencyGenerationService competencyGenerationService, IrisRewritingService rewritingService) {
this.pyrisJobService = pyrisJobService;
this.irisExerciseChatSessionService = irisExerciseChatSessionService;
this.irisTextExerciseChatSessionService = irisTextExerciseChatSessionService;
this.courseChatSessionService = courseChatSessionService;
this.competencyGenerationService = competencyGenerationService;
this.rewritingService = rewritingService;
}

/**
Expand Down Expand Up @@ -105,6 +111,18 @@ public void handleStatusUpdate(CompetencyExtractionJob job, PyrisCompetencyStatu
removeJobIfTerminatedElseUpdate(statusUpdate.stages(), updatedJob);
}

/**
* Handles the status update of a rewriting job and forwards it to
* {@link IrisRewritingService#handleStatusUpdate(RewritingJob, PyrisRewritingStatusUpdateDTO)}
*
* @param job the job that is updated
* @param statusUpdate the status update
*/
public void handleStatusUpdate(RewritingJob job, PyrisRewritingStatusUpdateDTO statusUpdate) {
var updatedJob = rewritingService.handleStatusUpdate(job, statusUpdate);
removeJobIfTerminatedElseUpdate(statusUpdate.stages(), updatedJob);
}

/**
* Removes the job from the job service if the status update indicates that the job is terminated; updates it to distribute changes otherwise.
* A job is terminated if all stages are in a terminal state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisPipelineExecutionDTO;

/**
* DTO to execute the Iris competency extraction pipeline on Pyris
* DTO to execute the Iris rewriting pipeline on Pyris
*
* @param execution The pipeline execution details
* @param courseDescription The description of the course
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.data;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.RewritingVariant;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PyrisRewriteTextRequestDTO(String toBeRewritten, RewritingVariant variant) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.iris.service.pyris.dto.PyrisPipelineExecutionDTO;

/**
* DTO to execute the Iris rewriting pipeline on Pyris
*
* @param execution The pipeline execution details
* @param toBeRewritten The text to be rewritten
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PyrisRewritingPipelineExecutionDTO(PyrisPipelineExecutionDTO execution, String toBeRewritten) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.core.domain.LLMRequest;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.status.PyrisStageDTO;

/**
* DTO for the Iris rewriting feature.
* Pyris sends callback updates back to Artemis during rewriting of the text. These updates contain the current status of the rewriting process,
* which are then forwarded to the user via Websockets.
*
* @param stages List of stages of the generation process
* @param result The result of the rewriting process so far
* @param tokens List of token usages send by Pyris for tracking the token usage and cost
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PyrisRewritingStatusUpdateDTO(List<PyrisStageDTO> stages, String result, List<LLMRequest> tokens) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting;

public enum RewritingVariant {
FAQ, PROBLEM_STATEMENT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.tum.cit.aet.artemis.iris.service.pyris.job;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.core.domain.Course;

/**
* A Pyris job to rewrite a text.
*
* @param jobId the job id
* @param courseId the course in which the rewriting is being done
* @param userId the user who started the job
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record RewritingJob(String jobId, long courseId, long userId) implements PyrisJob {

@Override
public boolean canAccess(Course course) {
return course.getId().equals(courseId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package de.tum.cit.aet.artemis.iris.web;

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

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import de.tum.cit.aet.artemis.core.repository.CourseRepository;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInCourse.EnforceAtLeastTutorInCourse;
import de.tum.cit.aet.artemis.iris.service.IrisRewritingService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.data.PyrisRewriteTextRequestDTO;

/**
* REST controller for managing Markdown Rewritings.
*/
@Profile(PROFILE_IRIS)
@RestController
@RequestMapping("api/")
public class IrisRewritingResource {

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

private static final String ENTITY_NAME = "rewriting";

private final UserRepository userRepository;

private final CourseRepository courseRepository;

private final Optional<IrisRewritingService> irisRewritingService;

public IrisRewritingResource(UserRepository userRepository, CourseRepository courseRepository, Optional<IrisRewritingService> irisRewritingService) {
this.userRepository = userRepository;
this.courseRepository = courseRepository;
this.irisRewritingService = irisRewritingService;

}

/**
* POST /courses/{courseId}/rewrite-text : Rewrite a given text.
*
* @param request the request containing the text to be rewritten and the corresponding variant
* @param courseId the id of the course
* @return the ResponseEntity with status 200 (OK)
*/
@EnforceAtLeastTutorInCourse
@PostMapping("courses/{courseId}/rewrite-text")
public ResponseEntity<Void> rewriteText(@RequestBody PyrisRewriteTextRequestDTO request, @PathVariable Long courseId) {
var rewritingService = irisRewritingService.orElseThrow();
var user = userRepository.getUserWithGroupsAndAuthorities();
var course = courseRepository.findByIdElseThrow(courseId);
rewritingService.executeRewritingPipeline(user, course, request.variant(), request.toBeRewritten());
log.debug("REST request to rewrite text: {}", request.toBeRewritten());
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
import de.tum.cit.aet.artemis.iris.service.pyris.dto.chat.textexercise.PyrisTextExerciseChatStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.competency.PyrisCompetencyStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.lectureingestionwebhook.PyrisLectureIngestionStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rewriting.PyrisRewritingStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.job.CompetencyExtractionJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.CourseChatJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.ExerciseChatJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.IngestionWebhookJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.PyrisJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.RewritingJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.TextExerciseChatJob;

/**
Expand Down Expand Up @@ -149,6 +151,31 @@ public ResponseEntity<Void> respondInTextExerciseChat(@PathVariable String runId
return ResponseEntity.ok().build();
}

/**
* POST public/pyris/pipelines/rewriting/runs/:runId/status : Send the rewritten text in a status update
* <p>
* Uses custom token based authentication.
*
* @param runId the ID of the job
* @param statusUpdateDTO the status update
* @param request the HTTP request
* @throws ConflictException if the run ID in the URL does not match the run ID in the request body
* @throws AccessForbiddenException if the token is invalid
* @return a {@link ResponseEntity} with status {@code 200 (OK)}
*/
@PostMapping("pipelines/rewriting/runs/{runId}/status")
@EnforceNothing
public ResponseEntity<Void> setRewritingJobStatus(@PathVariable String runId, @RequestBody PyrisRewritingStatusUpdateDTO statusUpdateDTO, HttpServletRequest request) {
var job = pyrisJobService.getAndAuthenticateJobFromHeaderElseThrow(request, RewritingJob.class);
if (!Objects.equals(job.jobId(), runId)) {
throw new ConflictException("Run ID in URL does not match run ID in request body", "Job", "runIdMismatch");
}

pyrisStatusUpdateService.handleStatusUpdate(job, statusUpdateDTO);

return ResponseEntity.ok().build();
}

/**
* {@code POST /api/public/pyris/webhooks/ingestion/runs/{runId}/status} : Set the status of an Ingestion job.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<jhi-markdown-editor-monaco
class="overflow-hidden flex-grow-1"
[domainActions]="domainActions"
[artemisIntelligenceActions]="artemisIntelligenceActions()"
[initialEditorHeight]="initialEditorHeight"
[useDefaultMarkdownEditorOptions]="false"
[enableResize]="enableResize"
Expand Down
Loading
Loading