Skip to content

Commit

Permalink
Merge branch 'develop' into feature/athena/popup-accept-external-llm
Browse files Browse the repository at this point in the history
  • Loading branch information
maximiliansoelch authored Jan 31, 2025
2 parents cbf3364 + 6d861a9 commit fd9ef36
Show file tree
Hide file tree
Showing 131 changed files with 2,388 additions and 857 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
name: Coverage Report Server Tests
path: build/reports/jacoco/test/html
- name: Append Per-Module Coverage to Job Summary
if: success() || failure()
if: success()
run: |
AGGREGATED_REPORT_FILE=./module_coverage_report.md
python3 ./supporting_scripts/code-coverage/per_module_cov_report/parse_module_coverage.py build/reports/jacoco $AGGREGATED_REPORT_FILE
Expand Down
8 changes: 8 additions & 0 deletions docs/user/exercises/programming-exercise-features.inc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Instructors can still use those templates to generate programming exercises and
+----------------------+----------+---------+
| Bash | yes | yes |
+----------------------+----------+---------+
| MATLAB | yes | no |
+----------------------+----------+---------+
| Ruby | yes | yes |
+----------------------+----------+---------+

- Not all ``templates`` support the same feature set and supported features can also change depending on the continuous integration system setup.
Depending on the feature set, some options might not be available during the creation of the programming exercise.
Expand Down Expand Up @@ -95,6 +99,10 @@ Instructors can still use those templates to generate programming exercises and
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| Bash | no | no | no | no | n/a | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| MATLAB | no | no | no | no | n/a | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+
| Ruby | no | no | no | no | n/a | no | L: yes, J: no |
+----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+

- *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand.
- *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools.
Expand Down
6 changes: 3 additions & 3 deletions gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ ext {
"CLASS": 1
],
"iris" : [
"INSTRUCTION": 0.792,
"CLASS": 17
"INSTRUCTION": 0.775,
"CLASS": 22
],
"lecture" : [
"INSTRUCTION": 0.867,
Expand Down Expand Up @@ -83,10 +83,10 @@ ext {
? ModuleCoverageThresholds.collect {element -> element.key}
: includedModules as ArrayList

// we want to ignore some generated files in the domain folders
ignoredDirectories = [
"**/$BasePath/**/domain/**/*_*",
"**/$BasePath/core/config/migration/entries/**",
"**/org/gradle/**",
"**/gradle-wrapper.jar/**"
]
}
Expand Down
539 changes: 266 additions & 273 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
"node_modules"
],
"dependencies": {
"@angular/animations": "19.1.3",
"@angular/cdk": "19.1.1",
"@angular/common": "19.1.3",
"@angular/compiler": "19.1.3",
"@angular/core": "19.1.3",
"@angular/forms": "19.1.3",
"@angular/localize": "19.1.3",
"@angular/material": "19.1.1",
"@angular/platform-browser": "19.1.3",
"@angular/platform-browser-dynamic": "19.1.3",
"@angular/router": "19.1.3",
"@angular/service-worker": "19.1.3",
"@angular/animations": "19.1.4",
"@angular/cdk": "19.1.2",
"@angular/common": "19.1.4",
"@angular/compiler": "19.1.4",
"@angular/core": "19.1.4",
"@angular/forms": "19.1.4",
"@angular/localize": "19.1.4",
"@angular/material": "19.1.2",
"@angular/platform-browser": "19.1.4",
"@angular/platform-browser-dynamic": "19.1.4",
"@angular/router": "19.1.4",
"@angular/service-worker": "19.1.4",
"@ctrl/ngx-emoji-mart": "9.2.0",
"@danielmoncada/angular-datetime-picker": "19.1.0",
"@fingerprintjs/fingerprintjs": "4.5.1",
Expand All @@ -36,7 +36,7 @@
"@ng-bootstrap/ng-bootstrap": "18.0.0",
"@ngx-translate/core": "16.0.4",
"@ngx-translate/http-loader": "16.0.1",
"@sentry/angular": "8.51.0",
"@sentry/angular": "8.52.1",
"@siemens/ngx-datatable": "22.4.1",
"@swimlane/ngx-charts": "21.1.3",
"@swimlane/ngx-graph": "9.0.1",
Expand All @@ -48,7 +48,7 @@
"crypto-js": "4.2.0",
"dayjs": "1.11.13",
"diff-match-patch-typescript": "1.1.0",
"dompurify": "3.2.3",
"dompurify": "3.2.4",
"emoji-js": "3.8.1",
"export-to-csv": "1.4.0",
"fast-json-patch": "3.1.1",
Expand All @@ -68,7 +68,7 @@
"ngx-infinite-scroll": "19.0.0",
"ngx-webstorage": "19.0.1",
"pako": "2.1.0",
"papaparse": "5.5.1",
"papaparse": "5.5.2",
"pdf-lib": "1.17.1",
"pdfjs-dist": "4.10.38",
"rxjs": "7.8.1",
Expand Down Expand Up @@ -138,11 +138,11 @@
"@angular-eslint/eslint-plugin-template": "19.0.2",
"@angular-eslint/schematics": "19.0.2",
"@angular-eslint/template-parser": "19.0.2",
"@angular/build": "19.1.4",
"@angular/cli": "19.1.4",
"@angular/compiler-cli": "19.1.3",
"@angular/language-service": "19.1.3",
"@sentry/types": "8.51.0",
"@angular/build": "19.1.5",
"@angular/cli": "19.1.5",
"@angular/compiler-cli": "19.1.4",
"@angular/language-service": "19.1.4",
"@sentry/types": "8.52.1",
"@testing-library/angular": "17.3.5",
"@types/crypto-js": "4.2.2",
"@types/d3-shape": "3.1.7",
Expand All @@ -151,7 +151,7 @@
"@types/jest": "29.5.14",
"@types/lodash-es": "4.17.12",
"@types/markdown-it": "14.1.2",
"@types/node": "22.10.10",
"@types/node": "22.12.0",
"@types/pako": "2.0.3",
"@types/papaparse": "5.3.15",
"@types/smoothscroll-polyfill": "0.3.4",
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/de/tum/cit/aet/artemis/ArtemisApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
import org.springframework.boot.info.GitProperties;
import org.springframework.core.env.Environment;

import de.tum.cit.aet.artemis.core.config.LicenseConfiguration;
import de.tum.cit.aet.artemis.core.config.ProgrammingLanguageConfiguration;
import de.tum.cit.aet.artemis.core.config.TheiaConfiguration;
import tech.jhipster.config.DefaultProfileUtil;
import tech.jhipster.config.JHipsterConstants;

@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class })
@EnableConfigurationProperties({ LiquibaseProperties.class, ProgrammingLanguageConfiguration.class, TheiaConfiguration.class, LicenseConfiguration.class })
public class ArtemisApp {

private static final Logger log = LoggerFactory.getLogger(ArtemisApp.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public CreateContainerResponse configureContainer(String containerName, String i
// container from exiting until it finishes.
// It waits until the script that is running the tests (see below execCreateCmdResponse) is completed, and until the result files are extracted which is indicated
// by the creation of a file "stop_container.txt" in the container's root directory.
.withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done")
.withEntrypoint().withCmd("sh", "-c", "while [ ! -f " + LOCALCI_WORKING_DIRECTORY + "/stop_container.txt ]; do sleep 0.5; done")
// .withCmd("tail", "-f", "/dev/null") // Activate for debugging purposes instead of the above command to get a running container that you can peek into using
// "docker exec -it <container-id> /bin/bash".
.exec();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.tum.cit.aet.artemis.core.config;

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

import jakarta.annotation.Nullable;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Profile;

@Profile(PROFILE_CORE)
@ConfigurationProperties(prefix = "artemis.licenses")
public class LicenseConfiguration {

private final MatLabLicense matlab;

public record MatLabLicense(String licenseServer) {
}

public LicenseConfiguration(MatLabLicense matlab) {
this.matlab = matlab;
}

@Nullable
public String getMatlabLicenseServer() {
if (matlab == null) {
return null;
}
return matlab.licenseServer();
}
}
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) {
}
Loading

0 comments on commit fd9ef36

Please sign in to comment.