diff --git a/app/database/chroma_db.py b/app/database/chroma_db.py index 25ccc5d..dbcd2ab 100644 --- a/app/database/chroma_db.py +++ b/app/database/chroma_db.py @@ -46,6 +46,17 @@ async def search_db_query(member_id, query): ) return result +# description: DB에서 검색하는 함수 - chat case 4에 사용 +# 후보들을 선정해서 gpt에게 가장 적합한 스케줄을 선택하라고 할 예정 +async def search_db_query_delete(member_id, query): + member = member_id + result = schedules.query( + query_texts=query, + n_results=10, + where={"member": {"$eq": int(member)}} + ) + return result + # description: DB에 저장하는 함수 # 스프링 백엔드로부터 chroma DB에 저장할 데이터를 받아 DB에 추가한다. async def add_db_data(schedule_data: AddScheduleDTO): @@ -64,6 +75,7 @@ async def add_db_data(schedule_data: AddScheduleDTO): "member": schedule_data.member_id, "category": schedule_data.category, "category_id": schedule_data.schedule_id, + "category_color": schedule_data.category_color, "location": schedule_data.location, "person": schedule_data.person }] @@ -101,6 +113,7 @@ async def update_db_data(schedule_data: UpdateScheduleDTO): "member": schedule_data.member_id, "category": schedule_data.category, "category_id": schedule_data.schedule_id, + "category_color": schedule_data.category_color, "location": schedule_data.location, "person": schedule_data.person }] diff --git a/app/dto/db_dto.py b/app/dto/db_dto.py index 2377e92..5420298 100644 --- a/app/dto/db_dto.py +++ b/app/dto/db_dto.py @@ -9,6 +9,7 @@ class AddScheduleDTO(BaseModel): member_id: int category: str category_id: int + category_color: str location: str person: str @@ -24,6 +25,7 @@ class UpdateScheduleDTO(BaseModel): member_id: int category: str category_id: int + category_color: str location: str person: str diff --git a/app/dto/openai_dto.py b/app/dto/openai_dto.py index 70eb0c3..9e66a61 100644 --- a/app/dto/openai_dto.py +++ b/app/dto/openai_dto.py @@ -13,6 +13,7 @@ class ChatResponse(BaseModel): class ChatCaseResponse(BaseModel): ness: str case: int + metadata: str class TagDescription(BaseModel): tag: str diff --git a/app/prompt/openai_config.ini b/app/prompt/openai_config.ini index 0b815c2..8d4d3b3 100644 --- a/app/prompt/openai_config.ini +++ b/app/prompt/openai_config.ini @@ -6,19 +6,19 @@ MODEL_NAME = gpt-4 [NESS_CASE] TEMPERATURE = 0 MAX_TOKENS = 1000 -MODEL_NAME = gpt-4 +MODEL_NAME = gpt-4-turbo [NESS_RECOMMENDATION] TEMPERATURE = 1 MAX_TOKENS = 2048 -MODEL_NAME = gpt-4 +MODEL_NAME = gpt-4-turbo [NESS_TAGS] TEMPERATURE = 0.5 MAX_TOKENS = 2048 -MODEL_NAME = gpt-4 +MODEL_NAME = gpt-4-turbo [NESS_EMAIL] TEMPERATURE = 0.5 MAX_TOKENS = 2048 -MODEL_NAME = gpt-4 \ No newline at end of file +MODEL_NAME = gpt-4-turbo \ No newline at end of file diff --git a/app/prompt/openai_prompt.py b/app/prompt/openai_prompt.py index 391c148..02bcd63 100644 --- a/app/prompt/openai_prompt.py +++ b/app/prompt/openai_prompt.py @@ -43,7 +43,7 @@ class Template: You will receive a list of the user's schedule information. Your task is to understand each schedule and generate a comment for each. There are a few rules you must follow in your comments: 1. YOU MUST USE {output_language} TO RESPOND TO THE USER INPUT. - 2. Each comment should be concise, relevant to the schedule details, and realistic. + 2. Each comment should be concise, relevant to the schedule details, and realistic. You are able to give advice to the user, if needed. 3. Comments should be in sentence form, suitable for displaying alongside the schedule. 4. Return the comments in a list format, without any additional commentary. @@ -59,11 +59,10 @@ class Template: AI Comments: """ - # case 분류 잘 안됨 - 수정 필요 case_classify_template = """ Task: User Chat Classification You are a case classifier integrated in scheduler application. - Please analyze User Chat according to the following criteria and return the appropriate case number (1, 2, 3). + Please analyze User Chat according to the following criteria and return the appropriate case number (1, 2, 3, 4). {chat_type} - Case 1: \ @@ -72,27 +71,30 @@ class Template: The question involves a request to create a new schedule for the user, including setting up events for specific dates or times. - Case 3: \ The question requires accessing or searching through the user's previous schedule information. This might involve past schedules, preferences, or other relevant details. + - Case 4: \ + The question involves a request to delete an event or events from the user's schedule, necessitating identification and removal of specific entries from the database. After analyzing the content of the question, return the most suitable case number. - YOU MUST ANSWER ONLY WITH NUMBER (1, 2, 3). OTHER WORDS ARE PROHIBITED. IT IS VERY IMPORTANT TO RETURN ONLY THE NUMBERS. NO YAPPING! + YOU MUST ANSWER ONLY WITH NUMBER (1, 2, 3, 4). OTHER WORDS ARE PROHIBITED. IT IS VERY IMPORTANT TO RETURN ONLY THE NUMBERS. NO YAPPING! + Task: Analyze the content of the question and return the most suitable case number. Example 1: User Chat: "What's the weather like tomorrow?" - Task: Analyze the content of the question and return the most suitable case number. Answer: 1 Example 2: User Chat: "I have a meeting with Dr. Lee next Monday at 10 AM." - Task: Analyze the content of the question and return the most suitable case number. Answer: 2 Example 3: User Chat: "Did I have any appointments on the last Friday?" - Task: Analyze the content of the question and return the most suitable case number. Answer: 3 - + Example 4: - Task: Analyze the content of the question and return the most suitable case number. + User Chat: "Please delete my appointment with Dr. Smith next Tuesday." + Answer: 4 + + Example 5: User Chat: {question} Answer: """ @@ -116,40 +118,58 @@ class Template: case2_template = """ {persona} {chat_type} - The user's input contains information about a new event they want to add to their schedule. You have two tasks to perform: - + The user's input contains information about several new events they want to add to their schedule. You have three tasks to perform: + 1. Respond kindly to the user's input. YOU MUST USE {output_language} TO RESPOND TO THE USER INPUT. - 2. Organize the event the user wants to add into a json format for saving in a database. The returned json will have keys for info, location, person, start_time, end_time, and category. The category should include the name, id, and color. - - info: Summarizes what the user wants to do. This value must always be present. - - location: If the user's event information includes a place, save that place as the value. - - person: If the user's event mentions a person they want to include, save that person as the value. - - start_time: If the user's event information includes a specific date and time, save that date and time in datetime format. Dates should be organized based on the current time at the user's location. Current time is {current_time}. - - end_time: If the user's event information includes an end time, save that date and time in datetime format. - - category: Choose the most appropriate category for the event from the following list: {categories}. The category should include the name, id, and color. - Separate the outputs for tasks 1 and 2 with a special token . - + 2. Organize the events the user wants to add into a json format for saving in a database. Each event should be represented as a separate json object within a list. Each json object will have keys for info, location, person, start_time, end_time, and category. The category should include the name, id, and color. + - info: Summarizes what the user wants to do. This value must always be present. + - location: If the user's event information includes a place, save that place as the value. + - person: If the user's event mentions a person they want to include, save that person as the value. + - start_time: If the user's event information includes a specific date and time, save that date and time in ISO 8601 datetime format. If not, feel free to set it to whatever time you think is appropriate. Dates should be organized based on the current time. Current time is {current_time}. + - end_time: If the user's event information includes an end time, save that date and time in ISO 8601 datetime format. + - category: Choose the most appropriate category for the event from the following list: {categories}. The category should include the name, id, and color. + 3. Generate a search keyword for each event that could assist the user in enhancing their planned activity. Include this as a key in each JSON object. + - search keyword: Devise a search term related to the event that can help facilitate or complement the user's plans, such as finding study materials for a study session or locating a nearby parking lot for an event venue. + Separate the outputs for tasks 1 and 2 with a special token . Even if there is only one JSON object, it must be enclosed within a list. + Example for one-shot learning: - - User input: I have a meeting with Dr. Smith at her office on March 3rd from 10am to 11am. - + + User input: I have a meeting with Dr. Smith at her office on March 3rd from 10am to 11am, and a dinner with John at the Italian restaurant on March 4th at 7pm. + Response to user: - Shall I add your meeting with Dr. Smith at her office on March 3rd from 10am to 11am to your schedule? + Shall I add your meeting with Dr. Smith at her office on March 3rd from 10am to 11am and your dinner with John at the Italian restaurant on March 4th at 7pm to your schedule? - {{ - "info": "meeting with Dr. Smith", - "location": "Dr. Smith's office", - "person": "Dr. Smith", - "start_time": "2023-03-03T10:00:00", - "end_time": "2023-03-03T11:00:00", - "category": {{ - "name": "Work", - "id": 1, - "color": "#FF0000" - }} - }} - + [ + {{ + "info": "meeting with Dr. Smith", + "location": "Dr. Smith's office", + "person": "Dr. Smith", + "start_time": "2023-03-03T10:00:00+09:00", + "end_time": "2023-03-03T11:00:00+09:00", + "category": {{ + "name": "Work", + "id": 1, + "color": "#FF0000" + }}, + "search keyword": "nearby parking Dr. Smith's office" + }}, + {{ + "info": "dinner with John", + "location": "Italian restaurant", + "person": "John", + "start_time": "2023-03-04T19:00:00+09:00", + "end_time": null, // Assuming end time is not specified + "category": {{ + "name": "Personal", + "id": 2, + "color": "#00FF00" + }}, + "search keyword": "top Italian wines" + }} + ] + User input: {question} - + Response to user: """ @@ -169,4 +189,65 @@ class Template: User input: {question}, RAG Retrieval: {schedule} Response: + """ + + case4_template = """ + {persona} + {chat_type} + The user's input contains information about several events they want to delete in their schedule. You have two tasks to perform: + + 1. Respond kindly to the user's input. YOU MUST USE {output_language} TO RESPOND TO THE USER INPUT. + 2. You will be given a list of potential candidates from the database for events that the user may want to delete, and you must organize those events that the user has indicated they want to delete into a list. Organize the events the user wants to delete into a json format to make a delete api call in a database. Each event should be represented as a separate json object within a list. Each json object will have keys for info, location, person, start_time, end_time, and category. The category should include the name, id, and color. + - id: Find the id of the schedule in the 'ids'. + - info: The document data of the schedule. + - location: Include the venue or place where the event was scheduled to occur. + - person: List any specific individuals involved in the event. + - start_time: The scheduled start time of the event in ISO 8601 datetime format. + - end_time: The scheduled end time of the event in ISO 8601 datetime format. + - category: The event category with name, id, and an optional color. + + Separate the outputs for tasks 1 and 2 with a special token . Even if there is only one JSON object, it must be enclosed within a list. + + Example for one-shot learning: + + User input: 개발 공부하기와 한강에서의 놀러가기 이벤트를 삭제해 주세요. + + Schedules: {{'ids': [['29', '8', '7', '25', '16']], 'distances': [[0.1681539537965312, 0.17174183647570107, 0.2014914961195574, 0.2014914961195574, 0.21989337760155114]], 'embeddings': None, 'metadatas': [[{{'category': '🍀미분류', 'category_id': 29, 'date': 27, 'datetime_end': '2024-05-27T16:00:00Z', 'datetime_start': '2024-05-27T15:00:00Z', 'location': '공대', 'member': 1, 'month': 5, 'person': '', 'year': 2024}}, {{'category': '공부', 'category_id': 8, 'date': 25, 'datetime_end': '2024-05-25T16:00:00Z', 'datetime_start': '2024-05-25T15:00:00Z', 'location': '카페', 'member': 1, 'month': 5, 'person': '민주, 채원', 'year': 2024}}, {{'category': '공부', 'category_id': 7, 'date': 10, 'datetime_end': '2024-05-10T00:00:00Z', 'datetime_start': '2024-05-10T00:00:00Z', 'location': '한강', 'member': 1, 'month': 5, 'person': '혜승', 'year': 2024}}, {{'category': '🍀미분류', 'category_id': 25, 'date': 10, 'datetime_end': '2024-05-10T00:00:00Z', 'datetime_start': '2024-05-10T00:00:00Z', 'location': '한강', 'member': 1, 'month': 5, 'person': '혜승', 'year': 2024}}, {{'category': '📖 공부', 'category_id': 16, 'date': 15, 'datetime_end': '2024-05-15T16:00:00Z', 'datetime_start': '2024-05-15T15:00:00Z', 'location': '', 'member': 3, 'month': 5, 'person': '', 'year': 2024}}]], 'documents': [['개발 공부하기', '열심히 개발하기', '한강 놀러가기', '한강 놀러가기', '리액트 공부하기']], 'uris': None, 'data': None}} + + Response to user: 다음의 일정을 삭제해드릴까요? + + [ + {{ + "id": 29 + "info": "개발 공부하기", + "location": "공대", + "person": "", + "start_time": "2024-05-27T15:00:00Z", + "end_time": "2024-05-27T16:00:00Z", + "category": {{ + "name": "🍀미분류", + "id": 29, + "color": "" + }} + }}, + {{ + "id": 7 + "info": "한강 놀러가기", + "location": "한강", + "person": "혜승", + "start_time": "2024-05-10T00:00:00Z", + "end_time": "2024-05-10T00:00:00Z", + "category": {{ + "name": "🍀미분류", + "id": 25, + "color": "" + }} + }} + ] + + User input: {question} + + Schedules: {schedule} + + Response to user: """ \ No newline at end of file diff --git a/app/routers/chat.py b/app/routers/chat.py index 605f78a..fd39608 100644 --- a/app/routers/chat.py +++ b/app/routers/chat.py @@ -13,6 +13,7 @@ from app.prompt import openai_prompt, persona_prompt import app.database.chroma_db as vectordb +import pytz router = APIRouter( prefix="/chat", @@ -61,18 +62,28 @@ async def get_langchain_case(data: PromptRequest) -> ChatCaseResponse: case = int(case) if case == 1: response = await get_langchain_normal(data, chat_type_prompt) + metadata = "null" elif case == 2: - response = await get_langchain_schedule(data, chat_type_prompt) + response_with_metadata = await get_langchain_schedule(data, chat_type_prompt) + response = response_with_metadata.split("")[0] + metadata = response_with_metadata.split("")[1] elif case == 3: response = await get_langchain_rag(data, chat_type_prompt) + metadata = "null" + + elif case == 4: + response_with_metadata = await delete_schedule(data, chat_type_prompt) + response = response_with_metadata.split("")[0] + metadata = response_with_metadata.split("")[1] else: response = "좀 더 명확한 요구가 필요해요. 다시 한 번 얘기해주실 수 있나요?" case = "Exception" + metadata = "null" - return ChatCaseResponse(ness=response, case=case) + return ChatCaseResponse(ness=response, case=case, metadata=metadata) # case 1 : normal @@ -95,7 +106,9 @@ async def get_langchain_normal(data: PromptRequest, chat_type_prompt): # case 1 my_template = openai_prompt.Template.case1_template prompt = PromptTemplate.from_template(my_template) - current_time = datetime.now() + seoul_timezone = pytz.timezone('Asia/Seoul') + current_time = datetime.now(seoul_timezone) + print(f'current time: {current_time}') response = chat_model.predict(prompt.format(persona=user_persona_prompt, output_language="Korean", question=question, current_time=current_time, chat_type=chat_type_prompt)) print(response) return response @@ -128,7 +141,9 @@ async def get_langchain_schedule(data: PromptRequest, chat_type_prompt): case2_template = openai_prompt.Template.case2_template prompt = PromptTemplate.from_template(case2_template) - current_time = datetime.now() + seoul_timezone = pytz.timezone('Asia/Seoul') + current_time = datetime.now(seoul_timezone) + print(f'current time: {current_time}') # OpenAI 프롬프트에 데이터 통합 response = chat_model.predict( @@ -146,6 +161,7 @@ async def get_langchain_schedule(data: PromptRequest, chat_type_prompt): return response except Exception as e: + print(e) raise HTTPException(status_code=500, detail=str(e)) # case 3 : rag @@ -172,7 +188,39 @@ async def get_langchain_rag(data: PromptRequest, chat_type_prompt): case3_template = openai_prompt.Template.case3_template prompt = PromptTemplate.from_template(case3_template) - current_time = datetime.now() + seoul_timezone = pytz.timezone('Asia/Seoul') + current_time = datetime.now(seoul_timezone) + print(f'current time: {current_time}') + response = chat_model.predict(prompt.format(persona=user_persona_prompt, output_language="Korean", question=question, schedule=schedule, current_time=current_time, chat_type=chat_type_prompt)) + print(response) + return response + +# case 4 : delete schedule +async def delete_schedule(data: PromptRequest, chat_type_prompt): + print("running case 4: delete schedule") + + config_normal = config['NESS_NORMAL'] + + chat_model = ChatOpenAI(temperature=config_normal['TEMPERATURE'], # 창의성 (0.0 ~ 2.0) + max_tokens=config_normal['MAX_TOKENS'], # 최대 토큰수 + model_name=config_normal['MODEL_NAME'], # 모델명 + openai_api_key=OPENAI_API_KEY # API 키 + ) + member_id = data.member_id + question = data.prompt + persona = data.persona + user_persona_prompt = persona_prompt.Template.from_persona(persona) + + # vectordb.search_db_query를 비동기적으로 호출합니다. + schedule = await vectordb.search_db_query_delete(member_id, question) # vector db에서 검색 + + # description: give NESS's ideal instruction as template + case4_template = openai_prompt.Template.case4_template + + prompt = PromptTemplate.from_template(case4_template) + seoul_timezone = pytz.timezone('Asia/Seoul') + current_time = datetime.now(seoul_timezone) + print(f'current time: {current_time}') response = chat_model.predict(prompt.format(persona=user_persona_prompt, output_language="Korean", question=question, schedule=schedule, current_time=current_time, chat_type=chat_type_prompt)) print(response) return response diff --git a/requirements.txt b/requirements.txt index 4f4da86..f867fc6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ python-dotenv==1.0.0 starlette==0.35.1 pydantic==2.5.3 sentence-transformers==2.5.1 -pymysql \ No newline at end of file +pymysql +pytz \ No newline at end of file