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