📕 서론
GitHub에는 diff를 바탕으로 다양한 정보를 얻을 수 있습니다.
대표적으로 커밋 내용이 있는데, 이 내용을 자동으로 분석하여
- 코드 리뷰 시간 절감
- 팀 협업에서 변경사항 빠르게 파악
- 문서화 자동화
- 대규모 레포지토리 변경 추적
을 위해 LLM을 활용하여 커밋 내용 자동 분석하는 기능을 만들어 보겠습니다.
🧩 1. Ollama 설치 및 LLM 모델 다운로드
Ollama는 인터넷이 없어도 로컬 환경에서 LLM 모델을 설치하고 사용할 수 있도록 해주는 도구입니다.
- Ollama 설치
# Linux/Mac
curl -fsSL https://ollama.com/install.sh | sh
# Windows
https://ollama.com/download
- Ollama 실행
ollama start &
- 모델 다운로드
# 2GB, 가볍고 빠름 (추천)
ollama pull llama3.2
# 4GB, 성능 좋음
ollama pull mistral
# 5GB, 한국어 최강
ollama pull qwen2.5:7b
# 5GB, 최신 모델
ollama pull deepseek-r1:7b
👉 이렇게 하면 LLM 모델을 사용할 준비가 완료되었습니다.
🧩 2. Git 커밋 정보 가져오기
Github 주소를 입력받아 최근 커밋 정보를 불러오기 위한 함수를 정의해줍니다.
# ========================================
# 1. Git 커밋 정보 가져오기
# ========================================
def get_recent_commits(repo_url, max_count=3):
"""GitHub 저장소에서 최근 커밋 정보 가져오기"""
temp_dir = tempfile.mkdtemp()
try:
print(f"📦 저장소 클론 중: {repo_url}")
repo = Repo.clone_from(repo_url, temp_dir, depth=max_count)
branch = repo.head.ref.name
commits_info = []
for commit in list(repo.iter_commits(branch, max_count=max_count)):
commit_data = {
"hash": commit.hexsha[:8],
"author": str(commit.author),
"message": commit.message.strip(),
"date": str(commit.committed_datetime),
"diff": "",
"files_changed": []
}
# diff 추출
diffs = commit.diff(commit.parents[0] if commit.parents else None, create_patch=True)
# 변경된 파일 목록
for d in diffs:
if d.a_path:
commit_data["files_changed"].append(d.a_path)
# diff 텍스트
diff_text = "\n".join(
d.diff.decode("utf-8", errors="ignore") for d in diffs if d.diff
)
# diff가 너무 길면 자르기 (LLM 컨텍스트 제한)
if len(diff_text) > 3000:
diff_text = diff_text[:3000] + "\n... (변경 사항이 더 있음)"
commit_data["diff"] = diff_text
commits_info.append(commit_data)
print(f"✅ {len(commits_info)}개 커밋 수집 완료\n")
return commits_info
except Exception as e:
print(f"❌ 오류 발생: {e}")
return []
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
🧩 3. Ollama 로컬 LLM 연결
# ========================================
# 2. Ollama 로컬 LLM
# ========================================
class OllamaLLM:
def __init__(self, model="llama3.2", base_url="http://localhost:11434"):
"""
Args:
model: Ollama 모델 이름
- llama3.2 (추천, 2GB)
- mistral (4GB)
- qwen2.5:7b (5GB, 한국어 강력)
- deepseek-r1:7b (5GB, 최신)
base_url: Ollama 서버 주소
"""
self.model = model
self.base_url = base_url
self.generate_url = f"{base_url}/api/generate"
self.chat_url = f"{base_url}/api/chat"
# Ollama 연결 확인
self._check_connection()
def _check_connection(self):
"""Ollama 서버 연결 확인"""
try:
response = requests.get(f"{self.base_url}/api/tags", timeout=3)
if response.status_code == 200:
models = response.json().get("models", [])
model_names = [m["name"] for m in models]
print(f"✅ Ollama 연결 성공!")
print(f"📦 설치된 모델: {', '.join(model_names) if model_names else '없음'}")
if self.model not in model_names and f"{self.model}:latest" not in model_names:
print(f"\n⚠️ 모델 '{self.model}'이(가) 설치되지 않았습니다.")
print(f"💡 다음 명령어로 설치하세요: ollama pull {self.model}")
else:
print("⚠️ Ollama 서버가 응답하지 않습니다.")
except Exception as e:
print("❌ Ollama 연결 실패!")
print("💡 해결 방법:")
print(" 1. Ollama 설치: https://ollama.ai")
print(" 2. 모델 다운로드: ollama pull llama3.2")
print(" 3. 서버 실행: ollama serve (보통 자동 실행됨)")
raise Exception(f"Ollama 미설치 또는 실행 중이 아닙니다: {str(e)}")
def summarize(self, prompt, stream=False):
"""텍스트 요약 생성"""
payload = {
"model": self.model,
"prompt": prompt,
"stream": stream,
"options": {
"temperature": 0.7,
"num_predict": 300 # 최대 토큰 수
}
}
try:
response = requests.post(
self.generate_url,
json=payload,
timeout=120
)
if response.status_code == 200:
result = response.json()
return result.get("response", "요약 생성 실패").strip()
else:
return f"❌ API 오류 (코드: {response.status_code})"
except Exception as e:
return f"❌ 오류 발생: {str(e)}"
def chat(self, messages, stream=False):
"""대화형 요약 (더 나은 품질)"""
payload = {
"model": self.model,
"messages": messages,
"stream": stream,
"options": {
"temperature": 0.7,
"num_predict": 300
}
}
try:
response = requests.post(
self.chat_url,
json=payload,
timeout=120
)
if response.status_code == 200:
result = response.json()
return result.get("message", {}).get("content", "요약 생성 실패").strip()
else:
return f"❌ API 오류 (코드: {response.status_code})"
except Exception as e:
return f"❌ 오류 발생: {str(e)}"
💡 핵심 요약
1. 서버 연결 점검: Ollama 서버와 모델이 정상적으로 준비되었는지 확인
2. summarize(): 일반 프롬프트 기반 요약/텍스트 생성
3. chat(): 채팅형 대화 모델 호출 (더 품질 높은 응답 생성)
🧩 4. 프롬프트 생성
# ========================================
# 3. 프롬프트 생성
# ========================================
def make_prompt(commit, style="detailed"):
"""
커밋 정보를 요약 프롬프트로 변환
Args:
commit: 커밋 정보 딕셔너리
style: "simple" (간단), "detailed" (상세), "technical" (기술적), "review" (코드리뷰)
작동 원리:
1. 변경된 파일 목록을 정리 (최대 5개까지 표시)
2. 선택한 스타일에 따라 다른 형식의 프롬프트 생성
3. LLM에게 명확한 지시사항 + 구조화된 데이터 제공
4. 원하는 형식의 답변을 유도
"""
files_changed = ", ".join(commit["files_changed"][:5])
if len(commit["files_changed"]) > 5:
files_changed += f" 외 {len(commit['files_changed']) - 5}개"
if style == "simple":
# 스타일 1: 간단 요약 (1-2문장)
# - 최소한의 정보만 제공
# - LLM이 핵심만 추출하도록 유도
prompt = f"""다음 Git 커밋을 1-2문장으로 간단히 요약해주세요.
커밋 메시지: {commit['message']}
변경된 파일: {files_changed}
요약:
"""
elif style == "detailed":
# 스타일 2: 상세 분석
# - 커밋 정보 + diff 코드 제공
# - 구조화된 형식으로 답변 요청
# - 목적, 변경사항, 영향 범위로 나눠서 분석
prompt = f"""다음 Git 커밋의 변경사항을 분석하고 요약해주세요.
[커밋 정보]
- 메시지: {commit['message']}
- 작성자: {commit['author']}
- 변경된 파일: {files_changed}
[주요 변경 내용]
{commit['diff'][:2000]}
다음 형식으로 요약해주세요:
1. 변경 목적: (한 문장)
2. 주요 변경사항: (2-3개 항목)
3. 영향 범위: (간단히)
"""
elif style == "technical":
# 스타일 3: 기술적 분석
# - 더 많은 diff 제공 (2500자)
# - 문제 해결, 패턴, 코드 품질 관점에서 분석
prompt = f"""다음 Git 커밋을 기술적으로 분석해주세요.
커밋 메시지: {commit['message']}
Diff:
{commit['diff'][:2500]}
분석 내용:
- 어떤 문제를 해결했나요?
- 어떤 기술/패턴을 사용했나요?
- 코드 품질에 미치는 영향은?
"""
elif style == "review":
# 스타일 4: 코드 리뷰 (NEW!)
# - 코드 리뷰어 관점에서 분석
# - 장점, 개선점, 보안, 성능 등 다각도 검토
prompt = f"""다음 Git 커밋에 대해 코드 리뷰를 수행해주세요.
[커밋 정보]
- 메시지: {commit['message']}
- 작성자: {commit['author']}
- 변경 파일: {files_changed}
[변경된 코드]
{commit['diff'][:3000]}
다음 항목을 검토해주세요:
✅ 좋은 점 (Good):
- 잘 작성된 부분
- 개선된 점
⚠️ 개선 사항 (Improvements):
- 코드 품질
- 가독성
- 성능
🔒 보안 체크 (Security):
- 잠재적 보안 이슈
- 데이터 검증
🐛 잠재적 버그 (Bugs):
- 버그 가능성
- 엣지 케이스
💡 제안 사항 (Suggestions):
- 대안적 접근법
- 추가 고려사항
"""
else:
prompt = commit['message']
return prompt
💡 핵심 요약
1. simple: 1~2 문장으로 간단 요약
2. detailed: 목적, 변경사항, 영향 범위로 나눠서 분석
3. technical: 문제 해결, 패턴, 코드 품질 관점에서 분석
4. review: 장점, 개선점, 보안, 성능 등에 대해 검토
🧩 5. 메인 실행 함수
# ========================================
# 4. 메인 실행 함수
# ========================================
def analyze_repo(repo_url, max_commits=3, model="llama3.2", style="detailed", use_chat=True):
"""
Git 저장소 커밋 분석
Args:
repo_url: Git 저장소 URL
max_commits: 분석할 커밋 개수
model: Ollama 모델 ("llama3.2", "mistral", "qwen2.5:7b")
style: 요약 스타일 ("simple", "detailed", "technical", "review")
use_chat: True면 chat API 사용 (더 나은 품질)
"""
print(f"\n{'='*70}")
print(f"🤖 Git Commit 요약기 (Ollama 로컬 LLM)")
print(f"{'='*70}\n")
# 1. LLM 초기화
try:
llm = OllamaLLM(model=model)
except Exception as e:
print(f"\n{e}")
return
# 2. 커밋 정보 수집
commits = get_recent_commits(repo_url, max_count=max_commits)
if not commits:
print("❌ 커밋 정보를 가져올 수 없습니다.")
return
# 3. 각 커밋 요약
print(f"\n{'='*70}")
print(f"📝 커밋 분석 중... (모델: {model}, 스타일: {style})")
print(f"{'='*70}\n")
for i, commit in enumerate(commits, 1):
print(f"\n{'─'*70}")
print(f"🔹 커밋 {i}/{len(commits)}")
print(f"{'─'*70}")
print(f"📌 Hash : {commit['hash']}")
print(f"👤 작성자 : {commit['author']}")
print(f"📅 날짜 : {commit['date']}")
print(f"💬 메시지 : {commit['message']}")
print(f"📁 변경 파일: {len(commit['files_changed'])}개")
# 요약 생성
if style == "review":
print(f"\n🔍 코드 리뷰 수행 중...", end=" ", flush=True)
else:
print(f"\n🔄 AI 요약 생성 중...", end=" ", flush=True)
if use_chat:
review_mode = (style == "review")
messages = make_chat_prompt(commit, style, review_mode=review_mode)
summary = llm.chat(messages)
else:
prompt = make_prompt(commit, style=style)
summary = llm.summarize(prompt)
print("완료!")
if style == "review":
print(f"\n📋 코드 리뷰 결과:")
else:
print(f"\n💡 AI 분석:")
print(f"{'─'*10}")
for line in summary.split('\n'):
if line.strip():
print(f" {line}")
print(f"{'─'*10}")
print(f"\n{'='*10}")
print("✅ 분석 완료!")
print(f"{'='*10}\n")
🧩 6. 대화형 챗봇 모드 추가
단순 실행보다 대화형 챗봇 모드도 있으면 좋을 것 같아서 추가해 보았습니다.
# ========================================
# 5. 대화형 챗봇 모드
# ========================================
def make_chat_prompt(commit, style, review_mode=False):
"""
대화형 프롬프트 (더 나은 품질)
Args:
commit: 커밋 정보
review_mode: True면 코드리뷰 모드
"""
files_changed = ", ".join(commit["files_changed"][:5])
if review_mode:
# 코드 리뷰 모드
messages = [
{
"role": "system",
"content": "당신은 시니어 개발자이자 코드 리뷰어입니다. 코드의 장단점을 명확히 지적하고, 건설적인 피드백을 제공합니다."
},
{
"role": "user",
"content": f"""다음 Git 커밋을 코드 리뷰해주세요.
커밋 메시지: {commit['message']}
변경 파일: {files_changed}
변경 코드:
{commit['diff'][:3000]}
다음을 분석해주세요:
1. ✅ 좋은 점
2. ⚠️ 개선할 점
3. 🔒 보안 이슈 (있다면)
4. 🐛 잠재적 버그 (있다면)
5. 💡 제안사항
"""
}
]
else:
if style == "simple":
# 스타일 1: 간단 요약 (1-2문장)
# - 최소한의 정보만 제공
# - LLM이 핵심만 추출하도록 유도
messages = [
{
"role": "system",
"content": "당신은 Git 커밋을 분석하고 요약하는 전문가입니다."
},
{
"role": "user",
"content": f"""다음 Git 커밋을 1-2문장으로 간단히 요약해주세요.
커밋 메시지: {commit['message']}
변경된 파일: {files_changed}
요약:
"""
}
]
elif style == "detailed":
# 스타일 2: 상세 분석
# - 커밋 정보 + diff 코드 제공
# - 구조화된 형식으로 답변 요청
# - 목적, 변경사항, 영향 범위로 나눠서 분석
messages = [
{
"role": "system",
"content": "당신은 Git 커밋을 분석하고 요약하는 전문가입니다."
},
{
"role": "user",
"content": f"""다음 Git 커밋의 변경사항을 분석하고 요약해주세요.
[커밋 정보]
- 메시지: {commit['message']}
- 작성자: {commit['author']}
- 변경된 파일: {files_changed}
[주요 변경 내용]
{commit['diff'][:2000]}
다음 형식으로 요약해주세요:
1. 변경 목적: (한 문장)
2. 주요 변경사항: (2-3개 항목)
3. 영향 범위: (간단히)
"""
}
]
elif style == "technical":
# 스타일 3: 기술적 분석
# - 더 많은 diff 제공 (2500자)
# - 문제 해결, 패턴, 코드 품질 관점에서 분석
messages = [
{
"role": "system",
"content": "당신은 Git 커밋을 분석하고 요약하는 전문가입니다."
},
{
"role": "user",
"content": f"""다음 Git 커밋을 기술적으로 분석해주세요.
커밋 메시지: {commit['message']}
Diff:
{commit['diff'][:2500]}
분석 내용:
- 어떤 문제를 해결했나요?
- 어떤 기술/패턴을 사용했나요?
- 코드 품질에 미치는 영향은?
"""
}
]
return messages
def interactive_mode(model="llama3.2"):
"""대화형 Git 분석 모드"""
print(f"\n{'='*70}")
print(f"💬 대화형 Git 분석 모드")
print(f"{'='*70}\n")
llm = OllamaLLM(model=model)
while True:
print("\n옵션을 선택하세요:")
print("1. 저장소 요약 분석")
print("2. 저장소 코드 리뷰")
print("3. 직접 질문하기")
print("4. 종료")
choice = input("\n선택 (1/2/3/4): ").strip()
if choice == "1":
repo_url = input("\n📦 Git 저장소 URL: ").strip()
max_commits = input("📊 분석할 커밋 개수 (기본 3): ").strip()
max_commits = int(max_commits) if max_commits.isdigit() else 3
style = input("📝 스타일 (simple/detailed/technical/review, 기본 detailed): ").strip()
style = style if style in ["simple", "detailed", "technical"] else "detailed"
analyze_repo(repo_url, max_commits, model, style)
elif choice == "2":
repo_url = input("\n📦 Git 저장소 URL: ").strip()
max_commits = input("📊 리뷰할 커밋 개수 (기본 3): ").strip()
max_commits = int(max_commits) if max_commits.isdigit() else 3
print("\n🔍 코드 리뷰 모드로 실행합니다...")
analyze_repo(repo_url, max_commits, model, style="review")
elif choice == "3":
question = input("\n💬 질문: ").strip()
if question:
response = llm.summarize(question)
print(f"\n🤖 답변:\n{response}")
elif choice == "4":
print("\n👋 종료합니다.")
break
🧩 7. 실행 예시
주석 처리한 부분은 각각 simple, technical, review, detailed 등 옵션을 실행할 수 있는 기본적인 예제입니다.
4번째 예제인 interactive_mode (대화형 모드)에서도 위의 옵션을 전부 실행해 볼 수 있습니다.
# ========================================
# 6. 실행 예시
# ========================================
if __name__ == "__main__":
# 예시 1: 일반 요약
# analyze_repo(
# repo_url="https://github.com/ZZmarkus/OCR-Model-Test-Using-RestfulAPI.git",
# max_commits=3,
# model="llama3.2",
# style="technical",
# use_chat=False
# )
# 예시 2: 코드 리뷰 모드
# analyze_repo(
# repo_url="https://github.com/ZZmarkus/OCR-Model-Test-Using-RestfulAPI.git",
# max_commits=5,
# model="llama3.2",
# style="review",
# use_chat=False
# )
# 예시 3: 간단 요약
# analyze_repo(
# repo_url="https://github.com/ZZmarkus/OCR-Model-Test-Using-RestfulAPI.git",
# max_commits=10,
# model="llama3.2",
# style="simple",
# use_chat=False
# )
# 예시 4: 대화형 모드
interactive_mode(model="qwen2.5:7b")
1) 옵션 선택하기

2) Git 저장소 URL 입력하기

3) 분석할 커밋 개수 입력하기

4) 분석 방법 입력하기

5) 코드리뷰 모드

💡 실행 예시
🔹 커밋 1/3
───────
📌 Hash : fe959083
👤 작성자 : DESKTOP-IGD4989\11703
📅 날짜 : 2024-06-27 15:04:49+09:00
💬 메시지 : 간단한 OCR모델 테스트 API
📁 변경 파일: 25개
🔍 코드 리뷰 수행 중... 완료!
📋 코드 리뷰 결과:
───────
Git 커밋을 코드 리뷰해본 결과以下은 좋은 점, 개선할 점, 보안 이슈, 잠재적 버그, 제안 사항입니다.
1. ✅ 좋은 점:
- 커밋 메시지가 간결하고 명확합니다.
- Gradle의 configuration과 dependencies를 잘 정의했습니다.
- Gradle wrapper에 대한 설정은 적절합니다.
2. ⚠️ 개선할 점:
- Gradle wrapper의 version을 7.6.1으로 setting해두었으나, Gradle의 latest 버전이 8.4.1이기 때문에 이중 사용을 피하기 위해 version을 updates 하시는 것을 권장합니다.
- Gradle의 version과 dependencies를 잘 확인하여 update를 하세요.
3. 🔒 보안 이슈 (있다면):
- None
-Gradle wrapper에 대한 configuration은 보안 관련이 없습니다.
4. 🐛 잠재적 버그 (있다면):
- Gradle wrapper의 version을 update 후, Gradle version이 변경되는 경우 Gradle wrapper가 updated되지 않는 문제가 발생할 수 있습니다.
- Gradle version을 확인하고, Gradle wrapper를 update하세요.
5. 💡 제안사항:
- Gradle configuration에서 `spring-boot-starter-data-jpa`를 두 번 반복적으로 설정하는 것을 피하세요
───────
───────
🔹 커밋 2/3
───────
📌 Hash : ebb5af14
👤 작성자 : Cho Hyeon Min
📅 날짜 : 2024-06-27 15:00:57+09:00
💬 메시지 : Update README.md
📁 변경 파일: 1개
🔍 코드 리뷰 수행 중... 완료!
📋 코드 리뷰 결과:
────────
👍 codes를 분석해 드리겠습니다.
**✅ 좋은 점**
- 커밋 메시지가 충분히 짧고 clear합니다. README.md에 대한 업데이트가.clear하게 나타나며, 쉽게 이해할 수 있습니다.
- 커뮤니티에서 도움이 되거나, 관련된 프로젝트에 영향을 줄 수 있는 변경 사항은 커밋 메시지의 첫 번째 단어로 시작하여 명확히합니다.
**⚠️ 개선할 점**
- 커밋 메시지가 너무 짧습니다. 짧은 커밋 메시지는 업데이트가 어떤 내용이인지를 알기 어렵습니다. 따라서 커뮤니티에서 도움이 되거나, 관련된 프로젝트에 영향을 줄 수 있는 변경 사항은 커밋 메시지의 첫 번째 단어로 시작하여 명확히하고, 한-Line 이외에는 더 많은 정보가 필요합니다.
- 커밋 메시지의 내용이 README.md에 대한 업데이트를 나타내는 것만으로 confines되어 있습니다. 커뮤니티가 어떤 변경 사항을 알 수 있는지 확인해 보십시오.
**🔒 보안 이슈 (있다면)**
- 현재는 보안 이슈가 없습니다. 하지만, Future의 보안에 대한 고려가 필요합니다.
**🐛 잠재적 버그 (있다면)**
📗 마치며
이번 포스팅에서는 온프레미스 환경에서 LLM 모델을 다운로드하여
Github의 Diff 정보를 바탕으로 코드리뷰 및 커밋내용 요약하는 기능을 만들어 보았습니다.
여러 LLM 오픈소스 모델을 Finetuning 하여 이러한 코드리뷰 자동화 프로세스를 구축할 수 있다면
개발 생산성을 크게 향상해 줄 수 있을 것 같습니다.
🎯 이 아이디어를 확장하면
- PR 기반 자동 리뷰 시스템 구축
- 커밋 history를 Notion/Confluence 문서로 자동 정리
- CI 과정 내에서 자동 정책 위반 검사
- Jira 자동 업데이트
등 다양한 방향으로 기능을 업데이트할 수 있을 것 같습니다.
'AI & ML > LLM' 카테고리의 다른 글
| PDF 기반 RAG 챗봇 만들기 - LangChain으로 LLM 문서 검색형 AI 구축하기 (0) | 2025.11.25 |
|---|---|
| LLM이란 무엇인가? - 쉽게 이해하는 트랜스포머 핵심 매커니즘 완전 분석 (0) | 2025.11.21 |