1+ package com .ai .lawyer .domain .kafka .consumer ;
2+
3+ import com .ai .lawyer .domain .chatbot .dto .ExtractionDto .KeywordExtractionDto ;
4+ import com .ai .lawyer .domain .chatbot .dto .ExtractionDto .TitleExtractionDto ;
5+ import com .ai .lawyer .domain .chatbot .entity .*;
6+ import com .ai .lawyer .domain .chatbot .repository .*;
7+ import com .ai .lawyer .domain .chatbot .service .KeywordService ;
8+ import com .ai .lawyer .domain .kafka .dto .ChatPostProcessEvent ;
9+ import com .ai .lawyer .domain .kafka .dto .DocumentDto ;
10+ import lombok .RequiredArgsConstructor ;
11+ import lombok .extern .slf4j .Slf4j ;
12+ import org .springframework .ai .chat .memory .ChatMemory ;
13+ import org .springframework .ai .chat .memory .ChatMemoryRepository ;
14+ import org .springframework .ai .chat .memory .MessageWindowChatMemory ;
15+ import org .springframework .ai .chat .messages .AssistantMessage ;
16+ import org .springframework .ai .chat .messages .MessageType ;
17+ import org .springframework .beans .factory .annotation .Value ;
18+ import org .springframework .kafka .annotation .KafkaListener ;
19+ import org .springframework .stereotype .Service ;
20+ import org .springframework .transaction .annotation .Transactional ;
21+
22+ import java .util .List ;
23+ import java .util .stream .Collectors ;
24+
25+ @ Slf4j
26+ @ Service
27+ @ RequiredArgsConstructor
28+ public class ChatPostProcessingConsumer {
29+
30+ private final KeywordService keywordService ;
31+ private final HistoryRepository historyRepository ;
32+ private final ChatRepository chatRepository ;
33+ private final KeywordRankRepository keywordRankRepository ;
34+ private final ChatMemoryRepository chatMemoryRepository ;
35+ private final ChatPrecedentRepository chatPrecedentRepository ;
36+ private final ChatLawRepository chatLawRepository ;
37+
38+ @ Value ("${custom.ai.title-extraction}" )
39+ private String titleExtraction ;
40+ @ Value ("${custom.ai.keyword-extraction}" )
41+ private String keywordExtraction ;
42+
43+ @ KafkaListener (topics = "chat-post-processing" , groupId = "chat-processing-group" )
44+ @ Transactional
45+ public void consume (ChatPostProcessEvent event ) {
46+ try {
47+ History history = historyRepository .findById (event .getHistoryId ())
48+ .orElseThrow (() -> new IllegalArgumentException ("존재하지 않는 채팅방입니다. historyId: " + event .getHistoryId ()));
49+
50+ // 1. 메시지 기억 저장 (Assistant 응답)
51+ ChatMemory chatMemory = MessageWindowChatMemory .builder ()
52+ .maxMessages (10 )
53+ .chatMemoryRepository (chatMemoryRepository )
54+ .build ();
55+
56+ chatMemory .add (String .valueOf (history .getHistoryId ()), new AssistantMessage (event .getChatResponse ()));
57+ chatMemoryRepository .saveAll (String .valueOf (history .getHistoryId ()), chatMemory .get (String .valueOf (history .getHistoryId ())));
58+
59+ // 2. 채팅방 제목 설정 / 및 필터
60+ setHistoryTitle (event .getUserMessage (), history , event .getChatResponse ());
61+
62+ // 3. 채팅 기록 저장
63+ saveChatWithDocuments (history , MessageType .USER , event .getUserMessage (), event .getSimilarCaseDocuments (), event .getSimilarLawDocuments ());
64+ saveChatWithDocuments (history , MessageType .ASSISTANT , event .getChatResponse (), event .getSimilarCaseDocuments (), event .getSimilarLawDocuments ());
65+
66+ // 4. 키워드 추출 및 랭킹 업데이트
67+ if (!event .getChatResponse ().contains ("해당 질문은 법률" )) {
68+ extractAndUpdateKeywordRanks (event .getUserMessage ());
69+ }
70+ } catch (Exception e ) {
71+ log .error ("Kafka 이벤트 처리 중 에러 발생 (historyId: {}): " , event .getHistoryId (), e );
72+ }
73+ }
74+
75+ private void setHistoryTitle (String userMessage , History history , String fullResponse ) {
76+ String targetText = fullResponse .contains ("해당 질문은 법률" ) ? userMessage : fullResponse ;
77+ TitleExtractionDto titleDto = keywordService .keywordExtract (targetText , titleExtraction , TitleExtractionDto .class );
78+ history .setTitle (titleDto .getTitle ());
79+ historyRepository .save (history );
80+ }
81+
82+ private void extractAndUpdateKeywordRanks (String message ) {
83+ KeywordExtractionDto keywordResponse = keywordService .keywordExtract (message , keywordExtraction , KeywordExtractionDto .class );
84+ if (keywordResponse == null || keywordResponse .getKeyword () == null ) {
85+ return ;
86+ }
87+
88+ KeywordRank keywordRank = keywordRankRepository .findByKeyword (keywordResponse .getKeyword ());
89+
90+ if (keywordRank == null ) {
91+ keywordRank = KeywordRank .builder ()
92+ .keyword (keywordResponse .getKeyword ())
93+ .score (1L )
94+ .build ();
95+ } else {
96+ keywordRank .setScore (keywordRank .getScore () + 1 );
97+ }
98+ keywordRankRepository .save (keywordRank );
99+ }
100+
101+ private void saveChatWithDocuments (History history , MessageType type , String message , List <DocumentDto > similarCaseDocuments , List <DocumentDto > similarLawDocuments ) {
102+ Chat chat = chatRepository .save (Chat .builder ()
103+ .historyId (history )
104+ .type (type )
105+ .message (message )
106+ .build ());
107+
108+ // Ai 메시지가 저장될 때 관련 문서 저장
109+ if (type == MessageType .ASSISTANT ) {
110+ if (similarCaseDocuments != null && !similarCaseDocuments .isEmpty ()) {
111+ List <ChatPrecedent > chatPrecedents = similarCaseDocuments .stream ()
112+ .map (doc -> ChatPrecedent .builder ()
113+ .chatId (chat )
114+ .precedentContent (doc .getText ())
115+ .caseNumber (doc .getMetadata ().get ("caseNumber" ).toString ())
116+ .caseName (doc .getMetadata ().get ("caseName" ).toString ())
117+ .build ())
118+ .collect (Collectors .toList ());
119+ chatPrecedentRepository .saveAll (chatPrecedents );
120+ }
121+
122+ if (similarLawDocuments != null && !similarLawDocuments .isEmpty ()) {
123+ List <ChatLaw > chatLaws = similarLawDocuments .stream ()
124+ .map (doc -> ChatLaw .builder ()
125+ .chatId (chat )
126+ .content (doc .getText ())
127+ .lawName (doc .getMetadata ().get ("lawName" ).toString ())
128+ .build ())
129+ .collect (Collectors .toList ());
130+ chatLawRepository .saveAll (chatLaws );
131+ }
132+ }
133+ }
134+ }
0 commit comments