Skip to content

Communication: Add first Iris Tutor Suggestion Interface #10666

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

Open
wants to merge 50 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4823709
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Mar 17, 2025
2c2409f
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Mar 18, 2025
632601c
First connection to Iris including DTOs & sessions for suggestions
alexjoham Mar 19, 2025
2f6eeae
Add TutorSuggestionJob Callback update
alexjoham Mar 26, 2025
1100dd2
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Mar 26, 2025
8a8d6b1
Add tutor suggestion Iris settings
alexjoham Mar 26, 2025
136c1e0
Fetch settings and check for tutor suggestion enabled
alexjoham Mar 26, 2025
99dc452
Add automated suggestion creation, send post as json to Iris
alexjoham Mar 29, 2025
efb15ce
Add first tests, update dtos
alexjoham Apr 5, 2025
300342b
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 5, 2025
83147a6
Update chat service to not save JSON in history
alexjoham Apr 6, 2025
20cd8c0
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 7, 2025
2850d26
Add first callback UI and update tests
alexjoham Apr 7, 2025
8281751
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 8, 2025
f5e1b07
Fix client changes after develop merge
alexjoham Apr 8, 2025
a1eb62f
Add check if user is at least tutor, fix external llm usage test, upd…
alexjoham Apr 8, 2025
5c5c7cd
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 9, 2025
7e1902f
Update suggestion UI
alexjoham Apr 10, 2025
e297361
Update session integration test
alexjoham Apr 10, 2025
90284d0
Finish TutorSuggestionService
alexjoham Apr 10, 2025
092ac38
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 10, 2025
52aa20a
Update JavaDoc comments
alexjoham Apr 11, 2025
8e63731
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 11, 2025
6b49658
Fix Tests
alexjoham Apr 12, 2025
37b6a87
Add missing JavaDoc
alexjoham Apr 13, 2025
74ab340
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 14, 2025
5510a2b
Increase test coverage for IrisTutorSuggestion
alexjoham Apr 14, 2025
26cd047
Merge remote-tracking branch 'origin/feature/iris/iris-tutor-suggesti…
alexjoham Apr 14, 2025
eef8e9e
Small changes based on coderabbit feedback
alexjoham Apr 14, 2025
8911a3e
Move AccountService test to conversation-thread-component
alexjoham Apr 14, 2025
61af444
Increase test coverage and fix small issues
alexjoham Apr 14, 2025
5d0c637
Fix failing tests
alexjoham Apr 14, 2025
839523a
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 14, 2025
98ed716
Minor changes, more tests
alexjoham Apr 15, 2025
d9a27c3
Don't show tutor suggestions for resolved posts
alexjoham Apr 15, 2025
47395f1
Remove unused elements, fix ng error
alexjoham Apr 15, 2025
04fe012
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 15, 2025
5c5fc87
Fetch only if no suggestion exists
alexjoham Apr 18, 2025
b7a868e
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 18, 2025
332b56d
Fix testCreateTutorSuggestionShouldProcessMessageAndReturnResponse test
alexjoham Apr 19, 2025
402e034
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 19, 2025
8168014
Add new test testExecuteTutorSuggestionPipelineShouldSendStatusUpdates
alexjoham Apr 19, 2025
9409180
Add AnswerPost to test to increase coverage
alexjoham Apr 19, 2025
3b5200f
Add new test
alexjoham Apr 19, 2025
1290783
Fix client tests
alexjoham Apr 19, 2025
f9228a8
Move all logic into tutor suggestion component, make course not optional
alexjoham Apr 20, 2025
2900c68
Update error handling
alexjoham Apr 21, 2025
3ef3a76
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 21, 2025
ae1738f
add darkmode for the component
alexjoham Apr 21, 2025
a6d9eaf
Merge branch 'develop' into feature/iris/iris-tutor-suggestions-commu…
alexjoham Apr 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
Expand Up @@ -7,9 +7,11 @@
* <p>
* LLM: A message generated by a large language model
* <p>
* TUT_SUG: A message send by the tutor suggestion system
* <p>
* Note: The ARTEMIS message sender variant was removed, as its original intent was
* based on an incomplete understanding of the requirements of the Iris subsystem.
*/
public enum IrisMessageSender {
USER, LLM
USER, LLM, TUT_SUG
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
@JsonSubTypes.Type(value = IrisExerciseChatSession.class, name = "chat"), // TODO: Legacy. Should ideally be "exercise_chat"
@JsonSubTypes.Type(value = IrisCourseChatSession.class, name = "course_chat"),
@JsonSubTypes.Type(value = IrisLectureChatSession.class, name = "lecture_chat"),
@JsonSubTypes.Type(value = IrisTutorSuggestionSession.class, name = "tutor_suggestion"),
})
// @formatter:on
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.tum.cit.aet.artemis.iris.domain.session;

import java.util.Optional;

import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.communication.domain.Post;
import de.tum.cit.aet.artemis.core.domain.User;

/**
* An IrisTutorSuggestionSession represents a conversation between a user and an LLM in the context of a tutor suggestion.
* This is used for tutors receiving assistance from Iris for answering student questions.
*/
@Entity
@DiscriminatorValue("TUTOR_SUGGESTION")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class IrisTutorSuggestionSession extends IrisChatSession {

@ManyToOne
@JsonIgnore
private Post post;

public IrisTutorSuggestionSession(Post post, User user) {
super(user);
this.post = post;
}

public IrisTutorSuggestionSession() {
}

public Post getPost() {
return post;
}

public void setPost(Post post) {
this.post = post;
}

@Override
public String toString() {
return "IrisTutorSuggestionSession{" + "user=" + Optional.ofNullable(getUser()).map(User::getLogin).orElse("null") + "," + "post=" + post + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public class IrisCourseSettings extends IrisSettings {
@JoinColumn(name = "iris_competency_generation_settings_id")
private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "iris_tutor_suggestion_settings_id")
private IrisTutorSuggestionSubSettings irisTutorSuggestionSettings;

public Course getCourse() {
return course;
}
Expand Down Expand Up @@ -129,4 +133,14 @@ public IrisFaqIngestionSubSettings getIrisFaqIngestionSettings() {
public void setIrisFaqIngestionSettings(IrisFaqIngestionSubSettings irisFaqIngestionSubSettings) {
this.irisFaqIngestionSettings = irisFaqIngestionSubSettings;
}

@Override
public IrisTutorSuggestionSubSettings getIrisTutorSuggestionSettings() {
return irisTutorSuggestionSettings;
}

@Override
public void setIrisTutorSuggestionSettings(IrisTutorSuggestionSubSettings irisTutorSuggestionSubSettings) {
this.irisTutorSuggestionSettings = irisTutorSuggestionSubSettings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,15 @@ public void setIrisFaqIngestionSettings(IrisFaqIngestionSubSettings irisFaqInges
// Empty because exercises don't have exercise faq settings

}

@Override
public IrisTutorSuggestionSubSettings getIrisTutorSuggestionSettings() {
// Empty because exercises don't have exercise tutor suggestion settings
return null;
}

@Override
public void setIrisTutorSuggestionSettings(IrisTutorSuggestionSubSettings irisTutorSuggestionSubSettings) {
// Empty because exercises don't have exercise tutor suggestion settings
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public class IrisGlobalSettings extends IrisSettings {
@JoinColumn(name = "iris_competency_generation_settings_id")
private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "iris_tutor_suggestion_settings_id")
private IrisTutorSuggestionSubSettings irisTutorSuggestionSubSettings;

@Override
public IrisLectureIngestionSubSettings getIrisLectureIngestionSettings() {
return irisLectureIngestionSettings;
Expand Down Expand Up @@ -117,4 +121,15 @@ public void setIrisFaqIngestionSettings(IrisFaqIngestionSubSettings irisFaqInges
this.irisFaqIngestionSubSettings = irisFaqIngestionSubSettings;

}

@Override
public IrisTutorSuggestionSubSettings getIrisTutorSuggestionSettings() {
return irisTutorSuggestionSubSettings;
}

@Override
public void setIrisTutorSuggestionSettings(IrisTutorSuggestionSubSettings irisTutorSuggestionSubSettings) {
this.irisTutorSuggestionSubSettings = irisTutorSuggestionSubSettings;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ public abstract class IrisSettings extends DomainObject {

public abstract void setIrisFaqIngestionSettings(IrisFaqIngestionSubSettings irisFaqIngestionSubSettings);

public abstract IrisTutorSuggestionSubSettings getIrisTutorSuggestionSettings();

public abstract void setIrisTutorSuggestionSettings(IrisTutorSuggestionSubSettings irisTutorSuggestionSubSettings);

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
@JsonSubTypes.Type(value = IrisCompetencyGenerationSubSettings.class, name = "competency-generation"),
@JsonSubTypes.Type(value = IrisLectureChatSubSettings.class, name = "lecture-chat"),
@JsonSubTypes.Type(value = IrisFaqIngestionSubSettings.class, name = "faq-ingestion"),
@JsonSubTypes.Type(value = IrisTutorSuggestionSubSettings.class, name = "tutor-suggestion"),
})
// @formatter:on
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public enum IrisSubSettingsType {
CHAT, // TODO: Rename to PROGRAMMING_EXERCISE_CHAT
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION, LECTURE_CHAT, FAQ_INGESTION
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION, LECTURE_CHAT, FAQ_INGESTION, TUTOR_SUGGESTION
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.tum.cit.aet.artemis.iris.domain.settings;

import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* An {@link IrisSubSettings} implementation for the settings for tutor suggestions.
*/
@Entity
@DiscriminatorValue("TUTOR_SUGGESTION")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class IrisTutorSuggestionSubSettings extends IrisSubSettings {

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public record IrisCombinedSettingsDTO(
IrisCombinedLectureIngestionSubSettingsDTO irisLectureIngestionSettings,
IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings,
IrisCombinedLectureChatSubSettingsDTO irisLectureChatSettings,
IrisCombinedFaqIngestionSubSettingsDTO irisFaqIngestionSettings
IrisCombinedFaqIngestionSubSettingsDTO irisFaqIngestionSettings,
IrisCombinedTutorSuggestionSubSettingsDTO irisTutorSuggestionSettings
) {}
// @formatter:on
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.tum.cit.aet.artemis.iris.dto;

import java.util.Set;

import jakarta.annotation.Nullable;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* Data transfer object for the IrisCombinedTutorSuggestionSubSettings.
*
* @param enabled true if settings are enabled
* @param allowedVariants a set of allowed variants
* @param selectedVariant the selected variant
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record IrisCombinedTutorSuggestionSubSettingsDTO(boolean enabled, @Nullable Set<String> allowedVariants, @Nullable String selectedVariant) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package de.tum.cit.aet.artemis.iris.repository;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;
import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD;

import java.util.Collections;
import java.util.List;

import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.stereotype.Repository;

import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository;
import de.tum.cit.aet.artemis.iris.domain.session.IrisTutorSuggestionSession;

@Repository
@Profile(PROFILE_IRIS)
public interface IrisTutorSuggestionSessionRepository extends ArtemisJpaRepository<IrisTutorSuggestionSession, Long> {

List<IrisTutorSuggestionSession> findByPostIdAndUserIdOrderByCreationDateDesc(Long postId, Long userId, Pageable pageable);

@EntityGraph(type = LOAD, attributePaths = "messages")
List<IrisTutorSuggestionSession> findSessionsWithMessagesByIdIn(List<Long> ids);

/**
* Finds the latest sessions for the given post and user with messages.
*
* @param postId the id of the post
* @param userId the id of the user
* @param pageable the pageable to use for the query
* @return the latest sessions for the given post and user with messages
*/
default List<IrisTutorSuggestionSession> findLatestSessionsByPostIdAndUserIdWithMessages(Long postId, Long userId, Pageable pageable) {
List<Long> ids = findByPostIdAndUserIdOrderByCreationDateDesc(postId, userId, pageable).stream().map(DomainObject::getId).toList();

if (ids.isEmpty()) {
return Collections.emptyList();
}

return findSessionsWithMessagesByIdIn(ids);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import de.tum.cit.aet.artemis.iris.domain.session.IrisLectureChatSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisTextExerciseChatSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisTutorSuggestionSession;
import de.tum.cit.aet.artemis.iris.service.session.IrisChatBasedFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisExerciseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisLectureChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisRateLimitedFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisSubFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisTextExerciseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisTutorSuggestionSessionService;

/**
* Service for managing Iris sessions.
Expand All @@ -42,14 +44,17 @@ public class IrisSessionService {

private final IrisLectureChatSessionService irisLectureChatSessionService;

private final IrisTutorSuggestionSessionService irisTutorSuggestionSessionService;

public IrisSessionService(UserRepository userRepository, IrisTextExerciseChatSessionService irisTextExerciseChatSessionService,
IrisExerciseChatSessionService irisExerciseChatSessionService, IrisCourseChatSessionService irisCourseChatSessionService,
IrisLectureChatSessionService irisLectureChatSessionService) {
IrisLectureChatSessionService irisLectureChatSessionService, IrisTutorSuggestionSessionService irisTutorSuggestionSessionService) {
this.userRepository = userRepository;
this.irisTextExerciseChatSessionService = irisTextExerciseChatSessionService;
this.irisExerciseChatSessionService = irisExerciseChatSessionService;
this.irisCourseChatSessionService = irisCourseChatSessionService;
this.irisLectureChatSessionService = irisLectureChatSessionService;
this.irisTutorSuggestionSessionService = irisTutorSuggestionSessionService;
}

/**
Expand All @@ -74,8 +79,10 @@ public void checkHasAccessToIrisSession(IrisSession session, @Nullable User user
if (user == null) {
user = userRepository.getUserWithGroupsAndAuthorities();
}
user.hasAcceptedExternalLLMUsageElseThrow();
var wrapper = getIrisSessionSubService(session);
if (!(wrapper.irisSession instanceof IrisTutorSuggestionSession)) {
user.hasAcceptedExternalLLMUsageElseThrow();
}
wrapper.irisSubFeatureInterface.checkHasAccessTo(user, wrapper.irisSession);
}

Expand Down Expand Up @@ -146,6 +153,8 @@ private <S extends IrisSession> IrisSubFeatureWrapper<S> getIrisSessionSubServic
case IrisExerciseChatSession chatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisExerciseChatSessionService, chatSession);
case IrisCourseChatSession courseChatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisCourseChatSessionService, courseChatSession);
case IrisLectureChatSession lectureChatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisLectureChatSessionService, lectureChatSession);
case IrisTutorSuggestionSession tutorSuggestionSession ->
(IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisTutorSuggestionSessionService, tutorSuggestionSession);
case null, default -> throw new BadRequestException("Unknown Iris session type " + session.getClass().getSimpleName());
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import de.tum.cit.aet.artemis.iris.service.pyris.job.LectureIngestionWebhookJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.PyrisJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.TranscriptionIngestionWebhookJob;
import de.tum.cit.aet.artemis.iris.service.pyris.job.TutorSuggestionJob;

/**
* The PyrisJobService class is responsible for managing Pyris jobs in the Artemis system.
Expand Down Expand Up @@ -96,6 +97,21 @@ public String addCourseChatJob(Long courseId, Long sessionId) {
return token;
}

/**
* adds a tutor suggestion job to the job map
*
* @param postId Id of the post the suggestion is created for
* @param courseId Id of the course the post belongs to
* @param sessionId Id of the session the suggestion is created for
* @return the token of the job
*/
public String addTutorSuggestionJob(Long postId, Long courseId, Long sessionId) {
var token = generateJobIdToken();
var job = new TutorSuggestionJob(token, postId, courseId, sessionId, null);
jobMap.put(token, job);
return token;
}

/**
* Adds a new lecture ingestion webhook job to the job map with a timeout.
*
Expand Down
Loading
Loading