국비학원 최종 프로젝트였던 HealthSync ...

 

비록 나름 고심해서 꽤나 로직에 공들였지만서도,공공기관 데이터 화재사건으로 ..문제도있었고..

당시 가장마음에 걸렸떤게 바로... 서버에 올리지 못했던것!

 

두번째로 마음에 걸리는 "챗봇" 의 1회성 답변을 수정하고 서버에 올리는 걸로 목표를 잡았다.

 

 

챗봇을 보면 대화가 이어지는것 처럼 보이지만,

>> 안녕 난 헤비스모커야     (컨텍스트제공)
 
 챗>> 예상답변으로 안녕 헤비스모커야~?    (컨텍스트를 활용해서 답변해야하지만 ... 인사도 건강주제가 아니라고 답변을 안해줌)

 

그리고 대화가 이어지지 않는다는 증거로는 .. 

 >> 내가 누구라고 ? ( 위쪽 컨텍스트에대해서 답변을 요구)
 
챗>>  예상답변으로 헤비스모커라고 말해주길 기대했지만, 위의 답변을 싹 무시하고 [로직에 정의된대로 왼쪽 프로필정보를 읊어준다.]

 

 

이 2가지 문제를 해결해야겠다. 

 

 

 

문제 1. 인사도 안받아줌 ...

 

private OpenAiRequest getOpenAiRequest(String context, String prompt) {
        String healthSyncPrompt = "너는 'HealthSync' 서비스 소속의 전문 헬스케어 및 식단 관리 AI 어시스턴트야. " +
                "[너의 핵심 임무] " +
                "사용자에게 건강한 식단, 운동 방법, 영양 정보, 스트레스 관리법에 대해 과학적 근거를 바탕으로 조언해야 해. " +
                "항상 사용자의 건강 목표 달성을 돕는 것을 최우선으로 생각해. " +
                "[답변 규칙] " +
                "1. 절대로 의료적 진단, 질병의 원인 규명, 의약품 처방 및 추천을 해서는 안 돼. 사용자가 진단을 요구하면, '저는 의료 전문가가 아니므로 정확한 진단은 병원을 방문하여 의사와 상담하시는 것을 강력히 권장합니다.'라고 답변해야 해. " +
                "2. 답변은 항상 친절하고 긍정적인 톤을 유지해 줘. " +
                "3. 건강, 운동, 식단과 전혀 관련 없는 주제(예: 정치, 금융, 연예, 기술 등)에 대한 질문은 정중하게 거절해. '저는 건강 및 식단 전문 AI라서 해당 주제에 대해서는 답변하기 어려워요. 건강 관련 질문이 있으시면 언제든지 말씀해주세요!'와 같이 답변해. " +
                "4. 모든 답변은 5문단의 길이로 요약해서 제공해 줘." +

                "\n\n[답변 형식 규칙]" +
                "\n1. 답변의 가독성을 높이기 위해 마크다운(Markdown)을 적극적으로 사용해줘." +
                "\n2. 각 식사(아침, 점심, 저녁, 간식)는 '### 아침', '### 점심'과 같이 마크다운 제목(헤딩 3단계)으로 명확히 구분해줘." +
                "\n3. 중요한 키워드나 음식 이름은 `**`로 감싸서 **굵은 글씨**로 강조해줘." +
                "\n4. 식단 예시처럼 나열이 필요한 정보는 반드시 `-` 기호를 사용한 목록(리스트) 형식으로 정리해줘.";

 

 

프롬프트 엔지니어링에서 너무 빡빡한 내용이 있음 

 

""3. 건강, 운동, 식단과 전혀 관련 없는 주제(예: 정치, 금융, 연예, 기술 등)에 대한 질문은 정중하게 거절해. '저는 건강 및 식단 전문 AI라서 해당 주제에 대해서는 답변하기 어려워요. 건강 관련 질문이 있으시면 언제든지 말씀해주세요!'와 같이 답변해. " +"

 

 

당시에는 api 비용이 나갈것을 우려해서 최대한 소극적으로 했지만..아무래도 챗봇인데 너무 정없다...(?)

 

    private OpenAiRequest getOpenAiRequest(String context, String prompt) {
        String healthSyncPrompt =
                "너는 'HealthSync' 서비스 소속의 전문 헬스케어 및 식단 관리 AI 어시스턴트야. " +

                        "[너의 핵심 임무] " +
                        "사용자에게 건강한 식단, 운동 방법, 영양 정보, 스트레스 관리법에 대해 과학적 근거를 바탕으로 조언해야 해. " +
                        "항상 사용자의 건강 목표 달성을 돕는 것을 최우선으로 생각해. " +

                        "[답변 규칙] " +
                        "1. 절대로 의료적 진단, 질병의 원인 규명, 의약품 처방 및 추천을 해서는 안 돼. " +
                        "2. 답변은 항상 친절하고 긍정적인 톤을 유지해 줘. " +

                        // ✅ 개선: 유연하면서도 절제하는 형태
                        "3. 인사나 일반적인 대화는 친절하게 받아줘. " +
                        "   예: '안녕하세요!' → '안녕하세요! 건강 관련 도움이 필요하신가요?'" +
                        "4. 사용자와 대화를 나누되, 만약 대화가 건강과 지속적으로 무관한 방향으로 진행되면 " +
                        "   부드럽게 건강 주제로 유도해. 예: '좋은 말씀이네요. 그런데 혹시 식단이나 운동으로 도움을 드릴 만한 게 있을까요?'" +
                        "5. 명백하게 건강과 무관한 주제(정치, 금융, 기술 심화 등)의 지속적인 질문에는 " +
                        "   정중하게 '저는 건강 및 식단 전문 AI라서...'라고 표현해. " +
                        "6. 모든 답변은 5문단의 길이로 요약해서 제공해 줘."+

 

 

사용자와 대화를 나누되, ... 너무 건강관련해서 대화주제가 다른곳으로가면 정중히 건강주제로 유도하면서 거절하도록 했다. 

 

 

담배피는것은 좋지않다고한다...

이정도면 인사는 잘받아주는거같으니 굳..

 

 

문제 2. 기억상실증

 

 

사용자의 요청 -> 답변은 잘하는데...

 

바로 전 질문이 뭔지를 신경쓰지않는다.

 

이 짧은 메서드에서 1번으로 대화를 받아오고.. setp1.으로 대화를 뱉어낼뿐...

    public String getAnswer(Long userId, ChatRequest chatRequest) {

        // 1. 프론트에서 받은 컨텍스트 데이터 DTO를 가져옵니다.
        ReportContextDto contextDto = chatRequest.getReportContext();

        // 2. 받은 DTO가 null인지 확인 (선택적 예외 처리)
        if (contextDto == null) {
            // 컨텍스트 없이 일반적인 답변을 하거나, 오류를 반환할 수 있습니다.
            // 여기서는 컨텍스트 없이 질문만 넘깁니다.
            OpenAiResponse openAiResponse = openAiClient.getChatCompletion(null, chatRequest.getMessage());
            return openAiResponse.getChoices().get(0).getMessage().getContent();
        }

        // 3. DTO를 AI가 이해하기 쉬운 문자열로 변환합니다.
        String context = buildUserContext(contextDto);

        // step 1. OpenAiClient를 통해 질문을 OpenAI에 전달
        log.info("Request from User ID: {}, Question: {}", userId, chatRequest.getMessage());
        OpenAiResponse openAiResponse = openAiClient.getChatCompletion(context, chatRequest.getMessage());
        return openAiResponse.getChoices().get(0).getMessage().getContent();

    }

 

 

이걸 해결하기 위해서 몇가지 추가적인 걸 작업해야한다.

차근차근 해보면될듯....

1️⃣ Entity/DB 설계 필요
   └─ ChatHistory (메시지 저장)
   └─ ChatSummary (요약 저장)

2️⃣ Repository 필요
   └─ ChatHistoryRepository
   └─ ChatSummaryRepository

3️⃣ ChatService.getAnswer() 
   └─ Dequeue 로직
   └─ 15개 체크
   └─ 요약 생성 로직
   └─ 저장 로직

4️⃣ 요약 생성 (새로운 메서드)
   └─ OpenAI에 "처음 5개 메시지 요약해줘" 호출
   └─ 요약 받음

5️⃣ OpenAiClient.getChatCompletion()
   └─ 히스토리 + 요약 함께 전달

6️⃣ 새 대화 시작 시 요약 삭제
   └─ 언제 "새 대화"인지 판단 로직 필요

 

 
 
 

 

원했던 내용인 이정도로 ... 

 

📋 구현 완료 요약

생성/수정된 파일들

파일상태설명

ChatHistory.java ✅ 수정 실시간 대화 저장 엔티티
ChatSummary.java ✅ 생성 요약 저장 (UUID_날짜 ID)
ChatHistoryRepository.java ✅ 생성 대화 CRUD
ChatSummaryRepository.java ✅ 생성 요약 CRUD + 날짜별 정리
ChatService.java ✅ 수정 히스토리 관리 + 요약 로직
OpenAiClient.java ✅ 수정 messages에 히스토리 포함

 


동작 흐름

 
 
1️⃣ 사용자 질문 들어옴
   ↓
2️⃣ cleanupOldSummaries() - 오늘이 아닌 요약 자동 삭제
   ↓
3️⃣ buildConversationHistory() - 오늘 요약 + 현재 히스토리 조합
   ↓
4️⃣ saveUserMessage() - 사용자 질문 DB 저장
   ↓
5️⃣ OpenAI API 호출 (system + 요약들 + 히스토리 + 현재 질문)
   ↓
6️⃣ saveAssistantMessage() - AI 응답 DB 저장
   ↓
7️⃣ checkAndSummarize() - 15개 이상이면 5개 요약 후 삭제

 

..쩝

이제 담번에 서버올려보는거롤 마무리하자 

+ Recent posts