diff --git a/api/summary.py b/api/summary.py index eb770b1..b998a56 100644 --- a/api/summary.py +++ b/api/summary.py @@ -1,23 +1,16 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Header, HTTPException from service.summary import call_assistant_summary -from config import settings -import requests -import json -from typing import List -from datetime import datetime from pydantic import ValidationError -from fastapi import HTTPException -from pydantic import BaseModel, Field + router = APIRouter(tags=["Summary"]) @router.get("/assistant") -async def call_assistant(userId: int, roomId: str): - +async def call_assistant(roomId: str, userId: str = Header(alias="x-user-id")): try: message = call_assistant_summary(userId, roomId) except ValidationError as e: - raise HTTPException(status_code=500, detail="Internal parsing error") + raise HTTPException(status_code=500, detail="Internal parsing error") - return {"message": message} + return {"message": message} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 701e18c..8081164 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,6 @@ requests openai==1.82.0 sentence-transformers schedule -APScheduler>=3.11.0 \ No newline at end of file +APScheduler>=3.11.0 +transformers==4.25.1 +Flask \ No newline at end of file diff --git a/service/auto.py b/service/auto.py index 111ce4b..db1dc30 100644 --- a/service/auto.py +++ b/service/auto.py @@ -5,6 +5,7 @@ from service.summary import ( extract_plain_reply, get_summary, + get_quarter_summary, AssistantSummary, ) import requests @@ -14,6 +15,7 @@ from openai import OpenAI from datetime import datetime from dotenv import load_dotenv +from transformers import pipeline scheduler = AsyncIOScheduler() @@ -52,6 +54,89 @@ def summary_scheduler(rooms): # 채팅 서버로 요약본 업데이트 post_summary_update(room_id, gpt_response_str, parsing_json_response) +# kobart 요약 스케줄러 6시간마다 요약 +def kobart_summary_message(quarter_message) -> str: + # 파이프라인 설정 + pipe = pipeline( + task="text2text-generation", + model="sangsin/kobart-emotional", + tokenizer="sangsin/kobart-emotional", + device_map="auto",) + + out = pipe( + quarter_message, + do_sample=False, + temperature=0.8, + max_length=128, + num_beams=5, + top_k=50, + top_p=0.9, + length_penalty=2.0, + eos_token_id=pipe.tokenizer.eos_token_id, + no_repeat_ngram_size=2,)[0]["generated_text"] + clean = out.split("\n")[0].strip() + return clean + +def kobart_summary_scheduler(time: str) -> None: + # 채팅방 리스트 요청 + rooms = requests.get(f"{settings.CHAT_SERVER_URL}/room/list/all", timeout=5).json() + + # 모든 채팅방 순회 + for room in rooms: + room_id = room["_id"] + resp = get_quarter_summary(room_id) + response_data = resp.json() + + if "today_messages" not in response_data: + response_data["today_messages"] = [] # + + + if "quarter_messages" not in response_data: + response_data["quarter_messages"] = [] + summary_obj: AssistantSummary = AssistantSummary.model_validate(response_data) + + # 파싱 + formatted_quarter_messages = [ + f"[{msg.createdAt.strftime('%Y-%m-%d %H:%M:%S')}][{msg.sender.name}] \"{msg.content}\"" + for msg in summary_obj.quarter_messages ] + + if not formatted_quarter_messages: + print(f"[{room_id}] 요약할 분기 메시지가 없습니다. 요약 생략.") + quarter_summary_sentence = "이 시간대의 요약은 없습니다." + else: + long_message_string = "\n".join(formatted_quarter_messages) + # kobart에 message 전달 -> 요약본 반환 + quarter_summary_sentence = kobart_summary_message(long_message_string) + + + if len(summary_obj.quarter) < 4: + # 현재 길이를 확인하고, 4개까지 부족한 만큼 빈 문자열로 채워 넣습니다. + summary_obj.quarter.extend([""] * (4 - len(summary_obj.quarter))) + + + # 하루의 몇 번째 요청인지 확인하는 로직 + if time == "first": + summary_obj.quarter[0] = quarter_summary_sentence + elif time == "second": + summary_obj.quarter[1] = quarter_summary_sentence + elif time == "third": + summary_obj.quarter[2] = quarter_summary_sentence + elif time == "fourth": + summary_obj.quarter[3] = quarter_summary_sentence + + # 6시간 요약 업데이트 + body = { + "roomId": summary_obj.roomId, + "persona": [p.model_dump() for p in summary_obj.persona], + "summaries": summary_obj.summaries.model_dump(mode='json'), + "quarter" : summary_obj.quarter + } + requests.post( + f"{settings.CHAT_SERVER_URL}/summary/update", json=body, timeout=5 + ).raise_for_status() + print(f"[{room_id}] kobart 요약 업데이트 완료") + + # 매일 채팅방 업데이트 post 함수 def post_summary_update( @@ -88,18 +173,18 @@ def summary_prompt(response) -> list[dict]: 2. hobbies_and_interests 3. emotional_and_mental_state 4. relationship_context - 5. social_activitie + 5. social_activity 6. intimacy ### 요약 {response.summaries} - ### 오늘 나눈 대화 내역 - {response.today_messages} + ### 오늘 나눈 대화 요약 내역 + {response.quarter} ### TASK - 1) 오늘 나눈 대화 내역을 읽고, 6개의 주제에 대한 내용을 추출하여 한 문장으로 요약한다. + 1) 오늘 나눈 대화 요약 내역을 읽고, 6개의 주제에 대한 내용을 추출하여 한 문장으로 요약한다. 2) 만일 일부 주제에 대한 내용을 추출할 내용을 없을 시에는 "주제에 대한 대화 없음"이라고 저장한다. 3) 반드시 기존에 있는 요약 내용에 추가하는 것이다. 대답할 때 오늘 나눈 대화 내역에서 각 주제에 추출한 내용을 이미 있는 내용에 day리스트 맨 뒤에 추가만 할 것. 4) {datetime.now()}가 현재 시각임으로 last_update에 반영할 것. @@ -118,6 +203,8 @@ def summary_prompt(response) -> list[dict]: ] + + def weekly_summary_scheduler() -> None: rooms = requests.get(f"{settings.CHAT_SERVER_URL}/room/list/all", timeout=5).json() @@ -145,7 +232,7 @@ def weekly_summary_scheduler() -> None: 지난 일주일({week_start} ~ {week_end}) 각 주제별로 day 리스트에 쌓인 항목을 읽고, 각 주제에 대해 한 문장으로 요약해라라 - 입력 데이터(딕셔너리): -{{ + """ for t in topics: day_list = getattr(summary_obj.summaries, t).day @@ -197,8 +284,6 @@ def monthly_summary_scheduler() -> None: week 리스트는 비웁니다. """ rooms = requests.get(f"{settings.CHAT_SERVER_URL}/room/list/all", timeout=5).json() - - # 오늘의 월 레이블 (예: 5월) today = datetime.now() month_label = f"{today.month}월" @@ -328,31 +413,61 @@ def yearly_summary_scheduler() -> None: "daily_summary_job", fetch_roomList, dict(hour=2, minute=0, second=0), + (), ), # 매일 02:00:00 ( "weekly_summary_job", weekly_summary_scheduler, dict(day_of_week="mon", hour=3, minute=0), + (), ), # 매주 월 03:00 ( "monthly_summary_job", monthly_summary_scheduler, dict(day="1", hour=4, minute=0), + (), ), # 매달 1일 04:00 ( "yearly_summary_job", yearly_summary_scheduler, dict(month="1", day="1", hour=5, minute=0), + (), ), # 매년 1월1일 05:00 + + + ( + "kobart_first_summary_job", + kobart_summary_scheduler, + dict(hour=20, minute=24, second=0), + ("first",), + ), # 매일 23:55:00 + ( + "kobart_second_summary_job", + kobart_summary_scheduler, + dict(hour=5, minute=55, second=0), + ("second",), + ), # 매일 05:55:00 + ( + "kobart_third_summary_job", + kobart_summary_scheduler, + dict(hour=11, minute=55, second=0), + ("third",), + ), # 매일 11:55:00 + ( + "kobart_fourth_summary_job", + kobart_summary_scheduler, + dict(hour=17, minute=55, second=0), + ("fourth",), + ), # 매일 17:55:00 ] -for job_id, func, cron_kwargs in jobs: +for job_id, func, cron_kwargs, args_tuple in jobs: # args_tuple 추가 trigger = CronTrigger(**cron_kwargs) scheduler.add_job( func, trigger, id=job_id, + args=args_tuple, # <-- 여기에 args 전달 replace_existing=True, ) - scheduler.start() \ No newline at end of file diff --git a/service/kobart_conversation_summary.py b/service/kobart_conversation_summary.py new file mode 100644 index 0000000..e69de29 diff --git a/service/summary.py b/service/summary.py index dba97b9..cdd5aca 100644 --- a/service/summary.py +++ b/service/summary.py @@ -2,7 +2,6 @@ import os import re import json -import time from config import settings from pydantic import BaseModel, Field from openai import OpenAI @@ -16,7 +15,7 @@ class Persona(BaseModel): name: str relationship: str | None = "" speech_style: str | None = "" - preferences: str | None = "" + preferences: List[str] critical_flags: List[str] @@ -56,6 +55,8 @@ class AssistantSummary(BaseModel): persona: List[Persona] summaries: Summaries today_messages: List[Message] + quarter_messages: List[Message] = None + quarter: List[str] def extract_plain_reply(messages: str) -> str: @@ -103,7 +104,7 @@ def user_prompt(response, userId) -> list[dict]: ### TASK 1) 상황 파악 → 사고 단계 기록 2) 반드시 요약에서 2개의 주제 "hobbies_and_interests"와 "relationship_context"를 먼저 파악하라. - 3) 최근 대화에 가장 많은 비중을 두어 {speaker} 입장에서 마지막 발화에 자연스럽게 이어질 **한 문장**을 작성해야 한다. + 3) 최근 대화에 가장 많은 비중을 두어 {speaker} 입장에서 마지막 발화에 자연스럽게 이어질 한 문장을 작성해야 한다. 3) 나머지 요약 내용도 최근 대화에 필요한 내용이라면 참고하여 최고의 한 문장으로 작성해라. 4) 문장을 총 7개 제안 → 셀프체크 후 3개 → 최종 1개 제안 5) JSON 만 출력 @@ -113,7 +114,7 @@ def user_prompt(response, userId) -> list[dict]: return [ { "role": "system", - "content": "너는 한국어 어르신 대화 코치이다. 두 어르신의 건강을 고려해 한 문장으로 답한다.", + "content": "너는 한국어 어르신 대화 코치이다. 두 어르신의 인간 관계를 고려하고 최근 대화에 큰 비중을 고려해 한 문장으로 답하자", }, {"role": "user", "content": user_content}, ] @@ -144,6 +145,19 @@ def get_summary(roomId: str) -> requests.Response: # chat 서버에 요약본 요청 url = f"{settings.CHAT_SERVER_URL}/summary" response = requests.get(url, params={"roomId": roomId}) + print(response) + if response.status_code == 404: + # 원하는 대로 처리 (빈 응답, 기본 메시지 반환 등) + raise ValueError(f"Summary not found for roomId={roomId}") + response.raise_for_status() + + return response + + +def get_quarter_summary(roomId: str) -> requests.Response: + # chat 서버에 요약본 요청 + url = f"{settings.CHAT_SERVER_URL}/summary/quarter" + response = requests.get(url, params={"roomId": roomId}) if response.status_code == 404: # 원하는 대로 처리 (빈 응답, 기본 메시지 반환 등) raise ValueError(f"Summary not found for roomId={roomId}") @@ -153,10 +167,12 @@ def get_summary(roomId: str) -> requests.Response: # 프론트에서 대답 time out으로 추천대화 get 요청 -def call_assistant_summary(userId: int, roomId: str) -> str: +def call_assistant_summary(userId: str, roomId: str) -> str: # chat 서버에 요약본 요청 + response = get_summary(roomId) + print(response.json()) # 파싱 모듈 parsing_json_response = AssistantSummary.model_validate(response.json()) diff --git a/service/test2.py b/service/test2.py new file mode 100644 index 0000000..e69de29