เนื้อหาในบทความนี้
สถานะบทความ
อัปเดตล่าสุด: กุมภาพันธ์ 2026 | ระดับความยาก: ระดับกลาง (Intermediate) | เวลาอ่าน: 45 นาที
ทดสอบกับ Ollama 0.5.x, GitLab 17.x, Python 3.11+ | รองรับ CodeLlama, DeepSeek-Coder, Qwen2.5-Coder
1 บทนำ: ทำไมต้องใช้ AI รีวิวโค้ด?
การรีวิวโค้ด (Code Review) เป็นหนึ่งในกระบวนการที่สำคัญที่สุดในการพัฒนาซอฟต์แวร์ แต่มักจะเป็นงานที่ใช้เวลานานและอาจเกิดความผิดพลาดจากมนุษย์ได้ AI Code Reviewer ช่วยแก้ปัญหานี้โดย:
ประโยชน์ของ AI Code Review
-
รวดเร็ว: รีวิวโค้ดได้ภายในไม่กี่วินาที ไม่ต้องรอ human reviewer
-
24/7 Available: พร้อมทำงานตลอดเวลา ไม่มีวันหยุด
-
Consistent: ตรวจสอบด้วยเกณฑ์เดียวกันทุกครั้ง
-
Knowledge Base: มีความรู้จาก codebase ขนาดใหญ่ทั่วโลก
-
Security Focus: ตรวจจับช่องโหว่ความปลอดภัยได้ดี
ข้อจำกัดที่ต้องรู้
-
Hallucination: AI อาจให้ข้อมูลผิดหรือแนะนำที่ไม่เหมาะสม
-
Context Limit: ไม่เข้าใจ business context เต็มที่
-
ไม่แทนมนุษย์: ควรใช้เป็น assistant ไม่ใช่ replacement
-
Resource Intensive: ต้องการ GPU หรือ RAM สูงสำหรับ model ใหญ่
-
Latency: อาจช้าถ้า codebase ใหญ่หรือ server ไม่แรง
ทำไมต้องใช้ Local LLM (Ollama)?
2 Architecture Overview
ระบบ AI Code Review ประกอบด้วยหลาย components ที่ทำงานร่วมกัน ด้านล่างคือ Architecture และ Flow การทำงานของระบบ
System Architecture
Workflow Flow
GitLab CI/CD
จัดการ pipeline และ trigger AI review เมื่อมี code push หรือ MR
Ollama Server
รัน Local LLM สำหรับ analyze code และให้ review comments
Review Script
Python script สำหรับเรียก Ollama API และประมวลผล
Report Generator
สร้างรายงานและตัดสินใจ pass/fail ตาม threshold
3 สิ่งที่ต้องเตรียม (Prerequisites)
ก่อนเริ่มต้น ตรวจสอบว่าคุณมีสิ่งต่อไปนี้พร้อมแล้ว:
GitLab Instance
Self-hosted หรือ GitLab.com
Ollama Server
สำหรับรัน LLM
Docker (Optional)
สำหรับ containerized deployment
GitLab Runner
สำหรับรัน CI jobs
ความต้องการ Hardware ตาม Model Size
| Model | Parameters | Minimum RAM | Recommended RAM | GPU VRAM |
|---|---|---|---|---|
| CodeLlama 7B | 7B | 8 GB | 16 GB | 6 GB |
| DeepSeek-Coder 6.7B | 6.7B | 8 GB | 16 GB | 6 GB |
| Qwen2.5-Coder 7B | 7B | 8 GB | 16 GB | 6 GB |
| CodeLlama 13B | 13B | 16 GB | 32 GB | 12 GB |
| DeepSeek-Coder 33B | 33B | 32 GB | 64 GB | 24 GB |
4 ขั้นตอนที่ 1: ติดตั้ง Ollama
เริ่มต้นด้วยการติดตั้ง Ollama บน server ของคุณ Ollama รองรับ Linux, macOS และ Windows
4.1 ติดตั้ง Ollama บน Linux
# ติดตั้ง Ollama ด้วย script อย่างเป็นทางการ
curl -fsSL https://ollama.com/install.sh | sh
# หรือติดตั้งด้วย Docker
docker pull ollama/ollama
# รัน Ollama container (CPU only)
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
# รัน Ollama ด้วย GPU support (NVIDIA)
docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
หมายเหตุ: Port 11434 เป็น default port ของ Ollama API คุณสามารถเปลี่ยนได้ตามต้องการ
4.2 Pull Model ที่เหมาะสม
เลือก model ที่เหมาะกับงาน code review ของคุณ:
CodeLlama
โดย Meta เฉพาะทาง code
ollama pull codellama:7b
ollama pull codellama:13b
ollama pull codellama:34b
DeepSeek-Coder
โดย DeepSeek เก่งมาก
ollama pull deepseek-coder:6.7b
ollama pull deepseek-coder:33b
Qwen2.5-Coder
โดย Alibaba ราคาถูก
ollama pull qwen2.5-coder:7b
ollama pull qwen2.5-coder:14b
คำแนะนำ: สำหรับเริ่มต้น แนะนำให้ใช้ deepseek-coder:6.7b เพราะให้ผลลัพธ์ดีและใช้ทรัพยากรน้อย
4.3 ทดสอบ API Endpoint
# ทดสอบ Ollama API ด้วย curl
curl http://localhost:11434/api/generate -d '{
"model": "deepseek-coder:6.7b",
"prompt": "Review this Python code: def add(a, b): return a + b",
"stream": false
}'
# ทดสอบ List Models
curl http://localhost:11434/api/tags
# ทดสอบด้วย Python
python3 -c "
import requests
import json
response = requests.post('http://localhost:11434/api/generate', json={
'model': 'deepseek-coder:6.7b',
'prompt': 'What is 2+2?',
'stream': False
})
print(response.json())
"
Expected Response:
{
"model": "deepseek-coder:6.7b",
"created_at": "2026-02-16T10:00:00.000Z",
"response": "4",
"done": true,
"context": [...],
"total_duration": 1500000000,
"load_duration": 500000000,
"prompt_eval_count": 10,
"eval_count": 3
}
กำหนดค่า Network Access (สำคัญ!)
GitLab Runner ต้องสามารถเข้าถึง Ollama API ได้ ดังนั้นต้องกำหนดให้ Ollama รับฟังจากทุก interface:
# กำหนด OLLAMA_HOST environment variable
export OLLAMA_HOST=0.0.0.0:11434
# หรือกำหนดใน systemd service
sudo systemctl edit ollama.service
# เพิ่มบรรทัดนี้:
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
# Restart service
sudo systemctl restart ollama
# ตรวจสอบว่ารับฟังจากทุก interface
ss -tlnp | grep 11434
# ควรเห็น: 0.0.0.0:11434
5 ขั้นตอนที่ 2: สร้าง GitLab CI/CD Pipeline
สร้าง pipeline สำหรับ AI code review ที่ครอบคลุม Code Quality, Security และ Best Practices
Pipeline Structure
5.1 ไฟล์ .gitlab-ci.yml
# .gitlab-ci.yml - GitLab CI/CD Pipeline for AI Code Review
# ตั้งชื่อ pipeline และกำหนด variables
variables:
OLLAMA_HOST: "http://your-ollama-server:11434"
OLLAMA_MODEL: "deepseek-coder:6.7b"
REVIEW_THRESHOLD: "70"
# Threshold สำหรับ pass/fail (0-100)
# กำหนด stages
stages:
- quality
- security
- best_practices
- decision
# กำหนด default settings
default:
image: python:3.11-slim
before_script:
- pip install --quiet requests python-gitlab
retry:
max: 2
when:
- script_failure
# ====================
# STAGE 1: CODE QUALITY
# ====================
code_quality_review:
stage: quality
script:
- echo "🔍 Running AI Code Quality Review..."
- python scripts/ai-review.py
--type quality
--ollama-host $OLLAMA_HOST
--model $OLLAMA_MODEL
--output quality_report.json
artifacts:
paths:
- quality_report.json
expire_in: 1 week
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
tags:
- docker
# ====================
# STAGE 2: SECURITY SCAN
# ====================
security_review:
stage: security
script:
- echo "🔒 Running AI Security Review..."
- python scripts/ai-review.py
--type security
--ollama-host $OLLAMA_HOST
--model $OLLAMA_MODEL
--output security_report.json
artifacts:
paths:
- security_report.json
expire_in: 1 week
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
tags:
- docker
# ====================
# STAGE 3: BEST PRACTICES
# ====================
best_practices_review:
stage: best_practices
script:
- echo "📚 Running AI Best Practices Review..."
- python scripts/ai-review.py
--type best_practices
--ollama-host $OLLAMA_HOST
--model $OLLAMA_MODEL
--output best_practices_report.json
artifacts:
paths:
- best_practices_report.json
expire_in: 1 week
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
tags:
- docker
# ====================
# STAGE 4: FINAL DECISION
# ====================
ai_review_decision:
stage: decision
script:
- echo "⚖️ Making final AI review decision..."
- python scripts/review-decision.py
--threshold $REVIEW_THRESHOLD
--quality-report quality_report.json
--security-report security_report.json
--best-practices-report best_practices_report.json
artifacts:
paths:
- final_review_report.json
- review_summary.md
expire_in: 1 month
dependencies:
- code_quality_review
- security_review
- best_practices_review
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
tags:
- docker
# ====================
# OPTIONAL: POST MR COMMENT
# ====================
post_mr_comment:
stage: .post
script:
- echo "💬 Posting review to Merge Request..."
- python scripts/post-mr-comment.py
--project-id $CI_PROJECT_ID
--mr-iid $CI_MERGE_REQUEST_IID
--report final_review_report.json
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure: true
tags:
- docker
กำหนด GitLab CI/CD Variables
ไปที่ Settings → CI/CD → Variables และเพิ่ม variables เหล่านี้:
| Variable | Value | Protected | Masked |
|---|---|---|---|
OLLAMA_HOST |
http://10.0.0.100:11434 | ✓ | ✓ |
OLLAMA_MODEL |
deepseek-coder:6.7b | - | - |
REVIEW_THRESHOLD |
70 | - | - |
GITLAB_TOKEN |
your-access-token | ✓ | ✓ |
6 ขั้นตอนที่ 3: สร้าง Review Script
สร้าง Python script สำหรับเรียก Ollama API และทำการ review โค้ด
6.1 โครงสร้างโฟลเดอร์
your-project/
├── .gitlab-ci.yml
├── scripts/
│ ├── ai-review.py # Main review script
│ ├── review-decision.py # Decision making script
│ ├── post-mr-comment.py # Post comment to MR
│ └── prompts/
│ ├── quality.txt # Code quality prompt
│ ├── security.txt # Security review prompt
│ └── best_practices.txt
└── src/
└── ...
6.2 Prompt Engineering สำหรับ Code Review
Code Quality Prompt
You are an expert code reviewer.
Analyze the following code for quality issues:
1. Code readability and maintainability
2. Naming conventions
3. Code duplication (DRY principle)
4. Function/method length
5. Complexity (cyclomatic complexity)
CODE TO REVIEW:
{code}
Respond in JSON format:
{
"score": 0-100,
"issues": [
{
"severity": "high|medium|low",
"file": "filename",
"line": 10,
"message": "description",
"suggestion": "how to fix"
}
],
"summary": "overall assessment"
}
Security Review Prompt
You are a security expert.
Analyze code for security vulnerabilities:
1. SQL injection risks
2. XSS vulnerabilities
3. Authentication issues
4. Sensitive data exposure
5. Insecure dependencies
6. Input validation issues
CODE TO REVIEW:
{code}
Respond in JSON format:
{
"score": 0-100,
"vulnerabilities": [
{
"severity": "critical|high|medium|low",
"type": "vulnerability type",
"cwe": "CWE-XXX",
"file": "filename",
"line": 10,
"description": "description",
"remediation": "how to fix"
}
],
"summary": "security assessment"
}
Performance Review Prompt
You are a performance optimization expert.
Analyze code for performance issues:
1. Time complexity (Big O)
2. Memory usage patterns
3. Database query efficiency
4. Caching opportunities
5. Async/await usage
6. Resource leaks
CODE TO REVIEW:
{code}
Respond in JSON format:
{
"score": 0-100,
"issues": [
{
"severity": "high|medium|low",
"type": "performance issue type",
"file": "filename",
"line": 10,
"current_complexity": "O(n)",
"suggested_complexity": "O(log n)",
"description": "description",
"optimization": "how to optimize"
}
],
"summary": "performance assessment"
}
Best Practices Prompt
You are a software best practices expert.
Analyze code for best practices:
1. SOLID principles
2. Design patterns usage
3. Error handling
4. Logging practices
5. Testing coverage hints
6. Documentation quality
CODE TO REVIEW:
{code}
Respond in JSON format:
{
"score": 0-100,
"recommendations": [
{
"principle": "SOLID/Pattern name",
"severity": "high|medium|low",
"file": "filename",
"line": 10,
"current": "current implementation",
"recommended": "recommended approach",
"reason": "why this matters"
}
],
"summary": "best practices assessment"
}
การ Parse และแสดงผลลัพธ์
หลังจากได้ response จาก Ollama แล้ว ต้อง parse JSON และ format สำหรับ GitLab:
import json
import re
def parse_ai_response(response_text: str) -> dict:
"""Parse AI response and extract JSON"""
try:
# ลอง parse โดยตรง
return json.loads(response_text)
except json.JSONDecodeError:
# หา JSON ใน text
json_match = re.search(r'\{[\s\S]*\}', response_text)
if json_match:
try:
return json.loads(json_match.group())
except:
pass
# Fallback: return default structure
return {
"score": 50,
"issues": [],
"summary": "Unable to parse AI response"
}
def format_for_gitlab(report: dict) -> str:
"""Format report for GitLab CI output"""
output = []
output.append("## 🤖 AI Code Review Report\n")
# Score
score = report.get('score', 0)
emoji = "🟢" if score >= 70 else "🟡" if score >= 50 else "🔴"
output.append(f"### {emoji} Overall Score: {score}/100\n")
# Issues
issues = report.get('issues', report.get('vulnerabilities', []))
if issues:
output.append("### 📋 Issues Found:\n")
for issue in issues:
severity = issue.get('severity', 'unknown')
sev_emoji = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢"}.get(severity, "⚪")
output.append(f"- {sev_emoji} **{severity.upper()}**: {issue.get('message', issue.get('description', 'N/A'))}")
if issue.get('file'):
output.append(f" - File: `{issue['file']}`")
if issue.get('line'):
output.append(f" - Line: {issue['line']}")
output.append("")
# Summary
output.append(f"### 📝 Summary\n{report.get('summary', 'No summary available')}")
return "\n".join(output)
7 ตัวอย่างโค้ดที่สมบูรณ์
นี่คือไฟล์โค้ดที่สมบูรณ์พร้อมใช้งาน สามารถ copy ไปใช้ได้ทันที
scripts/ai-review.py
#!/usr/bin/env python3
"""
AI Code Review Script for GitLab CI/CD
Uses Ollama Local LLM for code analysis
Usage:
python ai-review.py --type quality --ollama-host http://localhost:11434 --model deepseek-coder:6.7b
"""
import argparse
import json
import os
import re
import sys
import subprocess
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
from pathlib import Path
import requests
from requests.exceptions import RequestException
# ====================
# PROMPTS TEMPLATES
# ====================
PROMPTS = {
"quality": """You are an expert code reviewer specializing in code quality.
Analyze the following code changes and provide a comprehensive quality review.
Focus on:
1. Code readability and maintainability
2. Naming conventions (variables, functions, classes)
3. Code duplication (DRY principle violations)
4. Function/method length and complexity
5. Cyclomatic complexity concerns
6. Code organization and structure
CODE CHANGES TO REVIEW:
```diff
{code}
```
IMPORTANT: Respond ONLY with valid JSON in this exact format:
{{
"score": ,
"issues": [
{{
"severity": "",
"file": "",
"line": ,
"message": "",
"suggestion": ""
}}
],
"positive_points": [""],
"summary": ""
}}""",
"security": """You are a security expert specializing in application security.
Perform a thorough security review of the following code changes.
Focus on:
1. SQL injection vulnerabilities
2. Cross-site scripting (XSS) risks
3. Authentication and authorization issues
4. Sensitive data exposure (passwords, API keys, tokens)
5. Insecure dependencies or configurations
6. Input validation gaps
7. Path traversal vulnerabilities
8. Command injection risks
CODE CHANGES TO REVIEW:
```diff
{code}
```
IMPORTANT: Respond ONLY with valid JSON in this exact format:
{{
"score": ,
"vulnerabilities": [
{{
"severity": "",
"type": "",
"cwe": "",
"file": "",
"line": ,
"description": "",
"remediation": ""
}}
],
"secure_practices": [""],
"summary": ""
}}""",
"best_practices": """You are a software engineering expert specializing in best practices.
Review the following code changes for adherence to software engineering best practices.
Focus on:
1. SOLID principles adherence
2. Appropriate design patterns usage
3. Error handling strategies
4. Logging and monitoring practices
5. Code documentation quality
6. Testing considerations
7. API design principles
8. Dependency management
CODE CHANGES TO REVIEW:
```diff
{code}
```
IMPORTANT: Respond ONLY with valid JSON in this exact format:
{{
"score": ,
"recommendations": [
{{
"principle": "",
"severity": "",
"file": "",
"line": ,
"current": "",
"recommended": "",
"reason": ""
}}
],
"good_practices": [""],
"summary": ""
}}""",
"performance": """You are a performance optimization expert.
Analyze the following code changes for potential performance issues.
Focus on:
1. Time complexity analysis (Big O notation)
2. Memory usage patterns and potential leaks
3. Database query efficiency (N+1 problems, missing indexes)
4. Caching opportunities
5. Async/await usage and blocking operations
6. Resource management (connections, file handles)
7. Loop optimization opportunities
8. Unnecessary computations
CODE CHANGES TO REVIEW:
```diff
{code}
```
IMPORTANT: Respond ONLY with valid JSON in this exact format:
{{
"score": ,
"issues": [
{{
"severity": "",
"type": "",
"file": "",
"line": ,
"current_complexity": "",
"suggested_complexity": "",
"description": "",
"optimization": ""
}}
],
"optimizations_applied": [""],
"summary": ""
}}"""
}
# ====================
# DATA CLASSES
# ====================
@dataclass
class ReviewResult:
"""Container for review results"""
review_type: str
score: int
issues: List[Dict[str, Any]]
positive_points: List[str]
summary: str
raw_response: str
duration_ms: float
# ====================
# OLLAMA CLIENT
# ====================
class OllamaClient:
"""Client for interacting with Ollama API"""
def __init__(self, host: str, model: str, timeout: int = 120):
self.host = host.rstrip('/')
self.model = model
self.timeout = timeout
self.api_url = f"{self.host}/api/generate"
def generate(self, prompt: str) -> Dict[str, Any]:
"""Generate response from Ollama"""
payload = {
"model": self.model,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.3, # Lower temperature for more consistent reviews
"top_p": 0.9,
"num_predict": 4096 # Allow longer responses
}
}
try:
response = requests.post(
self.api_url,
json=payload,
timeout=self.timeout
)
response.raise_for_status()
return response.json()
except RequestException as e:
raise RuntimeError(f"Ollama API error: {e}")
def health_check(self) -> bool:
"""Check if Ollama server is healthy"""
try:
response = requests.get(f"{self.host}/api/tags", timeout=5)
return response.status_code == 200
except:
return False
# ====================
# GIT OPERATIONS
# ====================
class GitHelper:
"""Helper for Git operations in CI environment"""
@staticmethod
def get_changed_files() -> List[str]:
"""Get list of changed files in the current MR/commit"""
# Try to get from CI environment variables
if os.environ.get('CI_MERGE_REQUEST_DIFF_BASE_SHA'):
# Merge Request context
base_sha = os.environ['CI_MERGE_REQUEST_DIFF_BASE_SHA']
head_sha = os.environ['CI_COMMIT_SHA']
cmd = f"git diff --name-only {base_sha}...{head_sha}"
elif os.environ.get('CI_COMMIT_BEFORE_SHA'):
# Push context
before_sha = os.environ['CI_COMMIT_BEFORE_SHA']
head_sha = os.environ['CI_COMMIT_SHA']
cmd = f"git diff --name-only {before_sha}...{head_sha}"
else:
# Fallback: last commit
cmd = "git diff --name-only HEAD~1 HEAD"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode == 0:
return [f for f in result.stdout.strip().split('\n') if f]
return []
@staticmethod
def get_diff(file_path: Optional[str] = None) -> str:
"""Get git diff for code review"""
if os.environ.get('CI_MERGE_REQUEST_DIFF_BASE_SHA'):
base_sha = os.environ['CI_MERGE_REQUEST_DIFF_BASE_SHA']
head_sha = os.environ['CI_COMMIT_SHA']
if file_path:
cmd = f"git diff {base_sha}...{head_sha} -- {file_path}"
else:
cmd = f"git diff {base_sha}...{head_sha}"
elif os.environ.get('CI_COMMIT_BEFORE_SHA'):
before_sha = os.environ['CI_COMMIT_BEFORE_SHA']
head_sha = os.environ['CI_COMMIT_SHA']
if file_path:
cmd = f"git diff {before_sha}...{head_sha} -- {file_path}"
else:
cmd = f"git diff {before_sha}...{head_sha}"
else:
if file_path:
cmd = f"git diff HEAD~1 HEAD -- {file_path}"
else:
cmd = "git diff HEAD~1 HEAD"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout if result.returncode == 0 else ""
@staticmethod
def get_file_content(file_path: str) -> str:
"""Get content of a specific file"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception:
return ""
# ====================
# REVIEW ENGINE
# ====================
class CodeReviewer:
"""Main code review engine"""
# File extensions to review (exclude non-code files)
REVIEWABLE_EXTENSIONS = {
'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rs',
'.php', '.rb', '.cs', '.cpp', '.c', '.h', '.swift', '.kt',
'.scala', '.vue', '.svelte', '.sql', '.sh', '.yaml', '.yml',
'.json', '.html', '.css', '.scss', '.sass', '.less'
}
# Files/patterns to skip
SKIP_PATTERNS = {
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
'poetry.lock', 'Cargo.lock', 'go.sum',
'.min.js', '.min.css', 'dist/', 'build/', 'node_modules/'
}
def __init__(self, ollama_client: OllamaClient):
self.ollama = ollama_client
self.git = GitHelper()
def should_review_file(self, file_path: str) -> bool:
"""Check if file should be reviewed"""
# Check skip patterns
for pattern in self.SKIP_PATTERNS:
if pattern in file_path:
return False
# Check extension
ext = Path(file_path).suffix.lower()
return ext in self.REVIEWABLE_EXTENSIONS
def review(self, review_type: str, max_files: int = 20) -> ReviewResult:
"""Perform code review"""
import time
# Get prompt template
prompt_template = PROMPTS.get(review_type)
if not prompt_template:
raise ValueError(f"Unknown review type: {review_type}")
# Get changed files
changed_files = self.git.get_changed_files()
reviewable_files = [f for f in changed_files if self.should_review_file(f)]
if not reviewable_files:
return ReviewResult(
review_type=review_type,
score=100,
issues=[],
positive_points=["No reviewable code changes found"],
summary="No code files to review",
raw_response="",
duration_ms=0
)
# Limit files to prevent timeout
reviewable_files = reviewable_files[:max_files]
# Get combined diff
diff_parts = []
for file_path in reviewable_files:
file_diff = self.git.get_diff(file_path)
if file_diff:
diff_parts.append(f"--- {file_path} ---\n{file_diff}")
combined_diff = "\n\n".join(diff_parts)
# Truncate if too long
max_chars = 15000
if len(combined_diff) > max_chars:
combined_diff = combined_diff[:max_chars] + "\n... [truncated]"
# Build prompt
prompt = prompt_template.format(code=combined_diff)
# Call Ollama
start_time = time.time()
response = self.ollama.generate(prompt)
duration_ms = (time.time() - start_time) * 1000
# Parse response
raw_response = response.get('response', '')
parsed = self._parse_response(raw_response)
return ReviewResult(
review_type=review_type,
score=parsed.get('score', 50),
issues=parsed.get('issues', parsed.get('vulnerabilities', parsed.get('recommendations', []))),
positive_points=parsed.get('positive_points', parsed.get('secure_practices', parsed.get('good_practices', parsed.get('optimizations_applied', [])))),
summary=parsed.get('summary', 'Review completed'),
raw_response=raw_response,
duration_ms=duration_ms
)
def _parse_response(self, response: str) -> Dict[str, Any]:
"""Parse AI response to extract JSON"""
# Try direct JSON parse
try:
return json.loads(response)
except json.JSONDecodeError:
pass
# Try to find JSON in response
json_patterns = [
r'\{[\s\S]*\}', # Full JSON object
r'```json\s*([\s\S]*?)\s*```', # JSON in code block
r'```\s*([\s\S]*?)\s*```' # Any code block
]
for pattern in json_patterns:
match = re.search(pattern, response)
if match:
try:
json_str = match.group(1) if '```' in pattern else match.group()
return json.loads(json_str)
except (json.JSONDecodeError, IndexError):
continue
# Fallback: try to extract score
score_match = re.search(r'"?score"?\s*[:=]\s*(\d+)', response)
score = int(score_match.group(1)) if score_match else 50
return {
"score": score,
"issues": [],
"positive_points": [],
"summary": "Could not parse full AI response. Manual review recommended."
}
# ====================
# OUTPUT FORMATTERS
# ====================
def format_console_output(result: ReviewResult) -> str:
"""Format result for console output"""
lines = []
# Header
lines.append(f"\n{'='*60}")
lines.append(f"🤖 AI {result.review_type.upper()} REVIEW")
lines.append(f"{'='*60}")
# Score with emoji
if result.score >= 80:
emoji = "🟢"
status = "EXCELLENT"
elif result.score >= 70:
emoji = "🟢"
status = "GOOD"
elif result.score >= 50:
emoji = "🟡"
status = "NEEDS IMPROVEMENT"
else:
emoji = "🔴"
status = "POOR"
lines.append(f"\n{emoji} Score: {result.score}/100 ({status})")
lines.append(f"⏱️ Duration: {result.duration_ms:.0f}ms")
# Issues
if result.issues:
lines.append(f"\n📋 Issues Found: {len(result.issues)}")
for i, issue in enumerate(result.issues[:10], 1): # Limit to 10
severity = issue.get('severity', 'unknown')
sev_emoji = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢"}.get(severity, "⚪")
lines.append(f"\n {i}. {sev_emoji} [{severity.upper()}] {issue.get('file', 'unknown')}")
if issue.get('line'):
lines.append(f" Line: {issue['line']}")
msg = issue.get('message', issue.get('description', 'No description'))
lines.append(f" {msg[:100]}...")
# Positive points
if result.positive_points:
lines.append(f"\n✅ Positive Points:")
for point in result.positive_points[:5]:
lines.append(f" • {point}")
# Summary
lines.append(f"\n📝 Summary:")
lines.append(f" {result.summary}")
lines.append(f"\n{'='*60}\n")
return "\n".join(lines)
def format_json_output(result: ReviewResult) -> str:
"""Format result as JSON"""
return json.dumps({
"review_type": result.review_type,
"score": result.score,
"issues": result.issues,
"positive_points": result.positive_points,
"summary": result.summary,
"duration_ms": result.duration_ms,
"timestamp": os.environ.get('CI_JOB_STARTED_AT', ''),
"commit_sha": os.environ.get('CI_COMMIT_SHA', ''),
"branch": os.environ.get('CI_COMMIT_REF_NAME', '')
}, indent=2)
# ====================
# MAIN
# ====================
def main():
parser = argparse.ArgumentParser(description='AI Code Review using Ollama')
parser.add_argument('--type', '-t',
choices=['quality', 'security', 'best_practices', 'performance'],
required=True,
help='Type of review to perform')
parser.add_argument('--ollama-host', '-H',
default=os.environ.get('OLLAMA_HOST', 'http://localhost:11434'),
help='Ollama server URL')
parser.add_argument('--model', '-m',
default=os.environ.get('OLLAMA_MODEL', 'deepseek-coder:6.7b'),
help='Ollama model to use')
parser.add_argument('--output', '-o',
help='Output file for JSON report')
parser.add_argument('--timeout', type=int, default=120,
help='API timeout in seconds')
parser.add_argument('--max-files', type=int, default=20,
help='Maximum files to review')
parser.add_argument('--verbose', '-v', action='store_true',
help='Enable verbose output')
args = parser.parse_args()
print(f"🦙 AI Code Review starting...")
print(f" Type: {args.type}")
print(f" Model: {args.model}")
print(f" Ollama: {args.ollama_host}")
# Initialize client
client = OllamaClient(args.ollama_host, args.model, args.timeout)
# Health check
if not client.health_check():
print(f"❌ Error: Cannot connect to Ollama at {args.ollama_host}")
sys.exit(1)
print(f"✅ Ollama connection OK")
# Initialize reviewer
reviewer = CodeReviewer(client)
# Perform review
try:
result = reviewer.review(args.type, args.max_files)
except Exception as e:
print(f"❌ Review failed: {e}")
sys.exit(1)
# Output results
print(format_console_output(result))
# Save JSON report
json_output = format_json_output(result)
if args.output:
with open(args.output, 'w') as f:
f.write(json_output)
print(f"📄 Report saved to: {args.output}")
# Exit with appropriate code
if result.score < 50:
print("❌ Review FAILED: Score below 50")
sys.exit(1)
elif result.score < 70:
print("⚠️ Review PASSED with warnings: Score below 70")
sys.exit(0)
else:
print("✅ Review PASSED")
sys.exit(0)
if __name__ == '__main__':
main()
scripts/review-decision.py
#!/usr/bin/env python3
"""
Review Decision Script
Aggregates multiple review reports and makes pass/fail decision
Usage:
python review-decision.py --threshold 70 --quality-report quality_report.json
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, List, Optional
class ReviewDecision:
"""Aggregate review results and make decision"""
# Weights for different review types
WEIGHTS = {
'quality': 0.3,
'security': 0.4, # Security has highest weight
'best_practices': 0.2,
'performance': 0.1
}
def __init__(self, threshold: int = 70):
self.threshold = threshold
self.reports: Dict[str, dict] = {}
def load_report(self, review_type: str, file_path: str) -> bool:
"""Load a review report from file"""
try:
with open(file_path, 'r') as f:
self.reports[review_type] = json.load(f)
return True
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"⚠️ Could not load {review_type} report: {e}")
return False
def calculate_weighted_score(self) -> float:
"""Calculate weighted average score"""
total_weight = 0
weighted_sum = 0
for review_type, weight in self.WEIGHTS.items():
if review_type in self.reports:
score = self.reports[review_type].get('score', 50)
weighted_sum += score * weight
total_weight += weight
if total_weight == 0:
return 0
return weighted_sum / total_weight
def get_critical_issues(self) -> List[dict]:
"""Get all critical/high severity issues"""
critical = []
for review_type, report in self.reports.items():
issues = report.get('issues', report.get('vulnerabilities', report.get('recommendations', [])))
for issue in issues:
severity = issue.get('severity', '').lower()
if severity in ('critical', 'high'):
critical.append({
'type': review_type,
**issue
})
return critical
def make_decision(self) -> dict:
"""Make final pass/fail decision"""
weighted_score = self.calculate_weighted_score()
critical_issues = self.get_critical_issues()
# Decision logic
passed = True
reasons = []
# Check weighted score
if weighted_score < self.threshold:
passed = False
reasons.append(f"Weighted score ({weighted_score:.1f}) below threshold ({self.threshold})")
# Check for critical security issues (auto-fail)
security_critical = [i for i in critical_issues if i.get('type') == 'security' and i.get('severity') == 'critical']
if security_critical:
passed = False
reasons.append(f"Found {len(security_critical)} critical security vulnerabilities")
# Check for too many high severity issues
high_issues = [i for i in critical_issues if i.get('severity') == 'high']
if len(high_issues) > 5:
passed = False
reasons.append(f"Too many high severity issues ({len(high_issues)})")
return {
'passed': passed,
'weighted_score': round(weighted_score, 1),
'threshold': self.threshold,
'reasons': reasons,
'critical_issues_count': len(critical_issues),
'reports_summary': {
rtype: {
'score': report.get('score'),
'issues_count': len(report.get('issues', report.get('vulnerabilities', [])))
}
for rtype, report in self.reports.items()
}
}
def generate_summary_markdown(decision: dict) -> str:
"""Generate markdown summary for GitLab"""
lines = []
# Header
status_emoji = "✅" if decision['passed'] else "❌"
lines.append(f"# {status_emoji} AI Code Review Decision")
lines.append("")
# Result
result_text = "PASSED" if decision['passed'] else "FAILED"
lines.append(f"## Result: **{result_text}**")
lines.append("")
# Score
score = decision['weighted_score']
threshold = decision['threshold']
score_emoji = "🟢" if score >= 80 else "🟡" if score >= 60 else "🔴"
lines.append(f"### {score_emoji} Weighted Score: {score}/100 (threshold: {threshold})")
lines.append("")
# Reports summary
lines.append("### 📊 Review Breakdown")
lines.append("")
lines.append("| Review Type | Score | Issues |")
lines.append("|-------------|-------|--------|")
for rtype, summary in decision['reports_summary'].items():
lines.append(f"| {rtype.replace('_', ' ').title()} | {summary['score']} | {summary['issues_count']} |")
lines.append("")
# Reasons if failed
if decision['reasons']:
lines.append("### ⚠️ Failure Reasons")
lines.append("")
for reason in decision['reasons']:
lines.append(f"- {reason}")
lines.append("")
# Critical issues
if decision['critical_issues_count'] > 0:
lines.append(f"### 🔴 Critical/High Issues: {decision['critical_issues_count']}")
lines.append("")
lines.append("> Please review the detailed reports for more information.")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description='Make review decision')
parser.add_argument('--threshold', '-t', type=int, default=70,
help='Pass threshold (0-100)')
parser.add_argument('--quality-report', help='Quality review report')
parser.add_argument('--security-report', help='Security review report')
parser.add_argument('--best-practices-report', help='Best practices report')
parser.add_argument('--performance-report', help='Performance review report')
args = parser.parse_args()
decision_maker = ReviewDecision(args.threshold)
# Load available reports
if args.quality_report:
decision_maker.load_report('quality', args.quality_report)
if args.security_report:
decision_maker.load_report('security', args.security_report)
if args.best_practices_report:
decision_maker.load_report('best_practices', args.best_practices_report)
if args.performance_report:
decision_maker.load_report('performance', args.performance_report)
# Make decision
decision = decision_maker.make_decision()
# Output JSON
with open('final_review_report.json', 'w') as f:
json.dump(decision, f, indent=2)
# Output markdown summary
summary = generate_summary_markdown(decision)
with open('review_summary.md', 'w') as f:
f.write(summary)
# Console output
print(summary)
# Exit code
sys.exit(0 if decision['passed'] else 1)
if __name__ == '__main__':
main()
scripts/post-mr-comment.py
#!/usr/bin/env python3
"""
Post Review Comment to GitLab Merge Request
Usage:
python post-mr-comment.py --project-id 123 --mr-iid 45 --report final_review_report.json
"""
import argparse
import os
import json
import requests
from typing import Optional
class GitLabClient:
"""Client for GitLab API"""
def __init__(self, token: str, url: str = "https://gitlab.com"):
self.token = token
self.url = url.rstrip('/')
self.headers = {"PRIVATE-TOKEN": token}
def post_mr_comment(self, project_id: int, mr_iid: int, body: str) -> dict:
"""Post a comment to a merge request"""
url = f"{self.url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/notes"
response = requests.post(url, headers=self.headers, json={"body": body})
response.raise_for_status()
return response.json()
def format_mr_comment(report_path: str) -> str:
"""Format review report as MR comment"""
with open(report_path, 'r') as f:
report = json.load(f)
# Read summary markdown
summary_path = 'review_summary.md'
if os.path.exists(summary_path):
with open(summary_path, 'r') as f:
return f.read()
# Fallback to basic format
status = "✅ PASSED" if report.get('passed') else "❌ FAILED"
score = report.get('weighted_score', 0)
return f"""## 🤖 AI Code Review
{status}
**Score:** {score}/100
See pipeline artifacts for detailed reports.
"""
def main():
parser = argparse.ArgumentParser(description='Post MR comment')
parser.add_argument('--project-id', type=int, required=True)
parser.add_argument('--mr-iid', type=int, required=True)
parser.add_argument('--report', required=True)
parser.add_argument('--gitlab-url', default=os.environ.get('CI_SERVER_URL', 'https://gitlab.com'))
parser.add_argument('--token', default=os.environ.get('GITLAB_TOKEN'))
args = parser.parse_args()
if not args.token:
print("❌ Error: GITLAB_TOKEN not provided")
return 1
# Format comment
comment = format_mr_comment(args.report)
# Post comment
client = GitLabClient(args.token, args.gitlab_url)
try:
result = client.post_mr_comment(args.project_id, args.mr_iid, comment)
print(f"✅ Comment posted to MR !{args.mr_iid}")
return 0
except Exception as e:
print(f"❌ Failed to post comment: {e}")
return 1
if __name__ == '__main__':
exit(main())
8 การตั้งค่า Thresholds
กำหนดเกณฑ์การผ่าน/ไม่ผ่านของ code review เพื่อควบคุมคุณภาพของโค้ดที่ merge เข้าสู่ codebase
Basic Threshold Settings
แนะนำ:
- 50-60: Permissive (สำหรับทีมเริ่มต้น)
- 70-80: Standard (แนะนำสำหรับทั่วไป)
- 85-95: Strict (สำหรับ production code)
Auto-Fail Conditions
ไฟล์กำหนดค่า Thresholds
# ai-review-config.yaml
# การตั้งค่า thresholds และกฎการตัดสินใจ
thresholds:
# Global threshold
global: 70
# Per-review type thresholds
quality: 65
security: 75 # สูงกว่าเพราะ security สำคัญ
best_practices: 60
performance: 55
# การให้น้ำหนักของแต่ละประเภท
weights:
quality: 0.25
security: 0.40
best_practices: 0.20
performance: 0.15
# Auto-fail conditions
auto_fail:
# Security
critical_vulnerabilities: true # มี critical = fail ทันที
high_vulnerabilities_max: 3 # เกิน 3 high = fail
sql_injection: true
hardcoded_secrets: true
xss_vulnerabilities: true
# Quality
cyclomatic_complexity_max: 20 # complexity เกิน 20 = fail
duplicate_code_max_percent: 15 # duplicate เกิน 15% = fail
# GitLab MR Rules
mr_rules:
# ต้องผ่าน AI review กี่จากทั้งหมด
require_all_reviews: false
required_reviews:
- security # ต้องผ่าน security เสมอ
- quality # ต้องผ่าน quality
# อนุญาตให้ bypass ได้หรือไม่
allow_bypass: true
bypass_labels:
- "skip-ai-review"
- "emergency-fix"
# ใคร bypass ได้
bypass_maintainer_ids:
- 123
- 456
# Notifications
notifications:
# แจ้งเตือนเมื่อ score ต่ำ
alert_on_low_score: true
low_score_threshold: 50
# Slack/Teams webhook
webhook_url: "${SLACK_WEBHOOK_URL}"
# Email ถึงผู้เกี่ยวข้อง
email_on_fail: true
email_recipients:
- tech-lead@company.com
GitLab Pipeline Rules สำหรับควบคุม Merge
# เพิ่มใน .gitlab-ci.yml
# Rule: Block merge ถ้า AI review ไม่ผ่าน
ai_review_gate:
stage: .post
script:
- echo "Checking AI review results..."
- |
if [ -f final_review_report.json ]; then
PASSED=$(cat final_review_report.json | python3 -c "import json,sys; print(json.load(sys.stdin)['passed'])")
if [ "$PASSED" != "True" ]; then
echo "❌ AI Review did not pass. Merge blocked."
exit 1
fi
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
allow_failure: false
# Rule: Allow bypass ด้วย label
.bypass_check:
rules:
- if: $CI_MERGE_REQUEST_LABELS =~ /skip-ai-review/
when: never
- when: always
9 Advanced Features
ฟีเจอร์ขั้นสูงสำหรับการใช้งาน AI Code Review อย่างมีประสิทธิภาพ
9.1 รีวิวเฉพาะไฟล์ที่เปลี่ยนแปลง (Diff Only)
การรีวิวเฉพาะส่วนที่เปลี่ยนแปลงช่วยประหยัดเวลาและทรัพยากร:
# diff_review.py - Review only changed lines
import subprocess
import re
def get_diff_with_context(file_path: str, context_lines: int = 3) -> str:
"""Get diff with surrounding context for better review"""
cmd = f"git diff HEAD~1 HEAD --unified={context_lines} -- {file_path}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout
def extract_changed_lines(diff: str) -> list:
"""Extract only the changed lines from diff"""
changed = []
for line in diff.split('\n'):
if line.startswith('+') and not line.startswith('+++'):
changed.append(line[1:]) # Remove the + prefix
return changed
def filter_reviewable_changes(diff: str) -> str:
"""Filter out non-reviewable changes (whitespace, comments)"""
lines = []
for line in diff.split('\n'):
# Skip pure whitespace changes
if line.strip() in ['', '+', '-']:
continue
# Skip comment-only changes
if re.match(r'^[+\-]\s*(//|#|/\*|\*)', line):
continue
lines.append(line)
return '\n'.join(lines)
9.2 Multiple Reviewers (หลาย Model)
ใช้หลาย model รีวิวพร้อมกันเพื่อเพิ่มความแม่นยำ:
# .gitlab-ci.yml - Multiple AI reviewers
# Reviewer 1: DeepSeek-Coder (Code focused)
ai_review_deepseek:
stage: quality
script:
- python scripts/ai-review.py --model deepseek-coder:6.7b --output ds_report.json
artifacts:
paths: [ds_report.json]
# Reviewer 2: CodeLlama (General purpose)
ai_review_codellama:
stage: quality
script:
- python scripts/ai-review.py --model codellama:7b --output cl_report.json
artifacts:
paths: [cl_report.json]
# Reviewer 3: Qwen-Coder (Fast and efficient)
ai_review_qwen:
stage: quality
script:
- python scripts/ai-review.py --model qwen2.5-coder:7b --output qw_report.json
artifacts:
paths: [qw_report.json]
# Aggregate results
aggregate_reviews:
stage: decision
script:
- python scripts/aggregate-reviews.py
--reports ds_report.json,cl_report.json,qw_report.json
--method consensus # or: average, best, worst
--output final_report.json
needs:
- ai_review_deepseek
- ai_review_codellama
- ai_review_qwen
Consensus Algorithm
เฉลี่ยคะแนนจากทุก model
ต้องเห็นชอบร่วมกัน (majority)
ใช้คะแนนสูงสุด
9.3 บันทึกประวัติการรีวิว
เก็บประวัติการรีวิวเพื่อวิเคราะห์แนวโน้มและปรับปรุง:
# review_history.py - Track review history
import json
import sqlite3
from datetime import datetime
from pathlib import Path
class ReviewHistory:
"""Track and analyze review history"""
def __init__(self, db_path: str = "reviews.db"):
self.conn = sqlite3.connect(db_path)
self._init_db()
def _init_db(self):
self.conn.execute("""
CREATE TABLE IF NOT EXISTS reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
commit_sha TEXT,
branch TEXT,
author TEXT,
review_type TEXT,
score INTEGER,
issues_count INTEGER,
passed BOOLEAN,
report_json TEXT
)
""")
self.conn.commit()
def save_review(self, result: dict, metadata: dict):
"""Save review result to history"""
self.conn.execute("""
INSERT INTO reviews
(timestamp, commit_sha, branch, author, review_type, score, issues_count, passed, report_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
datetime.now().isoformat(),
metadata.get('commit_sha'),
metadata.get('branch'),
metadata.get('author'),
result.get('review_type'),
result.get('score'),
len(result.get('issues', [])),
result.get('score', 0) >= 70,
json.dumps(result)
))
self.conn.commit()
def get_trend(self, days: int = 30) -> dict:
"""Get score trend over time"""
cursor = self.conn.execute("""
SELECT date(timestamp) as day, AVG(score) as avg_score, COUNT(*) as count
FROM reviews
WHERE timestamp >= date('now', ?)
GROUP BY date(timestamp)
ORDER BY day
""", (f'-{days} days',))
return {
'trend': [dict(zip(['day', 'avg_score', 'count'], row)) for row in cursor],
'overall_avg': self._get_overall_avg()
}
def get_common_issues(self, limit: int = 10) -> list:
"""Get most common issues"""
cursor = self.conn.execute("""
SELECT review_type, COUNT(*) as count
FROM reviews, json_each(report_json, '$.issues')
GROUP BY review_type
ORDER BY count DESC
LIMIT ?
""", (limit,))
return [dict(zip(['review_type', 'count'], row)) for row in cursor]
9.4 Merge Request Comments จาก AI
AI แสดงความคิดเห็นโดยตรงบนไฟล์ใน Merge Request:
# post_line_comments.py - Post comments on specific lines
import requests
import json
def post_line_comment(
gitlab_url: str,
token: str,
project_id: int,
mr_iid: int,
file_path: str,
line_number: int,
comment: str,
severity: str = "info"
):
"""Post a comment on a specific line in MR"""
# Severity emoji
emoji = {
"critical": "🔴",
"high": "🟠",
"medium": "🟡",
"low": "🟢",
"info": "ℹ️"
}.get(severity, "💬")
url = f"{gitlab_url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/discussions"
headers = {"PRIVATE-TOKEN": token}
payload = {
"body": f"{emoji} **AI Review Comment**\n\n{comment}",
"position": {
"base_sha": get_base_sha(project_id, mr_iid),
"head_sha": get_head_sha(project_id, mr_iid),
"start_sha": get_start_sha(project_id, mr_iid),
"position_type": "text",
"new_path": file_path,
"new_line": line_number
}
}
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
return response.json()
def post_review_summary_comment(
gitlab_url: str,
token: str,
project_id: int,
mr_iid: int,
report: dict
):
"""Post overall review summary as MR comment"""
passed = report.get('passed', False)
score = report.get('weighted_score', 0)
status = "✅ PASSED" if passed else "❌ NEEDS ATTENTION"
score_bar = generate_score_bar(score)
comment = f"""## 🤖 AI Code Review Summary
{status}
### Score: {score}/100
{score_bar}
### Reviews Completed:
"""
for review_type, summary in report.get('reports_summary', {}).items():
icon = "✅" if summary['score'] >= 70 else "⚠️"
comment += f"- {icon} **{review_type}**: {summary['score']}/100 ({summary['issues_count']} issues)\n"
if report.get('reasons'):
comment += "\n### ⚠️ Issues to Address:\n"
for reason in report['reasons']:
comment += f"- {reason}\n"
comment += "\n---\n*Powered by Ollama AI Code Reviewer*"
# Post the comment
url = f"{gitlab_url}/api/v4/projects/{project_id}/merge_requests/{mr_iid}/notes"
headers = {"PRIVATE-TOKEN": token}
requests.post(url, headers=headers, json={"body": comment})
def generate_score_bar(score: int) -> str:
"""Generate visual score bar"""
filled = int(score / 10)
empty = 10 - filled
color = "🟩" if score >= 70 else "🟨" if score >= 50 else "🟥"
return f"{color * filled}{'⬜' * empty}"
10 Troubleshooting & Tips
ปัญหาที่พบบ่อยและวิธีแก้ไข เคล็ดลับการใช้งาน
ปัญหา: Ollama Timeout
# เพิ่ม timeout ใน .gitlab-ci.yml
ai_review:
script:
- python scripts/ai-review.py --timeout 300 # 5 นาที
timeout: 10m # GitLab job timeout
# หรือแบ่งเป็นหลาย jobs
ai_review_batch_1:
script:
- python scripts/ai-review.py --files "src/*.py"
ai_review_batch_2:
script:
- python scripts/ai-review.py --files "tests/*.py"
ปัญหา: Large Codebase
# จำกัดขนาด code ที่ส่งให้ AI
MAX_CODE_CHARS = 10000 # ~10K characters
def truncate_code(code: str, max_chars: int = MAX_CODE_CHARS) -> str:
"""Truncate code if too long"""
if len(code) <= max_chars:
return code
# ตัดเป็นส่วนๆ และเก็บ important parts
lines = code.split('\n')
important_lines = []
for line in lines:
# เก็บ function definitions, class definitions
if any(kw in line for kw in ['def ', 'class ', 'async def ', 'function ']):
important_lines.append(line)
# เก็บบรรทัดที่เปลี่ยนแปลง (จาก diff)
elif line.startswith('+') or line.startswith('-'):
important_lines.append(line)
result = '\n'.join(important_lines)
if len(result) > max_chars:
result = result[:max_chars] + "\n... [truncated]"
return result
Tips: Cost Optimization
-
ใช้ model เล็ก: deepseek-coder:6.7b เร็วกว่า codellama:34b 5-10 เท่า
-
รีวิวเฉพาะไฟล์ที่เปลี่ยน: ใช้
git diffแทนไฟล์ทั้งหมด -
Cache model: Ollama จะ cache model ในหน่วยความจำ อย่า restart บ่อย
-
Batch processing: รวมหลายไฟล์ในการเรียก API ครั้งเดียว
-
Skip ไฟล์ไม่จำเป็น: config, lock files, generated code
Model Selection Guide
| Use Case | Recommended Model | Why |
|---|---|---|
| General Code Review | deepseek-coder:6.7b |
ดีที่สุดเมื่อเทียบกับขนาด |
| Security Review | codellama:13b |
เข้าใจ security patterns ดี |
| Fast Feedback | qwen2.5-coder:1.5b |
เร็วมาก สำหรับการตรวจเบื้องต้น |
| Enterprise/Production | deepseek-coder:33b |
แม่นยำที่สุด ต้องการ GPU 24GB+ |
| Multi-language Projects | codellama:7b |
รองรับหลายภาษา |
Debug Commands
curl http://localhost:11434/api/tags | jq
ollama run deepseek-coder:6.7b "Review: def foo(): pass"
nvidia-smi -l 1
journalctl -u ollama -f
📚 สรุป
สิ่งที่คุณได้เรียนรู้:
- ติดตั้งและกำหนดค่า Ollama สำหรับ AI Code Review
- สร้าง GitLab CI/CD Pipeline สำหรับ automated review
- เขียน Python script สำหรับเรียก Ollama API
- Prompt Engineering สำหรับ code review
- กำหนด thresholds และ pass/fail rules
- Advanced features และ troubleshooting
ขั้นตอนถัดไป:
- ทดลองใช้กับโปรเจคจริงของคุณ
- ปรับแต่ง prompts ให้เหมาะกับทีม
- ตั้งค่า thresholds ตามมาตรฐานขององค์กร
- วิเคราะห์ review history เพื่อปรับปรุง
Tip: เริ่มต้นด้วย mode "advisory" ก่อน ไม่ block MR แล้วค่อยๆ เปลี่ยนเป็น "enforcing" เมื่อทีมเริ่มคุ้นเคย