From 18df2ef9c78409fe5413b614233e07ec9f19c0af Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:54:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 브랜치 명은 플젝네임이지만 실제 함수는 project_title로 작업함 -> 기존 플젝네임함수 버리지 않고 교차검증용으로 사용 --- app/config/settings.py | 3 +- app/dto/resume_modify_dto.py | 5 ++- app/prompts/resume_prompt.py | 6 +++ app/services/github_service.py | 37 ++++++++++++++++ app/services/gpt_service.py | 78 +++++++++++++++++++++++++++++----- 5 files changed, 117 insertions(+), 12 deletions(-) diff --git a/app/config/settings.py b/app/config/settings.py index 2415884..67dce81 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -1,7 +1,7 @@ from typing import List from pydantic_settings import BaseSettings from app.config.constant import GPT_MODEL, MAX_TOTAL_TOKENS, PROMPT_TOKEN_RESERVE, MAX_OUTPUT_TOKENS, MAX_CONTENT_TOKENS, DEFAULT_DATA, CODE_DATA, PR_DATA, COMMIT_DATA, PROJECT_DATA, REPO_DIRECTORY, FILE_EXTENSIONS -from app.prompts.resume_prompt import CODE_SUMMARY_PROMPT, PR_SUMMARY_PROMPT, COMMIT_DIFF_SUMMARY_PROMPT, FINAL_SUMMARY_PROMPT, FINAL_PROJECT_PROMPT, SIMPLIFY_PROJECT_PROMPT, ABOUTME_TECHSTACK_PROMPT, ABOUTME_PROMPT, RESUME_UPDATE_PROMPT +from app.prompts.resume_prompt import CODE_SUMMARY_PROMPT, PR_SUMMARY_PROMPT, COMMIT_DIFF_SUMMARY_PROMPT, FINAL_SUMMARY_PROMPT, FINAL_PROJECT_PROMPT, SIMPLIFY_PROJECT_PROMPT, ABOUTME_TECHSTACK_PROMPT, ABOUTME_PROMPT, RESUME_UPDATE_PROMPT, PROJECT_TITLE_PROMPT class Settings(BaseSettings): # API 키 openai_api_key: str @@ -39,6 +39,7 @@ class Settings(BaseSettings): aboutme_techstack_prompt: str = ABOUTME_TECHSTACK_PROMPT aboutme_prompt: str = ABOUTME_PROMPT resume_update_prompt: str = RESUME_UPDATE_PROMPT + project_title_prompt: str = PROJECT_TITLE_PROMPT class Config: env_file = ".env" diff --git a/app/dto/resume_modify_dto.py b/app/dto/resume_modify_dto.py index ad72835..de7febf 100644 --- a/app/dto/resume_modify_dto.py +++ b/app/dto/resume_modify_dto.py @@ -54,4 +54,7 @@ class ResumeResponseDto(BaseModel): class UpdateRequestDto(BaseModel): selectedText: str requirement: str - resumeInfo: ResumeResponseDto \ No newline at end of file + resumeInfo: ResumeResponseDto + +class ProjectTitleDto(BaseModel): + projectTitle: str \ No newline at end of file diff --git a/app/prompts/resume_prompt.py b/app/prompts/resume_prompt.py index 26656ca..f17566c 100644 --- a/app/prompts/resume_prompt.py +++ b/app/prompts/resume_prompt.py @@ -66,4 +66,10 @@ "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." +) + +PROJECT_TITLE_PROMPT = ( + "Return the best project title as plain text. " + "Do not include any additional explanation, formatting, or context. " + "Only provide the title itself." ) \ No newline at end of file diff --git a/app/services/github_service.py b/app/services/github_service.py index 1562694..7e7324a 100644 --- a/app/services/github_service.py +++ b/app/services/github_service.py @@ -274,3 +274,40 @@ def get_github_profile_and_repos(gh_token): except Exception as e: print(f"Error while fetching GitHub profile and repositories: {e}") return "사용자 GitHub 프로필 요약 정보가 없습니다.", "사용자 GitHub 프로젝트 정보를 가져올 수 없습니다." + +def project_title_candidate(gh_token, repo_url): + try: + # GitHub API 클라이언트 초기화 + g = Github(gh_token) + + # 레포지토리 이름 추출 + repo_name = "/".join(repo_url.rstrip('/').split('/')[-2:]) + repo = g.get_repo(repo_name) + + # 제목 후보 1: 레포지토리 이름 + title_candidate_1 = repo.name + print(f"Title Candidate 1 (Repo Name): {title_candidate_1}") + + # 제목 후보 2: README 첫 줄 + try: + readme_content = repo.get_readme().decoded_content.decode("utf-8") + title_candidate_2 = readme_content.splitlines()[0] if readme_content else "" + print(f"Title Candidate 2 (README First Line): {title_candidate_2}") + except Exception as e: + print(f"Error fetching README content: {e}") + title_candidate_2 = "" + + # 제목 후보 3 : topic + try: + topics = repo.get_topics() # 토픽 리스트 가져오기 + title_candidate_3 = ", ".join(topics) if topics else "No topics available" + print(f"Title Candidate 3 (Topics): {title_candidate_3}") + except Exception as e: + print(f"Error fetching topics: {e}") + title_candidate_3 = "No topics available" + + return title_candidate_1, title_candidate_2, title_candidate_3 + + except Exception as e: + print(f"Error creating project name: {e}") + return "프로젝트 제목을 생성할 수 없습니다." \ No newline at end of file diff --git a/app/services/gpt_service.py b/app/services/gpt_service.py index 4b22b48..c77a255 100644 --- a/app/services/gpt_service.py +++ b/app/services/gpt_service.py @@ -1,9 +1,8 @@ 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 -from app.services.github_service import get_github_profile_and_repos -# from app.services.json_service import find_key_by_value +from app.dto.resume_modify_dto import ResumeResponseDto, ProjectTitleDto +from app.services.github_service import get_github_profile_and_repos, project_title_candidate import tiktoken import json import os @@ -283,13 +282,6 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro print("Error: User request (requirements) is empty or missing.") return context_data # 오류 발생시 기존 데이터 반환 - # # 선택된 텍스트의 키 경로 탐색 - # key_path = find_key_by_value(context_data, selected_text) - - # if not key_path: - # print("Error: Selected text does not match any value in the JSON data.") - # return context_data # 오류 발생시 기존 데이터 반환 - # 수정 요청 print("이력서 수정") @@ -339,3 +331,69 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro except Exception as e: print(f"Error modifying resume with GPT: {e}") return context_data # 오류 발생 시 기존 데이터 반환 + +def create_project_title(openai_api_key, gh_token, repo_url, prompt=settings.project_title_prompt): + try: + # 제목 후보 1과 후보 2 가져오기 + title_candidate_1, title_candidate_2, title_candidate_3 = project_title_candidate(gh_token, repo_url) + + # 후보 검증 및 최종 제목 결정 + if title_candidate_1 == title_candidate_2: + print("Title Candidate 1 and 2 are identical.") + title = {"projectTitle": title_candidate_1} + return title + + print("프로젝트 제목을 추론합니다.") + + # OpenAI API 호출 + client = OpenAI(api_key=openai_api_key) + response = client.beta.chat.completions.parse( + model=settings.gpt_model, + messages=[ + { + "role": "system", + "content": ( + "You are a professional assistant tasked with deciding the best project title. " + "Evaluate the provided title candidates and choose the most appropriate one. " + "Focus on clarity, relevance, and alignment with typical project naming conventions." + ) + }, + { + "role": "user", + "content": ( + "You are tasked with evaluating and determining the best project title based on the following candidates:\n\n" + f"title_candidate_1 (Repository Name):\n{title_candidate_1}\n\n" + f"title_candidate_2 (README First Line):\n{title_candidate_2}\n\n" + f"title_candidate_3 (Topics):\n{title_candidate_3}\n\n" + "Candidate 1 and 2 are the primary sources for the project title as they are likely to directly reflect the project’s purpose. " + "Evaluate these first to infer a suitable title. " + "If these candidates lack sufficient clarity or relevance, use Candidate 3 (Topics) to provide additional context or inspiration for the title.\n\n" + "Your decision should prioritize the following criteria:\n" + "- Clarity: The title should be easy to understand and intuitive.\n" + "- Relevance: The title should accurately represent the project’s purpose, functionality, or key features.\n" + "- Alignment: The title should align with common naming conventions for projects of this type.\n\n" + "If none of the candidates are appropriate, synthesize information from all three candidates to create a new title that best fits the project.\n\n" + "Be concise and professional in your suggestion." + ) + }, + { + "role": "assistant", + "content": f"Sample summary format: {prompt}." + } + ], + max_tokens=settings.max_output_tokens, + response_format=ProjectTitleDto + ) + # GPT 응답 파싱 + response_text = response.choices[0].message.parsed + + # 최종 리턴값 확인 - 테스트용 + print(f"Final Response Text: {response_text}") + + # ProjectTitleDto 객체로 반환 + return response_text + + + except Exception as e: + print(f"Error modifying resume with GPT: {e}") + return "" \ No newline at end of file From d07375e13af3350b9d8bf207738db6ccd9348731 Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:21:07 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:api=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/v1/routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/v1/routes.py b/app/api/v1/routes.py index f1a6ee7..556b756 100644 --- a/app/api/v1/routes.py +++ b/app/api/v1/routes.py @@ -16,7 +16,7 @@ router = APIRouter() # 이력서 생성 api -@router.post("/api/resumes", response_model=ResumeResponse) +@router.post("/api/ai/resumes", response_model=ResumeResponse) async def generate_resume(request: ResumeRequest): logging.basicConfig(level=logging.INFO) @@ -62,7 +62,7 @@ async def generate_resume(request: ResumeRequest): -@router.put("/api/resumes", response_model=ResumeResponseDto) +@router.put("/api/ai/resumes", response_model=ResumeResponseDto) async def update_resume(request: UpdateRequestDto): try: # 요청 데이터 확인 From 6e50160061816ce04c53cdb1ece35387c642aa46 Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:56:54 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=ED=86=A0=ED=94=BD=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EB=B9=88=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/v1/routes.py | 2 +- app/services/github_service.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/api/v1/routes.py b/app/api/v1/routes.py index 556b756..d223e83 100644 --- a/app/api/v1/routes.py +++ b/app/api/v1/routes.py @@ -39,7 +39,7 @@ async def generate_resume(request: ResumeRequest): # aboutme_techstack = create_aboutme_techstack(project_summaries) - + # 레포 이름 repo_name = "/".join(str(request.selectedRepo[0]).rstrip('/').split('/')[-2:]) diff --git a/app/services/github_service.py b/app/services/github_service.py index 7e7324a..54efe9f 100644 --- a/app/services/github_service.py +++ b/app/services/github_service.py @@ -300,11 +300,11 @@ def project_title_candidate(gh_token, repo_url): # 제목 후보 3 : topic try: topics = repo.get_topics() # 토픽 리스트 가져오기 - title_candidate_3 = ", ".join(topics) if topics else "No topics available" + title_candidate_3 = ", ".join(topics) if topics else "" print(f"Title Candidate 3 (Topics): {title_candidate_3}") except Exception as e: print(f"Error fetching topics: {e}") - title_candidate_3 = "No topics available" + title_candidate_3 = "" return title_candidate_1, title_candidate_2, title_candidate_3 From e2a1a8e0b09d90797a9f618c7742d4979a443a7a Mon Sep 17 00:00:00 2001 From: Oh-JunTaek <143782929+Oh-JunTaek@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:57:02 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EC=A0=9C=EB=AA=A9=ED=9B=84=EB=B3=B4?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pr을 받고 제목후보 처리 과정에서 오류가 발생할 수 있음을 인지. --- app/services/gpt_service.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/services/gpt_service.py b/app/services/gpt_service.py index c77a255..bb9bf32 100644 --- a/app/services/gpt_service.py +++ b/app/services/gpt_service.py @@ -1,7 +1,7 @@ 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 +from app.dto.resume_modify_dto import ResumeResponseDto, ProjectTitleDto, RoleAndTaskDto from app.services.github_service import get_github_profile_and_repos, project_title_candidate import tiktoken import json @@ -140,7 +140,6 @@ def generate_project_summary_byJson(code_summary, pr_summary, commit_summary, op print(f"Error during summarization: {e}") return GptProject(projectName="", skillSet="", projectDescription="") - # 최종 요약된 부분, 더 간략화 시키기 및 Json형태 포맷으로 정리 def simplify_project_summary_byJson(summary_text, openai_api_key, requirements, prompt=settings.simplify_project_prompt): try: @@ -334,8 +333,11 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro def create_project_title(openai_api_key, gh_token, repo_url, prompt=settings.project_title_prompt): try: - # 제목 후보 1과 후보 2 가져오기 - title_candidate_1, title_candidate_2, title_candidate_3 = project_title_candidate(gh_token, repo_url) + # 제목 후보 가져오기 + title_candidates = project_title_candidate(gh_token, repo_url) + title_candidate_1 = title_candidates.get("title_candidate_1", "") + title_candidate_2 = title_candidates.get("title_candidate_2", "") + title_candidate_3 = title_candidates.get("title_candidate_3", "") # 후보 검증 및 최종 제목 결정 if title_candidate_1 == title_candidate_2: @@ -396,4 +398,11 @@ def create_project_title(openai_api_key, gh_token, repo_url, prompt=settings.pro except Exception as e: print(f"Error modifying resume with GPT: {e}") - return "" \ No newline at end of file + return { + "projectTitle": "", + "title_candidates": { + "title_candidate_1": "", + "title_candidate_2": "", + "title_candidate_3": "" + } + } \ No newline at end of file