diff --git a/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java b/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java index 6170a7a..47ee9c1 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/chat/service/ChatService.java @@ -101,85 +101,52 @@ public List getCategoryBudgetRemainingAmount(User user) { ); }).collect(Collectors.toList()); } -// private String generateScenarioBudgetAnalysisPrompt() { -// return """ -// **반드시 정확히 상황을 판단하고 예산 카테고리를 명확히 파악해 응답하세요!** -// -// 사용자의 메시지를 분석하여 다음 절차를 순서대로 수행하세요: -// -// 1단계. [상황 판단] -// - 사용자가 명확히 두 가지 이상의 상품(서비스 포함) 중에서 고민 중이라면 → `CHOICE_RECOMMENDATION` -// - 사용자가 하나의 상품(서비스 포함)을 구매할지 여부만을 고민 중이라면 → `YES_NO_DECISION` -// -// 2단계. [예산 카테고리 판단] -// - 사용자가 구매를 고민하는 상품이 어떤 소비 카테고리에 포함되는지 정확히 판단하세요. -// - 소비 카테고리는 [예산 카테고리별 남은 금액]에 언급됩니다. (ex. 쇼핑, 식비) -// - 판단할 수 없으면 '기타'로 분류하세요. -// -// 3단계. [남은 예산과 상품 가격 분석] -// - 판단한 예산 카테고리의 남은 예산 금액을 확인하세요. -// - 상품의 가격은 사용자 메시지에 상품의 정확한 가격이 있으면 이를 활용하고, 없다면 일반적이고 합리적인 가격을 가정하세요. -// - 상품의 가격과 남은 예산을 비교하여 구매 가능 여부를 판단하세요. -// 사용자의 남은 예산을 고려하여 두 상품 모두 예산 범위 내에서 구매할 수 있더라도 아직 이번달이 많이 남았다면, -// 앞으로 지출이 생길 가능성을 추론하여 비교적 고가의 상품보다는 상대적으로 합리적인 가격의 상품을 선택하도록 도움 -// 2단계에서 파악한 예산 카테고리를 답변 시 명시적으로 언급하세요. (예: "쇼핑 예산", "문화생활 예산") -// -// 2단계의 결과에 따라 다음 예시 답변 형식을 따르세요: -// -// [답변 예시] -// - 예산 범위 내에서 모두 구매 가능하지만 고가의 상품과 저렴한 상품 간의 고민이 있는 경우: -// "[이름]님, 현재 쇼핑 예산은 70,000원이 남아 있어서 A와 B 모두 선택 가능해요. 다만, B는 A에 비해 더 합리적인 가격이라 예산 관리 측면에서는 좋은 선택일 수 있어요. 이번엔 조금 절약하고, 다음 기회에 A를 선택하시는 것도 좋은 방법이에요. 어떤 결정을 하시든 항상 응원할게요!" -// - 예산은 충분하지만 가성비에 대한 고민이 있는 경우: -// "[이름]님, 현재 문화생활 예산은 30,000원이 남아 있어서 구매 가능해요. 다만 가격 대비 효용성을 중요하게 생각하시는 것 같으니, 가성비 좋은 다른 옵션을 고려하시는 것도 좋아요. 물론 이 상품이 너무 마음에 드신다면 자신을 위한 소소한 선물로 구매하셔도 괜찮답니다!" -// - 예산 초과로 구매가 불가능할 것으로 생각되는 경우: -// "남은 예산으로는 조금 어려워 보여요.😢 더 합리적인 대안을 추천해드릴게요." -// 충동성 지출을 절제하는 것이 장기적으로 더 만족스러운 결정이라는 점을 다정하고 설득력 있게 전달. -// -// 답변은 항상 친절하고 다정하게 사용자의 현명한 소비 의사를 응원하는 어조로 작성하세요. -// """; -// } // 상황 판단 프롬프트 (GPT가 CHOICE_RECOMMENDATION 또는 YES_NO_DECISION 판단) private String generateScenarioPrompt() { return """ - **정확하게 상황을 판단하세요!** - 사용자의 메시지를 분석해 아래 중 정확한 상황을 판단: - - - **CHOICE_RECOMMENDATION**: 사용자가 두 개 이상의 상품(A vs B)을 비교하여 하나를 선택하려는 경우. - 사용자의 남은 예산을 고려하여 두 상품 모두 예산 범위 내에서 구매할 수 있더라도 아직 이번달이 많이 남았다면, - 앞으로 지출이 생길 가능성을 추론하여 비교적 고가의 상품보다는 상대적으로 합리적인 가격의 상품을 선택하도록 도움 - - **YES_NO_DECISION**: 사용자가 특정 상품 하나(혹은 여러 상품을 묶어서)를 살지 말지 결정하려는 경우. - - **주의**: - - 사용자가 특정 상품을 단독으로 언급했어도, 명확한 다른 상품과의 비교가 없다면 YES_NO_DECISION으로 판단하세요. + ## 1. 상황 판단 + - **CHOICE_RECOMMENDATION**: 두 개 이상 상품 비교 요청 (상품 수 무관) + - 여러 상품이 예산 내에 있다면, 남은 기간 추가 지출을 고려해 더 합리적인 선택 제안 + + - **YES_NO_DECISION**: 특정 상품 구매 여부 결정 요청 """; } private String generateBudgetAnalysisPrompt() { return """ - **중요: 예산 분석과 구매 가능성 평가** - - 1. **예산 카테고리 판단** - - 사용자의 메시지에서 상품의 키워드를 분석해, 상품별로 관련있는 예산 카테고리를 매핑. - - 예산 카테고리 종류는 [예산 카테고리별 남은 금액]에 언급됨. (ex. 상품이 음식->식비, 상품이 옷->쇼핑) - - 관련 카테고리가 없으면 "기타"로 분류. - - 2. **남은 예산 확인** - - 해당 예산 카테고리의 현재 남은 예산 금액을 사용자에게 안내. - - 3. **상품 가격 분석** - - 사용자가 가격을 명시했다면 해당 가격을, 그렇지 않다면 일반적인 시장 가격을 가정. - - 4. **구매 가능성 평가 및 추천 답변** - **예산 내 충분히 구매 가능**: - - "충분히 예산 내에서 구매 가능해요! 😊 하지만 다른 지출 계획도 고려해보세요." - - **예산 내 가능하지만 부담되는 경우**: - - "예산이 빠듯할 수 있으니 신중히 결정하거나, 절약할 수 있는 대안을 고려해보는 것도 좋아요." - - **예산 초과로 불가능**: - - "남은 예산으로는 조금 어려워 보여요.😢 더 합리적인 대안을 추천해드릴게요." - - 충동성 지출을 절제하는 것이 장기적으로 더 만족스러운 결정이라는 점을 다정하고 설득력 있게 전달. + ## 2. 예산 분석과 구매 가능성 평가 + 1) 메시지에서 상품별 관련 예산 카테고리를 추출. 예산 카테고리 종류는 [예산 카테고리별 남은 금액]에 언급됨. (ex. 상품이 음식->식비) (없으면 기타) + 2) **[예산 카테고리별 남은 금액]**에서 해당 카테고리 잔여 금액 안내 + 3) 제시 가격 또는 시장가 가정 + 4) 지출 비율 계산(ex. 2만 원 = 잔여 예산의 50%) + 5) 과거 지출 내역 기반 남은 이번 달 소비 경향 추론 + [과거 지출 요약] 블록의 통계치를 참고해, + - 다음 기간 예상 지출 규모 + - 주요 지출 패턴(빈도·증가·감소 추이) + 등을 추론해서 반영 + 6) 남은 기간과 날짜 고려해 최종 소비 가능 여부·추천 옵션 산출 + + ## 3. 답변 구성 + - **충분히 가능** + - 구체적 수치(금액)로 설명 + - 기대 효과 언급 (만족감, 편리함) + - **부담되는 경우** + - 현재 부담 정도 설명 + - 대안 제시 + - 미래 소비 예측 반영 + - **불가능할 때** + - 친절하고 설득력 있게 공감 + - 충동 지출 자제 권유 + - 감성적 동기 부여 (“다음에 더 맛있게…” 등) + - **출력 구조** + 1) 비교·수치 분석 설명 단락 \s + 2) 최종 추천 + 감성 동기부여 단락 + ## 4. 톤 & 포맷 + - 숫자를 정확하게 + - 이모지 1~2개로 친근함 추가 + - 마크다운 사용 금지 + - 문장 끝은 모두 '~요'체로 마무리 """; } @@ -187,20 +154,14 @@ private String generateBudgetAnalysisPrompt() { private String generateSampleResponse(String userNickname) { return String.format( """ - [상황 예시] 쇼핑 카테고리 예산이 70,000원 남은 상황을 가정 + [적절한 응답 분량]: 250~350자 내외 [상황별 응답 예시] - - **if CHOICE_RECOMMENDATION** (구체적 상품명을 반드시 언급): - "%s님, 현재 쇼핑 예산은 70,000원이 남았어요. \n\ - 나이키 운동화와 아디다스 운동화 모두 예산 내에서 가능하지만 😊, 아디다스 제품이 더 저렴하고 부담이 적어요. 앞으로의 지출 계획까지 고려해 선택하는 게 현명할 것 같아요!" - - **if YES_NO_DECISION** (구체적 상품명을 반드시 언급): - "%s님, 쇼핑 예산 70,000원으로 무신사 티셔츠는 구매 가능해요. \n\ - 다만, 다른 필수 지출도 생각해보고 결정하면 좋겠어요!" - - [적절한 응답 분량]: - {사용자}님, 현재 남은 예산 내에서 고민하시는 두 상품 모두 충분히 선택할 수 있어요. 다만 {A}쪽이 {B}보다 조금 더 고급스러워서 예산 관리 측면에서 살짝 고민될 수 있을 것 같아요. 내심 더 고가의 {A}이 끌리시지만, 조금 참고 상대적으로 합리적인 {B}로 선택의 균형을 잡으려 하시는 것 같네요. - 이럴 때는 가격도 합리적이고, 만족감이나 실용성{B의 장점}에서도 절대 부족함 없는 {B}을 선택하시면 장기적으로 더 뿌듯하실 거예요. 다음에 더 고급스러운 상품을 선택할 때의 즐거움이 더 특별해질지도 모르죠! 😊 어떤 결정을 하시든 현명한 소비를 응원합니다! + - **if CHOICE_RECOMMENDATION** (구체적 상품명을 반드시 언급): + "%s님, 현재 식비 예산이 30,000원 남아있어요. 고기(20,000원)는 잔여 예산의 67%%를 차지해요. 조금 큰 비중이라, 하루 식사로 지출하기엔 조금 부담스러울 수 있어요. 컵밥(3,000원)을 선택하시면 다음 주 예상 식비 지출(적어도 4~5만 원 이상 예상)에 대한 부담이 훨씬 줄어들 거예요.\\n가끔은 작은 절약이 더 큰 만족으로 돌아와요. 오늘은 컵밥으로 아끼고, 고기는 다음에 조금 더 여유 있을 때 드시면 더 맛있고 기분 좋게 드실 수 있을 거라 생각해요!😊" + - **if YES_NO_DECISION** (구체적 상품명을 반드시 언급): + "%s님, 쇼핑 예산이 50,000원 남아있어요. 100,000원짜리 옷을 사면 예산을 50,000원 초과하게 됩니다. 아직 한 달이 많이 남았으니, 이번 달 다른 지출을 최소화해 예산을 재조정하면 가능할 수도 있어요.\\n예를 들어, 외식비나 카페비에서 50,000원만 절약하면 오늘 옷 구매가 부담 없이 이루어질 거예요. 😊 그렇지만, 조금 더 여유를 모아 다음 달 초에 구매하는 것도 한 방법이에요. %s님이 기분 좋게 쇼핑하실 수 있도록 응원할게요!" """, - userNickname, userNickname + userNickname, userNickname, userNickname ); } @@ -215,7 +176,11 @@ public String generateGPTResponseForChat(User user, String userMessage, String b String scenarioPrompt = generateScenarioPrompt(); String budgetAnalysisPrompt = generateBudgetAnalysisPrompt(); String sampleResponse = generateSampleResponse(user.getNickname()); - +// String expenseInfo = """ +// - 식비: 월평균 400,000원, 주 3회 지출 +// - 쇼핑: 월평균 80,000원, 월 2회 지출 +// - 교통: 월평균 70,000원, 주 5회 이용 +// """; LocalDate today = LocalDate.now(); String message = String.format( @@ -231,8 +196,8 @@ public String generateGPTResponseForChat(User user, String userMessage, String b %s --- - [오늘 날짜]: %s - [사용자 %s의 메시지]: "%s" + [오늘]: %s + [사용자 %s]: "%s" %s @@ -242,12 +207,13 @@ public String generateGPTResponseForChat(User user, String userMessage, String b scenarioPrompt, budgetAnalysisPrompt, budgetInfo, +// expenseInfo, sampleResponse, today, user.getNickname(), userMessage, finalInstruction ); - return openAIService.generateGPTResponse(message, OpenAIOptionEnum.LOGICAL); + return openAIService.generateGPTResponse(message, OpenAIOptionEnum.BALANCED); } } diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/controller/TransactionController.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/controller/TransactionController.java index f5c1e30..cad3e5d 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/controller/TransactionController.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/controller/TransactionController.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.time.LocalDate; +import java.util.List; @Tag(name = "Transaction") @RestController @@ -38,6 +39,17 @@ public ResponseEntity createExpense(@RequestBody @Valid ExpenseAddReques return ResponseEntity.ok(category); } + @Operation(summary = "지출 내역 여러 건 추가") + @PostMapping("/expenses-multiple") + public ResponseEntity createMultipleExpenses( + @RequestBody @Valid List requests) { + // 각 DTO마다 서비스 호출 + for (ExpenseAddRequestDto dto : requests) { + expenseService.createExpense(dto); + } + return ResponseEntity.ok().build(); + } + @Operation(summary = "수입 내역 추가") @PostMapping("/incomes") public ResponseEntity createIncome(@RequestBody @Valid IncomeAddRequestDto request) { diff --git a/mooney/src/main/java/tamtam/mooney/global/openai/OpenAIService.java b/mooney/src/main/java/tamtam/mooney/global/openai/OpenAIService.java index 1f5f52c..100acf2 100644 --- a/mooney/src/main/java/tamtam/mooney/global/openai/OpenAIService.java +++ b/mooney/src/main/java/tamtam/mooney/global/openai/OpenAIService.java @@ -24,8 +24,9 @@ public String generateGPTResponse(String message, OpenAIOptionEnum optionType) { public String generateUserAgentPrompt(UserAgent userAgent) { return String.format( """ - 너는 "%s"야. %s의 성격, %s한 어조를 가진 금융 어시스턴트야. - 항상 이 성격과 말투를 유지하며 대답해야 해. 한국어로 대답하고, 마크다운은 절대 사용하지 마. + 너는 "%s"야. + - %s의 성격, %s한 어조를 가진 금융 어시스턴트. 항상 이 성격과 말투를 유지. + - 한국어로 대답, 마크다운은 절대 사용 금지 """, userAgent.getAgent().getAgentName(), userAgent.getAgent().getPersonality(),