Skip to content

Conversation

@laadvo
Copy link
Contributor

@laadvo laadvo commented Jan 26, 2026

Summary

This PR migrates the FAQ rewriting functionality from the Iris module to the Hyperion module to transition to SpringAI.

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines and the REST API guidelines.
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding guidelines.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.

Motivation and Context

This PR migrates the FAQ rewriting functionality from the Iris module to the Hyperion module. This move aligns the FAQ feature with existing Hyperion services, such as Problem Statement Generation and Problem Statement Rewrite, which served as the architectural inspiration for this migration.
We want to slowly move from LangChain to SpringAI. By integrating Spring AI directly, we remove the dependency on external middleware services.

Description

  • Migrated FAQ rewrite logic to hyperion, following the design patterns of existing problem statement services in the same module.
  • Replaced the external Pyris/LangChain dependency with Spring AI (ChatModel), allowing for native LLM interactions.
  • updated the openApi Gradle task configuration to include required profiles (localci, localvc, etc.). This resolved a LinkageError and ensured that the ApiService interfaces are correctly generated.
  • Generated several missing Tutorial Group related files using the openApi Gradle task

Steps for Testing

Prerequisites:

  1. Log in to Artemis
  2. Navigate to Course Management > FAQ
  3. Click the " + Create a new FAQ" button on the top right
  4. Add a question title and a (preferably) longer question answer
  5. In the Markdown Editor select Artemis Intelligence > Rewrite (second to last button)
  6. Make sure that the text is rewritten
  7. Create another FAQ with similar text but conflicting information (e.g. first FAQ says calculators are allowed in the exam and the second one say they are not)
  8. Rewrite the markdown text of the second FAQ
  9. Make sure the text is rewritten and an inconsistencies textbox appears below
  10. Make sure the inconsistencies pointed out make sense (e.g. first FAQ is referenced and the fact that the calculator info is inconsistent between the two)

Testserver States

You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.

Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Test Coverage

Client

Class/File Line Coverage Lines Expects Ratio
faq-update.component.ts 98.92% 165 22 13.3
api.base.service.ts not found (modified) 72 ? ?
hyperionFaqApi.service.ts not found (added) 69 ? ?
hyperionProblemStatementApi.service.ts not found (modified) 163 ? ?
tutorialGroupApi.service.ts not found (modified) 705 ? ?
tutorialGroupFreePeriodApi.service.ts not found (modified) 61 ? ?
tutorialGroupSessionApi.service.ts not found (modified) 61 ? ?
encoder.ts not found (modified) 29 ? ?
examSeat.ts not found (added) 15 ? ?
rewriteFaqRequest.ts not found (added) 3 ? ?
rewriteFaqResponse.ts not found (added) 6 ? ?
teachingAssistant.ts not found (added) 3 ? ?
tutorialGroup.ts not found (added) 11 ? ?
tutorialGroupDetailGroup.ts not found (added) 15 ? ?
tutorialGroupDetailSession.ts not found (added) 10 ? ?
tutorialGroupSessionRequest.ts not found (modified) 6 ? ?
tutorialGroupUpdate.ts not found (modified) 6 ? ?
query.params.ts not found (added) 102 ? ?
artemis-intelligence.service.ts 100.00% 66 86 130.3

Server

Class/File Line Coverage Lines
RewriteFaqRequestDTO.java 100.00% 9
RewriteFaqResponseDTO.java 100.00% 8
HyperionFaqRewriteService.java 89.06% 122
HyperionFaqResource.java 100.00% 35
IrisRewritingService.java not found (deleted) ?
PyrisStatusUpdateService.java 93.02% 105
PyrisRewriteTextRequestDTO.java not found (deleted) ?
PyrisRewritingPipelineExecutionDTO.java not found (deleted) ?
PyrisRewritingStatusUpdateDTO.java not found (deleted) ?
RewritingVariant.java not found (deleted) ?
RewritingJob.java not found (deleted) ?
IrisRewritingResource.java not found (deleted) ?
PyrisInternalStatusUpdateResource.java 77.08% 132

Last updated: 2026-01-27 13:54:52 UTC

Screenshots

FAQ before rewrite
image

Artemis Intelligence > Rewrite
image

FAQ after rewrite
image

FAQ inconsistencies textbox
image

Summary by CodeRabbit

  • New Features

    • Added intelligent FAQ rewriting with automatic consistency checking against existing FAQs
    • Expanded tutorial group management capabilities with new endpoints and improved data handling
  • Removed

    • Removed legacy FAQ rewriting system
  • Updates

    • Improved HTTP query parameter serialization handling
    • Updated OpenAPI client generation tooling

✏️ Tip: You can customize this high-level summary in your review settings.

@laadvo laadvo requested review from a team as code owners January 26, 2026 23:47
@laadvo laadvo requested a review from krusche as a code owner January 26, 2026 23:47
@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Jan 26, 2026
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module core Pull requests that affect the corresponding module iris Pull requests that affect the corresponding module hyperion labels Jan 26, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Walkthrough

Replaces Iris-based FAQ rewriting with a new Hyperion-based system. Introduces Java backend services (HyperionFaqRewriteService, HyperionFaqResource), TypeScript API clients, and prompt templates for FAQ rewriting and consistency checking. Removes Iris rewriting endpoints, services, and DTOs while migrating frontend components to use the new Hyperion API.

Changes

Cohort / File(s) Summary
Hyperion FAQ Backend Implementation
src/main/java/de/tum/cit/aet/artemis/hyperion/dto/RewriteFaqRequestDTO.java, src/main/java/de/tum/cit/aet/artemis/hyperion/dto/RewriteFaqResponseDTO.java, src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java, src/main/java/de/tum/cit/aet/artemis/hyperion/web/HyperionFaqResource.java
New DTOs define request/response contracts. HyperionFaqRewriteService orchestrates FAQ rewriting with ChatClient, validates input, performs consistency checks against existing FAQs, and returns parsed results. HyperionFaqResource exposes POST endpoint with Tutor authorization.
Iris Rewriting Removal
src/main/java/de/tum/cit/aet/artemis/iris/service/IrisRewritingService.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisStatusUpdateService.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/data/PyrisRewriteTextRequestDTO.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/rewriting/PyrisRewritingPipelineExecutionDTO.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/rewriting/PyrisRewritingStatusUpdateDTO.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/dto/rewriting/RewritingVariant.java, src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/job/RewritingJob.java, src/main/java/de/tum/cit/aet/artemis/iris/web/IrisRewritingResource.java, src/main/java/de/tum/cit/aet/artemis/iris/web/internal/PyrisInternalStatusUpdateResource.java
Removes Iris-based rewriting pipeline infrastructure including services, DTOs, job tracking, and REST endpoints. PyrisStatusUpdateService no longer handles RewritingJob status updates.
Hyperion Prompt Templates
src/main/resources/prompts/hyperion/faq_consistency_system.st, src/main/resources/prompts/hyperion/faq_consistency_user.st, src/main/resources/prompts/hyperion/rewrite_faq_system.st, src/main/resources/prompts/hyperion/rewrite_faq_user.st
New StringTemplate prompt files define system and user instructions for FAQ rewriting (proofreading, grammar, clarity) and consistency checking (conflict detection, suggestions).
Frontend FAQ Component Migration
src/main/webapp/app/communication/faq/faq-update.component.ts, src/main/webapp/app/communication/faq/faq-update.component.spec.ts
Replaces Iris feature toggle with Hyperion module feature check; updates artemisIntelligenceActions to conditionally include RewriteAction based on hyperionEnabled flag.
ArtemisIntelligenceService Refactoring
src/main/webapp/app/shared/monaco-editor/model/actions/artemis-intelligence/artemis-intelligence.service.ts, src/main/webapp/app/shared/monaco-editor/model/actions/artemis-intelligence/artemis-intelligence.service.spec.ts
Migrates from Iris HTTP/WebSocket-based rewriting to OpenAPI Hyperion services (HyperionFaqApiService, HyperionProblemStatementApiService); removes manual HTTP posting and WebSocket subscriptions; introduces computed isLoading combining rewrite and consistency states.
OpenAPI Base Service Updates
src/main/webapp/app/openapi/api.base.service.ts
Replaces HttpParams-based addToHttpParams with OpenApiHttpParams supporting QueryParamStyle (DeepObject, Json, Form, etc.) with explode and delimiter configuration; adds style-aware parameter serialization logic.
OpenAPI Generated Models & Services
src/main/webapp/app/openapi/.openapi-generator/FILES, src/main/webapp/app/openapi/.openapi-generator/VERSION, src/main/webapp/app/openapi/api/hyperionFaqApi.service.ts, src/main/webapp/app/openapi/model/rewriteFaqRequest.ts, src/main/webapp/app/openapi/model/rewriteFaqResponse.ts, src/main/webapp/app/openapi/model/examSeat.ts, src/main/webapp/app/openapi/model/teachingAssistant.ts, src/main/webapp/app/openapi/model/tutorialGroup.ts, src/main/webapp/app/openapi/model/tutorialGroupDetailGroup.ts, src/main/webapp/app/openapi/model/tutorialGroupDetailSession.ts
New auto-generated API service for FAQ rewriting with overloads supporting different Observable types. New model types for rewrite requests/responses and tutorial group entities. OpenAPI generator version bumped 7.17.0 → 7.19.0.
TutorialGroup API Expansion
src/main/webapp/app/openapi/api/tutorialGroupApi.service.ts, src/main/webapp/app/openapi/api/tutorialGroupFreePeriodApi.service.ts, src/main/webapp/app/openapi/api/tutorialGroupSessionApi.service.ts, src/main/webapp/app/openapi/model/tutorialGroupUpdate.ts, src/main/webapp/app/openapi/model/tutorialGroupSessionRequest.ts
Adds create, update, deregister, export, import, and query endpoints; introduces OpenApiHttpParams with Form-style query serialization; makes date/startTime/endTime optional in TutorialGroupSessionRequest; adds tutorialGroup field to TutorialGroupUpdate.
Query Parameter Utilities
src/main/webapp/app/openapi/query.params.ts, src/main/webapp/app/openapi/encoder.ts
New OpenApiHttpParams class supporting QueryParamStyle enum and configurable explode/delimiter for parameter serialization. New IdentityHttpParameterCodec for no-op encoding. Helper concatHttpParamsObject for merging objects with delimiters.
Backend Test Updates
src/test/java/de/tum/cit/aet/artemis/core/connector/IrisRequestMockProvider.java, src/test/java/de/tum/cit/aet/artemis/iris/architecture/IrisCodeStyleArchitectureTest.java, src/test/java/de/tum/cit/aet/artemis/iris/PyrisRewritingIntegrationTest.java
Removes mock methods for Iris rewriting pipeline; lowers dtoNameEndingThreshold from 5 to 4; deletes PyrisRewritingIntegrationTest.
New Integration Tests
src/test/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteServiceTest.java, src/test/java/de/tum/cit/aet/artemis/hyperion/resource/HyperionFaqRewriteResourceIntegrationTest.java
Tests for HyperionFaqRewriteService covering rewriting with/without inconsistencies, error handling, and JSON parsing; tests for HyperionFaqRewriteResource covering authorization and input validation.
Configuration
gradle/openapi.gradle
Appends localci and localvc to Spring profiles in customBootRun task.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HyperionFaqResource
    participant HyperionFaqRewriteService
    participant ChatClient
    participant FaqRepository
    participant TemplateService

    Client->>HyperionFaqResource: POST /api/hyperion/courses/{courseId}/faq/rewrite<br/>RewriteFaqRequest

    HyperionFaqResource->>HyperionFaqRewriteService: rewriteFaq(courseId, faqText)
    
    rect rgba(100, 150, 200, 0.5)
        Note over HyperionFaqRewriteService: Rewrite Phase
        HyperionFaqRewriteService->>TemplateService: Render rewrite prompts<br/>(system + user)
        TemplateService-->>HyperionFaqRewriteService: Prompts
        HyperionFaqRewriteService->>ChatClient: Send rewrite request
        ChatClient-->>HyperionFaqRewriteService: Rewritten text
    end

    rect rgba(150, 200, 150, 0.5)
        Note over HyperionFaqRewriteService: Consistency Check Phase
        HyperionFaqRewriteService->>FaqRepository: Fetch recent FAQs (max 10)
        FaqRepository-->>HyperionFaqRewriteService: FAQ list
        HyperionFaqRewriteService->>TemplateService: Render consistency check prompts<br/>(with existing FAQs + proposed text)
        TemplateService-->>HyperionFaqRewriteService: Prompts
        HyperionFaqRewriteService->>ChatClient: Send consistency check request
        ChatClient-->>HyperionFaqRewriteService: JSON response<br/>(inconsistencies, suggestions, improvement)
    end

    HyperionFaqRewriteService-->>HyperionFaqResource: RewriteFaqResponseDTO

    HyperionFaqResource-->>Client: 200 OK<br/>RewriteFaqResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: migrating FAQ rewrite functionality from Iris to Hyperion, which aligns with the comprehensive changeset scope.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/hyperion/move-iris-rewrite-to-hyperion

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@src/main/java/de/tum/cit/aet/artemis/hyperion/web/HyperionFaqResource.java`:
- Around line 44-49: The controller method rewriteFaq is missing `@Valid` on the
`@RequestBody` parameter and the DTO lacks `@NotBlank`, so add
javax.validation.Valid to the request parameter in
HyperionFaqResource.rewriteFaq(`@PathVariable` long courseId, `@Valid` `@RequestBody`
RewriteFaqRequestDTO request) and annotate the faqText field (or corresponding
getter) in RewriteFaqRequestDTO with javax.validation.constraints.NotBlank
(instead of or in addition to `@NotNull`) to enforce non-empty input at the
controller boundary, mirroring the validation used by
ProblemStatementRewriteRequestDTO; ensure necessary imports are added so Spring
validation triggers before calling HyperionFaqRewriteService.

In `@src/main/resources/prompts/hyperion/rewrite_faq_system.st`:
- Around line 7-13: Fix the typos and stray backslash in the prompt rules:
remove the literal backslash after "bullet points," rewrite rule 8 to a single
smooth sentence ("If someone inputs a very short text that does not resemble an
answer to a potential question, respond accordingly and point out that the input
is too short."), fix "please make. sure" to "please respond accordingly", and
generally smooth wording for rules 4–8 so they read as complete, grammatical
instructions while preserving the intended meaning and Markdown formatting.

In `@src/main/resources/prompts/hyperion/rewrite_faq_user.st`:
- Around line 1-5: The template placeholder is wrong: the rendering code
(HyperionFaqRewriteService.renderTemplate / HyperionFaqRewriteService.java
around the template usage) provides the FAQ content under the context key
faqText, but the template uses {{rewritten_text}}, so the model gets empty
input; update the template in rewrite_faq_user.st to use {{faqText}} (or match
whatever key is used by HyperionFaqRewriteService) so the FAQ input is passed
into the prompt.

In `@src/main/webapp/app/openapi/.openapi-generator/VERSION`:
- Line 1: The VERSION file under src/main/webapp/app/openapi/.openapi-generator
currently contains "7.19.0" but gradle/openapi.gradle pins the
openapi-generator-gradle-plugin to "7.14.0", causing mismatch; either update the
plugin version in gradle/openapi.gradle (openapi-generator-gradle-plugin) to
7.19.0 to match the VERSION file, or regenerate the OpenAPI client code using
the 7.14.0 generator and update
src/main/webapp/app/openapi/.openapi-generator/VERSION to 7.14.0 so both the
VERSION file and the plugin (openapi-generator-gradle-plugin) are aligned.

In `@src/main/webapp/app/openapi/query.params.ts`:
- Around line 80-120: The toString method is collapsing exploded arrays into a
single "key=a,b" instead of emitting repeated key=value pairs; update toString
to iterate the record produced by toRecord() and for each entry: if the value is
an array (from toRecord's exploded branch) push one `${key}=${value}` per array
element, otherwise push a single `${key}=${value}`; use the encoded keys/values
already produced by toRecord() so that QueryParams.toString() and
QueryParams.toRecord() remain consistent (refer to the toString, toRecord
methods and encoder.encodeKey/encodeValue usage).
- Around line 134-159: The deep-object serialization currently calls
convertToString on null/undefined which throws; fix by guarding against
null/undefined before converting and by making convertToString defensive: in
concatHttpParamsObject (function concatHttpParamsObject) skip any keys/values
that are null or undefined (for arrays, filter out null/undefined before
mapping) and only call convertToString for non-null values, and update
convertToString to handle null/undefined (e.g., return an empty string) as a
safety net.

In
`@src/main/webapp/app/shared/monaco-editor/model/actions/artemis-intelligence/artemis-intelligence.service.spec.ts`:
- Around line 146-151: The test uses the outdated type FaqRewriteResponse for
mockResponse; update the type annotation to the corrected import
RewriteFaqResponse and ensure any other occurrences in
artemis-intelligence.service.spec.ts (e.g., the mockResponse declaration and any
typed variables or function parameters referencing FaqRewriteResponse) are
changed to RewriteFaqResponse so the test compiles against the fixed import.
- Around line 362-365: The test is still using the old type name
FaqRewriteResponse; update that type reference to the current response type used
by the rewriteFAQ API (match the type used elsewhere in your codebase/tests),
adjust the mockResponse object shape if needed to satisfy the new type, and
ensure mockHyperionApiService.rewriteFAQ.mockReturnValue(of(mockResponse)) uses
the updated typed mock. Target the declaration of mockResponse and the
FaqRewriteResponse identifier in this spec and replace it with the correct
exported response type name.
- Line 27: The import and type name are wrong: replace the import
"FaqRewriteResponse" from 'app/openapi/model/faqRewriteResponse' with the
correct import "RewriteFaqResponse" from 'app/openapi/model/rewriteFaqResponse',
and update all type usages of FaqRewriteResponse in this test (e.g., the
variables and assertions currently using FaqRewriteResponse) to use
RewriteFaqResponse instead so the file compiles.
🧹 Nitpick comments (10)
src/main/resources/prompts/hyperion/faq_consistency_system.st (1)

1-14: Specify an explicit output schema to stabilize parsing.
“Structured data requested” is vague; consider stating exact JSON keys to reduce model variance and downstream parsing errors.

💡 Suggested addition
@@
-Provide ONLY the structured data requested. Do not add conversational filler.
+Provide ONLY the structured data requested. Do not add conversational filler.
+
+Output JSON with keys:
+{
+  "inconsistent": boolean,
+  "conflictingFaqIds": string[],
+  "suggestions": string[],
+  "improvedAnswer": string
+}
src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionPromptTemplateService.java (1)

63-75: renderObject has null-safety inconsistency and duplicates render logic.

The render method (lines 29-52) now handles null values safely, but renderObject does not—calling entry.getValue().toString() on line 68 will throw an NPE if any value is null. Additionally, since Map<String, Object> is assignable to Map<String, ?>, renderObject is now redundant.

♻️ Consolidate by removing renderObject

Consider deprecating or removing renderObject entirely, as render now accepts Map<String, ?> and handles null values safely. If renderObject must remain, apply the same null-safety:

 public String renderObject(String resourcePath, Map<String, Object> variables) {
     try {
         var resource = new ClassPathResource(resourcePath);
         String rendered = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
+
+        if (variables == null) {
+            return rendered;
+        }
+
         for (var entry : variables.entrySet()) {
-            rendered = rendered.replace("{{" + entry.getKey() + "}}", entry.getValue().toString());
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            String stringValue = (value == null) ? "" : String.valueOf(value);
+            rendered = rendered.replace("{{" + key + "}}", stringValue);
         }
         return rendered;
     }
src/main/java/de/tum/cit/aet/artemis/hyperion/dto/RewriteFaqRequestDTO.java (1)

11-12: Consider using @NotBlank instead of @NotNull for stronger validation.

@NotNull allows empty or whitespace-only strings, which are likely invalid for FAQ text input. Using @NotBlank ensures the text is non-null, non-empty, and contains at least one non-whitespace character.

♻️ Proposed fix
-import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotBlank;

 `@JsonInclude`(JsonInclude.Include.NON_EMPTY)
 `@Schema`(description = "Request to rewrite a FAQ")
-public record RewriteFaqRequestDTO(`@NotNull` String faqText) {
+public record RewriteFaqRequestDTO(`@NotBlank` String faqText) {
 }

Based on learnings, always validate and sanitize inputs in server-side Java code.

src/test/java/de/tum/cit/aet/artemis/hyperion/service/HyperionConsistencyCheckServiceTest.java (1)

34-34: Align test class name with filename for discoverability.

The class name changed but the filename still reflects “Check”. It’s legal, yet it breaks naming conventions and makes search/discovery harder. Consider renaming the file or reverting the class name to match.

src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java (5)

47-60: Javadoc is misplaced and incomplete.

The Javadoc at lines 47-52 appears between the field declaration (observationRegistry) and the constructor, making it confusing. Additionally, it only documents 2 parameters (chatClient, templateService) but the constructor takes 4 parameters.

♻️ Proposed fix
-    /**
-     * Creates a new HyperionFaqRewriteService.
-     *
-     * `@param` chatClient      the AI chat client (optional)
-     * `@param` templateService prompt template service
-     */
     private final ObservationRegistry observationRegistry;

+    /**
+     * Creates a new HyperionFaqRewriteService.
+     *
+     * `@param` faqRepository       the repository for FAQ entities
+     * `@param` chatClient          the AI chat client
+     * `@param` templates           prompt template service
+     * `@param` observationRegistry the observation registry for metrics
+     */
     public HyperionFaqRewriteService(FaqRepository faqRepository, ChatClient chatClient, HyperionPromptTemplateService templates, ObservationRegistry observationRegistry) {

62-68: Fix typo in Javadoc.

Line 63 has a typo: "Executes the give n FAQ" should be "Executes the given FAQ" (or more accurately, "Rewrites the given FAQ").

♻️ Proposed fix
     /**
-     * Executes the give n FAQ for the provided course
+     * Rewrites the given FAQ text for the provided course.
      *
      * `@param` courseId the id of the course context
      * `@param` faqText  the FAQ to be rewritten

76-79: Remove dead commented code and clarify variable naming.

Line 79 contains dead commented code that should be removed. Also, the map key "rewritten_text" on line 76 is misleading since it contains the input text to be rewritten, not the output.

♻️ Proposed fix
-        Map<String, String> input = Map.of("rewritten_text", faqText.trim());
+        Map<String, String> input = Map.of("faq_text", faqText.trim());
         String systemPrompt = templateService.render(PROMPT_REWRITE_SYSTEM, Map.of());
         String userPrompt = templateService.render(PROMPT_REWRITE_USER, input);
-        // String prompt = templateService.render(PROMPT_REWRITE, input);

Note: Ensure the corresponding prompt template uses {{faq_text}} placeholder if you apply this change.


115-117: FAQ limit may silently omit consistency issues.

The limit(10) on line 116 silently restricts consistency checks to only the 10 most recent FAQs. If a course has more FAQs, potential inconsistencies with older FAQs will be missed without any indication to the user.

Consider one of:

  1. Document this limitation in the API response or logs
  2. Make the limit configurable
  3. Log a warning when FAQs are truncated
         List<ConsistencyIssue.Faq> faqData = faqs.stream().limit(10).map(faq -> new ConsistencyIssue.Faq(faq.getId(), faq.getQuestionTitle(), faq.getQuestionAnswer())).toList();
+        if (faqs.size() > 10) {
+            log.debug("Consistency check limited to 10 FAQs out of {} for course {}", faqs.size(), text);
+        }

143-147: Null element handling in parseInconsistencies.

The method handles a null faqs list but does not filter out potential null elements within the list, which could cause a NullPointerException if the AI returns malformed data.

♻️ Defensive null filtering
     private List<String> parseInconsistencies(List<ConsistencyIssue.Faq> faqs) {
         if (faqs == null)
             return List.of();
-        return faqs.stream().map(f -> String.format("FAQ ID: %s, Title: %s, Answer: %s", f.id(), f.title(), f.answer())).toList();
+        return faqs.stream()
+            .filter(f -> f != null)
+            .map(f -> String.format("FAQ ID: %s, Title: %s, Answer: %s", f.id(), f.title(), f.answer()))
+            .toList();
     }
src/main/webapp/app/openapi/query.params.ts (1)

55-78: Avoid object spread when merging ParamOptions.
Prefer an explicit merge to align with the project’s style guidance. As per coding guidelines, avoid object spread for objects.

♻️ Suggested change
-            if (options) {
-                entry.options = this.resolveOptions({...entry.options, ...options});
-            }
+            if (options) {
+                entry.options = this.resolveOptions({
+                    explode: options.explode ?? entry.options.explode,
+                    delimiter: options.delimiter ?? entry.options.delimiter,
+                });
+            }

@github-project-automation github-project-automation bot moved this from Work In Progress to Ready For Review in Artemis Development Jan 26, 2026
@github-actions
Copy link

@laadvo Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/main/webapp/app/shared/monaco-editor/model/actions/artemis-intelligence/artemis-intelligence.service.spec.ts`:
- Around line 43-47: TestBed is missing a provider for HyperionFaqApiService and
the mock uses the wrong method name; create a mockHyperionFaqApiService object
(or rename mockHyperionApiService) with a jest.fn() called rewriteFaq
(camelCase) and add { provide: HyperionFaqApiService, useValue:
mockHyperionFaqApiService } to the TestBed providers alongside the existing
HyperionProblemStatementApiService provider so ArtemisIntelligenceService can
inject both dependencies.
🧹 Nitpick comments (1)
src/main/webapp/app/shared/monaco-editor/model/actions/artemis-intelligence/artemis-intelligence.service.spec.ts (1)

360-370: Consider testing the loading state transition.

The test verifies isLoading() is false before and after the call, but since of() emits synchronously, it doesn't actually observe the loading state being true during execution. If you want to verify the loading indicator appears during async operations, consider using a Subject or delayed observable to control emission timing.

This is optional since the current test still validates the expected end state.

@github-actions
Copy link

@laadvo Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report223 ran221 passed1 skipped1 failed1h 34m 7s 287ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exam/test-exam/TestExamStudentExams.spec.ts
ts.Test Exam - student exams › Check exam participants and their submissions › Open the list of exam students❌ failure7m 4s 774ms

@laadvo laadvo force-pushed the chore/hyperion/move-iris-rewrite-to-hyperion branch from 8b78221 to 33c3ad4 Compare January 27, 2026 01:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Around line 62-68: Update the Javadoc for the method in
HyperionFaqRewriteService (the comment starting "Executes the give n FAQ for the
provided course") to correct the typo "give n FAQ" to "given FAQ" so the summary
reads "Executes the given FAQ for the provided course"; ensure the rest of the
`@param` and `@return` tags remain unchanged.

In `@src/main/java/de/tum/cit/aet/artemis/hyperion/web/HyperionFaqResource.java`:
- Around line 39-52: Update the Javadoc on HyperionFaqResource.rewriteFaq to
correct the `@param` request description: replace the incorrect "the id of the
course the FAQ belongs to" with a brief description that the parameter is the
RewriteFaqRequestDTO containing the FAQ text to be rewritten (e.g., "the request
DTO containing the FAQ text to rewrite"). Ensure the method JavaDoc references
RewriteFaqRequestDTO and matches the actual method signature.
🧹 Nitpick comments (2)
src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java (2)

47-60: Fix misplaced Javadoc and inconsistent parameter naming.

The Javadoc comment (lines 47-52) is incorrectly placed between a field declaration and the constructor. Additionally, the constructor parameter templates doesn't match the field name templateService.

♻️ Proposed fix
-    /**
-     * Creates a new HyperionFaqRewriteService.
-     *
-     * `@param` chatClient      the AI chat client (optional)
-     * `@param` templateService prompt template service
-     */
     private final ObservationRegistry observationRegistry;

+    /**
+     * Creates a new HyperionFaqRewriteService.
+     *
+     * `@param` faqRepository       the FAQ repository
+     * `@param` chatClient          the AI chat client
+     * `@param` templateService     prompt template service
+     * `@param` observationRegistry the observation registry for telemetry
+     */
-    public HyperionFaqRewriteService(FaqRepository faqRepository, ChatClient chatClient, HyperionPromptTemplateService templates, ObservationRegistry observationRegistry) {
+    public HyperionFaqRewriteService(FaqRepository faqRepository, ChatClient chatClient, HyperionPromptTemplateService templateService, ObservationRegistry observationRegistry) {
         this.faqRepository = faqRepository;
         this.chatClient = chatClient;
-        this.templateService = templates;
+        this.templateService = templateService;
         this.observationRegistry = observationRegistry;
     }

72-74: Consider renaming the map key for clarity.

The key "rewritten_text" is misleading since at this point faqText is the original text being sent for rewriting, not the result. Consider using a more accurate key like "faq_text" or "original_text" to match the prompt template variable semantics.

@github-actions
Copy link

@laadvo Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report223 ran220 passed1 skipped2 failed1h 41m 13s 305ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/exam/test-exam/TestExamParticipation.spec.ts
ts.Test exam participation › Early Hand-in › Using exercise sidebar to navigate within exam❌ failure4m 21s 194ms
ts.Test exam participation › Early Hand-in › Using exercise overview to navigate within exam❌ failure4m 34s 277ms

@github-actions
Copy link

@laadvo Test coverage has been automatically updated in the PR description.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Line 121: Fix the typo in the formatter comment inside
HyperionFaqRewriteService by replacing the incorrect marker `// `@formatter`:onó`
with the correct `// `@formatter`:on` so the IDE/source formatter recognizes the
directive; locate this comment in the HyperionFaqRewriteService class (around
the formatter block) and update the trailing character to a plain 'n' without
any extra unicode characters.

In
`@src/test/java/de/tum/cit/aet/artemis/hyperion/resource/HyperionFaqRewriteResourceIntegrationTest.java`:
- Around line 26-27: The test uses a Mockito mock that isn't injected into
Spring, so replace the `@Mock` annotation on the faqRewriteService field in
HyperionFaqRewriteResourceIntegrationTest with `@MockitoBean` so the mock is
registered in the Spring context and your when(...) stubbing on
faqRewriteService will be used when the controller endpoint is invoked; ensure
the field name faqRewriteService and class
HyperionFaqRewriteResourceIntegrationTest are updated accordingly to match the
other integration tests that use `@MockitoBean`.
🧹 Nitpick comments (4)
src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java (2)

47-60: Misplaced Javadoc comment.

The Javadoc comment (lines 47-52) appears between field declarations rather than directly above the constructor. Move it immediately before the constructor.

📝 Proposed fix
     private final HyperionPromptTemplateService templateService;

-    /**
-     * Creates a new HyperionFaqRewriteService.
-     *
-     * `@param` chatClient      the AI chat client (optional)
-     * `@param` templateService prompt template service
-     */
     private final ObservationRegistry observationRegistry;

+    /**
+     * Creates a new HyperionFaqRewriteService.
+     *
+     * `@param` faqRepository       repository for FAQ entities
+     * `@param` chatClient          the AI chat client
+     * `@param` templates           prompt template service
+     * `@param` observationRegistry observation registry for metrics
+     */
     public HyperionFaqRewriteService(FaqRepository faqRepository, ChatClient chatClient, HyperionPromptTemplateService templates, ObservationRegistry observationRegistry) {

100-132: Consider adding observability to the consistency check.

The rewriteFaq method includes observation tracking, but the checkFaqConsistency method does not. For complete observability of the FAQ rewriting flow, consider adding observation events or a nested scope for the consistency check phase.

src/test/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteServiceTest.java (1)

37-51: Avoid static field for test data that is reset per test.

existingFaq is declared as static but initialized in @BeforeEach. This works but is misleading—if tests run in parallel or the field were accessed before setup, it could cause issues. Use an instance field instead.

📝 Proposed fix
     private HyperionFaqRewriteService hyperionFaqRewriteService;

-    private static Faq existingFaq;
+    private Faq existingFaq;

     `@BeforeEach`
     void setup() {
src/test/java/de/tum/cit/aet/artemis/hyperion/resource/HyperionFaqRewriteResourceIntegrationTest.java (1)

41-50: Test assertion incomplete — response body not validated.

postWithResponseBody returns the deserialized response, but the test does not assert on the returned value. Consider validating that the response matches faqResponse.

📝 Proposed fix
     `@Test`
     `@WithMockUser`(username = TEST_PREFIX + "tutor1", roles = "TA")
     void testRewriteFaq_Success() throws Exception {
         RewriteFaqRequestDTO faqRequest = new RewriteFaqRequestDTO("Old FAQ Text");
         RewriteFaqResponseDTO faqResponse = new RewriteFaqResponseDTO("New Text", List.of(), List.of(), "Improved");

         when(faqRewriteService.rewriteFaq(anyLong(), anyString())).thenReturn(faqResponse);

-        request.postWithResponseBody("/api/hyperion/courses/" + courseId + "/faq/rewrite", faqRequest, RewriteFaqResponseDTO.class, HttpStatus.OK);
+        RewriteFaqResponseDTO result = request.postWithResponseBody("/api/hyperion/courses/" + courseId + "/faq/rewrite", faqRequest, RewriteFaqResponseDTO.class, HttpStatus.OK);
+        assertThat(result).isNotNull();
+        assertThat(result.rewrittenText()).isEqualTo("New Text");
     }

@github-actions
Copy link

@laadvo Test coverage has been automatically updated in the PR description.

@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report223 ran218 passed1 skipped4 failed1h 43m 38s 202ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/atlas/LearningPathManagement.spec.ts
ts.Learning Path Management › Create simple learning path❌ failure33s 385ms
e2e/exam/ExamTestRun.spec.ts
ts.Exam test run › Delete a test run › Deletes a test run❌ failure3m 9s 263ms
e2e/exam/test-exam/TestExamParticipation.spec.ts
ts.Test exam participation › Early Hand-in › Using exercise sidebar to navigate within exam❌ failure4m 18s 781ms
ts.Test exam participation › Early Hand-in › Using exercise overview to navigate within exam❌ failure4m 29s 712ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Around line 71-75: The call faqText.trim() in rewriteFaq can NPE if faqText is
null; update the rewriteFaq method to null- and blank-guard the input before
trimming (e.g., compute a sanitized string like sanitizedFaq = (faqText == null
? "" : faqText.trim()) and then check for sanitizedFaq.isBlank() and handle it
(return a meaningful error/empty response or throw an IllegalArgumentException)
instead of calling trim() directly when building Map.of("rewritten_text", ...);
modify the Map.of usage and any downstream logic in
rewriteFaq/RewriteFaqResponseDTO to use sanitizedFaq.

In
`@src/test/java/de/tum/cit/aet/artemis/hyperion/resource/HyperionFaqRewriteResourceIntegrationTest.java`:
- Around line 47-48: Remove the redundant POST invocation that causes duplicate
side effects: keep only the single call that assigns the response to
RewriteFaqResponseDTO result (the call to request.postWithResponseBody(...,
faqRequest, RewriteFaqResponseDTO.class, HttpStatus.OK)) and delete the earlier
identical call; ensure only the call that assigns to result remains and that
variables faqRequest and courseId are still used as before.
🧹 Nitpick comments (1)
src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java (1)

102-109: Prefer limiting FAQs at the database layer instead of in-memory.

findAllByCourseIdOrderByCreatedDateDesc loads all FAQs, then limit(10) trims in memory. For large courses this is wasted work. Add a repository method that limits in SQL (e.g., findTop10ByCourseIdOrderByCreatedDateDesc) and use it here.

♻️ Suggested refactor
-    List<Faq> faqs = faqRepository.findAllByCourseIdOrderByCreatedDateDesc(courseId);
+    List<Faq> faqs = faqRepository.findTop10ByCourseIdOrderByCreatedDateDesc(courseId);
@@
-    List<ConsistencyIssue.Faq> faqData = faqs.stream().limit(10).map(faq -> new ConsistencyIssue.Faq(faq.getId(), faq.getQuestionTitle(), faq.getQuestionAnswer())).toList();
+    List<ConsistencyIssue.Faq> faqData = faqs.stream().map(faq -> new ConsistencyIssue.Faq(faq.getId(), faq.getQuestionTitle(), faq.getQuestionAnswer())).toList();

@github-actions
Copy link

@laadvo Test coverage could not be fully measured because some tests failed. Please check the workflow logs for details.

@helios-aet helios-aet bot temporarily deployed to artemis-test6.artemis.cit.tum.de January 27, 2026 11:05 Inactive
@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
End-to-End (E2E) Test Report223 ran220 passed1 skipped2 failed1h 37m 54s 865ms
TestResultTime ⏱
End-to-End (E2E) Test Report
e2e/atlas/LearningPathManagement.spec.ts
ts.Learning Path Management › Create simple learning path❌ failure20s 803ms
e2e/exam/ExamTestRun.spec.ts
ts.Exam test run › Delete a test run › Deletes a test run❌ failure3m 9s 869ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Around line 114-119: The prompt is receiving faqData as a Java List which will
call toString() instead of JSON; before calling templateService.renderObject for
PROMPT_CONSISTENCY_USER, serialize faqData to JSON (e.g., use an ObjectMapper to
call writeValueAsString(faqData)) and pass that JSON string in the Map under
"faqs" (keeping other keys like "final_result" and "format" unchanged) so the
template gets valid JSON; update the code around faqData,
templateService.renderObject(...) and ensure any checked exceptions from
serialization are handled or propagated appropriately.

@github-actions
Copy link

@laadvo Test coverage has been automatically updated in the PR description.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Around line 106-109: The current null/blank check for rewrittenText in
HyperionFaqRewriteService returns an empty string in the RewriteFaqResponseDTO
which may discard the user's original FAQ; change the fallback to return the
normalized input FAQ instead of "" — i.e., when rewrittenText == null ||
rewrittenText.isBlank(), construct the RewriteFaqResponseDTO using the
previously computed normalized input FAQ (the normalized FAQ variable used
earlier in the method, e.g., normalizedFaqText or similar) so the original
content is preserved; keep the same warnings/logging (log.warn) and other
response fields unchanged.
🧹 Nitpick comments (1)
src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java (1)

115-120: Optimize FAQ retrieval to fetch only the top 10 FAQs at the database level.

The current implementation loads all FAQs from the database and limits them in memory (limit(10)), which is inefficient for courses with many FAQs. Add a repository method findTop10ByCourseIdOrderByCreatedDateDesc using Spring Data JPA's findTop<N>By convention to fetch only 10 FAQs directly from the database.

@github-actions
Copy link

@laadvo Test coverage has been automatically updated in the PR description.

@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅SkippedFailedTime ⏱
End-to-End (E2E) Test Report1 ran1 passed0 skipped0 failed2s 320ms
TestResultTime ⏱
No test annotations available


jvmArgs = [
"-Dspring.profiles.active=test,artemis,core,atlas,scheduling,athena,apollon,iris,aeolus,theia,dev",
"-Dspring.profiles.active=test,artemis,core,atlas,scheduling,athena,apollon,iris,aeolus,theia,dev,localci,localvc",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the two profiles to be able to run ./gradlew generateApiDocs -x webapp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/main/java/de/tum/cit/aet/artemis/hyperion/service/HyperionFaqRewriteService.java`:
- Around line 119-125: Add a repository-level limit so the DB returns only the
latest 10 FAQs and update the service to stop loading everything into memory:
add a new method findTop10ByCourseIdOrderByCreatedDateDesc(...) to FaqRepository
(or implement equivalent pagination), then change
HyperionFaqRewriteService.checkFaqConsistency to call that new repository method
and remove the stream().limit(10) usage when building faqData (keep mapping to
ConsistencyIssue.Faq as before); additionally, scan other usages of
findAllByCourseIdOrderByCreatedDateDesc (e.g., FaqImportService and FaqResource)
and replace with paged/top10 queries where appropriate.

@github-actions
Copy link

@laadvo Test coverage has been automatically updated in the PR description.

@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅Skipped ⚠️FailedTime ⏱
End-to-End (E2E) Test Report223 ran222 passed1 skipped0 failed1h 33m 39s 728ms
TestResultTime ⏱
No test annotations available

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module core Pull requests that affect the corresponding module hyperion iris Pull requests that affect the corresponding module ready for review server Pull requests that update Java code. (Added Automatically!) tests

Projects

Status: Ready For Review
Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants