diff --git a/app/api/v1/routes.py b/app/api/v1/routes.py index d223e83..144d97b 100644 --- a/app/api/v1/routes.py +++ b/app/api/v1/routes.py @@ -5,63 +5,14 @@ from concurrent.futures import ProcessPoolExecutor import asyncio import logging -import json from app.services.stack_service import generate_techstack from app.services.gpt_service import generate_aboutme, resume_update -from app.services.github_service import get_github_profile_and_repos from app.config.settings import settings router = APIRouter() -# 이력서 생성 api -@router.post("/api/ai/resumes", response_model=ResumeResponse) -async def generate_resume(request: ResumeRequest): - logging.basicConfig(level=logging.INFO) - - # 각 레포지토리 요약을 멀티프로세싱으로 처리 - with ProcessPoolExecutor() as executor: - loop = asyncio.get_event_loop() - tasks = [ - loop.run_in_executor(executor, process_repository, repo_url, request.githubID, request.githubName, request.requirements) - for repo_url in request.selectedRepo - ] - try: - # 실패 시 예외를 발생시키고 에러 메시지를 리턴 - project_summaries = await asyncio.gather(*tasks) - - except Exception as e: - logging.error(f"Error processing repositories: {e}") - return {"error": f"Error processing repositories: {str(e)}"} - - # # aboutme techstack 생성 - # aboutme_techstack = create_aboutme_techstack(project_summaries) - - - # 레포 이름 - repo_name = "/".join(str(request.selectedRepo[0]).rstrip('/').split('/')[-2:]) - - - # techstack 생성 - techstack = generate_techstack(settings.gh_token, repo_name) - aboutme = generate_aboutme(settings.openai_api_key) - - print(aboutme) - - - # 최종 이력서 응답 생성 - resume_response = ResumeResponse( - projects=project_summaries, - techStack=techstack, - aboutMe=aboutme - # aboutMe=aboutme_techstack.aboutMe - ) - return resume_response - - - - @router.put("/api/ai/resumes", response_model=ResumeResponseDto) async def update_resume(request: UpdateRequestDto): try: @@ -89,4 +40,42 @@ async def update_resume(request: UpdateRequestDto): except Exception as e: print(f"Error in update_resume: {e}") - raise HTTPException(status_code=500, detail="An error occurred while updating the resume.") \ No newline at end of file + 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): + logging.basicConfig(level=logging.INFO) + + # 각 레포지토리 요약을 멀티프로세싱으로 처리 + with ProcessPoolExecutor() as executor: + loop = asyncio.get_event_loop() + tasks = [ + loop.run_in_executor(executor, process_repository, request.template, repo_url, request.githubID, request.githubName, request.requirements) + for repo_url in request.selectedRepo + ] + try: + # 실패 시 예외를 발생시키고 에러 메시지를 리턴 + project_data = await asyncio.gather(*tasks) + + except Exception as e: + logging.error(f"Error processing repositories: {e}") + return {"error": f"Error processing repositories: {str(e)}"} + + # 레포 이름 + repo_name = "/".join(str(request.selectedRepo[0]).rstrip('/').split('/')[-2:]) + + # 공동생성 부분 + techStack = generate_techstack(settings.gh_token, repo_name) + aboutMe = generate_aboutme(settings.openai_api_key) + + resume_response = ResumeResponse( + template=request.template, + techStack=techStack, + aboutMe=aboutMe, + projects=project_data + ) + print(resume_response) + + return resume_response \ No newline at end of file diff --git a/app/api/v1/routes2.py b/app/api/v1/routes2.py new file mode 100644 index 0000000..67e268d --- /dev/null +++ b/app/api/v1/routes2.py @@ -0,0 +1,63 @@ +from fastapi import APIRouter, HTTPException +from app.dto.resume_dto import ResumeRequest, ResumeResponse, BasicDto, freedom_dto, gitfolio_dto, star_dto +from app.dto.resume_modify_dto import UpdateRequestDto,ResumeResponseDto +from app.services.api_service import process_repository, create_repo_start_end_date +from concurrent.futures import ProcessPoolExecutor +import asyncio +import logging +import json +from app.services.stack_service import generate_techstack +from app.services.gpt_service import generate_aboutme, resume_update, generate_project_title, generate_role_and_task +from app.services.github_service import get_github_profile_and_repos +from app.config.settings import settings + + + +router = APIRouter() +# 기존 코드 백업용 +# 이력서 생성 api +@router.post("/api/ai/resumes", response_model=ResumeResponse) +async def generate_resume(request: ResumeRequest): + logging.basicConfig(level=logging.INFO) + + # 각 레포지토리 요약을 멀티프로세싱으로 처리 + with ProcessPoolExecutor() as executor: + loop = asyncio.get_event_loop() + tasks = [ + loop.run_in_executor(executor, process_repository, repo_url, request.githubID, request.githubName, request.requirements) + for repo_url in request.selectedRepo + ] + try: + # 실패 시 예외를 발생시키고 에러 메시지를 리턴 + project_summaries = await asyncio.gather(*tasks) + + except Exception as e: + logging.error(f"Error processing repositories: {e}") + return {"error": f"Error processing repositories: {str(e)}"} + + # 레포 이름 + repo_name = "/".join(str(request.selectedRepo[0]).rstrip('/').split('/')[-2:]) + + # techstack 생성 + techstack = generate_techstack(settings.gh_token, repo_name) + aboutme = generate_aboutme(settings.openai_api_key) + + # # 선택적 구성 생성 + # role_and_task = generate_role_and_task(settings.openai_api_key) + # trouble_shooting = generate_trouble_shooting(settings.openai_api_key) + # star = generate_star_summary(settings.openai_api_key) + + + print("==========") + print(aboutme) + + # 최종 이력서 응답 생성 + resume_response = ResumeResponse( + template=request.template, + project_summaries=project_summaries, + ) + print("==================") + print(resume_response) + + return resume_response + # return JSONResponse(content=resume_response.dict()) \ No newline at end of file diff --git a/app/config/settings.py b/app/config/settings.py index 67dce81..211fdc1 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, PROJECT_TITLE_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, ROLE_AND_TASK_PROMPT, TROUBLE_SHOOTING_PROMPT, STAR_PROMPT class Settings(BaseSettings): # API 키 openai_api_key: str @@ -40,6 +40,9 @@ class Settings(BaseSettings): aboutme_prompt: str = ABOUTME_PROMPT resume_update_prompt: str = RESUME_UPDATE_PROMPT project_title_prompt: str = PROJECT_TITLE_PROMPT + role_and_task_prompt:str = ROLE_AND_TASK_PROMPT + trouble_shooting_prompt:str = TROUBLE_SHOOTING_PROMPT + star_prompt:str = STAR_PROMPT class Config: env_file = ".env" diff --git a/app/dto/resume_dto.py b/app/dto/resume_dto.py index af75ddb..30e234a 100644 --- a/app/dto/resume_dto.py +++ b/app/dto/resume_dto.py @@ -1,5 +1,7 @@ from pydantic import BaseModel, HttpUrl -from typing import List +from typing import List, Union +from app.dto.resume_modify_dto import StarDto, TroubleShootingDto + # 요청 데이터 모델 class ResumeRequest(BaseModel): @@ -8,16 +10,22 @@ class ResumeRequest(BaseModel): personalRepo: HttpUrl selectedRepo: List[HttpUrl] requirements: str + template: str -# 응답 데이터 모델 - 프로젝트 정보 +# 응답 데이터 모델 - 프로젝트 정보(공통DTO) class Project(BaseModel): projectName: str projectStartedAt: str # YYYY-MM-DD 형식 projectEndedAt: str # YYYY-MM-DD 형식 skillSet: str - projectDescription: str + roleAndTask: List[str] # BASIC 템플릿에서 사용 repoLink: HttpUrl +class StarProject(Project): + star: StarDto +class GitfolioProject(Project): + troubleShooting: TroubleShootingDto + # gpt 프로젝트 요약문, json형태 class GptProject(BaseModel): projectName: str @@ -31,7 +39,7 @@ class GptAboutmeTechstack(BaseModel): # 응답 데이터 모델 - 전체 이력서 class ResumeResponse(BaseModel): - projects: List[Project] + template: str techStack: List[str] aboutMe: str - + projects: List[Union[Project, StarProject, GitfolioProject]] diff --git a/app/dto/resume_modify_dto.py b/app/dto/resume_modify_dto.py index de7febf..ada5645 100644 --- a/app/dto/resume_modify_dto.py +++ b/app/dto/resume_modify_dto.py @@ -57,4 +57,19 @@ class UpdateRequestDto(BaseModel): resumeInfo: ResumeResponseDto class ProjectTitleDto(BaseModel): - projectTitle: str \ No newline at end of file + projectTitle: str + +class RoleAndTaskDto(BaseModel): + roleAndTask: List[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 diff --git a/app/prompts/resume_prompt.py b/app/prompts/resume_prompt.py index f17566c..e63d4d9 100644 --- a/app/prompts/resume_prompt.py +++ b/app/prompts/resume_prompt.py @@ -23,6 +23,7 @@ ) FINAL_PROJECT_PROMPT = ( + "Do not use code blocks (```), but markdown formatting such as bold or lists is allowed. Provide plain text instead of code blocks." "Write concisely and clearly, highlighting your unique strengths rather than common development details." ) @@ -61,15 +62,48 @@ "3. Use metaphorical or creative expressions to enhance engagement, avoiding direct repetition of the input text.\n" "4. Ensure each point reflects the provided company values and GitHub data." ) - +# 이력서 재생성 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." ) - +# 프로젝트 이름 생성 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." +) +# 맡은 업무 생성 +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." + "Create a summary of the main roles and responsibilities based on the provided data. " + "Use bullet points to list the key actions and tasks performed. Each point should:\n" + "- Begin with an action verb.\n" + "- Be concise and specific.\n" + "- Clearly reflect the user's contributions and achievements.\n\n" + "- Follow this pattern:" + "- Performed [specific action] to achieve [specific result], improving/enhancing [specific metric] by [quantifiable amount]. " + "- Identified [specific issue] and implemented [specific action], resulting in a performance improvement of [quantifiable amount]." + "- Utilized [specific tool or library] to develop/implement [specific functionality or feature]. " + "For example:\n" + "- Designed and implemented Redis caching to improve API response times by 30%.\n" + "- Refactored MongoDB queries to reduce average query time by 55.4%.\n" + "- Developed an event-driven architecture using Kafka for real-time notifications." +) +# 트러블슈팅 생성 +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." +) + +# 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]" ) \ No newline at end of file diff --git a/app/services/api_service.py b/app/services/api_service.py index dbfce05..888b5c1 100644 --- a/app/services/api_service.py +++ b/app/services/api_service.py @@ -1,13 +1,13 @@ -from app.dto.resume_dto import Project -from app.services.github_service import get_combined_pr_text, get_combined_commit_diffs, get_commit_dates, clone_and_extract_files, delete_cloned_repo_from_url -from app.services.gpt_service import slice_and_summarize, final_summarization, generate_project_summary, generate_project_summary_byJson, simplify_project_summary_byJson +from app.dto.resume_dto import Project, StarProject, GitfolioProject +from app.services.github_service import get_combined_pr_text, get_combined_commit_diffs, get_commit_dates, clone_and_extract_files, delete_cloned_repo_from_url, get_issues_data, get_repository_data +from app.services.gpt_service import slice_and_summarize, final_summarization, generate_project_summary, generate_project_summary_byJson, simplify_project_summary_byJson, generate_role_and_task, generate_star_summary, generate_trouble_shooting from app.services.data_service import save_summaries_to_file from app.config.settings import settings from concurrent.futures import ThreadPoolExecutor, as_completed import logging # 병렬로 레포지토리를 처리하고 요약을 반환하는 함수 -def process_repository(repo_url, githubID, githubName, requirements): +def process_repository(template, repo_url, githubID, githubName, requirements): try: with ThreadPoolExecutor() as executor: all_code = clone_and_extract_files(repo_url) @@ -35,19 +35,60 @@ def process_repository(repo_url, githubID, githubName, requirements): project_summary = create_project_summary(final_code_summary, final_pr_summary, final_commit_summary, githubID, repo_url, requirements) simplified_summary = simplify_project_info(project_summary, requirements) first_commit_date, latest_commit_date = create_repo_start_end_date(repo_url) + + # STAR 템플릿인 경우에만 추가 데이터 가져오기 + if template == "STAR": + repo_data = get_repository_data(settings.gh_token, str(repo_url)) + issues_data = get_issues_data(settings.gh_token, str(repo_url)) + star = create_star_summary(settings.openai_api_key, repo_data, issues_data, final_pr_summary, final_commit_summary, requirements) + else: + repo_data = None + issues_data = None + star = None + + # 내가 한 작업 + role_and_task = create_role_and_task(settings.openai_api_key, final_code_summary, final_pr_summary, final_commit_summary, requirements) + # star = create_star_summary(settings.openai_api_key,repo_data, issues_data, final_pr_summary, final_commit_summary, requirements) + trouble_shooting = create_trouble_shooting(settings.openai_api_key, final_code_summary, final_pr_summary, final_commit_summary, requirements) + delete_cloned_repo_from_url(repo_url) - + + # 템플릿 별 데이터 분기 처리 + project_summary += f"\n\n### 프로젝트 요약\n {simplified_summary.projectDescription}**" - project_summary = Project( - projectName=simplified_summary.projectName, - projectStartedAt=first_commit_date, - projectEndedAt=latest_commit_date, - skillSet=simplified_summary.skillSet, - projectDescription=project_summary, - repoLink=repo_url - ) - return project_summary + if template == "BASIC": + project_data = Project( + projectName=simplified_summary.projectName, + projectStartedAt=first_commit_date, + projectEndedAt=latest_commit_date, + skillSet=simplified_summary.skillSet, + roleAndTask=role_and_task, + repoLink=repo_url + ) + elif template == "STAR": + project_data = StarProject( + projectName=simplified_summary.projectName, + projectStartedAt=first_commit_date, + projectEndedAt=latest_commit_date, + skillSet=simplified_summary.skillSet, + roleAndTask=role_and_task, + repoLink=repo_url, + star=star + ) + elif template == "GITFOLIO": + project_data = GitfolioProject( + projectName=simplified_summary.projectName, + projectStartedAt=first_commit_date, + projectEndedAt=latest_commit_date, + skillSet=simplified_summary.skillSet, + roleAndTask=role_and_task, + repoLink=repo_url, + troubleShooting=trouble_shooting + ) + + print("process_repository 반환값 확인:", project_data) + return project_data except Exception as e: logging.error(f"Error in processing repository {repo_url}: {e}") @@ -92,6 +133,18 @@ def create_repo_start_end_date(repo_url): first_commit_date, latest_commit_date = get_commit_dates(settings.gh_token, repo_url) return first_commit_date, latest_commit_date -# # techStack과 aboutMe 생성 -# def create_aboutme_techstack(project_summaries): -# return generate_aboutme_techstack(project_summaries, settings.openai_api_key, prompt=settings.aboutme_techstack_prompt) \ No newline at end of file +# 맡은 역할 생성 +def create_role_and_task(openai_api_key, code_summary, pr_summary, commit_summary, requirements): + role_and_task = generate_role_and_task(openai_api_key, code_summary, pr_summary, commit_summary, requirements) + return role_and_task + +# star 기법으로 생성 +def create_star_summary(openai_api_key, repo_data, issues_data, pr_summary, commit_summary, requirements): + star = generate_star_summary(openai_api_key, repo_data, issues_data, pr_summary, commit_summary, requirements) + return star + +# 트러블 슈팅 생성 +def create_trouble_shooting(openai_api_key, code_summary, pr_summary, commit_summary, requirements): + trouble_shooting = generate_trouble_shooting(openai_api_key, code_summary, pr_summary, commit_summary, requirements) + return trouble_shooting + diff --git a/app/services/api_service2.py b/app/services/api_service2.py new file mode 100644 index 0000000..fa30a57 --- /dev/null +++ b/app/services/api_service2.py @@ -0,0 +1,93 @@ +from app.dto.resume_dto import Project +from app.services.github_service import get_combined_pr_text, get_combined_commit_diffs, get_commit_dates, clone_and_extract_files, delete_cloned_repo_from_url +from app.services.gpt_service import slice_and_summarize, final_summarization, generate_project_summary, generate_project_summary_byJson, simplify_project_summary_byJson +from app.services.data_service import save_summaries_to_file +from app.config.settings import settings +from concurrent.futures import ThreadPoolExecutor, as_completed +import logging + +# 병렬로 레포지토리를 처리하고 요약을 반환하는 함수 +def process_repository(repo_url, githubID, githubName, requirements): + try: + with ThreadPoolExecutor() as executor: + all_code = clone_and_extract_files(repo_url) + # 1, 2, 3번 작업을 병렬로 실행 + futures = { + executor.submit(process_code_files, all_code, repo_url, githubID, requirements): 'code_summary', + executor.submit(process_pr_text, repo_url, githubID, requirements): 'pr_summary', + executor.submit(process_commit_diffs, repo_url, githubID, githubName, requirements): 'commit_summary' + } + results = {} + for future in as_completed(futures): + key = futures[future] + try: + results[key] = future.result() + except Exception as e: + logging.error(f"Error in {key}: {e}") + raise # 에러를 다시 발생시켜 상위에서 예외 처리 + + # 병렬처리 결과 할당 + final_code_summary = results['code_summary'] + final_pr_summary = results['pr_summary'] + final_commit_summary = results['commit_summary'] + + # 이후 작업들 + project_summary = create_project_summary(final_code_summary, final_pr_summary, final_commit_summary, githubID, repo_url, requirements) + simplified_summary = simplify_project_info(project_summary, requirements) + first_commit_date, latest_commit_date = create_repo_start_end_date(repo_url) + delete_cloned_repo_from_url(repo_url) + + project_summary += f"\n\n### 프로젝트 요약\n {simplified_summary.projectDescription}**" + + project_summary = Project( + projectName=simplified_summary.projectName, + projectStartedAt=first_commit_date, + projectEndedAt=latest_commit_date, + skillSet=simplified_summary.skillSet, + projectDescription=project_summary, + repoLink=repo_url + ) + return project_summary + + except Exception as e: + logging.error(f"Error in processing repository {repo_url}: {e}") + raise # 에러를 다시 발생시켜 상위에서 예외 처리 + +# 클론하여 코드 파일을 가져오고 요약을 생성하는 함수 +def process_code_files(all_code, repo_url, githubID, requirements): + initial_summary = slice_and_summarize(all_code, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.code_summary_prompt) + final_code_summary = final_summarization(initial_summary, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.final_summary_prompt) + save_summaries_to_file(final_code_summary, githubID, repo_url, output_folder=settings.code_data) + return final_code_summary + +# PR 텍스트를 가져와서 요약을 생성하는 함수 +def process_pr_text(repo_url, githubID, requirements): + combined_pr_text = get_combined_pr_text(settings.gh_token, githubID, repo_url) + initial_pr_summary = slice_and_summarize(combined_pr_text, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.pr_summary_prompt) + final_pr_summary = final_summarization(initial_pr_summary, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.final_summary_prompt) + save_summaries_to_file(final_pr_summary, githubID, repo_url, output_folder=settings.pr_data) + return final_pr_summary + +# 커밋 diff 내용을 가져와서 요약을 생성하는 함수 +def process_commit_diffs(repo_url, githubID, githubName, requirements): + combined_commit_diffs = get_combined_commit_diffs(settings.gh_token, githubID, githubName, repo_url) + initial_commit_summary = slice_and_summarize(combined_commit_diffs, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.commit_diff_summary_prompt) + final_commit_summary = final_summarization(initial_commit_summary, settings.openai_api_key, requirements, max_output_tokens=settings.max_output_tokens, prompt=settings.final_summary_prompt) + save_summaries_to_file(final_commit_summary, githubID, repo_url, output_folder=settings.commit_data) + return final_commit_summary + +# 코드, PR, 커밋 요약을 종합하여 프로젝트 요약 생성 +def create_project_summary(code_summary, pr_summary, commit_summary, githubID, repo_url, requirements): + project_summary = generate_project_summary(code_summary, pr_summary, commit_summary, settings.openai_api_key, requirements, prompt=settings.final_project_prompt) + save_summaries_to_file(project_summary, githubID, repo_url, output_folder=settings.project_data) + return project_summary + +# 프로젝트 요약을 간단한 형태로 변환 +def simplify_project_info(project_summary, requirements): + simplified_summary = simplify_project_summary_byJson(project_summary, settings.openai_api_key, requirements, prompt=settings.simplify_project_prompt) + return simplified_summary + +# 시작 및 마감 날짜 반환 +def create_repo_start_end_date(repo_url): + first_commit_date, latest_commit_date = get_commit_dates(settings.gh_token, repo_url) + return first_commit_date, latest_commit_date diff --git a/app/services/github_service.py b/app/services/github_service.py index 54efe9f..0a2c37f 100644 --- a/app/services/github_service.py +++ b/app/services/github_service.py @@ -275,6 +275,7 @@ def get_github_profile_and_repos(gh_token): print(f"Error while fetching GitHub profile and repositories: {e}") return "사용자 GitHub 프로필 요약 정보가 없습니다.", "사용자 GitHub 프로젝트 정보를 가져올 수 없습니다." +# 프로젝트 제목 생성 정보 def project_title_candidate(gh_token, repo_url): try: # GitHub API 클라이언트 초기화 @@ -310,4 +311,65 @@ def project_title_candidate(gh_token, repo_url): except Exception as e: print(f"Error creating project name: {e}") - return "프로젝트 제목을 생성할 수 없습니다." \ No newline at end of file + return "프로젝트 제목을 생성할 수 없습니다." + +# star기법에 사용될 깃허브 데이터 +def get_repository_data(gh_token, repo_url): + """ + 리포지토리의 README, Description, Topics을 가져와 딕셔너리 형태로 반환합니다. + """ + try: + g = Github(gh_token) + repo_name = "/".join(repo_url.rstrip('/').split('/')[-2:]) + repo = g.get_repo(repo_name) + + # README 가져오기 + try: + readme_content = repo.get_readme().decoded_content.decode("utf-8") + except Exception: + readme_content = "No README provided." + + # Description 가져오기 + description = repo.description if repo.description else "No description provided." + + # Topics 가져오기 + topics = repo.get_topics() + topics_string = ", ".join(topics) if topics else "No topics available." + + # 결과 반환 + return { + "readme": readme_content, + "description": description, + "topics": topics_string + } + + except Exception as e: + print(f"Error fetching repository data: {e}") + return {"readme": "", "description": "", "topics": ""} + +# 이슈 관련 data ->star기법에 활용 +def get_issues_data(gh_token, repo_url): + """ + 리포지토리의 Open된 이슈 제목과 내용을 가져와 문자열로 반환합니다. + """ + try: + g = Github(gh_token) + repo_name = "/".join(repo_url.rstrip('/').split('/')[-2:]) + repo = g.get_repo(repo_name) + + issues = repo.get_issues(state="open") + combined_issues = "" + + for issue in issues: + title = issue.title + body = issue.body if issue.body else "No description provided." + combined_issues += f"Title: {title}\nDescription: {body}\n\n{'='*50}\n\n" + + if not combined_issues: + combined_issues = "No open issues found." + + return combined_issues + + except Exception as e: + print(f"Error fetching issues data: {e}") + return "No issues available." \ No newline at end of file diff --git a/app/services/gpt_service.py b/app/services/gpt_service.py index 90c8f2c..4e78c56 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, TroubleShootingDto, StarDto from app.services.github_service import get_github_profile_and_repos, project_title_candidate import tiktoken import json @@ -269,6 +269,7 @@ def generate_aboutme(openai_api_key, prompt=settings.aboutme_prompt): print(f"Error generating About Me: {e}") return "" +# 이력서 수정 def resume_update(openai_api_key, requirements, selected_text, context_data, prompt=settings.resume_update_prompt) : try: # 선택된 텍스트가 없을 때 처리 @@ -331,7 +332,8 @@ def resume_update(openai_api_key, requirements, selected_text, context_data, pro 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): +# 이력서 제목 생성 +def generate_project_title(openai_api_key, gh_token, repo_url, prompt=settings.project_title_prompt): try: # 제목 후보 가져오기 title_candidates = project_title_candidate(gh_token, repo_url) @@ -405,4 +407,177 @@ def create_project_title(openai_api_key, gh_token, repo_url, prompt=settings.pro "title_candidate_2": "", "title_candidate_3": "" } - } \ No newline at end of file + } + +# 맡은 업무 생성 +def generate_role_and_task(openai_api_key, code_summary, pr_summary, commit_summary, requirements, prompt=settings.role_and_task_prompt): + try: + # 담당업무 생성 + print("Generating Role and Task...") + + # 어떤 정보를 가져올건지 로직 구현 + if not code_summary.strip() and not pr_summary.strip() and not commit_summary.strip(): # 텍스트가 비어 있는 경우 처리 + print("No text provided for summarization. Skipping...") + return + + # gpt 호출 + 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 specializing in extracting roles and responsibilities from project data. " + "Your task is to identify the key responsibilities and actions taken by the user based on the provided summaries." + "The response must be generated in **Korean**." + )}, + {"role": "user", + "content": ( + f"Analyze the following data and extract the main roles and responsibilities:\n\n" + f"### Code Summary:\n{code_summary}\n\n" + f"### PR Summary:\n{pr_summary}\n\n" + f"### Commit Summary:\n{commit_summary}\n\n" + f"### Requirements:\n{requirements}\n\n" + "Please provide the roles and responsibilities in a concise list format." + )}, + {"role": "assistant", "content": f"{prompt}, focus on {requirements}"} + ], + max_tokens=settings.max_output_tokens, + response_format=RoleAndTaskDto, + ) + # GPT 응답 파싱 + response_text = response.choices[0].message.parsed + # print(f"response_text: {response_text}, {type(response_text)}, {response_text.roleAndTask}, {type(response_text.roleAndTask)}") + + print("Parsed GPT response:", response_text) + + # Role and task 객체로 반환 + return response_text.roleAndTask + + except Exception as e: + print(f"Error during summarization: {e}") + return RoleAndTaskDto(roleAndTask="") + +# 트러블 슈팅 생성 +def generate_trouble_shooting(openai_api_key,code_summary, pr_summary, commit_summary, requirements, prompt=settings.trouble_shooting_prompt): + try: + # 담당업무 생성 + print("Generating Troubleshooting...") + + # 어떤 정보를 가져올건지 로직 구현 + if not code_summary.strip() and not pr_summary.strip() and not commit_summary.strip(): # 텍스트가 비어 있는 경우 처리 + print("No text provided for summarization. Skipping...") + return TroubleShootingDto(problem="", hypothesis="", tring="", result="") + + + # gpt 호출 + client = OpenAI(api_key=openai_api_key) + response = client.beta.chat.completions.parse( + model=settings.gpt_model, + messages=[ + { + "role": "system", + "content": ( + "You are an experienced developer specializing in problem-solving. " + "Your task is to analyze the provided project summaries and identify one major issue faced during the project. " + "Then outline the hypothesis made to solve the issue, the actions taken, and the resulting improvements." + "The response must be generated in **Korean**." + ) + }, + {"role": "user", + "content": ( + f"Analyze the following summaries:\n\n" + f"### Code Summary:\n{code_summary}\n\n" + f"### PR Summary:\n{pr_summary}\n\n" + f"### Commit Summary:\n{commit_summary}\n\n" + f"### Requirements:\n{requirements}\n\n" + "Provide a detailed response in the following format:\n" + "- Problem: [Describe the issue]\n" + "- Hypothesis: [State the hypothesis or approach]\n" + "- Tring: [Describe the actions taken]\n" + "- Result: [Explain the improvement achieved with quantifiable metrics if possible]" + ) + }, + {"role": "assistant", "content": f"{prompt}, focus on {requirements}"} + ], + max_tokens=settings.max_output_tokens, + response_format=TroubleShootingDto, + ) + # GPT 응답 파싱 + response_text = response.choices[0].message.parsed + + # Role and task 객체로 반환 + return response_text + + except Exception as e: + print(f"Error during summarization: {e}") + return TroubleShootingDto(problem="", hypothesis="", tring="", result="") + +# STAR 기법 기반 생성 +def generate_star_summary(openai_api_key, repo_data, issues_data, pr_summary, commit_summary, requirements, prompt=settings.star_prompt): + try: + # 데이터 검증 + if not repo_data and not issues_data and not pr_summary and not commit_summary: + print("No data provided for STAR generation. Skipping...") + return StarDto(situation= "", task = "", action = "", result = "") + + print("star기법으로 프로젝트를 요약합니다...") + + # beta, parse형태로 구성됨. 주기적으로 공식문서 업데이트 확인할것 + client = OpenAI(api_key=openai_api_key) + response = client.beta.chat.completions.parse( + model=settings.gpt_model, + messages=[ + { + "role": "system", + "content": ( + "You are an experienced developer tasked with generating a STAR-based project summary. " + "Each STAR element (Situation, Task, Action, Result) must be derived from specific types of data. " + "When possible, prioritize **quantifiable metrics or measurable outcomes** (e.g., performance improvement percentages, response time reductions, or user adoption rates) " + "to make the summary more impactful and data-driven." + "The response must be generated in **Korean**." + ) + }, + { + "role": "user", + "content": ( + "Generate a STAR summary using the provided data. Use the following data sources for each element:\n\n" + "**Situation** (프로젝트의 목표와 배경):\n" + f"- README: {repo_data.get('readme', 'No README provided')}\n" + f"- Description: {repo_data.get('description', 'No description provided')}\n" + f"- Topics: {repo_data.get('topics', 'No topics available')}\n\n" + "**Task** (해결해야 할 문제):\n" + f"- Issues: {issues_data}\n" + f"- Pull Requests (Titles and Descriptions): {pr_summary}\n\n" + "**Action** (취한 행동):\n" + f"- Commit Diffs and Messages: {commit_summary}\n" + f"- Pull Request Changes: {pr_summary}\n\n" + "**Result** (결과와 성과):\n" + f"- Completed Issues and Merged PRs: {pr_summary}\n" + f"- Commit Performance Metrics: {commit_summary}\n" + f"- Project Outcome (if mentioned in README): {repo_data.get('readme', '')}\n\n" + "Generate the STAR summary in the following format:\n" + "- **Situation**: [Background and goals]\n" + "- **Task**: [Challenges and problems solved]\n" + "- **Action**: [Actions taken to address the tasks]\n" + "- **Result**: [Results with measurable outcomes or improvements]" + ) + }, + { + "role": "assistant", + "content": f"Focus on: {requirements}\n{prompt}" + } + ], + max_tokens=settings.max_output_tokens, + response_format=StarDto, + ) + + # GPT 응답 파싱 + response_text = response.choices[0].message.parsed + + # GptProject 객체로 반환 + return response_text + + except Exception as e: + print(f"An error occurred while simplifying the summary: {e}") + return StarDto(situation= "", task = "", action = "", result = "") \ No newline at end of file