LLM을 활용한 GitHub PR 코드 리뷰 자동화

1. 시작하게 된 계기

사이드 프로젝트 팀에서 우리는 코드 리뷰 프로세스의 효율성을 높이기 위해 다양한 방법을 고민하던 중이었다.

팀원들은 종종 PR이 너무 많이 쌓여 시간 내에 리뷰를 완료하기 힘들다는 문제를 제기했고, 코드 리뷰를 자동화할 수 있는 방안을 모색하게 되었다.

그 중, 자연어 처리 기술인 LLM(Large Language Model)을 활용해 코드 리뷰를 자동화하는 아이디어가 나왔다.

LLM은 주로 텍스트 분석에 사용되지만, 최근에는 코드 이해 및 생성 능력까지 발전하고 있어, 이를 통해 PR 리뷰 과정에서 코드의 품질을 평가하고, 개선할 수 있는 피드백을 제공할 수 있을 것이라고 생각했다. 이를 기반으로 우리는 LLM을 활용한 자동화 코드 리뷰 시스템을 구축하기로 했다.


2. 아키텍처

이 프로젝트의 기본 아키텍처는 비교적 간단하다.

  1. PR이 발생하면 GitHub Actions가 자동으로 트리거된다.
  2. 이때, Actions는 PR에서 변경된 git diff 내역을 추출하고, 이를 Kubernetes 클러스터 내에 배포된 LLM 서버로 전송한다.
  3. LLM 서버는 사전에 설정된 프롬프트와 git diff 내역을 바탕으로 코드 리뷰를 수행하고, 개선할 점에 대해 피드백을 제공한다.
  4. 마지막으로 이를 PR에 코멘트로 남긴다.

3. 구성

그럼 이제 아키텍처에 맞게 환경을 구성해자. 각 구성 요소와 설정 파일에 대한 설명을 포함하여 진행한다.

3.1. LLM K8S에 Pod로 서빙하기 w. Ollama

LLM을 Kubernetes 클러스터에서 서빙하기 위해 네임스페이스, 퍼시스턴트 볼륨, Ollama LLM 배포 및 서비스 설정을 단계별로 설명하겠다.

1. Namespace 생성 (ollama_ns.yaml)

Ollama LLM 관련 리소스를 별도의 네임스페이스 ollama-ns에 배포하여 다른 워크로드와의 충돌을 방지하고 리소스를 격리한다.

apiVersion: v1
kind: Namespace
metadata:
  name: ollama

2. PV 및 PVC 생성 (llama3_pv.yaml)

LLM 서버가 사용하는 LLM 모델 파일(예: GGUF 파일), 프롬프트, 파라미터 및 설정 값이 명시된 Modelfile 등을 지속적으로 저장하고 참조할 수 있도록 퍼시스턴트 볼륨(Persistent Volume, PV)을 설정한다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: llama3-pv
  namespace: ollama
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /srv/nfs/models/llama3
    server: k8s-master01
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: llama3-pvc
  namespace: ollama
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  • PersistentVolume: llama3-pv는 NFS(Network File System)를 사용하여 모델 파일을 저장할 경로 /srv/nfs/models/llama3에 10Gi의 용량을 할당한다. 이 경로는 NFS 서버 k8s-master01에 위치하고 있다. ReadWriteMany 모드를 사용하여 여러 Pod에서 동시에 읽고 쓸 수 있도록 설정한다.
  • PersistentVolumeClaim: llama3-pvc는 퍼시스턴트 볼륨을 요청하는 PVC로, 10Gi 용량을 요청하며, 마찬가지로 ReadWriteMany 모드로 여러 Pod가 동시에 접근할 수 있도록 설정한다. 이를 통해 Ollama LLM이 사용하는 모델 파일 및 설정을 지속적으로 저장하고, 여러 Pod에서 공유할 수 있게 한다.

3. Ollama LLM 배포 (llama3_ollama_deploy.yaml)

이 Deployment는 Ollama LLM 컨테이너를 Kubernetes에 배포하고, LLM 모델 파일과 설정을 /models/llama3 경로로 마운트하여 사용한다. 컨테이너가 시작되면 모델 파일이 있는 디렉토리로 이동하여 Modelfile에 정의된 모델을 자동으로 생성하도록 설정되어 있다.

나의 경우 MLP-KTLim/llama-3-Korean-Bllossom-8B-gguf-Q4_K_M 의 모델을 사용했고, 이를 /srv/nfs/llama3 에 저장하여 사용했다.

또한, Ollama의 경우 시스템프롬프트와 파라미터를 정의하는 Modelfile이 필요하여 작성 후 /srv/nfs/llama3 에 저장했다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
  namespace: ollama
spec:
  selector:
    matchLabels:
      name: ollama
  template:
    metadata:
      labels:
        name: ollama
    spec:
      containers:
      - name: ollama
        image: ollama/ollama:latest
        ports:
        - name: ollamaport
          containerPort: 11434
          protocol: TCP
        env:
        - name: OLLAMA_HOST
          value: "0.0.0.0:11434"
        - name: OLLAMA_ORIGINS
          value: "*"
        volumeMounts:
        - name: llama3-model-files
          mountPath: /models/llama3
        lifecycle:
          postStart:
            exec:
              command: ["sh", "-c", "cd /models/llama3 && ollama create llama-3-Korean-Bllossom-8B-Q4_K_M -f Modelfile_llama-3-Korean-Bllossom-8B-Q4_K_M"]
      volumes:
      - name: llama3-model-files
        persistentVolumeClaim:
          claimName: llama3-pvc

4. 서비스 설정 (ollama_service.yaml)

ollama-service는 ollama 레이블을 가진 Pod에 트래픽을 전달한다. 외부에서 접근할 때 30778번 포트를 사용하여 내부의 11434번 포트로 포워딩한다. 이를 통해 Kubernetes 클러스터 외부에서 Ollama LLM에 접근할 수 있게 한다.

apiVersion: v1
kind: Service
metadata:
  name: ollama
  namespace: ollama
spec:
  type: NodePort
  selector:
    name: ollama
  ports:
  - port: 11434
    name: ollamaport
    targetPort: ollamaport
    protocol: TCP
    nodePort: 30778

5. 포트 포워딩을 통한 외부 접근 설정

Ollama LLM 서버에 외부에서 접근할 수 있도록 추가적으로 kubectl port-forward 명령어를 사용하여 서비스를 외부에 노출시킬 수 있다. 이 방법을 사용하면 외부 API 요청이 가능하다.

nohup kubectl port-forward --address=0.0.0.0 svc/ollama -n ollama 11434:11434 &

ollama 서비스를 포트 포워딩하여 외부에서 0.0.0.0:11434로 접근할 수 있도록 설정한다. 이를 통해 API 서버가 외부 요청을 처리할 수 있게 된다.

환경이 온프레미스인지라, LoadBalancer를 따로 구축해야하는 번거로움이 있기 때문에, 테스트 용도로 사용하였다.
(추후 LoadBalancer로 적용할 예정)


3.2. GitHub Actions 연동하기 (code-review.yml)

이제 Kubernetes에 배포된 Ollama LLM 서버와 GitHub Actions를 연동하여 PR이 발생했을 때 자동으로 코드 리뷰가 수행되도록 설정해보자.

1. GitHub Actions 설정 (code-review.yml)

PR이 발생하거나 업데이트될 때, git diff를 추출하고 LLM 서버로 전송하여 코드 리뷰 결과를 받는 GitHub Actions 설정 파일이다.
Gihub Actions 파일은 코드리뷰를 진행할 레포지토리의 .github/workflows/code-review.yml 로 추가해야한다.
각 secrets 변수 값들은 레포지토리 -> Settings -> Secrets and variables -> Actions 에서 추가한다.

name: CodeReview

on:
  pull_request:
    branches:
      - release
      - dev

jobs:
  code_review:
    runs-on: ubuntu-latest

    steps:
    - name: Check out repository
      uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Set Server Info
      run: |
        echo "REVIEW_API_URL=${{ secrets.REVIEW_API_URL }}" >> $GITHUB_ENV

    - name: Get Git Diff
      id: git_diff
      run: |
            git fetch origin
            git diff --unified=3 ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} > diff.txt
            DIFF_CONTENT=$(cat diff.txt)
            echo "Diff content length: $(wc -c < diff.txt)"
            echo "DIFF_CONTENT<<EOF" >> $GITHUB_ENV
            echo "$DIFF_CONTENT" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV
            echo "Diff added to GITHUB_ENV"

    - name: API calling for CodeReview
      id: api_call
      run: |
            ESCAPED_DIFF=$(echo "$DIFF_CONTENT" | jq -sRr @json)
            REQUEST_BODY=$(jq -n \
              --arg model "${{ secrets.REVIEW_MODEL_NAME }}" \
              --arg prompt "Review the following code changes and provide feedback:$ESCAPED_DIFF" \
              '{model: $model, prompt: $prompt, stream: false}')
            FULL_RESPONSE=$(curl -X POST ${REVIEW_API_URL} \
              -H "Content-Type: application/json" \
              -d "$REQUEST_BODY")
            REVIEW_RESPONSE=$(echo $FULL_RESPONSE | jq -r '.response')
            echo "Review Response: $REVIEW_RESPONSE"
            echo "REVIEW_RESPONSE<<EOF" >> $GITHUB_ENV
            echo "$REVIEW_RESPONSE" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV

    - name: Add review to pull request
      uses: actions/github-script@v6
      env:
        DIFF_CONTENT: ${{ env.DIFF_CONTENT }}
      with:
        github-token: ${{ secrets.REVIEW_BOT_TOKEN }}
        script: |
            const diff = process.env.DIFF_CONTENT;
            const response = process.env.REVIEW_RESPONSE;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `${response}`
            });

1. PR 이벤트 감지: PR이 생성되거나 업데이트될 때(예: release, dev 브랜치에서), GitHub Actions가 자동으로 트리거된다.
2. 코드 체크아웃 및 git diff 추출: git diff 명령을 실행하여 변경된 코드를 diff.txt로 저장하고, 그 내용을 환경 변수에 추가한다.
3. LLM 서버에 코드 리뷰 요청: curl 명령을 사용해 git diff 내용을 LLM 서버로 전송한다. 이 서버는 PR에서 변경된 코드를 분석하고 피드백을 생성한다.
4. 리뷰 결과 PR 코멘트로 추가: LLM 서버에서 받은 리뷰 결과를 PR 코멘트로 작성하여 자동으로 피드백을 제공한다.


4. 테스트

아래 예시처럼 이 설정을 통해 PR이 생성될 때마다 자동으로 LLM을 활용한 코드 리뷰가 수행되며, 결과가 PR 코멘트로 피드백된다.
해당 링크에서 직접 확인할 수 있다.

팀원들로부터는 놓친 부분을 리마인드하는 데 도움이 된다는 긍정적인 피드백이 있었다. 그러나 다음 두 가지 이유로 인해 지속적인 개선이 필요할 것 같다:

  1. 프로젝트 전체 코드가 아닌, git diff 부분만을 확인하는 점
  2. 시스템 프롬프트의 완성도가 아직 미흡한 점

따라서 사용을 지속하면서 이러한 부분들을 개선해 나가야 할 것이다.