stable POC version 1 - chat and topics

This commit is contained in:
Adrien
2026-04-02 21:04:33 +02:00
parent 618e28b354
commit bcc80d250b
74 changed files with 11692 additions and 278 deletions
@@ -32,6 +32,21 @@ public class ChatController {
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@GetMapping("/sessions")
public ResponseEntity<List<Map<String, Object>>> getSessionsByTopic(@RequestParam String topicId) {
List<ChatSession> sessions = chatService.getSessionsByTopic(topicId);
List<Map<String, Object>> response = sessions.stream()
.map(s -> {
Map<String, Object> m = new LinkedHashMap<>();
m.put("sessionId", s.getId());
m.put("topicId", s.getTopicId());
m.put("createdAt", s.getCreatedAt());
return m;
})
.toList();
return ResponseEntity.ok(response);
}
@GetMapping("/sessions/{sessionId}/messages")
public ResponseEntity<List<Map<String, Object>>> getMessages(@PathVariable UUID sessionId) {
List<Message> messages = chatService.getMessages(sessionId);
@@ -26,17 +26,18 @@ public class ChatService {
private static final Logger log = LoggerFactory.getLogger(ChatService.class);
private static final String SYSTEM_PROMPT = """
You are an expert neurosurgery educator assistant. Your role is to answer
questions based ONLY on the content from uploaded medical textbooks that has been
retrieved for you as context.
You are an expert neurosurgery educator assistant. Answer questions using the
medical textbook content provided to you as context.
Rules:
- Answer only from the provided context chunks
- If the context does not contain enough information, explicitly state:
"I could not find relevant information about this topic in the uploaded books."
- Cite sources when possible (book title and page number from the context metadata)
- Give comprehensive, detailed answers — cover all relevant aspects found in the context: anatomy, indications, contraindications, techniques, procedural steps, complications, outcomes, and clinical considerations
- When the context contains related information, synthesize a thorough answer from it — do not apologize for missing definitions or lead with what you couldn't find
- Build answers from what is present: procedures, conditions, techniques, and descriptions all contribute; combine them into a rich, structured response
- Use clear structure: headings, bullet points, or numbered steps where appropriate to maximize clarity
- Only say you cannot answer if the context is entirely unrelated to the question
- Cite sources for each major point (book title and page number from the context metadata)
- Maintain continuity with the conversation history
- Never fabricate clinical information
- Never fabricate clinical information not present in the context
""";
private final ChatClient chatClient;
@@ -87,7 +88,7 @@ public class ChatService {
String fullQuestion = buildQuestionWithHistory(history, userContent, session.getTopicId());
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
.searchRequest(SearchRequest.builder().similarityThreshold(0.5d).topK(6).build())
.build();
ChatResponse response = chatClient.prompt()
@@ -106,6 +107,10 @@ public class ChatService {
return messageRepository.save(assistantMessage);
}
public List<ChatSession> getSessionsByTopic(String topicId) {
return sessionRepository.findByTopicIdOrderByCreatedAtDesc(topicId);
}
public void deleteSession(UUID sessionId) {
if (!sessionRepository.existsById(sessionId)) {
throw new NoSuchElementException("Session not found.");
@@ -115,16 +120,17 @@ public class ChatService {
private String buildQuestionWithHistory(List<Message> history, String currentQuestion,
String topicId) {
boolean hasTopic = topicId != null && !topicId.equals("free-form");
if (history.size() <= 1) {
// Only the current user message is in history; just ask the question
return topicId != null
return hasTopic
? String.format("[Context: This is a question about the neurosurgery topic '%s']\n%s",
topicId, currentQuestion)
: currentQuestion;
}
StringBuilder sb = new StringBuilder();
if (topicId != null) {
if (hasTopic) {
sb.append(String.format("[Context: This conversation is about the neurosurgery topic '%s']\n\n",
topicId));
}
@@ -154,6 +160,7 @@ public class ChatService {
Map<String, Object> source = new HashMap<>();
source.put("bookTitle", bookTitle);
source.put("page", page);
source.put("chunkText", doc.getText());
sources.add(source);
}
}
@@ -3,8 +3,10 @@ package com.aiteacher.chat;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.UUID;
@Repository
public interface ChatSessionRepository extends JpaRepository<ChatSession, UUID> {
List<ChatSession> findByTopicIdOrderByCreatedAtDesc(String topicId);
}