AI Agents · บทเรียน

ความคิดเห็นตรวจสอบ PR อัตโนมัติ

อ่านส่วนต่างของ PR และโพสต์ความคิดเห็นตรวจสอบแบบแทรกบรรทัดผ่าน API

บทเรียน 3 จาก 413 ขั้นตอน

ความคิดเห็นตรวจสอบ PR อัตโนมัติ เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

การตรวจสอบ PR แบบอัตโนมัติคืออะไร

เอเจนต์ตรวจสอบ PR แบบอัตโนมัติจะอ่านไฟล์ที่เปลี่ยนแปลง วิเคราะห์ส่วนต่าง และโพสต์ความคิดเห็นตรวจสอบแบบเจาะจง เช่นเดียวกับผู้ตรวจสอบที่เป็นมนุษย์ แต่เร็วกว่าและสม่ำเสมอกว่า กรณีใช้งาน ได้แก่ การตรวจสอบความปลอดภัย การบังคับใช้รูปแบบ การตรวจหาข้อบกพร่องทั่วไป การตรวจสอบความครอบคลุมของการทดสอบ หรือการสรุปว่า PR ทำอะไร

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# Get a specific pull request by number
pr = repo.get_pull(42)
print(f'PR #{pr.number}: {pr.title}')
print(f'Author: {pr.user.login}')
print(f'Base branch: {pr.base.ref} <- Head: {pr.head.ref}')
print(f'State: {pr.state}')
print(f'Changed files: {pr.changed_files}')
print(f'Additions: +{pr.additions}')
print(f'Deletions: -{pr.deletions}')

การแสดงรายการ PR ที่เปิดอยู่

ใช้ repo.get_pulls() เพื่อแสดงรายการ PR กรองตามสถานะ (open closed) สาขาฐาน หรือลำดับการเรียง สำหรับบอตตรวจสอบ โดยทั่วไปคุณจะประมวลผล PR ที่มีสถานะ open และยังไม่ได้รับการตรวจสอบ

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')

# All open PRs targeting main
open_prs = repo.get_pulls(
    state='open',
    base='main',
    sort='created',
    direction='desc'
)

for pr in open_prs:
    # Check if agent has already reviewed it
    reviews = list(pr.get_reviews())
    agent_reviewed = any(
        r.user.login == 'my-agent-bot[bot]'
        for r in reviews
    )
    if not agent_reviewed:
        print(f'Needs review: PR #{pr.number} - {pr.title}')

การรับไฟล์ที่เปลี่ยนแปลง

pr.get_files() ส่งคืนรายการออบเจ็กต์ File โดยแต่ละรายการแทนไฟล์ที่เปลี่ยนแปลงใน PR คุณสมบัติสำคัญ ได้แก่ filename status (added/modified/removed) additions deletions และ patch (ข้อความส่วนต่างแบบรวม)

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

changed_files = list(pr.get_files())
print(f'Changed files: {len(changed_files)}')

for file in changed_files:
    print(f'\n{file.status.upper()}: {file.filename}')
    print(f'  +{file.additions} -{file.deletions}')

    # The patch is the unified diff
    if file.patch:
        print(f'  Patch preview: {file.patch[:200]}')

# Filter to only Python files
python_files = [
    f for f in changed_files
    if f.filename.endswith('.py') and f.status != 'removed'
]

การอ่านแพตช์ไฟล์ (ส่วนต่าง)

file.patch เป็นสตริงส่วนต่างแบบรวม บรรทัดที่ขึ้นต้นด้วย + คือบรรทัดที่เพิ่มเข้ามา ส่วนบรรทัดที่ขึ้นต้นด้วย - คือบรรทัดที่ถูกนำออก ส่วนหัว @@ แสดงหมายเลขบรรทัดที่ส่วนย่อยเริ่มต้น นำข้อมูลนี้ไปวิเคราะห์เพื่อระบุตำแหน่งที่มีปัญหาในโค้ดใหม่ได้อย่างแม่นยำ

def parse_patch_hunks(patch):
    if not patch:
        return []

    hunks = []
    current_hunk = None
    new_line_num = 0

    for line in patch.split('\n'):
        if line.startswith('@@'):
            # Parse: @@ -old_start,old_count +new_start,new_count @@
            import re
            match = re.search(r'\+([0-9]+)', line)
            if match:
                new_line_num = int(match.group(1))
            current_hunk = {'header': line, 'lines': [], 'start_line': new_line_num}
            hunks.append(current_hunk)
        elif current_hunk is not None:
            if line.startswith('+'):
                current_hunk['lines'].append({'type': 'add', 'content': line[1:], 'line': new_line_num})
                new_line_num += 1
            elif line.startswith('-'):
                current_hunk['lines'].append({'type': 'remove', 'content': line[1:], 'line': None})
            else:
                new_line_num += 1

    return hunks

# --- demo ---
patch = (
    '@@ -10,3 +10,4 @@\n'
    ' def add(a, b):\n'
    '-    return a - b\n'
    '+    return a + b\n'
    '+    # fixed subtraction bug\n'
)
for hunk in parse_patch_hunks(patch):
    print(hunk['header'])
    for line in hunk['lines']:
        print(' ', line)

การรับคอมมิตของ PR

pr.get_commits() ส่งคืนคอมมิตใน PR แต่ละคอมมิตมีข้อความ ผู้เขียน และรายการไฟล์ที่เปลี่ยนแปลง การวิเคราะห์ข้อความคอมมิตช่วยให้บอตตรวจสอบสรุปได้ว่า PR ทำอะไร และตรวจสอบแนวปฏิบัติที่ดีในการคอมมิต

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

commits = list(pr.get_commits())
print(f'Commits in PR: {len(commits)}')

for commit in commits:
    print(f'\nCommit: {commit.sha[:8]}')
    print(f'Author: {commit.commit.author.name}')
    print(f'Message: {commit.commit.message[:80]}')
    print(f'Changes: +{commit.stats.additions} -{commit.stats.deletions}')

# Check commit message quality
poor_messages = [
    c for c in commits
    if len(c.commit.message.split()[0] if c.commit.message else '') < 3
    or c.commit.message.lower().startswith(('wip', 'fix', 'test'))
]
if poor_messages:
    print(f'{len(poor_messages)} commits have poor message quality')

การโพสต์รีวิว PR

ใช้ pr.create_review() เพื่อส่งรีวิวอย่างเป็นทางการ พารามิเตอร์ event ควบคุมประเภทรีวิว ได้แก่ 'APPROVE' 'REQUEST_CHANGES' หรือ 'COMMENT' รีวิวสามารถมีเนื้อหาทั่วไปและรายการความคิดเห็นแทรกในบรรทัดของบรรทัดที่ระบุได้

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

# Submit a review with a general comment (no inline comments)
pr.create_review(
    body=(
        '**Automated Code Review**\n\n'
        'I analyzed this PR and found the following:\n'
        '- No security issues detected\n'
        '- Style conforms to project guidelines\n'
        '- Test coverage looks adequate\n\n'
        '_This review was generated automatically._'
    ),
    event='COMMENT'  # 'APPROVE', 'REQUEST_CHANGES', or 'COMMENT'
)

การโพสต์ความคิดเห็นรีวิวแทรกในบรรทัด

ความคิดเห็นแทรกในบรรทัดจะแสดงโดยตรงบนบรรทัดที่ระบุในส่วนต่าง ความคิดเห็นแต่ละรายการระบุ path (เส้นทางไฟล์) line (หมายเลขบรรทัดในไฟล์ใหม่) และ body ส่งรายการความคิดเห็นไปยัง pr.create_review(comments=[...])

from github import Github
import os

g = Github(token=os.environ['GITHUB_TOKEN'])
repo = g.get_repo('myorg/myrepo')
pr = repo.get_pull(42)

# Build inline comments from analysis results
inline_comments = [
    {
        'path': 'src/auth.py',
        'line': 47,
        'body': (
            ':warning: **Security Issue:** Using `md5` for password hashing.\n'
            'Use `bcrypt` or `argon2` instead.\n'
            'Example: `import bcrypt; bcrypt.hashpw(password.encode(), bcrypt.gensalt())`'
        )
    },
    {
        'path': 'src/utils.py',
        'line': 23,
        'body': (
            ':bulb: Consider using `pathlib.Path` instead of `os.path.join` '
            'for better cross-platform compatibility.'
        )
    }
]

pr.create_review(
    body='Security and style review complete. See inline comments.',
    event='REQUEST_CHANGES',
    comments=inline_comments
)
print(f'Review submitted with {len(inline_comments)} inline comments')

การวิเคราะห์โค้ดด้วย LLM

ป้อนส่วนต่างของไฟล์ให้ LLM พร้อมคำสั่งสำหรับตรวจสอบความปลอดภัย/คุณภาพ ขอการตอบกลับ JSON แบบมีโครงสร้างที่แสดงรายการปัญหา พร้อมเส้นทางไฟล์ หมายเลขบรรทัด ระดับความรุนแรง และคำอธิบาย ใช้ข้อมูลนี้สร้างรายการความคิดเห็นแทรกในบรรทัด

import anthropic
import json
import os

client = anthropic.Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])

def analyze_file_with_llm(filename, patch):
    prompt = (
        f'Review this Python code diff for issues.\n'
        f'File: {filename}\n\n'
        f'Return JSON array: [{{"line": N, "severity": "error|warning|info", '
        f'"message": "..."}}]\n'
        f'Focus on: security vulnerabilities, bugs, and anti-patterns.\n'
        f'Diff:\n{patch[:3000]}'
    )

    response = client.messages.create(
        model='claude-opus-4-5',
        max_tokens=1000,
        messages=[{'role': 'user', 'content': prompt}]
    )

    try:
        issues = json.loads(response.content[0].text)
        return [
            {'path': filename, 'line': i.get('line', 1),
             'body': f'[{i.get("severity", "info").upper()}] {i.get("message", "")}'}
            for i in issues if isinstance(i, dict)
        ]
    except json.JSONDecodeError:
        return []

ลำดับการทำงานของเอเจนต์ตรวจสอบ PR แบบครบถ้วน

เชื่อมทุกส่วนเข้าด้วยกันเป็นลำดับการตรวจสอบ PR ที่สมบูรณ์: ดึงไฟล์ กรองเฉพาะไฟล์ที่ตรวจสอบได้ วิเคราะห์แต่ละไฟล์ด้วย LLM รวบรวมความคิดเห็น และส่งรีวิวด้วยการเรียก API เพียงครั้งเดียว จำกัดไว้ที่ไฟล์ที่เปลี่ยนแปลง 10 ไฟล์แรกเพื่อควบคุมค่าใช้จ่ายของ LLM

def run_pr_review(g, repo_name, pr_number):
    repo = g.get_repo(repo_name)
    pr = repo.get_pull(pr_number)

    print(f'Reviewing PR #{pr_number}: {pr.title}')

    # Get changed files (limit to 10)
    files = [
        f for f in list(pr.get_files())[:10]
        if f.filename.endswith(('.py', '.js', '.ts'))
        and f.patch
    ]

    all_comments = []
    for file in files:
        print(f'  Analyzing: {file.filename}')
        comments = analyze_file_with_llm(file.filename, file.patch)
        all_comments.extend(comments)

    # Determine review event based on findings
    critical = [c for c in all_comments if '[ERROR]' in c['body']]
    event = 'REQUEST_CHANGES' if critical else 'COMMENT'

    summary = (
        f'Analyzed {len(files)} files. '
        f'Found {len(all_comments)} issues '
        f'({len(critical)} critical).'
    )

    pr.create_review(body=summary, event=event, comments=all_comments[:25])
    print(f'Review submitted: {event} with {len(all_comments)} comments')

การตรวจสอบรีวิวของเอเจนต์ที่มีอยู่

ก่อนโพสต์รีวิว ให้ตรวจสอบว่าบอตเอเจนต์ของคุณได้รีวิว PR นี้ในสถานะปัจจุบันแล้วหรือไม่ ป้องกันรีวิวซ้ำโดยตรวจสอบรายการรีวิวที่มีอยู่เพื่อค้นหาชื่อผู้ใช้ของบอต

def should_review_pr(pr, bot_username):
    reviews = list(pr.get_reviews())

    # Check if bot already reviewed this PR
    bot_reviews = [
        r for r in reviews
        if r.user and r.user.login == bot_username
    ]

    if not bot_reviews:
        return True  # No previous review, proceed

    # Check if new commits were pushed since last review
    last_review_time = max(r.submitted_at for r in bot_reviews)
    commits = list(pr.get_commits())
    latest_commit_time = commits[-1].commit.author.date if commits else None

    if latest_commit_time and latest_commit_time > last_review_time:
        print(f'New commits since last review, re-reviewing...')
        return True

    print(f'PR #{pr.number} already reviewed by {bot_username}')
    return False

# --- demo: minimal stand-ins for PyGithub-style PR/review/commit objects ---
import datetime
from types import SimpleNamespace

class _FakeCommit:
    def __init__(self, date):
        self.commit = SimpleNamespace(author=SimpleNamespace(date=date))

class _FakeReview:
    def __init__(self, login, submitted_at):
        self.user = SimpleNamespace(login=login)
        self.submitted_at = submitted_at

class _FakePR:
    def __init__(self, number, reviews, commits):
        self.number = number
        self._reviews = reviews
        self._commits = commits
    def get_reviews(self):
        return self._reviews
    def get_commits(self):
        return self._commits

pr_never_reviewed = _FakePR(101, [], [_FakeCommit(datetime.datetime(2026, 8, 1))])
print('PR #101 needs review:', should_review_pr(pr_never_reviewed, 'coddy-bot'))

review_time = datetime.datetime(2026, 8, 1, 10, 0)
pr_up_to_date = _FakePR(102, [_FakeReview('coddy-bot', review_time)],
                        [_FakeCommit(review_time - datetime.timedelta(hours=1))])
print('PR #102 needs review:', should_review_pr(pr_up_to_date, 'coddy-bot'))

รูปแบบการสแกนความปลอดภัยในการรีวิว PR

รูปแบบการรีวิวอัตโนมัติที่ใช้กันทั่วไปคือการสแกนบรรทัดที่เพิ่มเข้ามาเพื่อค้นหารูปแบบการเขียนโค้ดที่ไม่ปลอดภัย เช่น ข้อมูลลับที่ฝังไว้ตายตัว การใช้ฟังก์ชันที่เลิกใช้แล้ว การต่อสตริง SQL หรือการเรียก eval() ตรวจสอบแต่ละบรรทัดที่เพิ่มเข้ามาเทียบกับชุดรูปแบบ แล้วทำเครื่องหมายสิ่งที่พบเป็นความคิดเห็นในบรรทัด

import re

SECURITY_PATTERNS = [
    (r'eval\s*\(', 'Dangerous eval() call — code injection risk'),
    (r'os\.system\s*\(', 'os.system() is unsafe; use subprocess with a list'),
    (r'pickle\.loads?\s*\(', 'pickle.load is unsafe with untrusted data'),
    (r'execute\(.*%\s*', 'Possible SQL injection — use parameterized queries'),
    (r'md5\s*\(|hashlib\.md5', 'MD5 is cryptographically broken for passwords; use bcrypt'),
]

def scan_patch_for_security(filename, patch):
    if not patch:
        return []
    comments = []
    new_line = 0
    for line in patch.split('\n'):
        if line.startswith('@@'):
            import re as _re
            m = _re.search(r'\+([0-9]+)', line)
            if m:
                new_line = int(m.group(1))
        elif line.startswith('+') and not line.startswith('+++'):
            content = line[1:]
            for pattern, message in SECURITY_PATTERNS:
                if re.search(pattern, content):
                    comments.append({'path': filename, 'line': new_line,
                                     'body': f':rotating_light: **Security:** {message}'})
            new_line += 1
        elif not line.startswith('-'):
            new_line += 1
    return comments

# --- demo ---
patch = (
    '@@ -1,2 +1,3 @@\n'
    ' def run(cmd):\n'
    '+    os.system(cmd)\n'
    '+    hashlib.md5(cmd.encode())\n'
)
for c in scan_patch_for_security('run.py', patch):
    print(c)

ตรวจสอบอย่างรวดเร็ว: เหตุการณ์ในการรีวิว

ทดสอบความเข้าใจเกี่ยวกับตัวเลือกการรีวิว PR

สรุปการรีวิว PR อัตโนมัติ

ตอนนี้เอเจนต์ของคุณสามารถรีวิวคำขอรวมโค้ดด้วยโปรแกรมได้แล้ว:

  • repo.get_pull(number) — รับอ็อบเจ็กต์ PR พร้อมข้อมูลเมตา
  • pr.get_files() — แสดงรายการไฟล์ที่เปลี่ยนแปลง พร้อม filename, patch, additions
  • pr.get_commits() — ตรวจสอบข้อความและสถิติของการคอมมิต
  • pr.create_review(body, event, comments) — ส่งรีวิวพร้อมความคิดเห็นในบรรทัดที่เลือกใส่ได้
  • รูปแบบความคิดเห็นในบรรทัด: {'path': filename, 'line': N, 'body': markdown}
  • ใช้ 'COMMENT' สำหรับข้อสังเกต และ 'REQUEST_CHANGES' สำหรับปัญหาที่ต้องแก้ไขก่อน
  • ตรวจสอบรีวิวที่มีอยู่ก่อนรีวิวซ้ำ เพื่อหลีกเลี่ยงรายการซ้ำ
  • จำกัดการวิเคราะห์ไว้ที่ 10 ไฟล์และความคิดเห็นในบรรทัด 25 รายการ เพื่อให้อยู่ภายในขีดจำกัดของ API
เริ่มต้นได้ฟรี

เรียนรู้ AI Agents ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
60
บทเรียน
239

คำถามที่พบบ่อย

บทเรียน “ความคิดเห็นตรวจสอบ PR อัตโนมัติ” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ความคิดเห็นตรวจสอบ PR อัตโนมัติ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ความคิดเห็นตรวจสอบ PR อัตโนมัติ”

อ่านส่วนต่างของ PR และโพสต์ความคิดเห็นตรวจสอบแบบแทรกบรรทัดผ่าน API คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “ความคิดเห็นตรวจสอบ PR อัตโนมัติ” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม

ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ภาพรวม GitHub REST API
  2. การแสดงรายการและจัดการประเด็น
  3. ความคิดเห็นตรวจสอบ PR อัตโนมัติ
  4. ประวัติการคอมมิตและการวิเคราะห์ส่วนต่าง
← กลับไปที่ AI Agents