-
Notifications
You must be signed in to change notification settings - Fork 347
Adaptive learning
: Atlas Companion Tools Integration
#11482
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
base: develop
Are you sure you want to change the base?
Changes from 17 commits
8d55bd6
8a9b451
ddb0ab1
7bc18c4
ce7fdca
427d70e
98b8c7c
7b8385a
8ffc8e2
50481c2
fda54a3
17fe7be
40da596
9509cf6
455a8d9
c83dff8
efcf434
5d36cfb
7be4264
a4fdeef
568a89d
a66035e
f596e9c
15f1feb
0085fe4
3c830a4
e30f369
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package de.tum.cit.aet.artemis.atlas.config; | ||
|
||
import org.springframework.ai.tool.ToolCallbackProvider; | ||
import org.springframework.ai.tool.method.MethodToolCallbackProvider; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Conditional; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Lazy; | ||
|
||
import de.tum.cit.aet.artemis.atlas.service.AtlasAgentToolsService; | ||
|
||
/** | ||
* Configuration for Atlas Agent tools integration with Spring AI. | ||
* This class registers the @Tool-annotated methods from AtlasAgentToolsService | ||
* so that Spring AI can discover and use them for function calling. | ||
*/ | ||
@Lazy | ||
@Configuration | ||
@Conditional(AtlasEnabled.class) | ||
public class AtlasAgentToolConfig { | ||
|
||
/** | ||
* Registers the tools found on the AtlasAgentToolsService bean. | ||
* MethodToolCallbackProvider discovers @Tool-annotated methods on the provided instances | ||
* and makes them available for Spring AI's tool calling system. | ||
* | ||
* @param toolsService the service containing @Tool-annotated methods | ||
* @return ToolCallbackProvider that exposes the tools to Spring AI | ||
*/ | ||
@Bean | ||
public ToolCallbackProvider atlasToolCallbackProvider(AtlasAgentToolsService toolsService) { | ||
// MethodToolCallbackProvider discovers @Tool-annotated methods on the provided instances | ||
Yhmidi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
return MethodToolCallbackProvider.builder().toolObjects(toolsService).build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package de.tum.cit.aet.artemis.atlas.dto; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.NotNull; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
||
/** | ||
* DTO for chat history messages retrieved from ChatMemory. | ||
*/ | ||
@JsonInclude(JsonInclude.Include.NON_EMPTY) | ||
public record ChatHistoryMessageDTO( | ||
Yhmidi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
@NotNull @NotBlank String role, | ||
|
||
@NotNull @NotBlank String content | ||
|
||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package de.tum.cit.aet.artemis.atlas.service; | ||
|
||
/** | ||
* Internal result object for Atlas Agent chat processing. | ||
* Contains the response message and whether competencies were modified. | ||
*/ | ||
public record AgentChatResult(String message, boolean competenciesModified) { | ||
MoritzSpengler marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,228 @@ | ||||||||||||||||||||||||||||||||
package de.tum.cit.aet.artemis.atlas.service; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import java.util.LinkedHashMap; | ||||||||||||||||||||||||||||||||
import java.util.Map; | ||||||||||||||||||||||||||||||||
import java.util.Optional; | ||||||||||||||||||||||||||||||||
import java.util.Set; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import org.slf4j.Logger; | ||||||||||||||||||||||||||||||||
import org.slf4j.LoggerFactory; | ||||||||||||||||||||||||||||||||
import org.springframework.ai.tool.annotation.Tool; | ||||||||||||||||||||||||||||||||
import org.springframework.ai.tool.annotation.ToolParam; | ||||||||||||||||||||||||||||||||
import org.springframework.context.annotation.Conditional; | ||||||||||||||||||||||||||||||||
import org.springframework.context.annotation.Lazy; | ||||||||||||||||||||||||||||||||
import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import com.fasterxml.jackson.core.JsonProcessingException; | ||||||||||||||||||||||||||||||||
import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.atlas.config.AtlasEnabled; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyTaxonomy; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.core.domain.Course; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.core.repository.CourseRepository; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.exercise.domain.Exercise; | ||||||||||||||||||||||||||||||||
import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Service providing tools for the Atlas Agent using Spring AI's @Tool annotation. | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
@Lazy | ||||||||||||||||||||||||||||||||
@Service | ||||||||||||||||||||||||||||||||
@Conditional(AtlasEnabled.class) | ||||||||||||||||||||||||||||||||
public class AtlasAgentToolsService { | ||||||||||||||||||||||||||||||||
Yhmidi marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private static final Logger log = LoggerFactory.getLogger(AtlasAgentToolsService.class); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private final ObjectMapper objectMapper; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private final CompetencyRepository competencyRepository; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private final CourseRepository courseRepository; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
private final ExerciseRepository exerciseRepository; | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
public AtlasAgentToolsService(ObjectMapper objectMapper, CompetencyRepository competencyRepository, CourseRepository courseRepository, ExerciseRepository exerciseRepository) { | ||||||||||||||||||||||||||||||||
this.objectMapper = objectMapper; | ||||||||||||||||||||||||||||||||
this.competencyRepository = competencyRepository; | ||||||||||||||||||||||||||||||||
this.courseRepository = courseRepository; | ||||||||||||||||||||||||||||||||
this.exerciseRepository = exerciseRepository; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Tool for getting course competencies. | ||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||
* @param courseId the course ID | ||||||||||||||||||||||||||||||||
* @return JSON representation of competencies | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
@Tool(description = "Get all competencies for a course") | ||||||||||||||||||||||||||||||||
public String getCourseCompetencies(@ToolParam(description = "the ID of the course") Long courseId) { | ||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
Yhmidi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
Optional<Course> courseOpt = courseRepository.findById(courseId); | ||||||||||||||||||||||||||||||||
if (courseOpt.isEmpty()) { | ||||||||||||||||||||||||||||||||
return toJson(Map.of("error", "Course not found with ID: " + courseId)); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Set<Competency> competencies = competencyRepository.findAllByCourseId(courseId); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Build competency list using Jackson | ||||||||||||||||||||||||||||||||
var competencyList = competencies.stream().map(competency -> { | ||||||||||||||||||||||||||||||||
Map<String, Object> compData = new LinkedHashMap<>(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
compData.put("id", competency.getId()); | ||||||||||||||||||||||||||||||||
compData.put("title", competency.getTitle()); | ||||||||||||||||||||||||||||||||
compData.put("description", competency.getDescription()); | ||||||||||||||||||||||||||||||||
compData.put("taxonomy", competency.getTaxonomy() != null ? competency.getTaxonomy().toString() : ""); | ||||||||||||||||||||||||||||||||
return compData; | ||||||||||||||||||||||||||||||||
}).toList(); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Map<String, Object> response = new LinkedHashMap<>(); | ||||||||||||||||||||||||||||||||
response.put("courseId", courseId); | ||||||||||||||||||||||||||||||||
response.put("competencies", competencyList); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return toJson(response); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
catch (Exception e) { | ||||||||||||||||||||||||||||||||
log.error("Error getting course competencies for course {}: {}", courseId, e.getMessage(), e); | ||||||||||||||||||||||||||||||||
return toJson(Map.of("error", "Failed to retrieve competencies: " + e.getMessage())); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Tool for creating a new competency in a course. | ||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||
* @param courseId the course ID | ||||||||||||||||||||||||||||||||
* @param title the competency title | ||||||||||||||||||||||||||||||||
* @param description the competency description | ||||||||||||||||||||||||||||||||
* @param taxonomyLevel the taxonomy level (REMEMBER, UNDERSTAND, APPLY, ANALYZE, EVALUATE, CREATE) | ||||||||||||||||||||||||||||||||
* @return JSON response indicating success or error | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
@Tool(description = "Create a new competency for a course") | ||||||||||||||||||||||||||||||||
public String createCompetency(@ToolParam(description = "the ID of the course") Long courseId, @ToolParam(description = "the title of the competency") String title, | ||||||||||||||||||||||||||||||||
@ToolParam(description = "the description of the competency") String description, | ||||||||||||||||||||||||||||||||
@ToolParam(description = "the taxonomy level (REMEMBER, UNDERSTAND, APPLY, ANALYZE, EVALUATE, CREATE)") String taxonomyLevel) { | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
marlonnienaber marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
Optional<Course> courseOpt = courseRepository.findById(courseId); | ||||||||||||||||||||||||||||||||
if (courseOpt.isEmpty()) { | ||||||||||||||||||||||||||||||||
return toJson(Map.of("error", "Course not found with ID: " + courseId)); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Course course = courseOpt.get(); | ||||||||||||||||||||||||||||||||
Competency competency = new Competency(); | ||||||||||||||||||||||||||||||||
competency.setTitle(title); | ||||||||||||||||||||||||||||||||
competency.setDescription(description); | ||||||||||||||||||||||||||||||||
competency.setCourse(course); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if (taxonomyLevel != null && !taxonomyLevel.isEmpty()) { | ||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
CompetencyTaxonomy taxonomy = CompetencyTaxonomy.valueOf(taxonomyLevel.toUpperCase()); | ||||||||||||||||||||||||||||||||
competency.setTaxonomy(taxonomy); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
catch (IllegalArgumentException e) { | ||||||||||||||||||||||||||||||||
log.warn("Invalid taxonomy level '{}', using default", taxonomyLevel); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Competency savedCompetency = competencyRepository.save(competency); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
// Build response using Jackson | ||||||||||||||||||||||||||||||||
Map<String, Object> competencyData = new LinkedHashMap<>(); | ||||||||||||||||||||||||||||||||
competencyData.put("id", savedCompetency.getId()); | ||||||||||||||||||||||||||||||||
competencyData.put("title", savedCompetency.getTitle()); | ||||||||||||||||||||||||||||||||
competencyData.put("description", savedCompetency.getDescription()); | ||||||||||||||||||||||||||||||||
competencyData.put("taxonomy", savedCompetency.getTaxonomy() != null ? savedCompetency.getTaxonomy().toString() : ""); | ||||||||||||||||||||||||||||||||
competencyData.put("courseId", courseId); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Map<String, Object> response = new LinkedHashMap<>(); | ||||||||||||||||||||||||||||||||
response.put("success", true); | ||||||||||||||||||||||||||||||||
response.put("competency", competencyData); | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return toJson(response); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
catch (Exception e) { | ||||||||||||||||||||||||||||||||
log.error("Error creating competency for course {}: {}", courseId, e.getMessage(), e); | ||||||||||||||||||||||||||||||||
return toJson(Map.of("error", "Failed to create competency: " + e.getMessage())); | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||
* Tool for getting course description. | ||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||
* @param courseId the course ID | ||||||||||||||||||||||||||||||||
* @return the course description or empty string if not found | ||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||
@Tool(description = "Get the description of a course") | ||||||||||||||||||||||||||||||||
public String getCourseDescription(@ToolParam(description = "the ID of the course") Long courseId) { | ||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||
Yhmidi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
Optional<Course> courseOpt = courseRepository.findById(courseId); | ||||||||||||||||||||||||||||||||
Yhmidi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
if (courseOpt.isEmpty()) { | ||||||||||||||||||||||||||||||||
return ""; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Course course = courseOpt.get(); | ||||||||||||||||||||||||||||||||
String description = course.getDescription(); | ||||||||||||||||||||||||||||||||
return description != null ? description : ""; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
catch (Exception e) { | ||||||||||||||||||||||||||||||||
log.error("Error getting course description for course {}: {}", courseId, e.getMessage(), e); | ||||||||||||||||||||||||||||||||
return ""; | ||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||
|
try { | |
Optional<Course> courseOpt = courseRepository.findById(courseId); | |
if (courseOpt.isEmpty()) { | |
return ""; | |
} | |
Course course = courseOpt.get(); | |
String description = course.getDescription(); | |
return description != null ? description : ""; | |
} | |
catch (Exception e) { | |
log.error("Error getting course description for course {}: {}", courseId, e.getMessage(), e); | |
return ""; | |
} | |
return courseRepository.findById(courseId).map(Course::getDescription).orElse(""); |
Could be written much more concisely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense,I applied your suggestion.
Uh oh!
There was an error while loading. Please reload this page.