GitHub Actions 안에서 Gemma로 PR 리뷰어 만들기: GemmaCI 제작기

포비·7일 전

cotor

목록 보기
12/13


최근 AI 코드 리뷰 도구를 써보면서 이런 생각이 들었다.

“PR 리뷰를 꼭 외부 SaaS에만 맡겨야 할까?”
“GitHub Actions 안에서 오픈 모델을 돌려서, 가볍게 PR 리뷰를 자동화할 수는 없을까?”

그래서 만들어본 프로젝트가 GemmaCI다.

GemmaCI는 GitHub Actions에서 Gemma 계열 모델을 실행해서 pull request를 자동으로 리뷰하는 오픈소스 프로젝트다.

GitHub Repository:
https://github.com/bssm-oss/gemmaci

현재 구현한 기능은 다음과 같다.

  • PR 전체 요약 댓글 생성
  • 변경된 라인에 inline review comment 작성
  • critical 또는 high severity finding 발견 시 check 실패 처리
  • Ollama runtime / model cache를 통한 cold start 비용 절감
  • evidence, confidence, recommendation 기반 리뷰 결과 출력
  • PR diff, model output, artifact를 모두 untrusted data로 다루는 보안 모델

만들게 된 이유

요즘은 PR 리뷰를 도와주는 AI 도구가 많다.

대표적으로 CodeRabbit 같은 서비스는 PR에 자동으로 코멘트를 달아주고, 변경 사항을 요약해주고, 위험해 보이는 코드를 지적해준다. 이런 경험은 꽤 편하다.

하지만 개인적으로 아쉬운 점도 있었다.

외부 SaaS에 의존해야 하고, 사용량이나 요금제의 영향을 받을 수 있고, 팀이나 프로젝트에 따라 코드를 외부 서비스로 보내는 것 자체가 부담스러울 수 있다.

그래서 목표를 단순하게 잡았다.

GitHub Actions 안에서
오픈 모델을 실행하고
PR diff를 읽어서
요약과 inline comment를 남기는 리뷰어를 만들자.

그 결과물이 GemmaCI다.


GemmaCI가 하는 일

GemmaCI는 PR이 열리거나 업데이트될 때 GitHub Actions workflow로 실행된다.

현재 구현된 주요 기능은 다음과 같다.

  • PR 전체 요약 댓글 생성
  • 변경된 라인에 inline review comment 작성
  • critical 또는 high severity finding이 있으면 check 실패
  • Ollama runtime과 model cache를 통한 cold start 비용 절감
  • evidence, confidence, recommendation 기반 리뷰 결과 출력
  • PR diff, model output, artifact를 모두 untrusted data로 다루는 보안 모델

핵심은 단순히 “AI에게 diff를 던지고 댓글을 달게 하는 것”이 아니다.

모델이 낸 결과를 그대로 신뢰하지 않고, schema 검증과 evidence grounding을 거친 뒤 실제 changed line에만 comment를 게시하도록 만들었다.


전체 구조

GemmaCI의 workflow는 크게 세 단계로 나뉜다.

워크플로우 파일은 다음 위치에 있다.

.github/workflows/gemma-review.yml

그리고 pull_request 이벤트에서 실행된다.

내부 job은 크게 다음 세 개로 나뉜다.

  • prepare
  • review
  • publish

1. prepare

첫 번째 단계는 PR diff를 준비하는 단계다.

여기서는 trusted base branch의 reviewer code를 checkout한다. 중요한 점은 PR에서 온 코드를 실행하지 않는다는 것이다.

PR diff는 코드가 아니라 데이터로만 읽는다.

그리고 다음과 같은 파일은 리뷰 대상에서 제외한다.

  • binary file
  • lockfile
  • generated/vendor file
  • 너무 큰 file

이후 모델에게 넘길 입력을 review-input.json artifact로 저장한다.


2. review

두 번째 단계는 실제 모델 리뷰 단계다.

여기서도 trusted base branch의 reviewer code를 다시 checkout한다.

그 다음 Ollama를 설치하고, Gemma 모델을 실행해서 diff chunk를 리뷰한다.

현재 기본 모델은 다음과 같다.

gemma3:1b

모델 리뷰 결과는 schema 검증을 거친 뒤 review-output.json artifact로 업로드된다.

이 단계에서 중요한 점은 모델 결과도 그대로 믿지 않는다는 것이다.

모델이 JSON 형식으로 finding을 반환하더라도, 그 결과가 schema에 맞는지 확인한다. 나중에 publish 단계에서도 다시 검증한다.


3. publish

세 번째 단계는 리뷰 결과를 PR에 게시하는 단계다.

이 단계에서는 GitHub PR에 실제 댓글을 남겨야 하므로 권한이 더 민감하다.

그래서 review-input.json, review-output.json도 hostile artifact로 보고 다시 검증한다.

그리고 유효한 changed line에 대해서만 inline comment를 게시한다.

만약 critical 또는 high severity finding이 있으면 check를 실패시킨다.

즉, publish 단계는 단순 출력 단계가 아니라 최종 방어선이다.


왜 pull_request_target을 기본으로 쓰지 않았나

이 프로젝트에서 가장 신경 쓴 부분은 보안이다.

GitHub Actions에서 PR에 댓글을 달려면 권한 문제가 생긴다. 이때 흔히 떠올릴 수 있는 이벤트가 pull_request_target이다.

하지만 pull_request_target은 조심해서 써야 한다.

특히 pull_request_target과 untrusted PR checkout을 함께 쓰면, PR 작성자가 바꾼 코드가 더 높은 권한을 가진 workflow 안에서 실행될 수 있다.

그래서 GemmaCI는 기본적으로 pull_request_target을 사용하지 않는다.

PR 작성자가 바꾼 workflow나 script가 write 권한으로 실행되는 상황을 피하기 위해서다.

대신 trusted base branch의 reviewer script만 실행하고, PR diff는 실행 가능한 코드가 아니라 데이터로만 취급한다.

내가 생각한 원칙은 이렇다.

PR에서 온 것은 전부 의심한다.
diff도 의심하고, artifact도 의심하고, model output도 의심한다.
write 권한이 있는 단계에서는 trusted code만 실행한다.

AI 리뷰어를 CI에 붙일 때는 모델 품질만큼이나 이 신뢰 경계가 중요하다고 봤다.


실제로 동작하는지 검증하기

로컬 dry-run만으로는 부족하다고 생각했다.

그래서 실제 GitHub Actions runner에서 smoke PR을 열고 검증했다.

테스트 PR에서는 일부러 unsafeDivide 함수를 추가했다.

export function unsafeDivide(a, b) {
  return a / b;
}

이 코드는 분모가 0일 때 InfinityNaN이 전파될 수 있다.

GemmaCI는 이 변경을 high severity finding으로 잡아냈고, PR summary comment와 inline comment를 남겼다.

실제 검증한 항목은 다음과 같다.

  • GitHub-hosted runner에서 workflow 실행
  • Ollama 설치 및 Gemma 모델 리뷰 job 실행
  • PR summary comment 게시
  • 변경 라인 inline comment 게시
  • high severity finding 감지 시 check 실패

실제 smoke PR:

https://github.com/bssm-oss/gemmaci/pull/2

실제 workflow run:

https://github.com/bssm-oss/gemmaci/actions/runs/26009436921

이 정도면 최소한 “실제로 CI에서 돌아가는 PR 리뷰어”라고 말할 수 있는 상태가 됐다.


설치 방법

GemmaCI는 reusable workflow 방식으로 쉽게 붙일 수 있게 만들었다.

사용하려는 저장소에 아래 workflow 파일을 추가하면 된다.

name: Gemma Review

on:
  pull_request:
    types: [opened, reopened, synchronize, ready_for_review]

permissions: {}

jobs:
  gemma-review:
    uses: bssm-oss/gemmaci/.github/workflows/gemma-review.yml@main
    with:
      model: gemma3:1b
      language: ko
      max-inline-comments: 20
      min-confidence: '0.6'

처음 테스트할 때는 @main으로 빠르게 붙여볼 수 있다.

다만 운영 저장소에서 쓴다면 @main보다는 release tag나 commit SHA로 고정하는 편이 좋다.

예를 들면 이런 식이다.

jobs:
  gemma-review:
    uses: bssm-oss/gemmaci/.github/workflows/gemma-review.yml@v1
    with:
      model: gemma3:1b
      language: ko

또는 더 엄격하게 commit SHA로 고정할 수도 있다.


리뷰 품질을 어떻게 제어했나

AI 리뷰어에서 가장 곤란한 부분은 “그럴듯하지만 근거 없는 지적”이다.

그래서 GemmaCI에서는 finding에 다음 필드를 요구한다.

  • category
  • severity
  • confidence
  • title
  • evidence
  • body
  • recommendation
  • suggestion

특히 evidence가 중요하다.

모델이 어떤 문제를 지적하려면 실제 changed line의 코드 조각을 근거로 제시해야 한다.

형식만 맞고 변경 코드에 근거하지 않은 finding은 게시 전에 제거한다.

confidence가 설정값보다 낮은 finding도 제거된다.

기본 confidence threshold는 다음과 같다.

0.6

이 방식이 완벽하다는 뜻은 아니다.

하지만 최소한 “모델이 아무 말이나 하면 그대로 PR에 댓글로 박히는 상황”은 피하고 싶었다.


deterministic rule도 넣은 이유

모델은 가끔 명백한 것을 놓친다.

그래서 GemmaCI에는 작은 deterministic safety pass도 넣었다.

예를 들어 새로 추가된 unsafeDivide류 함수가 분모 0 guard 없이 division return을 하면, 모델이 놓치더라도 high-signal finding으로 보강한다.

다만 이 deterministic finding도 모델 finding과 동일하게 evidence grounding과 publish-time validation을 통과해야만 게시된다.

이 부분은 앞으로 더 확장할 수 있을 것 같다.

예를 들면 다음 같은 rule을 추가할 수 있다.

  • hardcoded secret pattern
  • unsafe eval 사용
  • SQL string concatenation
  • missing auth check
  • dangerous shell command interpolation

물론 rule이 많아질수록 false positive도 늘어날 수 있기 때문에, 무작정 많이 넣기보다는 high-signal rule 위주로 가져가는 게 맞다고 본다.


한계

아직 초기 버전이라 한계도 분명하다.

첫째, GitHub-hosted runner는 기본적으로 CPU-only 환경이기 때문에 큰 모델을 돌리기에는 적합하지 않다.

그래서 현재 기본 모델은 가볍게 돌릴 수 있는 gemma3:1b로 설정했다.

둘째, 첫 실행은 느릴 수 있다.

Ollama와 model artifact를 다운로드해야 하기 때문이다. cache를 사용하긴 하지만 cache는 best-effort라서 evict되면 cold start가 다시 발생할 수 있다.

셋째, 리뷰 품질은 모델과 prompt/schema에 영향을 많이 받는다.

작은 모델은 빠르고 가볍지만, 복잡한 코드 리뷰에서는 한계가 있다.

그래서 지금은 “사람 리뷰어를 대체한다”보다는 “PR에서 놓치기 쉬운 obvious issue를 한 번 더 잡아주는 보조 리뷰어”에 가깝게 보는 게 맞다.


앞으로 해보고 싶은 것

앞으로는 다음을 개선해보고 싶다.

  • release tag 기반 설치 가이드 정리
  • npm package publish
  • self-hosted runner 지원 강화
  • GGUF / llama.cpp 기반 실행 옵션 검토
  • deterministic safety rule 확장
  • review prompt 개선
  • false positive 줄이기
  • 영어/한국어 리뷰 품질 비교
  • monorepo나 대형 diff에서의 동작 최적화

특히 self-hosted runner나 GPU 환경을 붙이면 더 큰 모델도 실험해볼 수 있을 것 같다.


마무리

GemmaCI는 아직 작고 초기 단계의 프로젝트다.

하지만 이번에 만들면서 느낀 점은 분명했다.

AI 코드 리뷰는 꼭 거대한 외부 SaaS 형태일 필요는 없다.
작게 시작하면 GitHub Actions 안에서도 충분히 실험할 수 있다.

물론 AI 리뷰어는 신뢰하면 안 된다.
모델 출력은 검증해야 하고, PR에서 온 데이터는 의심해야 하고, write 권한이 있는 CI 단계에서는 trusted code만 실행해야 한다.

그 선을 지키면, 오픈 모델 기반의 가벼운 PR 리뷰어도 꽤 쓸만한 도구가 될 수 있다고 생각한다.

GemmaCI는 그런 방향의 첫 실험이다.

피드백, 이슈, PR 모두 환영합니다.


참고

profile
무엇이든 필요한 것을 합니다. https://mint-middle-1e5.notion.site/2b7655e8316980ad9422d96a6f3947de

0개의 댓글