66import com .back .domain .chatbot .repository .ChatConversationRepository ;
77import lombok .RequiredArgsConstructor ;
88import lombok .extern .slf4j .Slf4j ;
9+ import org .springframework .beans .factory .annotation .Value ;
10+ import org .springframework .core .io .Resource ;
911import org .springframework .stereotype .Service ;
1012import org .springframework .transaction .annotation .Transactional ;
13+ import org .springframework .util .StreamUtils ;
1114
15+ import jakarta .annotation .PostConstruct ;
16+ import java .io .IOException ;
17+ import java .nio .charset .StandardCharsets ;
1218import java .util .List ;
1319import java .util .UUID ;
1420
@@ -20,6 +26,25 @@ public class ChatbotService {
2026 private final GeminiApiService geminiApiService ;
2127 private final ChatConversationRepository chatConversationRepository ;
2228
29+ @ Value ("classpath:prompts/chatbot-system-prompt.txt" )
30+ private Resource systemPromptResource ;
31+
32+ @ Value ("classpath:prompts/chatbot-response-rules.txt" )
33+ private Resource responseRulesResource ;
34+
35+ @ Value ("${chatbot.history.max-conversation-count:5}" )
36+ private int maxConversationCount ;
37+
38+ private String systemPrompt ;
39+ private String responseRules ;
40+
41+ @ PostConstruct
42+ public void init () throws IOException {
43+ this .systemPrompt = StreamUtils .copyToString (systemPromptResource .getInputStream (), StandardCharsets .UTF_8 );
44+ this .responseRules = StreamUtils .copyToString (responseRulesResource .getInputStream (), StandardCharsets .UTF_8 );
45+ log .info ("챗봇 시스템 프롬프트가 로드되었습니다. (길이: {} 문자)" , systemPrompt .length ());
46+ }
47+
2348 @ Transactional
2449 public ChatResponseDto sendMessage (ChatRequestDto requestDto ) {
2550 String sessionId = requestDto .getSessionId ();
@@ -50,108 +75,44 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
5075 }
5176
5277 private String buildContextualMessage (String userMessage , String sessionId ) {
53- List <ChatConversation > recentConversations = chatConversationRepository
54- .findBySessionIdOrderByCreatedAtAsc (sessionId );
55-
56- // 시스템 프롬프트 정의
57- String systemPrompt = """
58- 당신은 'Ssoul' 칵테일 전문 AI 바텐더입니다.
59-
60- ## 역할과 페르소나
61- - 이름: 쑤울 AI 바텐더
62- - 성격: 친근하고 전문적이며, 유머러스하면서도 신뢰할 수 있는 칵테일 전문가
63- - 말투: 반말이 아닌 존댓말을 사용하며, 친근한 바텐더처럼 대화
64- - 특징: 칵테일에 대한 깊은 지식과 함께 상황에 맞는 칵테일 추천 능력
65-
66- ## 핵심 기능
67- 1. **칵테일 정보 제공**: 레시피, 역사, 특징, 맛 프로필 설명
68- 2. **칵테일 추천**: 기분, 상황, 계절, 개인 취향에 따른 맞춤 추천
69- 3. **칵테일 제조 가이드**: 단계별 제조 방법과 팁 제공
70- 4. **칵테일 문화 소개**: 칵테일 에티켓, 바 문화, 트렌드 정보
71- 5. **초보자 가이드**: 칵테일 입문자를 위한 쉬운 설명
72-
73- ## 응답 가이드라인
74- 1. **정확성**: 칵테일 레시피는 표준 레시피를 기반으로 정확하게 제공
75- 2. **구조화**: 레시피 제공 시 재료와 제조법을 명확히 구분
76- 3. **개인화**: 사용자의 취향과 상황을 고려한 맞춤형 조언
77- 4. **안전성**: 과도한 음주를 권장하지 않고, 책임감 있는 음주 문화 조성
78- 5. **창의성**: 클래식 칵테일 외에도 현대적 변형이나 논알콜 대안 제시
79-
80- ## 응답 길이 제한
81- - **기본 답변**: 200자 이내로 간결하게 작성
82- - **레시피 제공**: 최대 300자 이내로 핵심만 전달
83- - **복잡한 설명**: 필요시 "더 알고 싶으시면 추가로 질문해주세요"로 마무리
84- - **한 문단**: 최대 3-4문장으로 제한
85-
86- ## 레시피 제공 형식 (간소화 버전)
87- 칵테일 레시피를 제공할 때는 다음 형식을 따라주세요:
88-
89- 🍹 **[칵테일 이름]**
90-
91- **📝 재료:**
92- - 재료1: 용량 (예: 보드카 45ml)
93- - 재료2: 용량
94- - 가니쉬: 설명
95-
96- **🥄 제조법:**
97- 1. 첫 번째 단계
98- 2. 두 번째 단계
99- 3. 마무리 단계
100-
101- **💡 팁:** 특별한 조언이나 변형 방법
102-
103- **🎭 특징:** 맛 프로필, 도수, 추천 상황
104-
105- ## 대화 원칙
106- 1. 칵테일과 관련 없는 질문에도 칵테일과 연결지어 창의적으로 답변
107- 2. 사용자가 초보자인지 전문가인지 파악하여 설명 수준 조절
108- 3. 계절, 시간대, 날씨를 고려한 추천 제공
109- 4. 한국의 음주 문화와 트렌드를 반영한 조언
110- 5. 이모지를 적절히 사용하여 친근감 형성
111-
112- ## 특별 지시사항
113- - 논알콜 칵테일(목테일)도 적극적으로 소개
114- - 홈바 입문자를 위한 기본 도구와 재료 안내
115- - 칵테일과 어울리는 안주나 분위기 추천
116- - 과음 방지를 위한 적절한 조언 포함
117- """ ;
78+ List <ChatConversation > recentConversations = getRecentConversations (sessionId );
11879
11980 StringBuilder contextBuilder = new StringBuilder ();
12081 contextBuilder .append (systemPrompt ).append ("\n \n " );
12182
122- // 이전 대화 내용 추가
123- if (!recentConversations .isEmpty ()) {
83+ appendConversationHistory (contextBuilder , recentConversations );
84+ appendCurrentQuestion (contextBuilder , userMessage );
85+ appendResponseInstructions (contextBuilder );
86+
87+ return contextBuilder .toString ();
88+ }
89+
90+ private List <ChatConversation > getRecentConversations (String sessionId ) {
91+ return chatConversationRepository .findBySessionIdOrderByCreatedAtAsc (sessionId );
92+ }
93+
94+ private void appendConversationHistory (StringBuilder contextBuilder , List <ChatConversation > conversations ) {
95+ if (!conversations .isEmpty ()) {
12496 contextBuilder .append ("=== 이전 대화 기록 ===\n " );
12597
126- // 최근 5개의 대화만 컨텍스트에 포함
127- int maxHistory = Math .min (recentConversations .size (), 5 );
128- int startIdx = Math .max (0 , recentConversations .size () - maxHistory );
98+ int maxHistory = Math .min (conversations .size (), maxConversationCount );
99+ int startIdx = Math .max (0 , conversations .size () - maxHistory );
129100
130- for (int i = startIdx ; i < recentConversations .size (); i ++) {
131- ChatConversation conv = recentConversations .get (i );
101+ for (int i = startIdx ; i < conversations .size (); i ++) {
102+ ChatConversation conv = conversations .get (i );
132103 contextBuilder .append ("사용자: " ).append (conv .getUserMessage ()).append ("\n " );
133104 contextBuilder .append ("AI 바텐더: " ).append (conv .getBotResponse ()).append ("\n \n " );
134105 }
135106 contextBuilder .append ("=================\n \n " );
136107 }
108+ }
137109
138- // 현재 질문 처리
110+ private void appendCurrentQuestion ( StringBuilder contextBuilder , String userMessage ) {
139111 contextBuilder .append ("현재 사용자 질문: " ).append (userMessage ).append ("\n \n " );
112+ }
140113
141- // 응답 지시 및 길이 제한
142- contextBuilder .append ("위의 시스템 프롬프트와 대화 기록을 참고하여, " );
143- contextBuilder .append ("'쑤울 AI 바텐더'로서 친근하고 전문적인 답변을 제공해주세요. " );
144- contextBuilder .append ("칵테일과 관련된 유용한 정보를 포함하되, 자연스럽고 대화하듯 응답해주세요.\n \n " );
145-
146- // 답변 길이 제한 추가
147- contextBuilder .append ("【중요한 응답 규칙】\n " );
148- contextBuilder .append ("- 답변은 반드시 500자 이내로 작성하세요.\n " );
149- contextBuilder .append ("- 핵심 정보만 간결하게 전달하세요.\n " );
150- contextBuilder .append ("- 불필요한 설명은 생략하고 요점만 말해주세요.\n " );
151- contextBuilder .append ("- 레시피 설명 시에도 간단명료하게 작성하세요.\n " );
152- contextBuilder .append ("- 한 문단은 최대 3-4문장을 넘지 않도록 하세요." );
153-
154- return contextBuilder .toString ();
114+ private void appendResponseInstructions (StringBuilder contextBuilder ) {
115+ contextBuilder .append (responseRules );
155116 }
156117
157118 @ Transactional (readOnly = true )
0 commit comments