stable POC version 1 - chat and topics
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
package com.aiteacher.topic;
|
||||
|
||||
public record Topic(
|
||||
String id,
|
||||
String name,
|
||||
String description,
|
||||
String category
|
||||
) {
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "topic")
|
||||
public class Topic {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String category;
|
||||
|
||||
protected Topic() {}
|
||||
|
||||
public Topic(String id, String name, String description, String category) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getCategory() { return category; }
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@ import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
// Replaced by TopicRepository (DB-backed). Kept for reference only.
|
||||
public class TopicConfigLoader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TopicConfigLoader.class);
|
||||
@@ -40,7 +38,7 @@ public class TopicConfigLoader {
|
||||
|
||||
public Optional<Topic> findById(String id) {
|
||||
return topics.stream()
|
||||
.filter(t -> t.id().equals(id))
|
||||
.filter(t -> t.getId().equals(id))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,23 @@ import java.util.NoSuchElementException;
|
||||
@RequestMapping("/api/v1/topics")
|
||||
public class TopicController {
|
||||
|
||||
private final TopicConfigLoader topicConfigLoader;
|
||||
private final TopicRepository topicRepository;
|
||||
private final TopicSummaryService topicSummaryService;
|
||||
|
||||
public TopicController(TopicConfigLoader topicConfigLoader,
|
||||
public TopicController(TopicRepository topicRepository,
|
||||
TopicSummaryService topicSummaryService) {
|
||||
this.topicConfigLoader = topicConfigLoader;
|
||||
this.topicRepository = topicRepository;
|
||||
this.topicSummaryService = topicSummaryService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Topic>> list() {
|
||||
return ResponseEntity.ok(topicConfigLoader.getAll());
|
||||
return ResponseEntity.ok(topicRepository.findAll());
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/summary")
|
||||
public ResponseEntity<TopicSummaryResponse> generateSummary(@PathVariable String id) {
|
||||
Topic topic = topicConfigLoader.findById(id)
|
||||
Topic topic = topicRepository.findById(id)
|
||||
.orElseThrow(() -> new NoSuchElementException("Topic not found."));
|
||||
|
||||
TopicSummaryResponse response = topicSummaryService.generateSummary(topic);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiteacher.topic;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface TopicRepository extends JpaRepository<Topic, String> {
|
||||
}
|
||||
@@ -65,8 +65,8 @@ public class TopicSummaryService {
|
||||
List<TopicSummaryResponse.SourceReference> sources = extractSources(response);
|
||||
|
||||
return new TopicSummaryResponse(
|
||||
topic.id(),
|
||||
topic.name(),
|
||||
topic.getId(),
|
||||
topic.getName(),
|
||||
summary,
|
||||
sources,
|
||||
Instant.now()
|
||||
@@ -78,7 +78,7 @@ public class TopicSummaryService {
|
||||
"Please provide a comprehensive educational summary of the following neurosurgery topic: " +
|
||||
"%s. Topic description: %s. " +
|
||||
"Include key concepts, clinical considerations, and important details that a neurosurgeon should know.",
|
||||
topic.name(), topic.description()
|
||||
topic.getName(), topic.getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
CREATE TABLE topic (
|
||||
id VARCHAR(100) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
category VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO topic (id, name, description, category) VALUES
|
||||
('intracranial-aneurysms',
|
||||
'Intracranial Aneurysms',
|
||||
'Diagnosis, classification, endovascular coiling, flow diversion, and surgical clipping of intracranial aneurysms.',
|
||||
'Neurointervention'),
|
||||
('arteriovenous-malformations',
|
||||
'Arteriovenous Malformations',
|
||||
'Pathophysiology, Spetzler-Martin grading, embolization strategies, and multimodality treatment of cerebral AVMs.',
|
||||
'Neurointervention'),
|
||||
('dural-arteriovenous-fistula',
|
||||
'Dural Arteriovenous Fistula (DAVF)',
|
||||
'Classification (Borden/Cognard), clinical presentation, transarterial and transvenous embolization of DAVFs.',
|
||||
'Neurointervention'),
|
||||
('acute-ischemic-stroke',
|
||||
'Acute Ischemic Stroke (AIS)',
|
||||
'IV thrombolysis, mechanical thrombectomy, patient selection, access strategies, and outcomes for acute ischemic stroke.',
|
||||
'Neurointervention'),
|
||||
('carotid-vertebral-artery-disease',
|
||||
'Carotid & Vertebral Artery Disease',
|
||||
'Carotid artery stenting, angioplasty, dissection management, and vertebral artery interventions.',
|
||||
'Neurointervention'),
|
||||
('spinal-vascular-malformations',
|
||||
'Spinal Vascular Malformations',
|
||||
'Classification, angiographic evaluation, and endovascular treatment of spinal dural and perimedullary fistulas and AVMs.',
|
||||
'Neurointervention'),
|
||||
('tumor-embolization',
|
||||
'Tumor Embolization',
|
||||
'Pre-operative embolization techniques, embolic agents, and complication management for hypervascular tumors.',
|
||||
'Neurointervention'),
|
||||
('cerebral-venous-disease',
|
||||
'Cerebral Venous Disease',
|
||||
'Cerebral venous sinus thrombosis, venous angioplasty, stenting for intracranial hypertension, and dural sinus interventions.',
|
||||
'Neurointervention'),
|
||||
('pediatric-neurointervention',
|
||||
'Pediatric Neurointervention',
|
||||
'Specific considerations, techniques, and outcomes for endovascular procedures in the pediatric population.',
|
||||
'Neurointervention'),
|
||||
('free-form',
|
||||
'Free-form Chat',
|
||||
'Open-ended questions across any neurointervention topic.',
|
||||
'General');
|
||||
@@ -1,74 +1,56 @@
|
||||
[
|
||||
{
|
||||
"id": "cerebral-aneurysm",
|
||||
"name": "Cerebral Aneurysm Management",
|
||||
"description": "Diagnosis, grading, and surgical/endovascular treatment of cerebral aneurysms.",
|
||||
"category": "Vascular"
|
||||
"id": "intracranial-aneurysms",
|
||||
"name": "Intracranial Aneurysms",
|
||||
"description": "Diagnosis, classification, endovascular coiling, flow diversion, and surgical clipping of intracranial aneurysms.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "subarachnoid-hemorrhage",
|
||||
"name": "Subarachnoid Hemorrhage",
|
||||
"description": "Pathophysiology, clinical presentation, and management of subarachnoid hemorrhage.",
|
||||
"category": "Vascular"
|
||||
"id": "arteriovenous-malformations",
|
||||
"name": "Arteriovenous Malformations",
|
||||
"description": "Pathophysiology, Spetzler-Martin grading, embolization strategies, and multimodality treatment of cerebral AVMs.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "arteriovenous-malformation",
|
||||
"name": "Arteriovenous Malformation (AVM)",
|
||||
"description": "Classification, natural history, and treatment options for cerebral AVMs.",
|
||||
"category": "Vascular"
|
||||
"id": "dural-arteriovenous-fistula",
|
||||
"name": "Dural Arteriovenous Fistula (DAVF)",
|
||||
"description": "Classification (Borden/Cognard), clinical presentation, transarterial and transvenous embolization of DAVFs.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "carotid-stenosis",
|
||||
"name": "Carotid Artery Stenosis",
|
||||
"description": "Evaluation and surgical or endovascular management of carotid artery stenosis.",
|
||||
"category": "Vascular"
|
||||
"id": "acute-ischemic-stroke",
|
||||
"name": "Acute Ischemic Stroke (AIS)",
|
||||
"description": "IV thrombolysis, mechanical thrombectomy, patient selection, access strategies, and outcomes for acute ischemic stroke.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "glioblastoma",
|
||||
"name": "Glioblastoma (GBM)",
|
||||
"description": "Pathophysiology, surgical resection strategies, and adjuvant therapy for GBM.",
|
||||
"category": "Oncology"
|
||||
"id": "carotid-vertebral-artery-disease",
|
||||
"name": "Carotid & Vertebral Artery Disease",
|
||||
"description": "Carotid artery stenting, angioplasty, dissection management, and vertebral artery interventions.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "meningioma",
|
||||
"name": "Meningioma",
|
||||
"description": "Classification, surgical approaches, and recurrence management for meningiomas.",
|
||||
"category": "Oncology"
|
||||
"id": "spinal-vascular-malformations",
|
||||
"name": "Spinal Vascular Malformations",
|
||||
"description": "Classification, angiographic evaluation, and endovascular treatment of spinal dural and perimedullary fistulas and AVMs.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "pituitary-adenoma",
|
||||
"name": "Pituitary Adenoma",
|
||||
"description": "Hormonal assessment, transsphenoidal surgery, and medical management of pituitary adenomas.",
|
||||
"category": "Oncology"
|
||||
"id": "tumor-embolization",
|
||||
"name": "Tumor Embolization",
|
||||
"description": "Pre-operative embolization techniques, embolic agents, and complication management for hypervascular tumors.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "lumbar-disc-herniation",
|
||||
"name": "Lumbar Disc Herniation",
|
||||
"description": "Anatomy, clinical presentation, conservative and surgical treatment of lumbar disc herniation.",
|
||||
"category": "Spine"
|
||||
"id": "cerebral-venous-disease",
|
||||
"name": "Cerebral Venous Disease",
|
||||
"description": "Cerebral venous sinus thrombosis, venous angioplasty, stenting for intracranial hypertension, and dural sinus interventions.",
|
||||
"category": "Neurointervention"
|
||||
},
|
||||
{
|
||||
"id": "cervical-myelopathy",
|
||||
"name": "Cervical Spondylotic Myelopathy",
|
||||
"description": "Pathomechanics, clinical grading, and decompressive surgery for cervical myelopathy.",
|
||||
"category": "Spine"
|
||||
},
|
||||
{
|
||||
"id": "spinal-cord-injury",
|
||||
"name": "Spinal Cord Injury",
|
||||
"description": "ASIA classification, acute management protocols, and rehabilitation strategies for SCI.",
|
||||
"category": "Spine"
|
||||
},
|
||||
{
|
||||
"id": "traumatic-brain-injury",
|
||||
"name": "Traumatic Brain Injury (TBI)",
|
||||
"description": "GCS scoring, intracranial pressure monitoring, and surgical management of TBI.",
|
||||
"category": "Trauma"
|
||||
},
|
||||
{
|
||||
"id": "epidural-hematoma",
|
||||
"name": "Epidural Hematoma",
|
||||
"description": "Mechanism, radiological features, and emergency surgical evacuation of epidural hematomas.",
|
||||
"category": "Trauma"
|
||||
"id": "pediatric-neurointervention",
|
||||
"name": "Pediatric Neurointervention",
|
||||
"description": "Specific considerations, techniques, and outcomes for endovascular procedures in the pediatric population.",
|
||||
"category": "Neurointervention"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user