diff --git a/app/api/v1/routes.py b/app/api/v1/routes.py index f1a6ee7..d223e83 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) @@ -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:]) @@ -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: # 요청 데이터 확인 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..54efe9f 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 "" + print(f"Title Candidate 3 (Topics): {title_candidate_3}") + except Exception as e: + print(f"Error fetching topics: {e}") + title_candidate_3 = "" + + 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..bb9bf32 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, RoleAndTaskDto +from app.services.github_service import get_github_profile_and_repos, project_title_candidate import tiktoken import json import os @@ -141,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: @@ -283,13 +281,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 +330,79 @@ 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: + # 제목 후보 가져오기 + 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: + 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 { + "projectTitle": "", + "title_candidates": { + "title_candidate_1": "", + "title_candidate_2": "", + "title_candidate_3": "" + } + } \ No newline at end of file