Skip to content

Commit

Permalink
Iris: Enhance student support with proactive assistance (#9558)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaancayli authored Dec 10, 2024
1 parent b44dafa commit cae4fb5
Show file tree
Hide file tree
Showing 53 changed files with 1,581 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository;
import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException;
import de.tum.cit.aet.artemis.core.repository.UserRepository;
import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.pyris.PyrisEventService;
import de.tum.cit.aet.artemis.iris.service.pyris.event.CompetencyJolSetEvent;

/**
* Service Implementation for managing CompetencyJol.
Expand All @@ -44,15 +45,15 @@ public class CompetencyJolService {

private final UserRepository userRepository;

private final Optional<IrisCourseChatSessionService> irisCourseChatSessionService;
private final Optional<PyrisEventService> pyrisEventService;

public CompetencyJolService(CompetencyJolRepository competencyJolRepository, CompetencyRepository competencyRepository,
CompetencyProgressRepository competencyProgressRepository, UserRepository userRepository, Optional<IrisCourseChatSessionService> irisCourseChatSessionService) {
CompetencyProgressRepository competencyProgressRepository, UserRepository userRepository, Optional<PyrisEventService> pyrisEventService) {
this.competencyJolRepository = competencyJolRepository;
this.competencyRepository = competencyRepository;
this.competencyProgressRepository = competencyProgressRepository;
this.userRepository = userRepository;
this.irisCourseChatSessionService = irisCourseChatSessionService;
this.pyrisEventService = pyrisEventService;
}

/**
Expand Down Expand Up @@ -83,10 +84,10 @@ public void setJudgementOfLearning(long competencyId, long userId, short jolValu
final var jol = createCompetencyJol(competencyId, userId, jolValue, ZonedDateTime.now(), competencyProgress);
competencyJolRepository.save(jol);

irisCourseChatSessionService.ifPresent(service -> {
pyrisEventService.ifPresent(service -> {
// Inform Iris so it can send a message to the user
try {
service.onJudgementOfLearningSet(jol);
service.trigger(new CompetencyJolSetEvent(jol));
}
catch (Exception e) {
log.warn("Something went wrong while sending the judgement of learning to Iris", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ public interface SubmissionRepository extends ArtemisJpaRepository<Submission, L
@EntityGraph(type = LOAD, attributePaths = { "results", "results.assessor" })
List<Submission> findAllWithResultsAndAssessorByParticipationId(Long participationId);

/**
* Get all submissions of a participation and eagerly load results ordered by submission date in ascending order
*
* @param participationId the id of the participation
* @return a list of the participation's submissions
*/
@EntityGraph(type = LOAD, attributePaths = { "results" })
List<Submission> findAllWithResultsByParticipationIdOrderBySubmissionDateAsc(Long participationId);

/**
* Get all submissions with their results by the submission ids
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public class IrisChatSubSettings extends IrisSubSettings {
@Convert(converter = IrisListConverter.class)
private SortedSet<String> enabledForCategories = new TreeSet<>();

@Column(name = "disabled_proactive_events", nullable = false)
@Convert(converter = IrisListConverter.class)
private SortedSet<String> disabledProactiveEvents = new TreeSet<>();

@Nullable
public Integer getRateLimit() {
return rateLimit;
Expand All @@ -57,4 +61,12 @@ public SortedSet<String> getEnabledForCategories() {
public void setEnabledForCategories(SortedSet<String> enabledForCategories) {
this.enabledForCategories = enabledForCategories;
}

public SortedSet<String> getDisabledProactiveEvents() {
return disabledProactiveEvents;
}

public void setDisabledProactiveEvents(SortedSet<String> disabledProactiveEvents) {
this.disabledProactiveEvents = disabledProactiveEvents;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.tum.cit.aet.artemis.iris.domain.settings.event;

/**
* The type of event that can be triggered by the Iris system.
*/
public enum IrisEventType {

BUILD_FAILED, PROGRESS_STALLED, JOL
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record IrisCombinedChatSubSettingsDTO(boolean enabled, Integer rateLimit, Integer rateLimitTimeframeHours, @Nullable SortedSet<String> allowedVariants,
@Nullable String selectedVariant, @Nullable SortedSet<String> enabledForCategories) {
@Nullable String selectedVariant, @Nullable SortedSet<String> enabledForCategories, @Nullable SortedSet<String> disabledProactiveEvents) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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;

Expand Down Expand Up @@ -62,6 +64,7 @@ public void executeCompetencyExtractionPipeline(User user, Course course, String
pyrisPipelineService.executePipeline(
"competency-extraction",
"default",
Optional.empty(),
pyrisJobService.createTokenForJob(token -> new CompetencyExtractionJob(token, course.getId(), user.getId())),
executionDto -> new PyrisCompetencyExtractionPipelineExecutionDTO(executionDto, courseDescription, currentCompetencies, CompetencyTaxonomy.values(), 5),
stages -> websocketService.send(user.getLogin(), websocketTopic(course.getId()), new PyrisCompetencyStatusUpdateDTO(stages, null, null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -73,13 +74,13 @@ public List<PyrisVariantDTO> getOfferedVariants(IrisSubSettingsType feature) thr
try {
var response = restTemplate.getForEntity(pyrisUrl + "/api/v1/pipelines/" + feature.name() + "/variants", PyrisVariantDTO[].class);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new PyrisConnectorException("Could not fetch offered models");
throw new PyrisConnectorException("Could not fetch offered variants");
}
return Arrays.asList(response.getBody());
}
catch (HttpStatusCodeException e) {
log.error("Failed to fetch offered models from Pyris", e);
throw new PyrisConnectorException("Could not fetch offered models");
log.error("Failed to fetch offered variants from Pyris", e);
throw new PyrisConnectorException("Could not fetch offered variants");
}
}

Expand All @@ -89,9 +90,12 @@ public List<PyrisVariantDTO> getOfferedVariants(IrisSubSettingsType feature) thr
* @param feature The feature name of the pipeline to execute
* @param variant The variant of the feature to execute
* @param executionDTO The DTO sent as a body for the execution
* @param event The event to be sent as a query parameter, if the pipeline is getting executed due to an event
*/
public void executePipeline(String feature, String variant, Object executionDTO) {
public void executePipeline(String feature, String variant, Object executionDTO, Optional<String> event) {
var endpoint = "/api/v1/pipelines/" + feature + "/" + variant + "/run";
// Add event query parameter if present
endpoint += event.map(e -> "?event=" + e).orElse("");
try {
restTemplate.postForEntity(pyrisUrl + endpoint, objectMapper.valueToTree(executionDTO), Void.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.tum.cit.aet.artemis.iris.service.pyris;

/**
* Exception thrown when an error occurs during Pyris event processing.
*/
public class PyrisEventProcessingException extends RuntimeException {

public PyrisEventProcessingException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package de.tum.cit.aet.artemis.iris.service.pyris;

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

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.iris.domain.session.IrisChatSession;
import de.tum.cit.aet.artemis.iris.service.pyris.event.CompetencyJolSetEvent;
import de.tum.cit.aet.artemis.iris.service.pyris.event.NewResultEvent;
import de.tum.cit.aet.artemis.iris.service.pyris.event.PyrisEvent;
import de.tum.cit.aet.artemis.iris.service.session.AbstractIrisChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisExerciseChatSessionService;

/**
* Service to handle Pyris events.
*/
@Service
@Profile(PROFILE_IRIS)
public class PyrisEventService {

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

private final IrisCourseChatSessionService irisCourseChatSessionService;

private final IrisExerciseChatSessionService irisExerciseChatSessionService;

public PyrisEventService(IrisCourseChatSessionService irisCourseChatSessionService, IrisExerciseChatSessionService irisExerciseChatSessionService) {
this.irisCourseChatSessionService = irisCourseChatSessionService;
this.irisExerciseChatSessionService = irisExerciseChatSessionService;
}

/**
* Triggers a Pyris pipeline based on the received {@link PyrisEvent}.
*
* @param event The event object received to trigger the matching pipeline
* @throws UnsupportedPyrisEventException if the event is not supported
*
* @see PyrisEvent
*/
public void trigger(PyrisEvent<? extends AbstractIrisChatSessionService<? extends IrisChatSession>, ?> event) {
log.debug("Starting to process event of type: {}", event.getClass().getSimpleName());
try {
switch (event) {
case CompetencyJolSetEvent competencyJolSetEvent -> {
log.info("Processing CompetencyJolSetEvent: {}", competencyJolSetEvent);
competencyJolSetEvent.handleEvent(irisCourseChatSessionService);
log.debug("Successfully processed CompetencyJolSetEvent");
}
case NewResultEvent newResultEvent -> {
log.info("Processing NewResultEvent: {}", newResultEvent);
newResultEvent.handleEvent(irisExerciseChatSessionService);
log.debug("Successfully processed NewResultEvent");
}
default -> throw new UnsupportedPyrisEventException("Unsupported event type: " + event.getClass().getSimpleName());
}
}
catch (Exception e) {
log.error("Failed to process event: {}", event, e);
throw e;
}
}
}
Loading

0 comments on commit cae4fb5

Please sign in to comment.