Gitlab actions를 이용한 algorithm MR 피드백 처리
- -
이번 포스트에서는 github에 자바 알고리즘 문제에 대한 mr 이 전달된 경우 자동으로 AI가 피드백을 달아주도록 파이프라인을 구축해보자.
🚀 개요
- 목표: Merge Request(MR)가 생성되면 AI가 자동으로 코드를 읽고 분석 댓글 작성
- 사용 기술: GitLab CI/CD, Python, Google Gemini 2.5 Flash API
- 환경: Synology NAS (Self-hosted GitLab Runner)
준비물
Google Gemini API 키 발급
Google AI Studio에서 API 키를 발급받는다. Gemini 2.5 Flash 모델 정도 쓰면 매우 강력한 추론 성능을 무료(Free Tier 기준)로 사용할 수 있다.

GitLab Access Token 발급 받기
MR에 분석 결과를 comment로 달기 위해서는 권한이 필요하다.
GitLab의 preferences > Personal access tokens를 이용해서 token을 생성한다.

NAS에 Runner 배치하기
Runner 구성
Runner는 GitLab의 작업을 실제로 처리할 도구로 NAS의 Docker(Container Manager)환경으로 구축한다. Docker를 구성할 때 시스템 변수를 써야 하므로 docker composer로 구성하는 것이 정신 건강에 좋다.
Synology의 Container Manager > Project > 생성을 선택한다.

경로는 설정 파일이 저장될 경로이다. [경로설정]버튼을 클릭해서 구성해준다.
다음으로 원본 항목을 [docker-compose.yml 만들기]를 선택하고 다음 내용을 붙여 넣는다.
version: '3.8'
services:
runner:
image: gitlab/gitlab-runner:latest
container_name: gitlab-runner
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/etc/gitlab-runner
다음으로 나오는 [웹 포털 설정]은 신경쓰지 말고 그냥 다음으로 이동해서 마무리 한다.
컨테이너 재시작
설정을 마쳤으면 container를 재시작 해준다.

Gitlab CI/CD 설정
Runner 등록
이제 gitlab의 project에 위에서 생성한 Runner를 등록해보자.
Settings > CI/CD > Runners에서 [Create project runner]를 선택한다.

다음 화면에서 Tags에 이름을 입력해주고 [Run untagged jobs]에 체크하자. 안그러면 태그가 안맞는 경우 처리가 pending 된다.

[create runner]를 선택하면 사용할 runner에서 해야할 일이 나온다.

일단 platform에서는 linux이다.(synology가 linux!)
다음으로 Step 1에 있는 스크립트를 복사해서 synology에서 실행해준다. 권한 때문에 앞단에 sudo docker exec -it 가 추가되었다.
# 시놀로지 터미널에서 입력
sudo docker exec -it gitlab-runner gitlab-runner register \
--url https://lab.myserver.com \
--token [아까_발급받은_토큰]
처음에 비밀번호를 물어보는 부분이 지나면 interactive하게 몇 가지 설정을 하게 된다. 중요한 부분은 executor에 docker, docker image에 python:3.9-slim 부분이다.
Enter the GitLab instance URL (for example, https://gitlab.com/):
[https://lab.myserver.com]:
Verifying runner... is valid
Enter a name for the runner. This is stored only in the local config.toml file:
[9d605761ba1f]:
Enter an executor: ssh, parallels, docker-windows, docker+machine, docker-autoscaler, instance, custom, shell, virtualbox, docker, kubernetes:
docker
Enter the default Docker image (for example, ruby:3.3):
python:3.9-slim
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"
마지막으로 Enable for this project를 이용해서 runner를 동작시킨다.

키 등록
GitLab의 프로젝트로 이동 Settings > CI/CD > Variables > Project Variables에 다음과 같이 2개의 키를 등록한다.

이 값들은 준비물 쪽에서 생성한 값들이다.
프로젝트
코드 리뷰어 스크립트 작성
실제 코드를 리뷰할 프로그램을 만들어준다. 간단히 뭔가를 할때는 파이썬이 최고! 프로젝트 root에 code_reviewer.py 파일을 작성해주자.
import os
import requests
import google.generativeai as genai
def get_full_file_content(project_id, file_path, ref, token, gitlab_url):
"""GitLab API를 통해 파일의 전체 원본 소스코드를 가져옴"""
# 파일 경로의 /를 %2F로 인코딩
encoded_path = file_path.replace("/", "%2F")
url = f"{gitlab_url}/api/v4/projects/{project_id}/repository/files/{encoded_path}/raw?ref={ref}"
headers = {"PRIVATE-TOKEN": token}
try:
res = requests.get(url, headers=headers, timeout=30)
res.raise_for_status()
return res.text
except Exception as e:
print(f" ❌ 파일 내용 추출 실패 ({file_path}): {e}")
return None
def get_java_changes():
print("--- [1단계] GitLab MR에서 전체 자바 소스코드 추출 ---")
token = os.getenv("GITLAB_TOKEN")
project_id = os.getenv("CI_PROJECT_ID")
mr_iid = os.getenv("CI_MERGE_REQUEST_IID")
gitlab_url = os.getenv("CI_SERVER_URL")
# 학생이 작업 중인 소스 브랜치 명
source_branch = os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME")
# MR의 변경 파일 목록 가져오기
url = f"{gitlab_url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/changes"
headers = {"PRIVATE-TOKEN": token}
try:
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
res = response.json()
full_code_data = ""
changes = res.get('changes', [])
print(f"✅ 총 {len(changes)}개의 변경 파일을 확인했습니다.")
found_java = False
for change in changes:
file_path = change['new_path']
# 자바 파일이고 삭제되지 않은 경우만 전체 내용 가져오기
if file_path.endswith('.java') and not change.get('deleted_file'):
found_java = True
print(f" 📂 전체 분석 대상: {file_path}")
# 원본 소스코드 전체 가져오기
content = get_full_file_content(project_id, file_path, source_branch, token, gitlab_url)
if content:
full_code_data += f"\n[FILE START: {file_path}]\n"
full_code_data += content
full_code_data += f"\n[FILE END: {file_path}]\n"
return full_code_data if found_java else None
except Exception as e:
print(f"❌ GitLab API 오류: {e}")
return None
def get_gemini_feedback(all_code):
print("--- [2단계] Gemini 2.5 Flash 모델로 전체 코드 리뷰 생성 ---")
api_key = os.getenv("GEMINI_API_KEY")
genai.configure(api_key=api_key)
# 사용자 환경에서 확인된 gemini-2.5-flash 모델을 최우선 사용
try:
# 모델 목록을 다시 확인하여 2.5 버전 선택
models = [m.name for m in genai.list_models()]
if 'models/gemini-2.5-flash' in models:
model_name = 'gemini-2.5-flash'
elif 'models/gemini-2.0-flash' in models:
model_name = 'gemini-2.0-flash'
else:
model_name = 'gemini-1.5-flash'
print(f"🚀 활성화된 모델: {model_name}")
model = genai.GenerativeModel(model_name)
except Exception as e:
print(f"⚠️ 모델 선택 중 오류, 기본값 시도: {e}")
model = genai.GenerativeModel('gemini-2.5-flash')
prompt = f"""
너는 구글 최고의 자바 소프트웨어 엔지니어이자 알고리즘 코치야.
학생이 제출한 다음 자바 소스 코드 '전체'를 읽고 심도 있게 리뷰해줘.
리뷰 지침:
1. 코드의 전체적인 설계와 알고리즘 선택이 적절한지 평가해줘.
2. 시간 복잡도($O(N)$ 표현)와 공간 복잡도를 정확히 분석해줘.
3. 자바 1.8 기반으로 Java Collection Framework를 더 잘 활용할 수 있는 리팩토링 방안을 제시해줘.(절대 그 이상의 JDK 문법은 안돼!)
4. 변수명, 주석, 가독성 등 클린 코드 관점에서의 피드백을 줘.
5. 추가로 시도해볼 수 있는 새로운 방법이 있으면 알려줘.
소스 코드 내용:
{all_code}
"""
try:
response = model.generate_content(prompt)
print("✅ Gemini 리뷰 생성 성공!")
return response.text
except Exception as e:
print(f"❌ Gemini API 요청 중 오류: {e}")
return None
def post_comment(feedback):
print("--- [3단계] GitLab MR에 최종 피드백 등록 ---")
token = os.getenv("GITLAB_TOKEN")
project_id = os.getenv("CI_PROJECT_ID")
mr_iid = os.getenv("CI_MERGE_REQUEST_IID")
gitlab_url = os.getenv("CI_SERVER_URL")
url = f"{gitlab_url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/notes"
headers = {"PRIVATE-TOKEN": token}
data = {"body": f"## 🤖 Gemini 2.5 AI의 전체 코드 심층 리뷰\n\n{feedback}"}
try:
res = requests.post(url, headers=headers, data=data, timeout=30)
res.raise_for_status()
print("✅ MR 댓글 등록 완료!")
except Exception as e:
print(f"❌ 댓글 등록 실패: {e}")
if __name__ == "__main__":
print("🚀 Gemini 2.5 Flash 기반 AI 리뷰어 가동")
java_content = get_java_changes()
if java_content:
feedback_text = get_gemini_feedback(java_content)
if feedback_text:
post_comment(feedback_text)
else:
print("⏭️ 분석할 자바 코드가 없어 종료합니다.")
print("🏁 전체 작업 종료")
파이프라인 설정
다음으로 merge request가 왔을 때 위 스크립트를 수행하도록 파이프라인을 작성해주자. project root에 .gitlab-ci.yml 파일을 만들어주자. 이 파일은 GitLab 서버에서 ci를 위해 기본적으로 사용하는 config 파일 이름이다.
stages:
- ai_review
gemini_code_review:
stage: ai_review
image: python:3.10-slim
only:
- merge_requests
before_script:
- pip install -q -U requests google-generativeai
script:
- python code_reviewer.py
코드리뷰어 스크립트 변수화
코드리뷰어 스크립트가 공개되는 부분이 지저분하다면 변수화 할 수도 있다.
일반적인 변수와는 조금 다른데 일단 Type은 File, Visibility는 Visible을 선택해준다. 그리고 Value 항목에 파일의 내용을 작성해주자.
이제 스크립트 파일을 부를 때 변수를 이용하면 된다.
stages:
- ai_review
gemini_code_review:
stage: ai_review
image: python:3.10-slim
only:
- merge_requests
before_script:
- pip install -q -U requests google-generativeai
script:
- python $REVIEW_SCRIPT
'tools & libs > ETC' 카테고리의 다른 글
| SSH 키 생성 (0) | 2026.01.14 |
|---|---|
| [크롬개발자도구] 필터 (10) | 2025.07.10 |
| [synology] VPN 설치 (1) | 2025.07.07 |
| [synology] HTTPS 설정 (0) | 2025.07.06 |
| [oneNote]지우개, 브러쉬 단축키 조합 (0) | 2022.09.05 |
소중한 공감 감사합니다
