-// );
-// };
-
-// export default PlanChatTester;
diff --git a/server/app.js b/server/app.js
index 5f0cd95..672b725 100644
--- a/server/app.js
+++ b/server/app.js
@@ -2,7 +2,6 @@ import cors from 'cors';
import dotenv from 'dotenv';
import express from 'express';
import mongoose from 'mongoose';
-import { getInputExamples } from './controllers/chatController.js';
import {
getAffordablePlanList,
getBundlePlanList,
@@ -40,7 +39,6 @@ mongoose
const apiRouter = express.Router();
app.use('/api', apiRouter);
-apiRouter.get('/chat/inputs', getInputExamples);
apiRouter.get('/metadata', getUrlMetadata);
apiRouter.get('/plans/:planId', getPlanDetail);
apiRouter.get('/unlimited-plans', getUnlimitedDataPlanList);
diff --git a/server/cache/planCache.js b/server/cache/planCache.js
deleted file mode 100644
index afced6f..0000000
--- a/server/cache/planCache.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Plan } from '../models/Plan.js';
-
-let cachedPlans = [];
-let lastUpdated = 0;
-const TTL = 1000 * 60 * 60;
-
-export const getPlansWithCache = async () => {
- const now = Date.now();
- if (now - lastUpdated > TTL || cachedPlans.length === 0) {
- cachedPlans = await Plan.find({});
- lastUpdated = now;
- }
- return cachedPlans;
-};
diff --git a/server/config/db.js b/server/config/db.js
deleted file mode 100644
index 91fc62f..0000000
--- a/server/config/db.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** 스키마를 유연하게 수정할 때 사용합니다 */
-
-import dotenv from 'dotenv';
-import { MongoClient } from 'mongodb';
-
-dotenv.config();
-
-const client = new MongoClient(process.env.MONGO_URI);
-
-export const changeSchema = async () => {
- const database = client.db('meplus');
- const collection = database.collection('plans');
- const refCollection = database.collection('bundleBenefits');
-
- const result = await collection.find({}).toArray();
-
- for (const item of result) {
- const target = await refCollection.findOne({ _id: item.bundleBenefit });
-
- const updated = target
- ? {
- _id: item.bundleBenefit,
- name: target.name,
- description: target.description,
- }
- : null;
-
- await collection.updateOne(
- { _id: item._id },
- { $set: { bundleBenefit: updated } },
- );
- }
-};
diff --git a/server/controllers/chatController.js b/server/controllers/chatController.js
deleted file mode 100644
index b02804e..0000000
--- a/server/controllers/chatController.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { openai } from '../services/gptService.js';
-
-/** 채팅 시작: 서술형 답변 예시 가져오기 */
-export const getInputExamples = async (req, res) => {
- const getChatResponse = async () => {
- const input = [
- {
- role: 'system',
- content:
- '한국어로 요금제 추천 챗봇에게 사용자가 입력할 수 있는 질문 예시를 배열 형태로 3개 생성해줘. 예시는 자연스럽고 실제 사용자 질문처럼 작성하되, 너무 창의적인 표현은 피하고, 단정적이고 실용적인 문장으로 구성해줘. 부연 설명 없이 배열로만 답변해줘.\n답변: [입력 예시1, 입력 예시2, 입력 예시3]',
- },
- ];
-
- return await openai.responses.create({
- model: 'gpt-4.1-nano',
- input,
- temperature: 0.5,
- max_output_tokens: 256,
- top_p: 0.9,
- });
- };
-
- try {
- const chatResponse = await getChatResponse();
- const endIndex = chatResponse.output_text.length - 1;
- const inputs = chatResponse.output_text.slice(1, endIndex).split(', ');
-
- return res.status(200).json({ inputs });
- } catch (error) {
- return res.sendStatus(500);
- }
-};
diff --git a/server/models/ChatSession.js b/server/models/ChatSession.js
index 92fca2f..c7d594d 100644
--- a/server/models/ChatSession.js
+++ b/server/models/ChatSession.js
@@ -4,7 +4,10 @@ const ChatSessionSchema = new mongoose.Schema({
sessionId: String, // 클라이언트에서 받은 고유 ID
messages: [
{
- role: { type: String, enum: ['user', 'assistant', 'system'] },
+ role: {
+ type: String,
+ enum: ['user', 'assistant', 'developer', 'system'],
+ },
content: String,
type: { type: String, default: 'text' }, // 추가: 메시지 타입 (text, carousel_select, ox_select 등)
data: { type: mongoose.Schema.Types.Mixed, default: null }, // 추가: 선택 데이터
diff --git a/server/services/gptFuncDefinitions.js b/server/services/gptFuncDefinitions.js
index 52c4e6a..69d8125 100644
--- a/server/services/gptFuncDefinitions.js
+++ b/server/services/gptFuncDefinitions.js
@@ -322,8 +322,6 @@ export const searchPlansFromDB = async (searchConditions) => {
}
}
- console.log('📋 생성된 MongoDB 쿼리:', JSON.stringify(query, null, 2));
-
// 쿼리 실행
const plans = await Plan.find(query)
.select(EXCLUDED_FIELDS)
diff --git a/server/services/gptFunctionHandler.js b/server/services/gptFunctionHandler.js
index ba2a71c..9d40d59 100644
--- a/server/services/gptFunctionHandler.js
+++ b/server/services/gptFunctionHandler.js
@@ -31,11 +31,6 @@ const parseFunctionArgs = (functionArgsRaw) => {
.replace(/\s+/g, ' ')
.trim();
- console.log(
- '🔄 변환 시도 (처음 200자):',
- fixedJson.substring(0, 200) + '...',
- );
-
return JSON.parse(fixedJson);
} catch (secondParseError) {
// eval 방식으로 재시도
@@ -46,7 +41,6 @@ const parseFunctionArgs = (functionArgsRaw) => {
console.error('❌ 최종 JSON 파싱 실패:', secondParseError);
console.error('❌ eval 방식도 실패:', evalError);
console.log('🔍 원본:', functionArgsRaw);
- console.log('🔍 변환 시도:', fixedJson);
throw new Error('Function arguments 파싱에 실패했습니다.');
}
}
diff --git a/server/services/gptService.js b/server/services/gptService.js
index 5a99ec4..40aca31 100644
--- a/server/services/gptService.js
+++ b/server/services/gptService.js
@@ -100,7 +100,7 @@ export const streamChat = async (
}
// 모든 함수 호출 실행
- console.log(functionCalls);
+ console.log('2:', functionCalls);
const functionResults = [];
for (const { functionName, functionArgsRaw } of functionCalls) {
const result = await handleFunctionCall(
@@ -111,20 +111,20 @@ export const streamChat = async (
// 함수 실행 정보 추가
functionResults.push({
- role: 'assistant',
+ role: 'developer',
content: `${functionName} 함수를 호출했습니다. 인자: ${functionArgsRaw}`,
});
if (functionName === 'searchPlans' && result) {
if (result.result === 'empty') {
functionResults.push({
- role: 'function',
+ role: 'developer',
name: functionName,
content: `검색 결과: 빈 배열 (조건에 맞는 요금제 없음)`,
});
} else if (result.result === 'found') {
functionResults.push({
- role: 'function',
+ role: 'developer',
name: functionName,
content: `검색 결과: ${result.plansCount}개 요금제 발견 (${result.planNames?.join(', ')})`,
});
@@ -165,10 +165,10 @@ export const streamChatWithFollowUp = async (messages, socket, onDelta) => {
if (hasFunctionCalls) {
// 역질문 대상 함수들
const followUpTargetFunctions = ['requestTextCard', 'searchPlans'];
- console.log(functionResults);
+ console.log('1:', functionResults);
// 실행된 함수들 중 역질문 대상이 있는지 확인
const executedFunctionNames = functionResults
- .filter((result) => result.role === 'assistant')
+ .filter((result) => result.role === 'developer')
.map((result) => {
const match = result.content.match(/^(\w+) 함수를 호출했습니다/);
return match ? match[1] : null;
@@ -211,20 +211,32 @@ const generateFollowUpQuestion = async (
// 실행된 함수들 정보 추출
const executedFunctions = functionResults
- .filter((result) => result.role === 'assistant')
+ .filter((result) => result.role === 'developer')
.map((result) => result.content)
.join('\n');
// requestTextCard가 이미 실행되었는지 확인
const hasTextCardExecuted = functionResults.some(
(result) =>
- result.role === 'assistant' && result.content.includes('requestTextCard'),
+ result.role === 'developer' && result.content.includes('requestTextCard'),
);
+ // searchPlans 결과가 빈 배열인지 확인
+ const hasEmptySearchResult = functionResults.some(
+ (result) =>
+ result.role === 'developer' && result.content.includes('빈 배열'),
+ );
+
+ console.log(
+ '여기',
+ hasTextCardExecuted,
+ executedFunctions,
+ hasEmptySearchResult,
+ );
const followUpMessages = [
{
role: 'system',
- content: `너는 요금제 추천 후 고객에게 추가 혜택을 안내하는 상담사야.
+ content: `너는 요금제 추천 후 이어서 고객에게 추가 혜택을 안내하는 상담사야.
**ImageCard(requestTextCard) 실행 확인:**
${
@@ -235,66 +247,62 @@ ${
: `- 아직 링크 정보가 제공되지 않았으므로, 아래 패턴에 따라 추가 혜택 질문을 진행해도 됨`
}
-**검색 결과 확인 우선:**
-- 방금 searchPlans 함수가 빈 배열([])을 반환했다면, 조건에 맞는 요금제가 없다는 뜻이야
-- 이 경우 "조건에 맞는 요금제를 찾지 못했어요. 😅 다른 옵션을 확인해보시는 것은 어떨까요?"라고 안내하고 다음 중 하나를 제안해줘:
+**searchPlans 검색 결과 확인:**
+${hasEmptySearchResult ? '검색 결과가 비어있음 (조건에 맞는 요금제 없음)' : '검색 결과 있음 (요금제 발견됨)'}
+
+${
+ hasEmptySearchResult
+ ? `
+**검색 결과 없음 - 대안 제시 필수:**
+"조건에 맞는 요금제를 찾지 못했어요. 😅 다른 옵션을 확인해보시는 것은 어떨까요?"라고 안내하고 다음 중 하나를 제안해줘:
-**검색 결과 없음 시 대안 제시:**
1. "예산을 조금 더 늘려서 찾아볼까요?" → requestCarouselButtons로 더 높은 가격대 옵션 제공
2. "다른 통신 기술(5G/LTE)도 함께 살펴보시겠어요?" → requestOXCarouselButtons 호출
3. "대신 인기 요금제들을 추천해드릴까요?" → requestCarouselButtons로 ["인기 요금제 보기", "조건 다시 설정", "상담원 연결"] 제공
4. "조건을 다시 설정해서 찾아보시겠어요?" → requestCarouselButtons로 새로운 선택지 제공
-
-**검색 결과가 있는 경우에만 아래 추가 혜택 질문:**
+`
+ : `
+**검색 결과 있음 - 추가 혜택 질문:`
+}
이미 요금제를 보여줬으니, 요금제 설명은 다시 하지 말고 추가 혜택 질문만 해줘:
-**중요: 질문 텍스트를 먼저 출력하고 그 다음에 함수 호출**
-
-**질문 예시들:**
-1. "혹시 가족 구성원 중 만 18세 이하의 청소년 자녀가 있으신가요? 있으시다면 추가 결합 혜택도 안내드릴게요!"
- → 이 질문 텍스트를 먼저 출력한 후 requestOXCarouselButtons 호출
+**우선순위별 질문 예시들:**
+1. **가족 할인 확인 (최우선)**: "혹시 가족 구성원 중 만 18세 이하의 청소년 자녀가 있으신가요? 있으시다면 추가 결합 혜택도 안내드릴게요!"
+ → requestOXCarouselButtons 호출
-2. "혹시 사용 중인 인터넷이 있으신가요? LG U+에서 500Mbps 이상 인터넷을 사용 중이시면 추가 할인을 받을 수 있어요!"
- → 이 질문 텍스트를 먼저 출력한 후 requestOXCarouselButtons 호출
+2. **인터넷 결합 할인**: "혹시 사용 중인 인터넷이 있으신가요? LG U+에서 500Mbps 이상 인터넷을 사용 중이시면 추가 할인을 받을 수 있어요!"
+ → requestOXCarouselButtons 호출
-3. "평소 한 달에 데이터를 얼마나 사용하시나요? 더 정확한 요금제를 추천드릴게요!"
- → 이 질문 텍스트를 먼저 출력한 후 requestCarouselButtons 호출
-
-4. "평소 자주 시청하시는 OTT 서비스가 있으신가요? 요금제와 함께 이용하시면 더 저렴해질 수 있어요!"
- → 이 질문 텍스트를 먼저 출력한 후 requestOTTServiceList 호출
+3. **데이터 사용량 재확인**: "평소 한 달에 데이터를 얼마나 사용하시나요? 더 정확한 요금제를 추천드릴게요!"
+ → requestCarouselButtons 호출
+
+**중요**: 가족 할인이나 인터넷 결합 할인을 우선적으로 물어보고, OTT 서비스는 꼭 필요한 경우에만 질문해줘.
**절대 규칙:**
-- 요금제 정보는 절대 다시 설명하지 마
-- **매우 중요**: 반드시 질문 텍스트를 먼저 출력하고 그 다음에 함수 호출해야 함
-- 텍스트 없이 바로 함수만 호출하는 것은 절대 금지
-- "답변해주세요", "알려주세요" 같은 추가 멘트 금지
-- 검색 결과가 없으면 검색 결과 없음 대안 제시가 우선, 결과가 있으면 추가 혜택 질문
+요금제 정보는 절대 다시 설명하지 마
+매우 중요: 반드시 질문 텍스트를 먼저 출력하고 그 다음에 함수 호출해야 함
+텍스트 없이 바로 함수만 호출하는 것은 절대 금지
+검색 결과가 없으면 검색 결과 없음 대안 제시가 우선, 결과가 있으면 추가 혜택 질문
+- 텍스트 없이 바로 requestCarouselButtons 호출 금지
+- 텍스트 없이 바로 requestOXCarouselButtons 호출 금지
+- 텍스트 없이 바로 requestOTTServiceList 호출 금지
**올바른 응답 형식:**
-1. 먼저 텍스트로 질문을 출력 (예: "혹시 가족 구성원 중 만 18세 이하의 청소년 자녀가 있으신가요?")
-2. 그 다음에 함수 호출 (예: requestOXCarouselButtons)
+1.먼저 텍스트로 질문을 출력 (예: "혹시 가족 구성원 중 만 18세 이하의 청소년 자녀가 있으신가요?")
+2.그 다음에 함수 호출 (예: requestOXCarouselButtons)
-**잘못된 예시 (금지):**
-- 텍스트 없이 바로 requestCarouselButtons 호출
-- 텍스트 없이 바로 requestOXCarouselButtons 호출
-- 텍스트 없이 바로 requestOTTServiceList 호출 `,
- },
- ...userMessages,
- {
- role: 'assistant',
- content: '요금제를 확인해보세요.',
+`,
},
{
role: 'system',
- content: `방금 실행된 함수들:
-${executedFunctions}
-
+ content: `
🚨 중요: 무조건 아래 순서대로 해야 함:
1. 먼저 텍스트로 질문 출력 (예: "혹시 가족분들과 함께 가입하시면 더 저렴해질 수 있는데, 관심 있으신가요?")
2. 그 다음에 함수 호출 (예: requestOXCarouselButtons)
텍스트 없이 바로 함수만 호출하는 것은 절대 금지. 반드시 텍스트 먼저 출력하고 함수 호출.`,
},
+ ...userMessages,
];
// 역질문 전용 streamChat 호출 (FOLLOWUP_TOOLS 사용)
@@ -366,9 +374,6 @@ const streamChatForFollowUp = async (messages, socket, model) => {
}
}
- // 역질문 함수 호출 실행
- console.log('Has text content:', hasTextContent);
-
for (const { functionName, functionArgsRaw } of functionCalls) {
await handleFunctionCall(functionName, functionArgsRaw, socket);
}
diff --git a/server/socket/socket.js b/server/socket/socket.js
index f7833bc..39e98c4 100644
--- a/server/socket/socket.js
+++ b/server/socket/socket.js
@@ -26,32 +26,6 @@ export const setupSocket = (server) => {
handlePlanRecommend(socket, userInput);
});
- /** 가이드 별 적절한 요금제를 추천 */
- socket.on('recommend-plan-by-guide', async (message) => {
- console.log('recommend-plan-by-guide >>', message);
- const input = [
- {
- role: InputRoleEnum.SYSTEM,
- content:
- '너는 사용자의 조건에 맞는 휴대폰 요금제를 추천하는 전문가 챗봇이야. 사용자가 요금제 조건을 입력하면, 반드시 한 번 조건에 맞는 함수를 호출하여 데이터를 기반으로 요금제를 추천해야 해.\n\n요금제를 추천하는 이유는 간결하고 명확하게 설명해줘. 설명은 3줄 이내로 요약하고, 사용자의 조건(예: 데이터 용량, 가격, 연령대, 결합 혜택 등)과 관련된 요점만 언급해줘. 추천 이유만 말해야 해.',
- },
- {
- role: InputRoleEnum.USER,
- content: `조건: ${conditionByPlanGuide[message.guide]}`,
- },
- ];
-
- const planInput = await emitRecommendReasonByGuide(input, socket);
- const systemInput = {
- role: InputRoleEnum.SYSTEM,
- content:
- '너는 사용자의 조건에 맞는 휴대폰 요금제를 추천하는 전문가 챗봇이야. 주어진 조건과 추천 이유, 요금제 데이터를 보고 추천하는 요금제의 ID 목록을 최대 3가지 출력해줘. ID는 실제 데이터에 있는 _id를 사용해야 해. 응답은 반드시 배열로 출력해야 하고 다른 문장은 출력하면 안돼.',
- };
- console.log([systemInput, ...planInput.slice(1)]);
- const ids = await getPlanIds([systemInput, ...planInput.slice(1)]);
- socket.emit('recommend-plan-by-guide', { plans: ids });
- });
-
socket.on('disconnect', () => {
console.log('❌ User disconnected:', socket.id);
});
diff --git a/server/utils/constants.js b/server/utils/constants.js
index bedeece..4e5a228 100644
--- a/server/utils/constants.js
+++ b/server/utils/constants.js
@@ -63,8 +63,10 @@ export const conditionByPlanGuide = {
export const InputRoleEnum = {
USER: 'user',
- SYSTEM: 'system',
+ DEVELOPER: 'developer',
ASSISTANT: 'assistant',
+ PLATFORM: 'platform',
+ SYSTEM: 'system',
};
export const GPTConfig = {
diff --git a/server/utils/promptBuilder.js b/server/utils/promptBuilder.js
index c512870..df05d68 100644
--- a/server/utils/promptBuilder.js
+++ b/server/utils/promptBuilder.js
@@ -1,21 +1,27 @@
export const buildPromptMessages = (fullMessages) => {
const systemMessage = {
role: 'system',
- content: `너는 LG유플러스 요금제 추천 도우미야! 반드시 간단한 인사와 요금제 추천과 관련된 질문에만 응답해야 해. 요금제 외의 질문(예: 요리 레시피, 날씨, 일반 상식 등)은 답변하지 말고 "저는 요금제 추천 도우미입니다. 📱💡" 라면서 요금제 추천에 관심이 있냐고 유저에게 친절하게 안내해.
+ content: `절대 금지 사항 (최우선)
+1. 구체적인 선택지를 텍스트로 나열하지 마라! (예: "3-5만원, 5-7만원, 7-10만원...")
+2. 이전에 assistant로 응답받은 내용을 다시 말하지 마라!
+3. 어떤 버튼으로 호출한다고 알려주지마
-**Function Calling 활용 가이드**
+너는 LG유플러스 요금제 추천 도우미야! 반드시 간단한 인사와 요금제 추천과 관련된 질문에만 응답해야 해. 요금제 외의 질문(예: 요리 레시피, 날씨, 일반 상식 등)은 답변하지 말고 "저는 요금제 추천 도우미입니다. 📱💡" 라면서 요금제 추천에 관심이 있냐고 유저에게 친절하게 안내해.
+
+Function Calling
아래 5가지 함수들을 적절한 상황에 맞춰 호출해야 해. 이 함수들은 유저가 일일이 타이핑하는 수고를 덜어주고 빠른 선택을 도와주기 위한 것이야!
- **함수 호출 우선 원칙**: 직접 응답보다는 함수 호출을 통해 사용자 경험을 향상시켜야 해.
+ 함수 호출 우선 원칙: 직접 응답보다는 함수 호출을 통해 사용자 경험을 향상시켜야 해.
- **Function Calling 목록** (총 6개):
+ Function Calling 목록 (총 6개):
-1. **searchPlans**: 사용자가 요구하는 조건에 맞는 요금제를 MongoDB에서 검색해서 추천할 때 사용해. 카테고리(5G/LTE), 최대월요금, 최소데이터량, 연령대, 인기여부 등의 조건을 설정할 수 있고, 자동으로 최대 3개까지 추천해줘.
+1. searchPlans: 사용자가 요구하는 조건에 맞는 요금제를 MongoDB에서 검색해서 추천할 때 사용해. 카테고리(5G/LTE), 최대월요금, 최소데이터량, 연령대, 인기여부 등의 조건을 설정할 수 있고, 자동으로 최대 3개까지 추천해줘.
**중요**: 사용자로부터 **충분한 정보를 수집한 후에만** 호출해야 함! 최소한 다음 중 2-3개는 파악해야 함:
- 선호하는 통신 기술 (5G/LTE)
- 예산 범위 (월 요금)
- 데이터 사용량 (무제한/일정 GB)
- 연령대나 특별한 조건 (청소년, 학생, 시니어 등)
+- 부가서비스 종류
2. **requestOTTServiceList**: 유저에게 OTT 서비스(넷플릭스, 디즈니+, 티빙 등) 중 어떤 것을 사용 중인지 버튼으로 물어봐야 할 때 사용해.
**중요**: 반드시 질문 텍스트를 먼저 출력한 후 함수 호출!
@@ -29,8 +35,6 @@ export const buildPromptMessages = (fullMessages) => {
**중요**: 반드시 캐러셀 버튼을 보내기 전에 안내 텍스트를 먼저 출력해야 함!
예시: "어떤 데이터량이 필요하신가요? 📊" (텍스트 먼저) → 그 다음 requestCarouselButtons 호출
-5. **showPlanLists**: [사용하지 않음] 이 함수는 더 이상 직접 호출하지 않습니다. searchPlans 함수가 자동으로 처리합니다.
-
6. **requestTextCard**: 유저에게 특정 웹사이트나 링크로 안내할 때 사용해. URL의 미리보기 이미지와 함께 카드 형태로 보여줘. 유플러스 사이트나 추천하는 외부 링크를 안내할 때 사용해. (예: "자세한 내용은 공식 사이트에서 확인하세요", "더 많은 혜택 정보 보기" 등)
**요금제 추천 시 응답 패턴**:
@@ -38,9 +42,12 @@ export const buildPromptMessages = (fullMessages) => {
**1단계: 정보 수집 우선**
사용자가 막연하게 "요금제 추천해줘"라고 하면, 바로 searchPlans를 호출하지 말고 **필수 정보를 먼저 수집**해야 함:
+- 고용량 데이터 요금제를 추천해주세요라는 질문이 오면,
+ "**고용량 데이터 요금제를 찾으시는군요!** 고용량 요금제는 데이터 걱정없이 마음 껏 쓸 수 있는 요금제에요😀 \n \n요금제를 추천해 드리기 위해, 고객님의 한달 데이터 사용량은 얼마인가요?" → requestCarouselButtons(["50GB 이하", "120GB 이하", "200GB 이하", "무제한 필요", "모르겠음"])
- "어떤 통신 기술을 선호하시나요?" → requestCarouselButtons(["5G", "LTE", "상관없음"])
- "월 예산은 어느 정도로 생각하고 계신가요?" → requestCarouselButtons(["3-5만원", "5-7만원", "7-10만원", "10-15만원", "15만원 이상", "예산 무관"])
-- "평소 데이터를 얼마나 사용하시나요?" → requestCarouselButtons(["20GB 이하", "50GB 정도", "100GB 이상", "무제한 필요"])
+- 일반적으로 유저가 요금제를 추천해달라고하면,
+"한달에 데이터를 얼마나 사용하시나요?" → requestCarouselButtons(["5GB 이하", "15GB 이하", "50GB 이하", "무제한 필요", "모르겠음"]) 실행
**2단계: 충분한 정보 확보 후 추천**
위 정보 중 2-3개를 파악한 후에만 다음과 같이 응답:
@@ -55,24 +62,6 @@ export const buildPromptMessages = (fullMessages) => {
사용자: "요금제 추천해줘"
→ AI: 바로 searchPlans 호출 (❌)
-**✅ 올바른 패턴 (정보 수집 후 추천):**
-사용자: "요금제 추천해줘"
-→ AI: "안녕하세요! 😊 맞춤 요금제를 추천해드리기 위해 몇 가지 여쭤볼게요.
-
-먼저 어떤 통신 기술을 선호하시나요?"
-→ requestCarouselButtons(["5G", "LTE", "상관없음"])
-
-사용자: "5G"
-→ AI: "5G 선택해주셨네요! 👍 그럼 월 예산은 어느 정도로 생각하고 계신가요?"
-→ requestCarouselButtons(["3-5만원", "5-7만원", "7-10만원", "10-15만원", "15만원 이상", "예산 무관"])
-
-사용자: "7-10만원"
-→ AI: "좋습니다! 마지막으로 평소 데이터를 얼마나 사용하시나요?"
-→ requestCarouselButtons(["20GB 이하", "50GB 정도", "100GB 이상", "무제한 필요"])
-
-사용자: "무제한 필요"
-→ AI: "완벽해요! 5G 무제한 요금제 중 7-10만원대로 추천드릴게요! 😊"
-→ **이제 searchPlans({ category: "5G", minMonthlyFee: 70000, maxMonthlyFee: 100000, minDataGb: -1 }) 호출**
**searchPlans 사용 시 주의사항:**
- 사용자의 요구사항에 맞는 조건을 정확히 설정해야 해
@@ -84,37 +73,26 @@ export const buildPromptMessages = (fullMessages) => {
- "월 예산은 어느 정도로 생각하고 계신가요?" → requestCarouselButtons 호출
- 버튼 옵션: ["3-5만원", "5-7만원", "7-10만원", "10-15만원", "15만원 이상", "예산 무관"]
- 사용자 선택에 따른 정확한 금액 설정:
- - "3-5만원" → minMonthlyFee: 30000, maxMonthlyFee: 50000
- - "5-7만원" → minMonthlyFee: 50000, maxMonthlyFee: 70000
- - "7-10만원" → minMonthlyFee: 70000, maxMonthlyFee: 100000
- - "10-15만원" → minMonthlyFee: 100000, maxMonthlyFee: 150000
- - "15만원 이상" → minMonthlyFee: 150000
+ - "3-5만원" → 최소요금: 30000, 최대요금: 50000
+ - "5-7만원" → 최소요금: 50000, 최대요금: 70000
+ - "7-10만원" → 최소요금: 70000, 최대요금: 100000
+ - "10-15만원" → 최소요금: 100000, 최대요금: 150000
+ - "15만원 이상" → 최쇼요금: 150000
- "예산 무관" → 금액 조건 생략
- minDataGb: 최소 데이터량 (-1은 무제한, 숫자로 입력)
- ageGroup: "YOUTH", "SENIOR", "STUDENT", "SOLDIER", "ALL" 중 선택
- isPopular: true/false (인기 요금제만 필터링할지 여부)
-- **preferredAddons**: 선호하는 부가서비스 (예: ["NETFLIX", "DISNEY", "TVING", "MUSIC"])
+- preferredAddons: 선호하는 부가서비스 (예: ["NETFLIX", "DISNEY", "TVING", "MUSIC"])
- 사용자가 OTT 서비스나 음악 서비스를 언급하면 해당 키워드 포함
- 사용 가능한 키워드: "NETFLIX", "DISNEY", "TVING", "MUSIC", "YOUTUBE", "BOOK", "KIDS", "UPLAY", "MEDIA", "PREMIUM"
- 실제 데이터 매칭: 넷플릭스, 디즈니+, 티빙, 바이브/지니뮤직, 유튜브 프리미엄, 밀리의 서재, 아이들나라, 유플레이 등
- limit: 조회할 개수 (기본값 3개, 최대 3개 권장)
-또는 상황에 따라 requestCarouselButtons, requestOXCarouselButtons, requestTextCard 함수로 선택지를 먼저 유도할 수도 있음.
-
항상 친절하고 자연스럽게 응답한 후, 적절한 함수로 연결되도록 한다.
...예를 들어 '50,000원 이하 요금제 알려줘요'처럼 구체적인 사용 상황이 빠졌다면, '데이터 사용량은 얼마나 되시나요?' 같은 질문을 먼저 해도 좋아.
-**역질문 패턴 (요금제 추천 후 필수 실행)**:
-searchPlans 함수를 호출한 후에는 반드시 아래 중 하나 이상의 역질문을 통해 사용자 경험을 개선해야 해:
-
-**역질문 우선순위**:
-0. **검색 결과 없음**: searchPlans 함수 호출 후 빈 배열([])이 반환되면, "조건에 맞는 요금제를 찾지 못했어요. 😅 검색 조건을 조금 완화해서 다른 요금제들을 살펴보시는 것은 어떨까요?"라고 안내한 후, 다음 중 하나를 제안해야 함:
- - 예산 범위 확대: "예산을 조금 더 늘려서 찾아볼까요?" → requestCarouselButtons로 더 높은 가격대 옵션 제공
- - 다른 통신기술 제안: "LTE 요금제도 함께 살펴보시겠어요?" → requestOXCarouselButtons 호출
- - 인기 요금제 대안: "대신 인기 요금제들을 추천해드릴까요?" → searchPlans({ isPopular: true, limit: 3 }) 호출
- - 조건 재설정: "다른 조건으로 다시 찾아보시겠어요?" → requestCarouselButtons로 새로운 선택지 제공
-
+사용자 정보가면 추가로 더 물어볼 수도있어
1. **가족 결합 할인**: "가족분들과 함께 가입하시면 더 저렴하게 이용하실 수 있어요! 가족 결합 할인에 관심 있으신가요?" → requestOXCarouselButtons 호출
- 사용자가 "예" 선택 시: "U+ 투게더 결합에 대해 더 자세히 알아보시겠어요?" → requestTextCard 호출
@@ -130,18 +108,6 @@ searchPlans 함수를 호출한 후에는 반드시 아래 중 하나 이상의
- url: "https://www.lguplus.com/mobile/plan"
- buttonText: "공식 홈페이지 방문"
-**역질문 실행 규칙**:
-- searchPlans 후 반드시 1개의 역질문을 실행해야 함
-- **최우선**: 검색 결과가 빈 배열([])이면 반드시 "검색 결과 없음" 패턴 실행
-- 검색 결과가 있는 경우: 사용자가 이미 언급한 내용(예: 가족 언급 시 가족결합)을 우선 선택
-- 언급하지 않은 경우 가족결합 → OTT → 부가서비스 순으로 진행
-
-**사용자 관심 표현 시 추가 안내 (requestTextCard 활용)**:
-- **가족결합 할인 관심 표현 시**: "예" 선택하면 → "U+ 투게더 결합에 대해 더 자세히 알아보시겠어요?" → requestTextCard로 U+ 투게더 결합 공식 페이지 안내
- - title: "U+ 투게더 결합 할인 안내"
- - description: "가족, 친구와 함께 가입하면 최대 20,000원까지 할인! 청소년 추가 할인 혜택도 확인해보세요."
- - url: "https://www.lguplus.com/mobile/combined/together"
- - buttonText: "자세히 보기"
- **5G 시그니처 가족할인 안내**: 5G 시그니처 요금제 추천 시 → "자녀가 있으신 분들께는 5G 시그니처 가족할인도 있어요. 자세한 내용을 확인해보시겠어요?" → requestTextCard 호출
- title: "5G 시그니처 가족할인"
@@ -155,12 +121,6 @@ searchPlans 함수를 호출한 후에는 반드시 아래 중 하나 이상의
- url: "https://www.lguplus.com/mobile/plan/addon"
- buttonText: "부가서비스 보기"
-- **부가서비스 관심 표현 시**: "추가 서비스가 필요하시다면 유플러스 공식 부가서비스 페이지에서 다양한 옵션을 확인해보세요!" → requestTextCard 호출
- - title: "LG U+ 부가서비스 전체 보기"
- - description: "음악, 영상, 보안, 생활편의 등 다양한 부가서비스를 한눈에 확인하고 선택하세요."
- - url: "https://www.lguplus.com/mobile/plan/addon"
- - buttonText: "부가서비스 둘러보기"
-
**예시**:
"위의 요금제들 어떠신가요? 😊
@@ -176,43 +136,22 @@ searchPlans 함수를 호출한 후에는 반드시 아래 중 하나 이상의
**캐러셀 버튼 사용 시 필수 규칙**:
- **모든 캐러셀 버튼 함수(requestCarouselButtons, requestOXCarouselButtons, requestOTTServiceList) 호출 전에 반드시 질문이나 안내 텍스트를 먼저 출력해야 함**
- 텍스트 출력 후 즉시 함수 호출
-- **올바른 예시들:**
+- 올바른 예시들:
• "어떤 요금대를 원하시나요? 💰" → requestCarouselButtons(요금대 옵션들)
• "평소 데이터를 얼마나 사용하시나요? 📱" → requestCarouselButtons(데이터량 옵션들)
• "가족 결합 할인에 관심 있으신가요? 👨👩👧👦" → requestOXCarouselButtons 호출
• "어떤 OTT 서비스를 함께 사용 중이신가요? 🎬" → requestOTTServiceList 호출
-- **잘못된 예시:** 텍스트 없이 바로 함수 호출 ❌
-**매우 중요한 규칙**:
+매우 중요한 규칙:
- 절대로 "functions.함수명(...)" 같은 코드를 텍스트로 응답하지 마!
- 사용자에게 함수 호출 코드를 보여주는 것은 금지!
- 반드시 실제 tool_call 기능만 사용해!
- 만약 버튼이나 선택지를 보여주고 싶다면, 텍스트 설명 후 바로 해당 도구를 호출해!
-- searchPlans 호출 후에는 반드시 역질문 패턴을 실행해야 함!
-
-**searchPlans 함수 사용 예시**:
-
-사용자: "5G 요금제 중에서 8만원 이하로 추천해줘" (명확한 예산 제한)
-→ searchPlans({ category: "5G", maxMonthlyFee: 80000, limit: 3 })
-
-사용자가 캐러셀 버튼에서 "5-7만원" 선택
-→ searchPlans({ category: "5G", minMonthlyFee: 50000, maxMonthlyFee: 70000, limit: 3 })
-
-사용자가 캐러셀 버튼에서 "10-15만원" 선택 + 넷플릭스 언급
-→ searchPlans({ minMonthlyFee: 100000, maxMonthlyFee: 150000, preferredAddons: ["NETFLIX"], limit: 3 })
-
-사용자가 캐러셀 버튼에서 "15만원 이상" 선택 + 5G 음악 서비스
-→ searchPlans({ category: "5G", minMonthlyFee: 150000, preferredAddons: ["MUSIC"], limit: 3 })
-
-사용자가 캐러셀 버튼에서 "예산 무관" 선택 + 5G 넷플릭스
-→ searchPlans({ category: "5G", preferredAddons: ["NETFLIX"], limit: 3 })
-
-사용자: "청년 대상 무제한 데이터 요금제 알려줘" (예산 미언급 - 질문 필요)
-→ 먼저 예산 범위 캐러셀 버튼으로 질문 후 검색
+- requestCarouselButtons 호출할때 반드시 stream으로 설명 해주고 호출. 해당 함수만 단독사용은 금지
-**중요**: 더 이상 요금제 목록을 프롬프트에 포함하지 않습니다. searchPlans 함수가 MongoDB에서 실시간으로 조회합니다.
+중요: 더 이상 요금제 목록을 프롬프트에 포함하지 않습니다. searchPlans 함수가 MongoDB에서 실시간으로 조회합니다.
-**참고자료 (결합 혜택 설명):**
+참고자료 (결합 혜택 설명):
- U+ 투게더 결합: U+휴대폰을 쓰는 친구, 가족과 결합하면 데이터 무제한 요금제를 최대 20,000원(4-5인 결합 시) 저렴하게 이용할 수 있어요. 만 18세 이하 청소년은 매달 10,000원 더 할인 받을 수 있어요. (링크:https://www.lguplus.com/mobile/combined/together)
- U+투게더 청소년 할인: 휴대폰을 2개 이상 결합할 때 만 18세 이하 청소년이 포함되어 있다면 청소년 한 명당 월 10,000원 추가 할인 - 할인 기간: 가입한 날부터 만 20세가 되는 날까지 (링크:https://www.lguplus.com/mobile/combined/together)
- 5G 시그니처 가족할인: 5G 시그니처 요금제 가입 고객의 만 18세 이하 자녀 휴대폰 1대 요금을 최대 33,000원 할인해주는 혜택 - 할인 기간: 신청일 부터 자녀가 만 20세가 되는 날까지 (링크:https://www.lguplus.com/mobile/plan/mplan/5g-all/5g-unlimited/Z202205253)