44import com .back .domain .chatbot .dto .ChatResponseDto ;
55import com .back .domain .chatbot .entity .ChatConversation ;
66import com .back .domain .chatbot .repository .ChatConversationRepository ;
7+ import jakarta .annotation .PostConstruct ;
78import lombok .RequiredArgsConstructor ;
89import lombok .extern .slf4j .Slf4j ;
910import org .springframework .ai .chat .client .ChatClient ;
10- import org .springframework .ai .chat .client .advisor .MessageChatMemoryAdvisor ;
11- import org .springframework .ai .chat .memory .InMemoryChatMemory ;
1211import org .springframework .ai .chat .model .ChatModel ;
13- import org .springframework .ai .chat .messages .*;
1412import org .springframework .ai .openai .OpenAiChatOptions ;
1513import org .springframework .beans .factory .annotation .Value ;
1614import org .springframework .core .io .Resource ;
15+ import org .springframework .data .domain .Pageable ;
1716import org .springframework .stereotype .Service ;
1817import org .springframework .transaction .annotation .Transactional ;
1918import org .springframework .util .StreamUtils ;
2019
21- import jakarta .annotation .PostConstruct ;
2220import java .io .IOException ;
2321import java .nio .charset .StandardCharsets ;
2422import java .time .LocalDateTime ;
25- import java .util .* ;
26- import java .util .concurrent . ConcurrentHashMap ;
23+ import java .util .Collections ;
24+ import java .util .List ;
2725
2826@ Service
2927@ RequiredArgsConstructor
@@ -33,8 +31,6 @@ public class ChatbotService {
3331 private final ChatModel chatModel ;
3432 private final ChatConversationRepository chatConversationRepository ;
3533
36- // 세션별 메모리 관리 (Thread-Safe)
37- private final ConcurrentHashMap <String , InMemoryChatMemory > sessionMemories = new ConcurrentHashMap <>();
3834
3935 @ Value ("classpath:prompts/chatbot-system-prompt.txt" )
4036 private Resource systemPromptResource ;
@@ -80,23 +76,24 @@ public void init() throws IOException {
8076
8177 @ Transactional
8278 public ChatResponseDto sendMessage (ChatRequestDto requestDto ) {
83- String sessionId = ensureSessionId (requestDto .getSessionId ());
84-
8579 try {
8680 // 메시지 타입 감지
8781 MessageType messageType = detectMessageType (requestDto .getMessage ());
8882
89- // 세션별 메모리 가져오기
90- InMemoryChatMemory chatMemory = getOrCreateSessionMemory (sessionId );
83+ // 최근 대화 기록 조회 (최신 5개)
84+ List <ChatConversation > recentChats =
85+ chatConversationRepository .findTop5ByUserIdOrderByCreatedAtDesc (requestDto .getUserId ());
86+
87+ // 대화 히스토리를 시간순으로 정렬 (오래된 것부터)
88+ Collections .reverse (recentChats );
9189
92- // 이전 대화 기록 로드
93- loadConversationHistory ( sessionId , chatMemory );
90+ // 대화 컨텍스트 생성
91+ String conversationContext = buildConversationContext ( recentChats );
9492
9593 // ChatClient 빌더 생성
9694 var promptBuilder = chatClient .prompt ()
97- .system (buildSystemMessage (messageType ))
98- .user (buildUserMessage (requestDto .getMessage (), messageType ))
99- .advisors (new MessageChatMemoryAdvisor (chatMemory ));
95+ .system (buildSystemMessage (messageType ) + conversationContext )
96+ .user (buildUserMessage (requestDto .getMessage (), messageType ));
10097
10198 // RAG 기능은 향후 구현 예정 (Vector DB 설정 필요)
10299
@@ -109,42 +106,31 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
109106 // 응답 후처리
110107 response = postProcessResponse (response , messageType );
111108
112- // 대화 저장
113- saveConversation (requestDto , response , sessionId );
109+ // 대화 저장 (sessionId 없이)
110+ saveConversation (requestDto , response );
114111
115- return new ChatResponseDto (response , sessionId );
112+ return new ChatResponseDto (response );
116113
117114 } catch (Exception e ) {
118115 log .error ("채팅 응답 생성 중 오류 발생: " , e );
119- return handleError (sessionId , e );
116+ return handleError (e );
120117 }
121118 }
122119
123- private String ensureSessionId (String sessionId ) {
124- return (sessionId == null || sessionId .isEmpty ())
125- ? UUID .randomUUID ().toString ()
126- : sessionId ;
127- }
128120
129- private InMemoryChatMemory getOrCreateSessionMemory (String sessionId ) {
130- return sessionMemories .computeIfAbsent (
131- sessionId ,
132- k -> new InMemoryChatMemory ()
133- );
134- }
121+ private String buildConversationContext (List <ChatConversation > recentChats ) {
122+ if (recentChats .isEmpty ()) {
123+ return "" ;
124+ }
135125
136- private void loadConversationHistory (String sessionId , InMemoryChatMemory chatMemory ) {
137- List <ChatConversation > conversations =
138- chatConversationRepository .findBySessionIdOrderByCreatedAtAsc (sessionId );
139-
140- // 최근 N개의 대화만 메모리에 로드
141- String sessionIdForMemory = sessionId ;
142- conversations .stream ()
143- .skip (Math .max (0 , conversations .size () - maxConversationCount ))
144- .forEach (conv -> {
145- chatMemory .add (sessionIdForMemory , new UserMessage (conv .getUserMessage ()));
146- chatMemory .add (sessionIdForMemory , new AssistantMessage (conv .getBotResponse ()));
147- });
126+ StringBuilder context = new StringBuilder ("\n \n 【최근 대화 기록】\n " );
127+ for (ChatConversation chat : recentChats ) {
128+ context .append ("사용자: " ).append (chat .getUserMessage ()).append ("\n " );
129+ context .append ("봇: " ).append (chat .getBotResponse ()).append ("\n \n " );
130+ }
131+ context .append ("위 대화를 참고하여 자연스럽게 이어지는 답변을 해주세요.\n " );
132+
133+ return context .toString ();
148134 }
149135
150136 private String buildSystemMessage (MessageType type ) {
@@ -208,19 +194,18 @@ private String postProcessResponse(String response, MessageType type) {
208194 return response ;
209195 }
210196
211- private void saveConversation (ChatRequestDto requestDto , String response , String sessionId ) {
197+ private void saveConversation (ChatRequestDto requestDto , String response ) {
212198 ChatConversation conversation = ChatConversation .builder ()
213199 .userId (requestDto .getUserId ())
214200 .userMessage (requestDto .getMessage ())
215201 .botResponse (response )
216- .sessionId (sessionId )
217202 .createdAt (LocalDateTime .now ())
218203 .build ();
219204
220205 chatConversationRepository .save (conversation );
221206 }
222207
223- private ChatResponseDto handleError (String sessionId , Exception e ) {
208+ private ChatResponseDto handleError (Exception e ) {
224209 String errorMessage = "죄송합니다. 잠시 후 다시 시도해주세요." ;
225210
226211 if (e .getMessage ().contains ("rate limit" )) {
@@ -229,7 +214,7 @@ private ChatResponseDto handleError(String sessionId, Exception e) {
229214 errorMessage = "응답 시간이 초과되었습니다. 다시 시도해주세요." ;
230215 }
231216
232- return new ChatResponseDto (errorMessage , sessionId );
217+ return new ChatResponseDto (errorMessage );
233218 }
234219
235220 public enum MessageType {
@@ -254,25 +239,9 @@ private MessageType detectMessageType(String message) {
254239 }
255240
256241 @ Transactional (readOnly = true )
257- public List <ChatConversation > getChatHistory (String sessionId ) {
258- return chatConversationRepository .findBySessionIdOrderByCreatedAtAsc (sessionId );
259- }
260-
261- @ Transactional (readOnly = true )
262- public List <ChatConversation > getUserChatHistory (Long userId , String sessionId ) {
263- return chatConversationRepository .findByUserIdAndSessionIdOrderByCreatedAtAsc (userId , sessionId );
242+ public List <ChatConversation > getUserChatHistory (Long userId ) {
243+ return chatConversationRepository .findByUserIdOrderByCreatedAtDesc (userId , Pageable .unpaged ()).getContent ();
264244 }
265245
266- // 정기적인 메모리 정리 (스케줄러로 호출)
267- public void cleanupInactiveSessions () {
268- long thirtyMinutesAgo = System .currentTimeMillis () - (30 * 60 * 1000 );
269-
270- sessionMemories .entrySet ().removeIf (entry -> {
271- // 실제로는 마지막 사용 시간을 추적해야 함
272- return false ;
273- });
274-
275- log .info ("세션 메모리 정리 완료. 현재 활성 세션: {}" , sessionMemories .size ());
276- }
277246}
278247
0 commit comments