From c82176a4d07bb46a4229d985f45c46df84afc8fd Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:40:09 +0900 Subject: [PATCH 1/5] =?UTF-8?q?perf:=20=ED=94=84=EB=A1=AC=ED=94=84?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/gpt_service.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/services/gpt_service.py b/app/services/gpt_service.py index 4e78c56..cf6bded 100644 --- a/app/services/gpt_service.py +++ b/app/services/gpt_service.py @@ -1,7 +1,6 @@ from openai import OpenAI from app.config.settings import settings -from app.dto.resume_dto import GptProject -from app.dto.resume_modify_dto import ResumeResponseDto, ProjectTitleDto, RoleAndTaskDto, TroubleShootingDto, StarDto +from app.dto.resume_dto import GptProject, ResumeResponseDto, ProjectTitleDto, RoleAndTaskDto, TroubleShootingDto, StarDto from app.services.github_service import get_github_profile_and_repos, project_title_candidate import tiktoken import json @@ -293,9 +292,10 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro { "role": "system", "content": ( - "You are a friendly and professional resume modification expert." - "Your task is to update only the specified sections of the resume based on the user's request while leaving all other parts unchanged." - "Ensure the modifications are concise, professional, and aligned with the tone of the original resume." + "You are a professional resume modification assistant. Your task is to update only the `selected_text` " + "based on the user's request while leaving all other fields in the context completely unchanged. " + "You are forbidden from changing any part of the context or other values besides the `selected_text`." + "Ensure that the updated text aligns with the style and tone of the original text." ) }, { From 0f713cfb3ccb551b22c5f575449c43fa991cd0cc Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:40:40 +0900 Subject: [PATCH 2/5] =?UTF-8?q?perf:=20=ED=94=84=EB=A1=AC=ED=94=84?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/prompts/resume_prompt.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/prompts/resume_prompt.py b/app/prompts/resume_prompt.py index e63d4d9..506767a 100644 --- a/app/prompts/resume_prompt.py +++ b/app/prompts/resume_prompt.py @@ -64,11 +64,14 @@ ) # 이력서 재생성 RESUME_UPDATE_PROMPT = ( - "Do not use code blocks (```), but markdown formatting such as bold or lists is allowed. Provide plain text instead of code blocks." - "Update the provided `selected_text` based on the user's request. " - "Modify only the specified text, and ensure the updated text aligns with the tone and style of the surrounding context. " - "Return only the modified text without altering the structure or other parts of the resume." + "You are a professional resume editor. Your task is to modify only the specified text within the resume. " + "Do not change, update, or correct any other fields, sections, or even typos in the resume, even if you think it improves the content. " + "Focus exclusively on the `selected_text` and apply the user's request strictly. " + "Make sure your modifications are concise, professional, and match the tone and style of the original text. " + "Do not perform spell-checking or grammatical corrections unless explicitly requested. " + "Return only the updated value for the `selected_text` without altering the structure, keys, or other values in the JSON." ) + # 프로젝트 이름 생성 PROJECT_TITLE_PROMPT = ( @@ -76,6 +79,7 @@ "Do not include any additional explanation, formatting, or context. " "Only provide the title itself." ) + # 맡은 업무 생성 ROLE_AND_TASK_PROMPT = ( "Do not use code blocks (```), but markdown formatting such as bold or lists is allowed. Provide plain text instead of code blocks." @@ -96,14 +100,14 @@ # 트러블슈팅 생성 TROUBLE_SHOOTING_PROMPT = ( "Do not use code blocks (```), but markdown formatting such as bold or lists is allowed. Provide plain text instead of code blocks." + "The output should be concise, with 1-2 sentences summarizing the troubleshooting steps and resolution." ) -# star기법 기반 생성 STAR_PROMPT = ( "Do not use code blocks (```), but markdown formatting such as bold or lists is allowed. Provide plain text instead of code blocks." - "The output should be generated in the following format:\n\n" - "- **Situation**: [Background and goals of the project]\n" - "- **Task**: [Challenges or problems addressed]\n" - "- **Action**: [Specific actions taken to solve the problem]\n" - "- **Result**: [Outcomes and improvements, including measurable metrics if possible]" + "The output should be generated in the following format:\n\n" + "- **Situation**: [1 sentence describing the background and goals of the project]\n" + "- **Task**: [1 sentence summarizing the challenges or problems addressed]\n" + "- **Action**: [1 sentence explaining the specific actions taken to solve the problem]\n" + "- **Result**: [1 sentence describing the outcomes and improvements, including measurable metrics if possible]" ) \ No newline at end of file From 187eb4d38d0d171fe4523ac5de26e8ab2c5868ae Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Fri, 20 Dec 2024 21:41:05 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20dto=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dto/resume_dto.py | 120 ++++++++++++++++++++++++++++++- app/dto/resume_modify_dto.py | 133 ++++++++++++++++++----------------- 2 files changed, 185 insertions(+), 68 deletions(-) diff --git a/app/dto/resume_dto.py b/app/dto/resume_dto.py index 30e234a..f92241c 100644 --- a/app/dto/resume_dto.py +++ b/app/dto/resume_dto.py @@ -1,6 +1,5 @@ from pydantic import BaseModel, HttpUrl -from typing import List, Union -from app.dto.resume_modify_dto import StarDto, TroubleShootingDto +from typing import List, Union, Optional, Literal # 요청 데이터 모델 @@ -14,16 +13,44 @@ class ResumeRequest(BaseModel): # 응답 데이터 모델 - 프로젝트 정보(공통DTO) class Project(BaseModel): + # type: str + type: Literal['BASIC'] = "BASIC" projectName: str projectStartedAt: str # YYYY-MM-DD 형식 projectEndedAt: str # YYYY-MM-DD 형식 skillSet: str roleAndTask: List[str] # BASIC 템플릿에서 사용 - repoLink: HttpUrl + repoLink: str +class StarDto(BaseModel): + situation: str + task: str + action: str + result: str + +class ProjectTitleDto(BaseModel): + projectTitle: str + +class RoleAndTaskDto(BaseModel): + roleAndTask: List[str] + +class TroubleShootingDto(BaseModel): + problem: str + hypothesis: str + tring: str + result: str + +# class Basic(Project): + # type: str = "BASIC" + class StarProject(Project): + # type: str = "STAR" + type: Literal['STAR'] = "STAR" star: StarDto + class GitfolioProject(Project): + # type: str = "GITFOLIO" + type: Literal['GITFOLIO'] = "GITFOLIO" troubleShooting: TroubleShootingDto # gpt 프로젝트 요약문, json형태 @@ -43,3 +70,90 @@ class ResumeResponse(BaseModel): techStack: List[str] aboutMe: str projects: List[Union[Project, StarProject, GitfolioProject]] + +class WorkExperience(BaseModel): + companyName: str + departmentName: str + role: str + workType: str # FULL_TIME, PART_TIME 등 + employmentStatus: str # EMPLOYMENT, UNEMPLOYMENT 등 + startedAt: str # YYYY-MM 형식 + endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 + +class ProjectResponse(BaseModel): + projectName: str + projectStartedAt: str # YYYY.MM 형식 + projectEndedAt: str # YYYY.MM 형식 + skillSet: str + projectDescription: str + repoLink: str + +class Link(BaseModel): + linkTitle: str + linkUrl: str + +class Education(BaseModel): + schoolType: str # UNIVERSITY_BACHELOR 등 + schoolName: str + major: str + graduationStatus: str # ATTENDING, GRADUATED 등 + startedAt: str # YYYY-MM 형식 + endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 + +class Certificate(BaseModel): + certificateName: str + certificateGrade: str + certificatedAt: str # YYYY-MM 형식 + certificateOrganization: str + +# 응답 데이터 모델 - 전체 이력서 +class ResumeResponseDto(BaseModel): + template: str + resumeId: str + memberId: int + memberName: str + avatarUrl: str + email: str + position: str + techStack: List[str] + aboutMe: str + tags: Optional[List[str]] # null 가능 + workExperiences: List[WorkExperience] + # projects: List[ProjectResponse] + projects: List[Union[Project, StarProject, GitfolioProject]] + links: Optional[List[Link]] # null 가능 + educations: List[Education] + certificates: List[Certificate] + +class updateResumeDto(BaseModel): + template: str + resumeId: str + memberId: int + memberName: str + avatarUrl: str + email: str + position: str + techStack: List[str] + aboutMe: str + tags: Optional[List[str]] # null 가능 + workExperiences: List[WorkExperience] + type: Literal['BASIC'] = "BASIC" + projectName: str + projectStartedAt: str # YYYY-MM-DD 형식 + projectEndedAt: str # YYYY-MM-DD 형식 + skillSet: str + links: Optional[List[Link]] # null 가능 + educations: List[Education] + certificates: List[Certificate] + roleAndTask: List[str] # BASIC 템플릿에서 사용 + + repoLink: str + star: Optional[StarDto] + troubleShooting: Optional[TroubleShootingDto] + +class UpdateRequestDto(BaseModel): + selectedText: str + requirement: str + resumeInfo: updateResumeDto + + \ No newline at end of file diff --git a/app/dto/resume_modify_dto.py b/app/dto/resume_modify_dto.py index ada5645..1f5a9ab 100644 --- a/app/dto/resume_modify_dto.py +++ b/app/dto/resume_modify_dto.py @@ -1,75 +1,78 @@ -from pydantic import BaseModel -from typing import List, Optional +# from pydantic import BaseModel +# from typing import List, Optional, Union +# from app.dto.resume_dto import Project, StarProject, GitfolioProject -class WorkExperience(BaseModel): - companyName: str - departmentName: str - role: str - workType: str # FULL_TIME, PART_TIME 등 - employmentStatus: str # EMPLOYMENT, UNEMPLOYMENT 등 - startedAt: str # YYYY-MM 형식 - endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 +# class WorkExperience(BaseModel): +# companyName: str +# departmentName: str +# role: str +# workType: str # FULL_TIME, PART_TIME 등 +# employmentStatus: str # EMPLOYMENT, UNEMPLOYMENT 등 +# startedAt: str # YYYY-MM 형식 +# endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 -class ProjectResponse(BaseModel): - projectName: str - projectStartedAt: str # YYYY.MM 형식 - projectEndedAt: str # YYYY.MM 형식 - skillSet: str - projectDescription: str - repoLink: str -class Link(BaseModel): - linkTitle: str - linkUrl: str +# class ProjectResponse(BaseModel): +# projectName: str +# projectStartedAt: str # YYYY.MM 형식 +# projectEndedAt: str # YYYY.MM 형식 +# skillSet: str +# projectDescription: str +# repoLink: str + +# class Link(BaseModel): +# linkTitle: str +# linkUrl: str -class Education(BaseModel): - schoolType: str # UNIVERSITY_BACHELOR 등 - schoolName: str - major: str - graduationStatus: str # ATTENDING, GRADUATED 등 - startedAt: str # YYYY-MM 형식 - endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 +# class Education(BaseModel): +# schoolType: str # UNIVERSITY_BACHELOR 등 +# schoolName: str +# major: str +# graduationStatus: str # ATTENDING, GRADUATED 등 +# startedAt: str # YYYY-MM 형식 +# endedAt: Optional[str] # YYYY-MM 형식 또는 빈 값 -class Certificate(BaseModel): - certificateName: str - certificateGrade: str - certificatedAt: str # YYYY-MM 형식 - certificateOrganization: str +# class Certificate(BaseModel): +# certificateName: str +# certificateGrade: str +# certificatedAt: str # YYYY-MM 형식 +# certificateOrganization: str -class ResumeResponseDto(BaseModel): - resumeId: str - memberId: int - memberName: str - avatarUrl: str - email: str - position: str - techStack: List[str] - aboutMe: str - tags: Optional[List[str]] # null 가능 - workExperiences: List[WorkExperience] - projects: List[ProjectResponse] - links: Optional[List[Link]] # null 가능 - educations: List[Education] - certificates: List[Certificate] +# class ResumeResponseDto(BaseModel): +# resumeId: str +# memberId: int +# memberName: str +# avatarUrl: str +# email: str +# position: str +# techStack: List[str] +# aboutMe: str +# tags: Optional[List[str]] # null 가능 +# workExperiences: List[WorkExperience] +# # projects: List[ProjectResponse] +# projects: List[Union[Project, StarProject, GitfolioProject]] +# links: Optional[List[Link]] # null 가능 +# educations: List[Education] +# certificates: List[Certificate] -class UpdateRequestDto(BaseModel): - selectedText: str - requirement: str - resumeInfo: ResumeResponseDto +# class UpdateRequestDto(BaseModel): +# selectedText: str +# requirement: str +# resumeInfo: ResumeResponseDto -class ProjectTitleDto(BaseModel): - projectTitle: str +# class ProjectTitleDto(BaseModel): +# projectTitle: str -class RoleAndTaskDto(BaseModel): - roleAndTask: List[str] +# class RoleAndTaskDto(BaseModel): +# roleAndTask: List[str] -class TroubleShootingDto(BaseModel): - problem: str - hypothesis: str - tring: str - result: str +# class TroubleShootingDto(BaseModel): +# problem: str +# hypothesis: str +# tring: str +# result: str -class StarDto(BaseModel): - situation: str - task: str - action: str - result: str \ No newline at end of file +# class StarDto(BaseModel): +# situation: str +# task: str +# action: str +# result: str \ No newline at end of file From a68185ba40ae614d69d424b4b59155c1603d25a4 Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:36:47 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=EA=B2=BD=EB=A1=9C=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/data_service.py | 71 +++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/app/services/data_service.py b/app/services/data_service.py index 306fb31..b75478d 100644 --- a/app/services/data_service.py +++ b/app/services/data_service.py @@ -1,4 +1,5 @@ import os +import json from app.config.settings import settings # 폴더 경로 생성 함수 @@ -30,4 +31,72 @@ def save_summaries_to_file(summary, githubID, repo_url, output_folder=settings.d f.write(f"Summary:\n{summary}\n") f.write("="*50 + "\n") except Exception as e: - print(f"Error saving code summary to file: {e}") \ No newline at end of file + print(f"Error saving code summary to file: {e}") + +# 수정된 내용의 value 찾기 +def find_field_for_text(context_data: dict, selected_text: str) -> list: + matching_fields = [] + + def search_field(data, path=""): + if isinstance(data, dict): + for key, value in data.items(): + # print(f"딕셔너리 탐색 중: 현재 키 = {key}, 현재 경로 = {path}") + search_field(value, f"{path}.{key}" if path else key) + elif isinstance(data, list): + for idx, item in enumerate(data): + # print(f"리스트 탐색 중: 현재 인덱스 = {idx}, 현재 경로 = {path}") + search_field(item, f"{path}[{idx}]") + else: + # print(f"Checking value: {data} at Path: {path}") + if data == selected_text: + print(f"선택된 텍스트와 일치: 경로 = {path}") + matching_fields.append(path) + + search_field(context_data) + print(f"최종 매칭된 경로: {matching_fields}") + return matching_fields + +# 경로에 해당하는 필드 업데이트 +def update_field_by_path(context_data: dict, path: str, new_value): + print(f"필드 업데이트 시작: 경로 = {path}, 새 값 = {new_value}") + + # new_value가 Pydantic 객체인 경우 직렬화 + if hasattr(new_value, 'dict'): + new_value = new_value.dict() + elif hasattr(new_value, 'json'): + new_value = json.loads(new_value.json()) + + keys = path.replace("[", ".").replace("]", "").split(".") + current = context_data + + + for key in keys[:-1]: + if key.isdigit(): # 리스트 인덱스 처리 + current = current[int(key)] + else: + current = current[key] + + last_key = keys[-1] + print(f"최종 업데이트할 키: {last_key}") + if last_key.isdigit(): + current[int(last_key)] = new_value + else: + current[last_key] = new_value + + print(f"업데이트 완료! 최종 데이터:\n{json.dumps(context_data, indent=4, ensure_ascii=False)}") + +def get_value_by_path(data: dict, path: str): + """ + 경로(path)를 따라 데이터에서 값을 가져옵니다. + """ + keys = path.replace("[", ".").replace("]", "").split(".") + current = data + + for key in keys: + if isinstance(current, list) and key.isdigit(): + current = current[int(key)] + elif isinstance(current, dict): + current = current.get(key) + else: + return None # 경로가 잘못된 경우 None 반환 + return current \ No newline at end of file From 4f13e63d9e05ce774d5b7e714fa46a32d9c7da7c Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:30:47 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=EC=9D=B4=EB=A0=A5=EC=84=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이력서 수정기능이 잘 됩니다!! ## 작동방식 * 일단 이력서를 수정한다 * 선택된 텍스트가 어느 경로(value)값인지 찾는다 * 수정한 이력서에 그 해당 경로값만 뽑아낸다 * 원본데이터를 카피했는데 그 카피본에 수정된 경로를 넣는다 * 그걸 반환한다 이렇게하는 이유 : 프롬프트통제가 안되는데 (선택된 부분만 수정하는게 불가능) 그래서 저렇게 강제 수술을함 --- app/api/v1/routes.py | 62 ++++++++++++++++++++++++++++++------ app/dto/resume_dto.py | 17 ++-------- app/prompts/resume_prompt.py | 4 +-- app/services/data_service.py | 26 +++++++-------- app/services/gpt_service.py | 51 ++++++++++++++++++++++++++--- 5 files changed, 115 insertions(+), 45 deletions(-) diff --git a/app/api/v1/routes.py b/app/api/v1/routes.py index 144d97b..75a71b9 100644 --- a/app/api/v1/routes.py +++ b/app/api/v1/routes.py @@ -1,18 +1,20 @@ from fastapi import APIRouter, HTTPException -from app.dto.resume_dto import ResumeRequest, ResumeResponse -from app.dto.resume_modify_dto import UpdateRequestDto,ResumeResponseDto +from app.dto.resume_dto import ResumeRequest, ResumeResponse, UpdateRequestDto,ResumeResponseDto from app.services.api_service import process_repository from concurrent.futures import ProcessPoolExecutor import asyncio +import json import logging from app.services.stack_service import generate_techstack from app.services.gpt_service import generate_aboutme, resume_update +from app.services.data_service import find_field_for_text, update_field_by_path, get_value_by_path from app.config.settings import settings - - +from copy import deepcopy +from pydantic import ValidationError router = APIRouter() + @router.put("/api/ai/resumes", response_model=ResumeResponseDto) async def update_resume(request: UpdateRequestDto): try: @@ -31,18 +33,58 @@ async def update_resume(request: UpdateRequestDto): context_data=request.resumeInfo.dict() ) + print(updated_resume) + # return updated_resume + # 업데이트된 결과 확인 print("=== Updated Resume Data ===") - print(updated_resume) + if hasattr(updated_resume, "dict"): + updated_resume_dict = updated_resume.dict() + else: + updated_resume_dict = updated_resume + + print("=== Updated Resume 딕트 ===") + print(updated_resume_dict) + print(json.dumps(updated_resume_dict, indent=4, ensure_ascii=False)) - # 업데이트된 결과 반환 - return updated_resume + # 선택된 텍스트가 어느 필드에 있는지 찾기 + print("=== 선택된 텍스트의 필드 찾기 ===") + matching_fields = find_field_for_text(request.resumeInfo.dict(), request.selectedText) + print(f"🔍 매칭된 필드 경로: {matching_fields}") - except Exception as e: - print(f"Error in update_resume: {e}") - raise HTTPException(status_code=500, detail="An error occurred while updating the resume.") + if not matching_fields: + raise HTTPException(status_code=400, detail="Selected text does not match any field in the context data.") + + # 원본 데이터를 deepcopy + original_data = deepcopy(request.resumeInfo.dict()) + + print(original_data) + # return updated_resume + # 수정된 필드만 병합 + for field_path in matching_fields: + updated_value = get_value_by_path(updated_resume_dict, field_path) + print(f"✅ 수정된 값 for path {field_path}: {updated_value}") + if updated_value is not None: + update_field_by_path(original_data, field_path, updated_value) + + # 병합 후 데이터 확인 + print("=== 병합된 최종 데이터 확인 ===") + print(json.dumps(original_data, indent=4, ensure_ascii=False)) + + # DTO 변환 + updated_response = ResumeResponseDto(**original_data) + print("=== DTO 변환 성공 ===") + return updated_response + except ValidationError as e: + print(f"❌ DTO Validation Error: {e.json()}") + raise HTTPException(status_code=422, detail=f"DTO validation failed: {e.json()}") + + except Exception as e: + print(f"❌ Error in update_resume: {e}") + raise HTTPException(status_code=500, detail="An error occurred while updating the resume.") + # 이력서 생성 api @router.post("/api/ai/resumes", response_model=ResumeResponse) async def generate_resume(request: ResumeRequest): diff --git a/app/dto/resume_dto.py b/app/dto/resume_dto.py index f92241c..9baa865 100644 --- a/app/dto/resume_dto.py +++ b/app/dto/resume_dto.py @@ -136,24 +136,13 @@ class updateResumeDto(BaseModel): techStack: List[str] aboutMe: str tags: Optional[List[str]] # null 가능 - workExperiences: List[WorkExperience] - type: Literal['BASIC'] = "BASIC" - projectName: str - projectStartedAt: str # YYYY-MM-DD 형식 - projectEndedAt: str # YYYY-MM-DD 형식 - skillSet: str + workExperiences: List[WorkExperience] + projects: List[Union[Project, StarProject, GitfolioProject]] # 추가된 필드 links: Optional[List[Link]] # null 가능 educations: List[Education] certificates: List[Certificate] - roleAndTask: List[str] # BASIC 템플릿에서 사용 - - repoLink: str - star: Optional[StarDto] - troubleShooting: Optional[TroubleShootingDto] class UpdateRequestDto(BaseModel): selectedText: str requirement: str - resumeInfo: updateResumeDto - - \ No newline at end of file + resumeInfo: updateResumeDto \ No newline at end of file diff --git a/app/prompts/resume_prompt.py b/app/prompts/resume_prompt.py index 506767a..e208040 100644 --- a/app/prompts/resume_prompt.py +++ b/app/prompts/resume_prompt.py @@ -65,11 +65,9 @@ # 이력서 재생성 RESUME_UPDATE_PROMPT = ( "You are a professional resume editor. Your task is to modify only the specified text within the resume. " - "Do not change, update, or correct any other fields, sections, or even typos in the resume, even if you think it improves the content. " "Focus exclusively on the `selected_text` and apply the user's request strictly. " "Make sure your modifications are concise, professional, and match the tone and style of the original text. " - "Do not perform spell-checking or grammatical corrections unless explicitly requested. " - "Return only the updated value for the `selected_text` without altering the structure, keys, or other values in the JSON." + # "Return only the updated value for the `selected_text` without altering the structure, keys, or other values in the JSON." ) # 프로젝트 이름 생성 diff --git a/app/services/data_service.py b/app/services/data_service.py index b75478d..c30a498 100644 --- a/app/services/data_service.py +++ b/app/services/data_service.py @@ -48,8 +48,8 @@ def search_field(data, path=""): search_field(item, f"{path}[{idx}]") else: # print(f"Checking value: {data} at Path: {path}") - if data == selected_text: - print(f"선택된 텍스트와 일치: 경로 = {path}") + if isinstance(data, str) and selected_text in data: + print(f"선택된 텍스트 포함: 경로 = {path}") matching_fields.append(path) search_field(context_data) @@ -57,17 +57,17 @@ def search_field(data, path=""): return matching_fields # 경로에 해당하는 필드 업데이트 -def update_field_by_path(context_data: dict, path: str, new_value): - print(f"필드 업데이트 시작: 경로 = {path}, 새 값 = {new_value}") +def update_field_by_path(original_data: dict, path: str, updated_value): + print(f"필드 업데이트 시작: 경로 = {path}, 새 값 = {updated_value}") - # new_value가 Pydantic 객체인 경우 직렬화 - if hasattr(new_value, 'dict'): - new_value = new_value.dict() - elif hasattr(new_value, 'json'): - new_value = json.loads(new_value.json()) + # updated_value가 Pydantic 객체인 경우 직렬화 + if hasattr(updated_value, 'dict'): + updated_value = updated_value.dict() + elif hasattr(updated_value, 'json'): + updated_value = json.loads(updated_value.json()) keys = path.replace("[", ".").replace("]", "").split(".") - current = context_data + current = original_data for key in keys[:-1]: @@ -79,11 +79,11 @@ def update_field_by_path(context_data: dict, path: str, new_value): last_key = keys[-1] print(f"최종 업데이트할 키: {last_key}") if last_key.isdigit(): - current[int(last_key)] = new_value + current[int(last_key)] = updated_value else: - current[last_key] = new_value + current[last_key] = updated_value - print(f"업데이트 완료! 최종 데이터:\n{json.dumps(context_data, indent=4, ensure_ascii=False)}") + print(f"업데이트 완료! 최종 데이터:\n{json.dumps(original_data, indent=4, ensure_ascii=False)}") def get_value_by_path(data: dict, path: str): """ diff --git a/app/services/gpt_service.py b/app/services/gpt_service.py index cf6bded..7a47cc1 100644 --- a/app/services/gpt_service.py +++ b/app/services/gpt_service.py @@ -1,4 +1,5 @@ from openai import OpenAI +import anthropic from app.config.settings import settings from app.dto.resume_dto import GptProject, ResumeResponseDto, ProjectTitleDto, RoleAndTaskDto, TroubleShootingDto, StarDto from app.services.github_service import get_github_profile_and_repos, project_title_candidate @@ -293,9 +294,8 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro "role": "system", "content": ( "You are a professional resume modification assistant. Your task is to update only the `selected_text` " - "based on the user's request while leaving all other fields in the context completely unchanged. " - "You are forbidden from changing any part of the context or other values besides the `selected_text`." "Ensure that the updated text aligns with the style and tone of the original text." + "한글로 작성해주세요." ) }, { @@ -305,11 +305,9 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro f"{json.dumps(context_data, indent=4)}\n\n" "Selected Text:\n" f"{selected_text}\n\n" - # "Key Path:\n" - # f"{key_path}\n\n" "User Request:\n" f"{requirements}\n\n" - "Please update the selected text based on the instructions provided." + "User Request에 맞게 선택된 텍스트를 수정해주세요." ) }, { @@ -332,6 +330,49 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro print(f"Error modifying resume with GPT: {e}") return context_data # 오류 발생 시 기존 데이터 반환 +def generate_resume_update(anthropic_api_key, requirements, selected_text, context_data, prompt): + try: + # 선택된 텍스트가 없을 때 처리 + if not selected_text or not selected_text.strip(): + print("Error: Selected text is empty or missing.") + return context_data # 오류 발생 시 기존 데이터 반환 + + # 수정 요구사항이 없을 때 처리 + if not requirements or not requirements.strip(): + print("Error: User request (requirements) is empty or missing.") + return context_data # 오류 발생 시 기존 데이터 반환 + + # 수정 요청 + print("이력서 수정") + + # Anthropic API 호출 + client = anthropic.Anthropic(api_key=anthropic_api_key) + response = client.completions.create( + model="claude-3.5", + max_tokens_to_sample=500, + temperature=0.7, + prompt=( + f"You are a professional resume modification assistant. Your task is to update the following `selected_text` " + f"based on the `requirements` provided, considering the `context_data`. " + f"Ensure that the updated text aligns with the style and tone of the original text.\n\n" + f"Context Data:\n{json.dumps(context_data, indent=4)}\n\n" + f"Selected Text:\n{selected_text}\n\n" + f"User Request:\n{requirements}\n\n" + f"Sample summary format: {prompt}.\n\n" + f"Please update the selected text based on the instructions provided." + ) + ) + + # 응답 텍스트 추출 + response_text = response.completion.strip() + + # 업데이트된 이력서 반환 + return response_text + + except Exception as e: + print(f"Error modifying resume with Claude: {e}") + return context_data # 오류 발생 시 기존 데이터 반환 + # 이력서 제목 생성 def generate_project_title(openai_api_key, gh_token, repo_url, prompt=settings.project_title_prompt): try: