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

Iris: Add Artemis Intelligence rewrite action for FAQs #10157

Open
wants to merge 33 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
33 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
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
7418566
Fixed tests
cremertim Jan 17, 2025
23d0830
Prettier
cremertim Jan 17, 2025
76fb2a1
add RewriteRequest
cremertim Jan 17, 2025
fbd97b2
Merge branch 'develop' into feature/communication/rephrasing-pipeline
cremertim 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
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,61 @@
package de.tum.cit.aet.artemis.communication.web.conversation;

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.beans.factory.annotation.Value;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
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.iris.service.IrisRephrasingService;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing.RephrasingVariant;

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

@Value("${jhipster.clientApp.name}")
private String applicationName;
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved

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

private static final String ENTITY_NAME = "rephrasing";

private final UserRepository userRepository;

private final CourseRepository courseRepository;

private final Optional<IrisRephrasingService> irisRephrasingService;

public RephrasingResource(UserRepository userRepository, CourseRepository courseRepository, Optional<IrisRephrasingService> irisRephrasingService) {
this.userRepository = userRepository;
this.courseRepository = courseRepository;
this.irisRephrasingService = irisRephrasingService;

}

@PostMapping("courses/{courseId}/rephrase-text")
public ResponseEntity<Void> rephraseText(@RequestParam String toBeRephrased, @RequestParam RephrasingVariant variant, @PathVariable Long courseId) {
var rephrasingService = irisRephrasingService.orElseThrow();
var user = userRepository.getUserWithGroupsAndAuthorities();
var course = courseRepository.findByIdElseThrow(courseId);
rephrasingService.executeRephrasingPipeline(user, course, variant, toBeRephrased);
log.debug("REST request to rephrase text: {}", toBeRephrased);
return ResponseEntity.ok().build();
}
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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.rephrasing.PyrisRephrasingPipelineExecutionDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing.PyrisRephrasingStatusUpdateDTO;
import de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing.RephrasingVariant;
import de.tum.cit.aet.artemis.iris.service.pyris.job.RephrasingJob;
import de.tum.cit.aet.artemis.iris.service.websocket.IrisWebsocketService;

/**
* Service to handle the Competency generation subsystem of Iris.
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved
*/
@Service
@Profile(PROFILE_IRIS)
public class IrisRephrasingService {

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 IrisRephrasingService(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 competency extraction pipeline on Pyris for a given course, user and course description
*
* @param user the user for which the pipeline should be executed
* @param course the course for which the pipeline should be executed
* @param toBeRephrased the description of the course
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved
*/
public void executeRephrasingPipeline(User user, Course course, RephrasingVariant variant, String toBeRephrased) {
// @formatter:off
pyrisPipelineService.executePipeline(
"rephrasing",
variant.toString(),
Optional.empty(),
pyrisJobService.createTokenForJob(token -> new RephrasingJob(token, course.getId(), user.getId())),
executionDto -> new PyrisRephrasingPipelineExecutionDTO(executionDto, toBeRephrased),
stages -> websocketService.send(user.getLogin(), websocketTopic(course.getId()), new PyrisRephrasingStatusUpdateDTO(stages, null, null))
);
// @formatter:on
}
cremertim marked this conversation as resolved.
Show resolved Hide resolved

/**
* Takes a status update from Pyris containing a new competency extraction result and sends it to the client via websocket
*
* @param job Job related to the status update
* @param statusUpdate the status update containing the new competency recommendations
* @return the same job that was passed in
*/
public RephrasingJob handleStatusUpdate(RephrasingJob job, PyrisRephrasingStatusUpdateDTO 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 "rephrasing/" + courseId;
}

}
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.IrisRephrasingService;
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.rephrasing.PyrisRephrasingStatusUpdateDTO;
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.RephrasingJob;
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 IrisRephrasingService rephrasingService;

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

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

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

/**
* Handles the status update of a competency extraction job and forwards it to
* {@link IrisCompetencyGenerationService#handleStatusUpdate(CompetencyExtractionJob, PyrisCompetencyStatusUpdateDTO)}
*
* @param job the job that is updated
* @param statusUpdate the status update
*/
cremertim marked this conversation as resolved.
Show resolved Hide resolved
public void handleStatusUpdate(RephrasingJob job, PyrisRephrasingStatusUpdateDTO statusUpdate) {
var updatedJob = rephrasingService.handleStatusUpdate(job, statusUpdate);
removeJobIfTerminatedElseUpdate(statusUpdate.stages(), updatedJob);
}
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
@@ -0,0 +1,16 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* DTO for the Iris rephrasing feature.
* A rephrasing is just a text and determines the variant of the rephrasing.
*
* @param text The rephrased text
* @param variant The variant of the rephrasing
*
*
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PyrisRephrasingDTO(String text, RephrasingVariant 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.rephrasing;

import com.fasterxml.jackson.annotation.JsonInclude;

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

/**
* DTO to execute the Iris competency extraction pipeline on Pyris
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved
*
* @param execution The pipeline execution details
* @param toBeRephrased The text to be rephrased
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record PyrisRephrasingPipelineExecutionDTO(PyrisPipelineExecutionDTO execution, String toBeRephrased) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing;

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 rephrasing feature.
* Pyris sends callback updates back to Artemis during rephrasing of the text. These updates contain the current status of the rephrasing 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 rephrasing 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 PyrisRephrasingStatusUpdateDTO(List<PyrisStageDTO> stages, String result, List<LLMRequest> tokens) {
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.tum.cit.aet.artemis.iris.service.pyris.dto.rephrasing;

public enum RephrasingVariant {
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 that rephrases a text.
*
* @param jobId the job id
* @param courseId the course in which the rephrasing is being done
* @param userId the user who started the job
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record RephrasingJob(String jobId, long courseId, long userId) implements PyrisJob {

@Override
public boolean canAccess(Course course) {
return course.getId().equals(courseId);
}
FelixTJDietrich marked this conversation as resolved.
Show resolved Hide resolved

}
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.rephrasing.PyrisRephrasingStatusUpdateDTO;
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.RephrasingJob;
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/rephrasing/runs/:runId/status : Send the rephrased 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/rephrasing/runs/{runId}/status")
@EnforceNothing
public ResponseEntity<Void> setRephrasingJobStatus(@PathVariable String runId, @RequestBody PyrisRephrasingStatusUpdateDTO statusUpdateDTO, HttpServletRequest request) {
var job = pyrisJobService.getAndAuthenticateJobFromHeaderElseThrow(request, RephrasingJob.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
1 change: 1 addition & 0 deletions src/main/webapp/app/faq/faq-update.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ <h2 id="jhi-faq-heading" jhiTranslate="artemisApp.faq.home.createOrEditLabel"></
id="field_description"
class="markdown-editor"
[domainActions]="domainActionsDescription"
[metaActions]="metaActions"
[markdown]="faq.questionAnswer"
(markdownChange)="handleMarkdownChange($event)"
/>
Expand Down
Loading
Loading