codellama 자동 리뷰 (Pipeline Auto Review) [Local LLM]


#### Requirements - 깃랩 러너(executor = "shell") 설정이 되어 있어야 합니다. - codellama는 러너 서버에 설치를 진행했습니다. #### Architecture ![d](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEMsJ%2FbtsJBQyJBls%2Fyak4KkeAN5CRFIN9baygIk%2Fimg.jpg)
codellama 실행 ``` docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama ```
.gitlab-ci.yml ``` stages: - review review_code: stage: review image: python:3.9 # 필요한 Python 버전으로 설정 before_script: - git config --global http.postBuffer 1048576000 - pip install requests transformers torch # 필요한 라이브러리 설치 script: - python script/review_mr.py # 파이썬 스크립트를 실행하여 MR 리뷰 수행 tags: - review_runner_tag only: - merge_requests variables: GITLAB_TOKEN: $GITLAB_TOKEN GIT_STRATEGY: clone GIT_CLEAN_FLAGS: -ffdx CI_DEBUG_TRACE: "true" GIT_SSL_NO_VERIFY: "true" GITLAB_USER_EMAIL: "hyeonkyeong.park@direa.co.kr" GITLAB_USER_PASSWORD: $CI_JOB_TOKEN ```
review_mr.py ``` import os import requests import subprocess # 설정된 CI/CD 변수에서 GitLab 환경을 가져옵니다. gitlab_token = os.getenv('GITLAB_TOKEN') # GitLab 토큰 project_id = os.getenv('CI_PROJECT_ID') # 프로젝트 ID merge_request_iid = os.getenv('CI_MERGE_REQUEST_IID') # MR ID source_branch = os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME") # 리뷰할 파일의 확장자를 정의 REVIEW_EXTENSIONS = (".java", ".xml", ".jsp", ".js", ".css", ".html", ".vue") def get_mr_diff(): url = f"https://gitlab.direa.synology.me/api/v4/projects/{project_id}/repository/compare" params = { 'to': source_branch, 'from': "develop" } headers = { "PRIVATE-TOKEN": gitlab_token } response = requests.get(url, headers=headers, params=params) if response.status_code == 200: compare_data = response.json() diffs = compare_data.get('diffs', []) if compare_data.get('too_large'): print("Warning: The diff is too large to be shown in full.") print(f"Number of total Diffs: {len(diffs)}") return diffs else: raise Exception(f"Failed to get diff: {response.status_code}, {response.text}") def filter_diffs_by_extension(diffs): reviewable_diffs = [] for diff in diffs: # new_path 또는 old_path에서 특정 확장자인 파일만 선택 new_path = diff.get('new_path', '').lower() # 새 파일의 경로 old_path = diff.get('old_path', '').lower() # 기존 파일의 경로 # 파일이 특정 확장자인 경우만 처리 if new_path.endswith(REVIEW_EXTENSIONS) or old_path.endswith(REVIEW_EXTENSIONS): reviewable_diffs.append(diff) else: print(f"Skipping non-reviewable file: {new_path or old_path}") print(f"Number of Diffs after filtering: {len(reviewable_diffs)}") return reviewable_diffs def review_code_with_ai(diff): prompt = ("\nReview this code, provide suggestions for improvement, coding best practices, improve readability, and maintainability. " "Remove any code smells and anti-patterns. Provide code examples for your suggestion. Respond in markdown format. " "If the file does not have any code or does not need any changes, say 'No changes needed'.") # Docker 명령어 실행을 위한 subprocess 사용 docker_command = [ 'docker', 'exec', 'ollama', 'ollama', 'run', 'codellama', f"Code: {diff} {prompt}" ] print(f"Running Docker command: {' '.join(docker_command)}") try: result = subprocess.run(docker_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if result.returncode != 0: raise Exception(f"Error running Docker command: {result.stderr}") return result.stdout.strip() # 출력을 정리하고 반환 except Exception as e: raise Exception(f"Failed to review code with AI: {str(e)}") def post_review_comment(comment): url = f"https://gitlab.direa.synology.me/api/v4/projects/{project_id}/merge_requests/{merge_request_iid}/notes" headers = { "Authorization": f"Bearer {gitlab_token}", "Content-Type": "application/json" } data = { "body": comment } response = requests.post(url, headers=headers, json=data) if response.status_code != 201: raise Exception(f"Failed to post review comment: {response.status_code}, {response.text}") def main(): diffs = get_mr_diff() # Diff 목록을 가져옴 if not diffs: print("No diffs found.") return # 특정 파일 확장자만 필터링 (java, xml, jsp, js, css, html) filtered_diffs = filter_diffs_by_extension(diffs) all_review_comments = [] # 각 diff의 리뷰 결과를 담을 리스트 # 필터링된 diff에 대해 하나씩 AI 리뷰를 수행 for idx, diff_entry in enumerate(filtered_diffs): diff_text = diff_entry['diff'] # 현재 diff 값을 가져옴 new_path = diff_entry.get('new_path', 'Unknown Path') # 새 파일 경로 print(f"Processing diff {idx + 1}/{len(filtered_diffs)} for file: {new_path}") # 한 개의 diff에 대해 AI 리뷰 실행 try: review_comment = review_code_with_ai(diff_text) all_review_comments.append(f"**Diff {idx + 1}: {new_path}**\n{review_comment}\n") except Exception as e: print(f"Failed to review diffs: {str(e)}") # 모든 리뷰 결과를 GitLab MR 코멘트에 게시 if all_review_comments: full_review_comment = "\n".join(all_review_comments) post_review_comment(full_review_comment) else: print("No valid reviews to post.") if __name__ == "__main__": main() ```
### Results ![d](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0o0Kx%2FbtsJz7Ws0M7%2FylRcstCkldrXlkxtbjggSK%2Fimg.png)
되돌아가기 수정
  • hkpark130
    ci.yml에서 openai 패키지 설치하는 라인 삭제후 피드백에서 openai 관련 내용 없어짐