Skip to content

Commit 918aeb1

Browse files
authored
Merge pull request #42 from AET-DevOps25/feature/make-server-work-with-genai-improvements
Feature/make server work with genai improvements
2 parents 1da70f0 + 53132f2 commit 918aeb1

File tree

11 files changed

+418
-193
lines changed

11 files changed

+418
-193
lines changed

.env.dev

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ DOCUMENT_SERVICE_URL=http://document-service:8084
1717
# GenAI container
1818
WEAVIATE_HOST=weaviate
1919
WEAVIATE_PORT=8090
20-
OPEN_WEBUI_API_KEY_CHAT=dummy-key-chat
21-
OPEN_WEBUI_API_KEY_GEN=dummy-key-gen
20+
OPEN_WEBUI_API_KEY_CHAT=sk-5760c1bddad74624a09046ef2bc6c8dc
21+
OPEN_WEBUI_API_KEY_GEN=sk-8cc49d1e38ee436b93b9fbd31795b320
2222
LANGSMITH_TRACING=true
23-
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
24-
LANGSMITH_API_KEY=dummy-langsmith-key
25-
LANGSMITH_PROJECT=studymate
23+
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
24+
LANGSMITH_API_KEY="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"
25+
LANGSMITH_PROJECT="studymate"
2626

2727
# Grafana
2828
GF_SECURITY_ADMIN_USER=admin

.github/workflows/deploy-genai-service.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ jobs:
140140
echo "🔍 Verifying GenAI service deployment..."
141141
142142
# Wait for pod to be ready
143-
kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=genai-service -n study-mate --timeout=300s || {
144-
echo "❌ GenAI service pod failed to become ready"
145-
echo "🔍 Pod status:"
146-
kubectl get pods -n study-mate -l app.kubernetes.io/component=genai-service -o wide
147-
echo "🔍 Pod logs:"
148-
kubectl logs -l app.kubernetes.io/component=genai-service -n study-mate --tail=50 || echo "⚠️ Could not get logs"
143+
kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=genai-service -n study-mate --timeout=300s || {
144+
echo "❌ GenAI service pod failed to become ready"
145+
echo "🔍 Pod status:"
146+
kubectl get pods -n study-mate -l app.kubernetes.io/component=genai-service -o wide
147+
echo "🔍 Pod logs:"
148+
kubectl logs -l app.kubernetes.io/component=genai-service -n study-mate --tail=50 || echo "⚠️ Could not get logs"
149149
exit 1
150150
}
151151

database_setup_microservices.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
-- Create separate databases for each microservice
22
CREATE DATABASE auth_db;
33
CREATE DATABASE document_db;
4+
CREATE DATABASE genai_db;
45

56
-- Grant permissions
67
GRANT ALL PRIVILEGES ON DATABASE auth_db TO postgres;
78
GRANT ALL PRIVILEGES ON DATABASE document_db TO postgres;
9+
GRANT ALL PRIVILEGES ON DATABASE genai_db TO postgres;
810

911
-- Connect to auth_db and create users table
1012
\c auth_db;

server/genai-service/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
kotlin("jvm") version "1.9.25"
33
kotlin("plugin.spring") version "1.9.25"
4+
kotlin("plugin.jpa") version "1.9.25" // Added for JPA entities
45
id("org.springframework.boot") version "3.2.12"
56
id("io.spring.dependency-management") version "1.1.7"
67
}
@@ -23,6 +24,7 @@ dependencies {
2324
implementation("org.springframework.boot:spring-boot-starter-webflux") // Added for WebClient support
2425
implementation("org.springframework.boot:spring-boot-starter-validation")
2526
implementation("org.springframework.boot:spring-boot-starter-actuator")
27+
implementation("org.springframework.boot:spring-boot-starter-data-jpa") // Added for JPA/Hibernate
2628
implementation("org.postgresql:postgresql")
2729
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
2830
implementation("org.jetbrains.kotlin:kotlin-reflect")

server/genai-service/src/main/kotlin/de/tum/cit/aet/genai/GenAiServiceApplication.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package de.tum.cit.aet.genai
33
import org.springframework.boot.autoconfigure.SpringBootApplication
44
import org.springframework.boot.runApplication
55
import org.springframework.scheduling.annotation.EnableAsync
6+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
67

78
@SpringBootApplication
89
@EnableAsync
10+
@EnableJpaRepositories
911
class GenAiServiceApplication
1012

1113
fun main(args: Array<String>) {

server/genai-service/src/main/kotlin/de/tum/cit/aet/genai/controller/GenAiController.kt

Lines changed: 149 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.tum.cit.aet.genai.controller
22

33
import de.tum.cit.aet.genai.dto.*
44
import de.tum.cit.aet.genai.service.GenAiService
5+
import de.tum.cit.aet.genai.service.ChatMessageService
56
import jakarta.validation.Valid
67
import org.slf4j.LoggerFactory
78
import org.springframework.http.HttpStatus
@@ -16,7 +17,8 @@ import org.springframework.web.context.request.async.DeferredResult
1617
@RequestMapping("/api/genai")
1718
@CrossOrigin(origins = ["*"])
1819
class GenAiController(
19-
private val genAiService: GenAiService
20+
private val genAiService: GenAiService,
21+
private val chatMessageService: ChatMessageService
2022
) {
2123

2224
private val logger = LoggerFactory.getLogger(GenAiController::class.java)
@@ -145,7 +147,7 @@ class GenAiController(
145147

146148
val result = DeferredResult<ResponseEntity<Any>>(600000L) // 10 minutes timeout
147149

148-
genAiService.getSessionAsync(sessionId, userId) { response, error ->
150+
genAiService.getSessionAsync(userId) { response, error ->
149151
if (error != null) {
150152
logger.error("Error getting session: {}", error.message, error)
151153
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
@@ -376,7 +378,7 @@ class GenAiController(
376378
return result
377379
}
378380

379-
// New chat endpoints that match frontend expectations
381+
// Chat session endpoints (simplified to match Python service pattern)
380382
@PostMapping("/chat/sessions")
381383
fun createChatSession(
382384
@Valid @RequestBody request: ChatSessionRequest,
@@ -386,19 +388,41 @@ class GenAiController(
386388

387389
val result = DeferredResult<ResponseEntity<Any>>(600000L) // 10 minutes timeout
388390

389-
genAiService.createChatSessionAsync(request, userId) { response, error ->
390-
if (error != null) {
391-
logger.error("Error creating chat session: {}", error.message, error)
392-
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
393-
ErrorResponse(
394-
error = "Chat session creation failed",
395-
message = "Failed to create chat session: ${error.message}",
396-
timestamp = LocalDateTime.now().format(dateFormatter)
391+
// Python service doesn't have sessions - it just loads documents per user
392+
// If documents are provided, load the first one for this user (for chat only, no content generation)
393+
if (request.documentIds.isNotEmpty()) {
394+
val primaryDocumentId = request.documentIds.first()
395+
396+
genAiService.loadDocumentForChatAsync(primaryDocumentId, userId) { success, error ->
397+
if (error != null || !success) {
398+
logger.error("Error loading document for chat: {}", error?.message ?: "Unknown error")
399+
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
400+
ErrorResponse(
401+
error = "Failed to initialize chat",
402+
message = "Could not load document for chat: ${error?.message ?: "Unknown error"}",
403+
timestamp = LocalDateTime.now().format(dateFormatter)
404+
)
405+
))
406+
} else {
407+
// Return session response with actual message history
408+
val messages = chatMessageService.getSessionMessages(userId) // sessionId = userId
409+
val response = mapOf(
410+
"sessionId" to userId,
411+
"messages" to messages,
412+
"documentsInContext" to request.documentIds
397413
)
398-
))
399-
} else {
400-
result.setResult(ResponseEntity.ok(response))
414+
result.setResult(ResponseEntity.ok(response))
415+
}
401416
}
417+
} else {
418+
// No documents provided, return session with existing message history
419+
val messages = chatMessageService.getSessionMessages(userId) // sessionId = userId
420+
val response = mapOf(
421+
"sessionId" to userId,
422+
"messages" to messages,
423+
"documentsInContext" to emptyList<String>()
424+
)
425+
result.setResult(ResponseEntity.ok(response))
402426
}
403427

404428
return result
@@ -413,19 +437,26 @@ class GenAiController(
413437

414438
val result = DeferredResult<ResponseEntity<Any>>(600000L) // 10 minutes timeout
415439

416-
genAiService.getChatSessionAsync(sessionId, userId) { response, error ->
417-
if (error != null) {
418-
logger.error("Error getting chat session: {}", error.message, error)
419-
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
420-
ErrorResponse(
421-
error = "Chat session retrieval failed",
422-
message = "Failed to get chat session: ${error.message}",
423-
timestamp = LocalDateTime.now().format(dateFormatter)
424-
)
425-
))
426-
} else {
427-
result.setResult(ResponseEntity.ok(response))
428-
}
440+
try {
441+
// Get actual message history from database
442+
val messages = chatMessageService.getSessionMessages(sessionId)
443+
444+
val response = mapOf(
445+
"sessionId" to sessionId,
446+
"messages" to messages,
447+
"documentsInContext" to emptyList<String>() // Would need to extract from message context if needed
448+
)
449+
450+
result.setResult(ResponseEntity.ok(response))
451+
} catch (e: Exception) {
452+
logger.error("Error getting chat session: {}", e.message, e)
453+
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
454+
ErrorResponse(
455+
error = "Failed to get chat session",
456+
message = "Could not retrieve chat history: ${e.message}",
457+
timestamp = LocalDateTime.now().format(dateFormatter)
458+
)
459+
))
429460
}
430461

431462
return result
@@ -441,24 +472,103 @@ class GenAiController(
441472

442473
val result = DeferredResult<ResponseEntity<Any>>(600000L) // 10 minutes timeout
443474

444-
genAiService.sendChatMessageAsync(sessionId, request, userId) { response, error ->
445-
if (error != null) {
446-
logger.error("Error sending chat message: {}", error.message, error)
447-
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
448-
ErrorResponse(
449-
error = "Message sending failed",
450-
message = "Failed to send message: ${error.message}",
451-
timestamp = LocalDateTime.now().format(dateFormatter)
452-
)
453-
))
454-
} else {
455-
result.setResult(ResponseEntity.ok(response))
475+
try {
476+
// Save user message to database first
477+
chatMessageService.saveUserMessage(
478+
sessionId = sessionId,
479+
userId = userId,
480+
content = request.message,
481+
documentIds = request.documentIds
482+
)
483+
484+
// Use the simple chat functionality - Python service uses user_id for context
485+
val chatRequest = ChatRequest(message = request.message, userId = userId)
486+
487+
genAiService.chatAsync(chatRequest) { chatResponse, error ->
488+
if (error != null) {
489+
logger.error("Error sending chat message: {}", error.message, error)
490+
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
491+
ErrorResponse(
492+
error = "Message sending failed",
493+
message = "Failed to send message: ${error.message}",
494+
timestamp = LocalDateTime.now().format(dateFormatter)
495+
)
496+
))
497+
} else {
498+
try {
499+
// Save bot response to database
500+
val botMessage = chatMessageService.saveBotMessage(
501+
sessionId = sessionId,
502+
userId = userId,
503+
content = chatResponse!!.response
504+
)
505+
506+
// Transform to expected response format
507+
val messageResponse = mapOf(
508+
"id" to (botMessage.id ?: java.util.UUID.randomUUID().toString()),
509+
"content" to chatResponse.response,
510+
"sender" to "bot",
511+
"timestamp" to chatResponse.timestamp,
512+
"sources" to null,
513+
"documentReferences" to null
514+
)
515+
result.setResult(ResponseEntity.ok(messageResponse))
516+
} catch (e: Exception) {
517+
logger.error("Error saving bot message: {}", e.message, e)
518+
// Still return the response even if saving failed
519+
val messageResponse = mapOf(
520+
"id" to java.util.UUID.randomUUID().toString(),
521+
"content" to chatResponse!!.response,
522+
"sender" to "bot",
523+
"timestamp" to chatResponse.timestamp,
524+
"sources" to null,
525+
"documentReferences" to null
526+
)
527+
result.setResult(ResponseEntity.ok(messageResponse))
528+
}
529+
}
456530
}
531+
} catch (e: Exception) {
532+
logger.error("Error saving user message: {}", e.message, e)
533+
result.setResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
534+
ErrorResponse(
535+
error = "Message saving failed",
536+
message = "Failed to save user message: ${e.message}",
537+
timestamp = LocalDateTime.now().format(dateFormatter)
538+
)
539+
))
457540
}
458541

459542
return result
460543
}
461544

545+
@DeleteMapping("/chat/sessions/{sessionId}/messages")
546+
fun clearChatHistory(
547+
@PathVariable sessionId: String,
548+
@RequestHeader("X-User-ID") userId: String
549+
): ResponseEntity<Map<String, Any>> {
550+
logger.info("DELETE /genai/chat/sessions/{}/messages for user: {}", sessionId, userId)
551+
552+
return try {
553+
val deletedCount = chatMessageService.clearSessionMessages(sessionId)
554+
555+
ResponseEntity.ok(mapOf<String, Any>(
556+
"message" to "Chat history cleared successfully",
557+
"deletedMessages" to deletedCount,
558+
"sessionId" to sessionId,
559+
"timestamp" to LocalDateTime.now().format(dateFormatter)
560+
))
561+
} catch (e: Exception) {
562+
logger.error("Error clearing chat history: {}", e.message, e)
563+
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(mapOf<String, Any>(
564+
"error" to "Failed to clear chat history",
565+
"message" to (e.message ?: "Unknown error"),
566+
"timestamp" to LocalDateTime.now().format(dateFormatter)
567+
))
568+
}
569+
}
570+
571+
462572
@GetMapping("/health")
463573
fun healthCheck(): ResponseEntity<Map<String, String>> {
464574
return ResponseEntity.ok(mapOf(
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package de.tum.cit.aet.genai.entity
2+
3+
import jakarta.persistence.*
4+
import java.time.LocalDateTime
5+
6+
@Entity
7+
@Table(name = "chat_messages")
8+
data class ChatMessage(
9+
@Id
10+
@GeneratedValue(strategy = GenerationType.UUID)
11+
val id: String? = null,
12+
13+
@Column(name = "session_id", nullable = false)
14+
val sessionId: String,
15+
16+
@Column(name = "user_id", nullable = false)
17+
val userId: String,
18+
19+
@Column(name = "content", nullable = false, columnDefinition = "TEXT")
20+
val content: String,
21+
22+
@Enumerated(EnumType.STRING)
23+
@Column(name = "sender", nullable = false)
24+
val sender: MessageSender,
25+
26+
@Column(name = "timestamp", nullable = false)
27+
val timestamp: LocalDateTime = LocalDateTime.now(),
28+
29+
@Column(name = "document_ids")
30+
val documentIds: String? = null // JSON string of document IDs
31+
)
32+
33+
enum class MessageSender {
34+
USER, BOT
35+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.tum.cit.aet.genai.repository
2+
3+
import de.tum.cit.aet.genai.entity.ChatMessage
4+
import org.springframework.data.jpa.repository.JpaRepository
5+
import org.springframework.data.jpa.repository.Query
6+
import org.springframework.data.repository.query.Param
7+
import org.springframework.stereotype.Repository
8+
9+
@Repository
10+
interface ChatMessageRepository : JpaRepository<ChatMessage, String> {
11+
12+
// Get messages for a specific session, ordered by timestamp
13+
fun findBySessionIdOrderByTimestampAsc(sessionId: String): List<ChatMessage>
14+
15+
// Get messages for a user across all sessions
16+
fun findByUserIdOrderByTimestampDesc(userId: String): List<ChatMessage>
17+
18+
// Get recent messages for a session (limit)
19+
@Query("SELECT m FROM ChatMessage m WHERE m.sessionId = :sessionId ORDER BY m.timestamp DESC LIMIT :limit")
20+
fun findRecentMessagesBySessionId(@Param("sessionId") sessionId: String, @Param("limit") limit: Int): List<ChatMessage>
21+
22+
// Count messages in a session
23+
fun countBySessionId(sessionId: String): Long
24+
25+
// Delete all messages for a session
26+
fun deleteBySessionId(sessionId: String): Long
27+
28+
// Check if session exists (has any messages)
29+
fun existsBySessionId(sessionId: String): Boolean
30+
}

0 commit comments

Comments
 (0)