GitHub Actions를 사용하여 처음부터 커스텀 AI PR 리뷰어 만들기
요약
Gemini API와 GitHub Actions를 활용하여 커스텀 AI PR 리뷰어 시스템을 구축하는 방법을 설명합니다. Python 스크립트를 통해 Git Diff를 추출하고, LLM의 구조화된 응답을 받아 PR에 직접 인라인 댓글을 다는 자동화 워크플로우를 구현합니다.
핵심 포인트
- GitHub Actions와 Gemini API를 결합한 가벼운 리뷰어 구축
- Git Diff 추출 및 LLM 컨텍스트 제한을 위한 데이터 트리밍 기법
- JSON 스키마를 활용한 구조화된 AI 피드백 생성
- GitHub API를 통한 PR 인라인 댓글 자동 게시 프로세스
오픈 소스 프로젝트에 Pull Request (PR)를 제출할 때마다, 저는 CI 파이프라인이 시작되는 것을 지켜봅니다. 점점 더 자주, 몇 분 후에 CodeRabbit, Copilot 또는 Claude와 같은 AI 어시스턴트가 나타나 제 변경 사항을 검토하거나, 버그를 분류하거나, 업데이트를 제안하곤 합니다.
이것을 보며 궁금해졌습니다: 이런 도구를 처음부터 직접 만드는 것이 얼마나 어려울까?
알고 보니, 무겁고 비싼 제3자 플랫폼이 필요하지 않았습니다. 짧은 Python 스크립트와 Gemini API를 사용하여 GitHub Actions 내부에서 직접 완전히 커스텀된 라인 단위 AI 코드 리뷰어를 구축할 수 있습니다.
이것이 정확히 어떻게 작동하는지, 코드가 어떻게 생겼는지, 그리고 각 구성 요소가 어떻게 기능하는지에 대한 분석을 소개합니다.
우리가 만드는 것
목표는 팀의 시니어 엔지니어처럼 행동하는 가볍고 의존성이 없는 자동화 루프를 만드는 것입니다.
-
PR 이벤트 발생 시 트리거: 1단계.
PR이 생성(opened), 업데이트(updated) 또는 다시 열릴(reopened) 때마다 워크플로우가 트리거됩니다. -
Git Diff 추출: 2단계.
정확한 라인 변경 사항을 격리하고, LLM 컨텍스트 제약 조건 내에 편안하게 들어갈 수 있도록 다듬습니다. -
구조화된 출력과 함께 Gemini로 전송: 3단계.
인라인 Python 스크립트가 diff를 Gemini로 전달하며, 특정 파일 및 라인과 연결된 구조화된 댓글을 보장하기 위해 엄격한 JSON 스키마를 사용합니다. -
인라인 댓글 게시: 4단계.
GitHub 스크립트가 JSON 페이로드를 읽고 PR의 수정된 라인에 직접 네이티브 코드 리뷰 댓글을 부착합니다.
전체 워크플로우 코드
저장소에 .github/workflows/ai-review.yml이라는 이름의 파일을 생성하고 다음 설정을 붙여넣으세요:
name: AI PR Review
on:
...
```
{% endraw %}
json\s*|\s*
{% raw %}
```$", "", raw_text) # 어쨌든 감싸지는 경우를 대비하여
parsed = json.loads(raw_text)
status = parsed.get("status", "issues_found")
...
구성 요소별 상세 분석
이 과정을 원활하게 수행하기 위해 각 구성 요소가 정확히 어떻게 작동하는지 살펴보겠습니다.
1. 권한 및 설정
워크플로의 최상단에서 이벤트 트리거(event triggers)와 권한(permissions)을 정의합니다:
pull_request유형은 코드 변경이 발생하거나 새로운 PR이 생성될 때 이 프로세스가 실행되도록 보장합니다.- 작업(job)에
pull-requests: write권한을 명시적으로 부여합니다. 이 권한이 없으면 러너(runner)가 PR 스레드에 댓글을 남길 수 없습니다. fetch-depth: 0은 전체 git 히스토리를 가져오도록 하여 브랜치를 정확하게 비교할 수 있게 합니다.
2. Diff 추출 및 트리밍 (Extracting and Trimming the Diff)
git diff "$BASE_SHA...$HEAD_SHA" > diff.txt
head -c 800000 diff.txt > diff_trimmed.txt
무거운 에코시스템 의존성에 의존하는 대신, 표준 git diff 텍스트를 가져옵니다. 거대한 풀 리퀘스트(pull requests)는 대규모 언어 모델(LLM)의 제한 사항이나 API 제한을 빠르게 초과할 수 있으므로, head -c 800000을 사용하여 페이로드(payload) 크기를 약 800KB로 안전하게 제한함으로써 구조적 누락을 방지합니다.
3. Python 스크립트 (라인 검증 및 Gemini 호출)
핵심 로직은 표준 라이브러리(urllib)를 사용하는 의존성 없는 Python 단계에서 실행됩니다. 이는 세 가지 주요 문제를 처리합니다:
- 라인 타겟 매칭 (
parse_commentable_lines): LLM은 때때로 환각(hallucination)을 일으켜 변경되지 않은 라인에 댓글을 달려고 시도할 수 있습니다. 이 함수는 유니파이드 디프(unified diff) 헤더(@@ -line,v +line,v @@)를 파싱하여 새로 들어온 코드 버전에서 실제로 존재하는 라인의 맵(map)을 구축합니다. - 컨텍스트 규칙 주입 (Injecting Context Rules):
prompt_text는 구조적인 가드레일(guardrails)을 제공합니다. 이를 특정 스택의 기술적 요구 사항(예: UI 라이프사이클, CSS 프레임워크 또는 구조적 선호도에 대한 조언)에 맞게 조정할 수 있습니다. - 구조화된 JSON 강제 (Enforcing Structured JSON): API 요청 옵션 내에
response_schema를 직접 정의합니다. 이를 통해AI 모델이 상태 문자열(status string), 마크다운 요약(markdown summary), 그리고 인라인 댓글(inline comments) 배열을 포함하는 객체와 정확히 일치하는 원시 데이터(raw data)를 반환하도록 강제합니다.
스크립트는 AI의 피드백이 변경된 라인과 엄격하게 일치하는지 확인하고, 출력 페이로드를 로컬 텍스트 파일에 쓰는 것으로 종료됩니다.
4. GitHub에 피드백 제출하기
const reviewEvent = (status === 'issues_found' && comments.length > 0) ? 'REQUEST_CHANGES' : 'COMMENT';
...
await github.rest.pulls.createReview({ ...base, body: summary, comments });
...
마지막 단계에서는 공식 actions/github-script 액션을 사용하여 해당 텍스트 파일들을 처리합니다. 만약 AI가 실제 로직 오류를 찾아내고 유효한 코멘트(comments)를 생성했다면, 스크립트는 PR 리뷰 상태를 REQUEST_CHANGES로 표시합니다. 모든 것이 문제없어 보인다면, 기본적으로 표준 추적용 COMMENT로 설정됩니다.
특정 라인 인덱스 불일치가 발생하는 경우, try/catch 블록이 폴백(fallback) 역할을 수행하여 AI의 통찰력을 놓치지 않도록 핵심적인 상위 수준의 요약 리뷰를 어떻게든 게시합니다.
나만의 방식으로 만들기
이 설정의 묘미는 변경이 매우 쉽다는 점에 있습니다. Python 블록의 prompt_text 섹션을 조정함으로써, LLM(대규모 언어 모델)이 특정 언어로 작성하도록 하거나, 보안 취약점에 엄격하게 집중하게 하거나, 혹은 성능 벤치마크(performance benchmarks)를 제외한 모든 것을 무시하도록 쉽게 지시할 수 있습니다. GEMINI_API_KEY를 GitHub 저장소의 Secrets에 등록하기만 하면, 완전히 여러분의 통제하에 실행되는 자동화된 셀프 호스팅(self-hosted) 코드 가드레일(code guardrail)을 갖게 됩니다!
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기