SSD Nodes Learn 🎉 VPS $5.50/महिन्यापासून
मार्गदर्शक Matt Connorद्वारे Matt Connor · अपडेटेड 2026-08-13

VPS वर स्वतःचा AI PR Review Agent कसा सेट करावा

तुमच्या स्वतःच्या VPS वर AI PR रिव्ह्यूअर सेट करण्याची संपूर्ण प्रक्रिया जाणून घ्या. यात सेल्फ-होस्टेड रनर, डिफ-स्कोप प्रॉम्प्ट्स, पाथ फिल्टर्स आणि इनलाइन कमेंट्सचा समावेश आहे.

Self-hosted PR review agent काय करते

Self-hosted PR review agent हा तुमच्या मालकीच्या सर्व्हरवर चालणारा एक छोटा प्रोग्राम आहे. तो pull request (PR) मधील diff वाचतो आणि फक्त बदललेल्या ओळी मॉडेलकडे पाठवतो. त्यातून आलेला प्रतिसाद inline review comments म्हणून पोस्ट केला जातो. हा एजंट तुमची branch कधीही checkout करत नाही आणि pull request मध्ये समाविष्ट नसलेली कोणतीही फाईल वाचत नाही. त्याच्याकडे फक्त एक मॉडेल API (application programming interface) की आणि एक टोकन असते, ज्याचा वापर फक्त कमेंट करण्यासाठी केला जाऊ शकतो; त्याशिवाय त्याला इतर कोणतेही अधिकार नसतात.

मॉडेल diff वाचू शकते. हा भाग पूर्ण झाला आहे. महत्त्वाचे हे आहे की diff कुठे जातो आणि की (key) कोणाकडे असते. Hosted review bot वापरल्यास, प्रत्येक खाजगी रिपॉझिटरीमधील diff तुमच्या नेटवर्कबाहेर जातो, तिसऱ्या पक्षाच्या लॉगमध्ये साठवला जातो आणि त्यांच्या retention policy अंतर्गत राहतो. तुमच्या मालकीच्या VPS (virtual private server) वर, diff GitHub कडून तुमच्या सर्व्हरवर आणि तिथून मॉडेल API कडे जातो. काय पाठवायचे हे ठरवणाऱ्या चाळीस ओळींचा कोड तुम्ही स्वतः वाचू शकता.

सुरुवात करण्यापूर्वी आवश्यक गोष्टी

  • Ubuntu 24.04 वर चालणारा एक VPS, ज्यावर self-hosted GitHub Actions runner आधीच रिपॉझिटरीसाठी नोंदणीकृत आहे. नोंदणी करताना त्याला pr-review हे अतिरिक्त लेबल द्या, कारण खालील वर्कफ्लो त्या लेबलवर आधारित निवड करते.
  • Claude Console कडून मिळालेली एक Anthropic API key.
  • अशी रिपॉझिटरी जिथे पुल रिक्वेस्ट (pull request) कोण उघडू शकते यावर तुमचे नियंत्रण आहे. खाजगी (private) रिपॉझिटरीसाठी हे काम सोपे आहे. खालील फोर्क (fork) विभागामध्ये सार्वजनिक (public) रिपॉझिटरीबद्दल माहिती दिली आहे, परंतु त्यातील उपाय थोडा क्लिष्ट आहे.

VPS वर reviewer इंस्टॉल करा

runner सेवा तुम्ही ./svc.sh install रन करताना तयार केलेल्या unprivileged अकाउंटद्वारे चालते. reviewer ला त्याच अकाउंट अंतर्गत इंस्टॉल करा, जेणेकरून job ला sudo शिवाय तो रन करता येईल. खालील runner च्या जागी तुमच्या अकाउंटचे नाव लिहा.

sudo apt update && sudo apt install -y gh python3-venv
sudo install -d -m 755 -o runner -g runner /opt/pr-review
sudo -u runner python3 -m venv /opt/pr-review/venv
sudo -u runner /opt/pr-review/venv/bin/pip install anthropic
gh --version

ऑगस्ट 2026 पर्यंत Ubuntu 24.04 वर gh --version हे gh version 2.45.0 प्रिंट करते. 2.20 नंतरच्या कोणत्याही रिलीजमध्ये खाली वापरलेला --input फ्लॅग उपलब्ध आहे. Command 'gh' not found संदेशाचा अर्थ असा आहे की universe घटक सक्षम (enabled) नाही, म्हणून sudo add-apt-repository universe रन करा आणि पुन्हा प्रयत्न करा.

की आणि टोकन कोठे साठवले जातात

दोन गुपिते (secrets), दोन भिन्न कालावधी. यापैकी कोणतेही रिपॉझिटरीमध्ये ठेवू नका.

ANTHROPIC_API_KEY हे एक रिपॉझिटरी सीक्रेट आहे, जे Settings, त्यानंतर Secrets and variables आणि मग Actions अंतर्गत सेट केले जाते. GitHub ते एनक्रिप्ट करते आणि रन-टाइम दरम्यान स्टेपच्या एन्वायरमेंटमध्ये इंजेक्ट करते. ही फाईल कधीही डिस्कवर नसते आणि git हिस्ट्रीमध्येही नसते.

GITHUB_TOKEN वेगळ्या पद्धतीने काम करते. Actions प्रत्येक जॉबसाठी एक नवीन टोकन तयार करते आणि जॉब संपल्यावर ते नष्ट करते. ते टोकन काय करू शकते हे वर्कफ्लोमधील permissions: ब्लॉकद्वारे ठरवले जाते, त्यामुळे 'least privilege' (किमान अधिकार) तत्त्व येथेच प्रत्यक्षात येते:

permissions:
  contents: read
  pull-requests: write

ते टोकन रिव्ह्यू पोस्ट करू शकते. ते कमिट पुश करू शकत नाही, ब्रांच मर्ज करू शकत नाही, वर्कफ्लो फाईल एडिट करू शकत नाही किंवा दुसऱ्या रिपॉझिटरीला स्पर्श करू शकत नाही. जो एजंट कमेंट करू शकतो तो रिव्ह्यूअर असतो. जो एजंट पुश करू शकतो तो कमिटर असतो, आणि तसे करण्यास कोणीही सहमती दिलेली नाही. मॉडेल की (model key) हाताळतानाही तितकीच काळजी घ्या, कारण ती तुमच्या खात्यातून पैसे खर्च करते. या प्रकारच्या समस्येबद्दल अधिक माहिती keeping secrets out of an AI agent's reach मध्ये दिली आहे.

Actions जॉब लॉगमध्ये मूळ सीक्रेट स्ट्रिंगच्या जागी *** दर्शवते. हे फक्त तंतोतंत जुळणाऱ्या स्ट्रिंगसाठीच काम करते, त्यामुळे जर तुम्ही की base64 एनकोड केली, दोन ओळींत विभागली किंवा एका वेळी एक अक्षर प्रिंट केले, तर ती स्पष्टपणे (clear text) दिसेल. एन्वायरमेंट डंप करणारी कोणतीही डीबग स्टेप जोडू नका.

फोर्क (fork) कडून आलेली पुल रिक्वेस्ट तुमची API key का पाहू शकत नाही

GitHub चा नियम स्पष्ट आहे: GITHUB_TOKEN चा अपवाद वगळता, जेव्हा एखादा वर्कफ्लो फोर्क केलेल्या रिपॉझिटरीमधून ट्रिगर होतो, तेव्हा सीक्रेट्स रनरकडे पाठवले जात नाहीत. त्यामुळे फोर्कवरून चालवलेला pull_request रन तुमच्या स्क्रिप्टला कोणत्याही ANTHROPIC_API_KEY शिवाय सुरू करतो आणि पहिली API कॉल invalid x-api-key मुळे अयशस्वी होते.

यावर एक सोपा उपाय म्हणजे ट्रिगर pull_request_target वर बदलणे, जो बेस रिपॉझिटरीच्या संदर्भात चालतो आणि त्याला सीक्रेट्स मिळतात. मात्र, येथे असे करू नका. GitHub च्या स्वतःच्या सुरक्षा मार्गदर्शक तत्त्वांनुसार, असे वर्कफ्लो "प्रिव्हिलेज्ड (privileged) असतात, याचा अर्थ ते मुख्य शाखेचा कॅशे इतर प्रिव्हिलेज्ड वर्कफ्लो ट्रिगर्ससोबत शेअर करतात आणि त्यांना रिपॉझिटरीमध्ये लिहिण्याचा (write access) आणि संदर्भित सीक्रेट्सचा ॲक्सेस असू शकतो", आणि याचा परिणाम "रिपॉझिटरीचा ताबा मिळवण्यासाठी वापरला जाऊ शकतो".

रनरबद्दलही हीच मार्गदर्शक तत्त्वे स्पष्ट आहेत: "GitHub वरील सार्वजनिक रिपॉझिटरीजसाठी सेल्फ-होस्टेड रनर्सचा वापर जवळजवळ कधीही करू नये, कारण कोणताही वापरकर्ता रिपॉझिटरीवर पुल रिक्वेस्ट उघडून वातावरणाशी (environment) तडजोड करू शकतो."

यामुळे दोन डिझाइन निर्णय घेतले जातात. जॉबमध्ये एक गार्ड असतो, त्यामुळे तो फक्त तुमच्या स्वतःच्या रिपॉझिटरीमध्ये पुश केलेल्या शाखांवरच चालतो. आणि वर्कफ्लोमध्ये कोणताही actions/checkout टप्पा नसतो. एजंटकडे ती शाखा कधीही डिस्कवर नसते, त्यामुळे एक घातक पुल रिक्वेस्ट म्हणजे फक्त मजकूर असतो जो मॉडेलकडे पाठवला जातो. तो तुमच्या VPS वर कोणतीही बिल्ड स्क्रिप्ट चालवू शकत नाही, कारण तुमच्या VPS वर काहीही ते चालवत नाही. मात्र, मजकूर म्हणजे निरुपद्रवी असे नाही: अनोळखी व्यक्तीने लिहिलेली diff ही मॉडेलकडे येणारी एक अविश्वसनीय इनपुट असते, ही तीच विश्वासाची सीमा (trust boundary) आहे जी तुम्हाला एजंटला वेब सर्च करण्याची परवानगी देताना आढळते, आणि येथे ती मर्यादित ठेवण्याचा एकमेव मार्ग म्हणजे हा एजंट फक्त कमेंट पोस्ट करण्यापलीकडे काहीही करू शकत नाही.

संपूर्ण रिपॉझिटरीऐवजी फक्त diff मिळवा

एका विनंतीद्वारे तुम्ही संपूर्ण diff प्लेन टेक्स्ट स्वरूपात मिळवू शकता.

export GH_TOKEN=your_token   # in the workflow this comes from secrets.GITHUB_TOKEN
gh api /repos/OWNER/REPO/pulls/42 -H "Accept: application/vnd.github.diff"

Accept: application/vnd.github.diff मीडिया प्रकारामुळे रिस्पॉन्सचे रूपांतर पुल रिक्वेस्टचे वर्णन करणाऱ्या JSON ऑब्जेक्टमधून थेट युनिफाइड diff मध्ये होते आणि gh api त्या बॉडीला कोणत्याही बदलाशिवाय प्रिंट करते. तुम्हाला दिसणारी पहिली ओळ diff --git a/ ने सुरू झाली पाहिजे. gh: Not Found (HTTP 404) चा अर्थ असा आहे की टोकनला रिपॉझिटरी पाहण्याची परवानगी नाही, ज्याचा अर्थ फाइन-ग्रेन्ड पर्सनल टोकनच्या बाबतीत सहसा असा होतो की Pull requests ही परवानगी निवडलेली नाही.

टोकन खर्च करण्यापूर्वी फिल्टर करा

हा विभाग अशा बॉटमध्ये आणि ज्याला लोक म्यूट करतात अशा बॉटमध्ये फरक करतो. खालील प्रत्येक फिल्टर मॉडेलला एकही बाइट मिळण्यापूर्वी कार्यान्वित होतो.

  • Path filters. लॉक फाइल्स, vendored डिरेक्टरीज, minified बंडल्स आणि जनरेट केलेला कोड वगळा. package-lock.json वरील मॉडेलची टिप्पणी केवळ गोंधळ निर्माण करते आणि अशा फाइल्स diff मधील सर्वाधिक बाइट्स व्यापतात.
  • A size cap. मर्यादेपेक्षा जास्त आकार असल्यास, रिव्ह्यू वगळा आणि यशस्वी (exit green) म्हणून बाहेर पडा. 4,000 ओळींच्या रिफॅक्टरसाठी साठ चुकीचे अंदाज देण्याऐवजी, तो रिव्ह्यू करण्यासाठी खूप मोठा आहे अशी एक स्पष्ट ओळ द्या.
  • A severity threshold and a comment cap. उच्च आणि मध्यम तीव्रतेचे निष्कर्ष कळवा, जास्तीत जास्त दहा, ज्यामध्ये सर्वाधिक तीव्रतेचे निष्कर्ष आधी असतील. अकरावी टिप्पणी कोणीही वाचत नाही.

स्क्रिप्ट

हे /opt/pr-review/review.py म्हणून सेव्ह करा. ही स्क्रिप्ट तिचे कॉन्फिगरेशन environment मधून वाचते, त्यामुळे कोडमध्ये बदल न करता वर्कफ्लोमध्ये मॉडेल्स बदलता येतात.

#!/usr/bin/env python3
"""Review only the changed lines of one pull request."""
import json
import os
import subprocess
import sys

import anthropic

REPO = os.environ["GITHUB_REPOSITORY"]
PR = os.environ["PR_NUMBER"]
MODEL = os.environ.get("REVIEW_MODEL", "claude-haiku-4-5-20251001")
MAX_DIFF_BYTES = int(os.environ.get("MAX_DIFF_BYTES", "120000"))
MIN_SEVERITY = os.environ.get("MIN_SEVERITY", "medium")
MAX_COMMENTS = 10
RANK = {"low": 0, "medium": 1, "high": 2}
SKIP = ("package-lock.json", "poetry.lock", "/vendor/", "/node_modules/", ".min.js")

raw_diff = subprocess.run(
    ["gh", "api", f"/repos/{REPO}/pulls/{PR}",
     "-H", "Accept: application/vnd.github.diff"],
    check=True, capture_output=True, text=True,
).stdout

प्रत्येक फाईलनुसार diff वेगळे केल्यामुळेच path filtering शक्य होते. प्रत्येक ओळीला क्रमांक दिल्यानेच रिव्ह्यू कमेंट्स योग्य ठिकाणी पोहोचतात. GitHub केवळ diff चा भाग असलेल्या ओळीवरच इनलाइन कमेंट स्वीकारते, त्यामुळे मॉडेलला प्रत्यक्ष ओळ क्रमांक देणे आवश्यक असते. त्याला क्रमांक दिल्यास, तो स्वतःचे क्रमांक तयार करण्याऐवजी दिलेला क्रमांक वापरू शकतो.

def per_file(diff_text):
    """Split a unified diff into one string per file."""
    sections, current = [], []
    for line in diff_text.splitlines():
        if line.startswith("diff --git ") and current:
            sections.append("\n".join(current))
            current = []
        current.append(line)
    if current:
        sections.append("\n".join(current))
    return sections


def annotate(section):
    """Prefix every line that exists in the new file with its line number."""
    out, n, in_hunk = [], 0, False
    for line in section.splitlines():
        if line.startswith("@@"):
            n = int(line.split("+")[1].split(",")[0].split(" ")[0])
            in_hunk = True
            out.append(line)
        elif not in_hunk or line.startswith(("-", "\\")):
            out.append(line)
        else:
            out.append(f"{n}\t{line}")
            n += 1
    return "\n".join(out)


kept = [s for s in per_file(raw_diff)
        if not any(p in s.split("\n", 1)[0] for p in SKIP)]
payload = "\n".join(annotate(s) for s in kept)

if not payload.strip():
    print("every changed file was filtered out")
    raise SystemExit(0)
if len(payload) > MAX_DIFF_BYTES:
    print(f"diff is {len(payload)} bytes, over the {MAX_DIFF_BYTES} cap")
    raise SystemExit(0)

hunk header मध्ये क्रमांकाची माहिती असते. @@ -12,7 +12,9 @@ सांगते की नवीन फाईलचा hunk ओळ 12 पासून सुरू होतो, म्हणून काउंटर तिथून सुरू होतो आणि केवळ नवीन जोडलेल्या किंवा न बदललेल्या ओळींवर पुढे जातो. काढून टाकलेल्या ओळींना क्रमांक दिला जात नाही, कारण त्या नवीन फाईलमध्ये अस्तित्वात नसतात. बॅकस्लॅशने सुरू होणाऱ्या ओळींवरील गार्ड git ने फाईलच्या शेवटी लिहिलेला no-newline मार्कर वगळतो, अन्यथा तो पुढील प्रत्येक क्रमांक एकाने पुढे ढकलला असता.

दोन्ही exits स्टेटस 0 वापरतात, 1 नाही. फिल्टर केलेले किंवा मर्यादेपेक्षा मोठे pull request हिरव्या रंगाच्या टिकसह दिसले पाहिजेत. ज्या लाल टिकवर माणूस काहीही कृती करू शकत नाही, ती दुर्लक्षित केली जाते आणि एकदा का एक टिक दुर्लक्षित झाली की, सर्वच टिक दुर्लक्षित केल्या जातात.

SYSTEM = (
    "You review one pull request diff. Every line that exists in the new file is "
    "prefixed with its line number and a tab character. "
    "Report only defects you can see in the lines shown: a crash, a resource leak, "
    "a security mistake, a wrong boundary condition, a broken contract with code "
    "that is visible in this diff. Do not comment on style, naming or formatting. "
    "Do not guess about code you cannot see. Leave out anything you are not "
    "certain about. An empty findings list is a normal and common answer. "
    'Reply with JSON only, in this shape: {"findings": [{"path": "src/app.py", '
    '"line": 42, "severity": "high", "comment": "what is wrong, then why"}]} '
    "Every line number must be one you can see in the left column of that file."
)

client = anthropic.Anthropic()
message = client.messages.create(
    model=MODEL,
    max_tokens=2000,
    system=SYSTEM,
    messages=[{"role": "user", "content": payload}],
)
print(f"stop={message.stop_reason} in={message.usage.input_tokens} "
      f"out={message.usage.output_tokens}", file=sys.stderr)

text = message.content[0].text
findings = json.loads(text[text.find("{"):text.rfind("}") + 1])["findings"]
findings = [f for f in findings if RANK.get(f["severity"], 0) >= RANK[MIN_SEVERITY]]
findings.sort(key=lambda f: -RANK.get(f["severity"], 0))
del findings[MAX_COMMENTS:]

if not findings:
    print("nothing above the severity threshold; posting no comment")
    raise SystemExit(0)

review = {
    "event": "COMMENT",
    "body": f"Automated review of the changed lines. {len(findings)} finding(s).",
    "comments": [
        {"path": f["path"].removeprefix("b/"), "line": f["line"], "side": "RIGHT",
         "body": f"**{f['severity']}** {f['comment']}"}
        for f in findings
    ],
}
subprocess.run(
    ["gh", "api", "-X", "POST", f"/repos/{REPO}/pulls/{PR}/reviews", "--input", "-"],
    input=json.dumps(review), text=True, check=True,
)

त्या ब्लॉक मधील तीन तपशील अत्यंत महत्त्वाचे आहेत. JSON हे पहिल्या { आणि शेवटच्या } च्या दरम्यान कापले जाते, कारण मॉडेल कधीकधी त्याचे उत्तर code fence मध्ये गुंडाळते आणि json.loads त्या fence मुळे अडकते. path मधून सुरुवातीचा b/ काढून टाकला जातो, कारण तो prefix diff header मधून येतो आणि GitHub ला repository-relative path हवा असतो. आणि --input - संपूर्ण रिव्ह्यू एकाच API कॉलद्वारे पाठवते, जेणेकरून दहा निष्कर्ष दहा वेगवेगळ्या नोटिफिकेशन्सऐवजी एकाच नोटिफिकेशनमध्ये मिळतील.

जेव्हा रिपोर्ट करण्यासारखे काहीही नसते, तेव्हा स्क्रिप्ट काहीही पोस्ट करत नाही. प्रत्येक pull request वर "no issues found" असे लिहिणारा बॉट लोकांना त्याकडे दुर्लक्ष करायला शिकवतो आणि मग ते महत्त्वाच्या गोष्टींकडेही दुर्लक्ष करू लागतात.

वर्कफ्लोमध्ये समाविष्ट करा

हे .github/workflows/pr-review.yml म्हणून सेव्ह करा:

name: pr-review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths-ignore:
      - '**.md'
      - 'docs/**'

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: pr-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    if: github.event.pull_request.head.repo.full_name == github.repository && !contains(github.event.pull_request.labels.*.name, 'no-ai-review')
    runs-on: [self-hosted, linux, pr-review]
    steps:
      - name: Review the changed lines
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REVIEW_MODEL: claude-haiku-4-5-20251001
          MIN_SEVERITY: medium
        run: /opt/pr-review/venv/bin/python /opt/pr-review/review.py

GITHUB_REPOSITORY हे त्या env: ब्लॉकमध्ये नाही कारण Actions ते प्रत्येक जॉबसाठी आधीच सेट करते. concurrency ग्रुप बिलासाठी महत्त्वाचा आहे: त्याशिवाय, एका ब्रांचवर तीन क्विक फिक्स पुश केल्यास तीन पूर्ण रिव्ह्यू रन होतात आणि तुम्हाला तिन्हींसाठी पैसे द्यावे लागतात, तर या ग्रुपमुळे फक्त शेवटचा रिव्ह्यू शिल्लक राहतो.

if: ओळ दोन कामे करते. पहिला भाग फोर्कवरून येणाऱ्या पुल रिक्वेस्टना वगळतो, ज्या की (key) नसल्यामुळे तसेही फेल झाल्या असत्या. दुसरा भाग तुमच्या टीमला एक ऑफ स्विच देतो: पुल रिक्वेस्टला no-ai-review लेबल जोडल्यास जॉब रन होत नाही.

एक पुल रिक्वेस्ट ओपन करा आणि काय होते ते पहा:

gh run list --workflow=pr-review.yml --limit 3
gh run view --log
gh pr view 42 --comments

काही सेकंदात पूर्ण झालेला आणि लॉगमध्ये nothing above the severity threshold; posting no comment असलेला रन योग्यरित्या काम करत आहे. एका लहान आणि क्लीन पुल रिक्वेस्टसाठी हाच अपेक्षित निकाल आहे.

स्वयंचलित पुल रिक्वेस्ट रिव्ह्यूचा खर्च किती असतो?

diff हा इनपुटचा मुख्य भाग असतो, त्यामुळे diff चा आकारच किंमत ठरवतो. खाली 500 ओळींच्या एका diff चे आणि सिस्टिम प्रॉम्प्टचे मोजमाप दिले आहे. हे मोजमाप अंदाजे नसून टोकन काउंटिंग एंडपॉइंटद्वारे मोजलेले आहे.

ChartTokens for one 500 line pull request diff, measured August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "input_tokens": "8,000",
    "output_tokens": "1,200"
  },
  {
    "label": "Sonnet 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  },
  {
    "label": "Opus 5",
    "input_tokens": "10,400",
    "output_tokens": "1,560"
  }
]

या diff साठी Haiku 4.5 वर 8,000 इनपुट टोकन्स आणि Sonnet 5 वर 10,400 इनपुट टोकन्स लागले. मजकूर तोच असला तरी टोकन संख्या वेगळी आहे. 4.7 नंतरच्या Claude मॉडेल्समध्ये नवीन टोकनायझर वापरला जातो, जो समान इनपुटसाठी सुमारे 30% जास्त टोकन्स तयार करतो. Anthropic ने त्यांच्या किंमत पृष्ठावर (pricing page) याची नोंद केली आहे. त्यामुळे जेव्हा तुम्ही नवीन मॉडेलची जुन्या मॉडेलशी प्रति दशलक्ष टोकन्सच्या किमतीनुसार तुलना करता, तेव्हा हे लक्षात ठेवा.

ऑगस्ट 2026 पर्यंतच्या अधिकृत किमती: Haiku 4.5 साठी प्रति दशलक्ष इनपुट टोकन्स $1 आणि आउटपुटसाठी $5 आहेत. Sonnet 5 साठी 31 ऑगस्ट 2026 पर्यंतच्या सुरुवातीच्या किमतीनुसार प्रति दशलक्ष इनपुट टोकन्स $2 आणि आउटपुटसाठी $10 आहेत, त्यानंतर त्या $3 आणि $15 होतील. Opus 5 साठी या किमती $5 आणि $25 आहेत.

ChartCost of that one review at list prices, August 2026
The data behind this chart
[
  {
    "label": "Haiku 4.5",
    "cost_per_pr_cents": 1.4,
    "cost_200_prs_usd": "2.80"
  },
  {
    "label": "Sonnet 5",
    "cost_per_pr_cents": 3.64,
    "cost_200_prs_usd": "7.28"
  },
  {
    "label": "Opus 5",
    "cost_per_pr_cents": 9.1,
    "cost_200_prs_usd": "18.20"
  }
]

Haiku 4.5 वर प्रति पुल रिक्वेस्ट 1.4 सेंट्स आणि Opus 5 वर 9.1 सेंट्स खर्च येतो. एखादी टीम महिन्याला 200 पुल रिक्वेस्ट मर्ज करत असेल, तर Haiku 4.5 वर सुमारे $2.80, Sonnet 5 वर $7.28 आणि Opus 5 वर $18.20 खर्च येतो. 1 सप्टेंबर 2026 पासून, Sonnet 5 च्या किमतीला 1.5 ने गुणा.

दोन गोष्टींमुळे प्रत्यक्ष बिल या अंदाजापेक्षा जास्त येऊ शकते. synchronize प्रत्येक पुशवर रिव्ह्यू ट्रिगर करते, त्यामुळे आठ पुश असलेल्या सक्रिय ब्रांचसाठी आठ रिव्ह्यूचा खर्च येतो. कॉन्करन्सी नियम फक्त तेव्हाच मदत करतो जेव्हा पुश जवळजवळ एकाच वेळी येतात. हे आकडे पाथ फिल्टर्स व्यवस्थित काम करत आहेत असे गृहीत धरतात: एकही फिल्टर न केलेली lock file इनपुट दुप्पट करू शकते.

प्रॉम्प्ट कॅशिंग (prompt caching) येथे मदत करत नाही. कॅश केलेले प्रीफिक्स प्रत्येक कॉलसाठी बाइट-टू-बाइट समान असावे लागते, परंतु diff प्रत्येक वेळी वेगळा असतो. सिस्टिम प्रॉम्प्ट हा एकमेव स्थिर भाग आहे, आणि तो किमान कॅशे करण्यायोग्य लांबीपेक्षा खूपच लहान आहे. सामान्य नियमासाठी, प्रॉम्प्ट कॅशिंग कधी फायदेशीर ठरते हे पहा आणि वरील तीन मॉडेल्सपैकी निवड करण्यासाठी, कोणत्या कामासाठी कोणते Claude मॉडेल वापरावे हे पहा.

हे सुरू करण्यापूर्वी तुमच्या स्वतःच्या diffs चे मोजमाप करा

payload तयार झाल्यानंतर खालील ओळ जोडा आणि गेल्या महिन्यातील काही पुल रिक्वेस्टवर मॅन्युअली स्क्रिप्ट चालवून पहा:

print(client.messages.count_tokens(
    model=MODEL, system=SYSTEM, messages=[{"role": "user", "content": payload}]
).input_tokens)

काउंट एंडपॉइंट मॉडेल चालवत नाही, त्यामुळे ते इनपुट किंवा आउटपुट टोकन्स वापरत नाही. ते तुम्ही नमूद केलेल्या मॉडेलच्या टोकनायझरचा वापर करते. तुमच्या रिपॉझिटरीमधील दहा प्रत्यक्ष पुल रिक्वेस्टवर हे चालवा आणि सरासरीऐवजी मध्यक (median) मूल्य घ्या, जेणेकरून एखादे मोठे मायग्रेशन अंदाजावर परिणाम करणार नाही.

रिव्ह्यू बॉट्सना म्यूट का केले जाते आणि ते कसे टाळावे

दोन प्रकारच्या वर्तनांमुळे या बॉट्सवरील विश्वास उडतो आणि वरील कोडमध्ये या दोन्ही गोष्टींवर उपाय आहेत.

एकाच वेळी सर्व गोष्टींचा रिव्ह्यू करणे. जो बॉट चाळीस कमेंट्स करतो, त्यातील एकही कमेंट वाचली जात नाही. 'severity threshold' आणि दहा कमेंट्सची मर्यादा ही सौजन्याची बाब नसून, त्या खऱ्या त्रुटी स्पष्ट दिसण्यासाठी आवश्यक आहेत. ट्रंकेट करण्यापूर्वी 'severity' नुसार सॉर्ट केल्यामुळे, मर्यादा ओलांडली गेल्यास सर्वात कमी महत्त्वाच्या त्रुटी वगळल्या जातात, केवळ यादृच्छिक दहा कमेंट्स नाही.

ज्या गोष्टी तपासता येत नाहीत त्यावर आत्मविश्वासाने कमेंट करणे. यामुळेच इंजिनिअर्स हे बॉट्स कायमचे बंद करतात. 40,000 ओळींच्या कोडबेसपैकी 200 ओळी पाहणारा मॉडेल सुद्धा "हे redis_client.py मधील कॅशे इनव्हॅलिडेशन खराब करते" अशी कमेंट अशा फाईलवर करतो जी त्याने कधी पाहिलेलीच नाही. सिस्टिम प्रॉम्प्ट स्पष्ट भाषेत सूचना देते: फक्त दाखवलेल्या ओळींमध्ये दिसणारे दोष सांगा आणि ज्याबद्दल खात्री नाही ते सोडून द्या. सामान्यपणे अचूकतेची मागणी करण्यापेक्षा त्रुटीचे थेट नाव घेणे अधिक प्रभावी ठरते. तसेच, रिकामे रिझल्ट मिळणे ही सामान्य बाब आहे हे मॉडेलला सांगितल्यामुळे, दोन ओळींच्या बदलावरही काहीतरी सांगण्यासाठी तो खोटे शोधत नाही.

रिव्ह्यू COMMENT म्हणून पोस्ट करा, कधीही REQUEST_CHANGES म्हणून करू नका. मॉडेलचे मत मर्ज रोखण्यासाठी वापरले जाऊ नये. ज्या क्षणी ते तसे करते, त्या क्षणी डेडलाईन असलेला कोणीही व्यक्ती त्याशी वाद घालण्याऐवजी संपूर्ण वर्कफ्लो काढून टाकेल.

अपयशाचे प्रकार आणि दिसणारे संदेश

रिव्ह्यू पोस्ट करताना HTTP 422 एरर. gh हे gh: Unprocessable Entity (HTTP 422) प्रिंट करते आणि रिस्पॉन्स बॉडीमध्ये संबंधित फील्डचे नाव असते: Pull request review thread line must be part of the diff. GitHub त्या कमेंटला अँकर करू शकत नाही. याची सामान्य कारणे म्हणजे मॉडेलने सुचवलेली चुकीची लाईन नंबर, path ज्यामध्ये अजूनही b/ प्रीफिक्स आहे, किंवा काढून टाकलेल्या लाईनवर केलेली कमेंट. अशा वेळी side ला RIGHT ऐवजी LEFT वर सेट करणे आवश्यक असते. पोस्ट करण्यापूर्वी रिव्ह्यू JSON प्रिंट करा आणि एका कमेंटची diff सोबत हाताने पडताळणी करा.

मॉडेल API कडून invalid x-api-key. ही पायरी पहिल्या messages.create कॉलवर अपयशी ठरते. एकतर रिपॉझिटरीवर ANTHROPIC_API_KEY सीक्रेट सेट केलेले नाही, किंवा पुल रिक्वेस्ट फोर्कवरून आल्यामुळे Actions ने कोणतेही सीक्रेट्स पास केलेले नाहीत. if: लाईनवरील फोर्क गार्डने हे वगळायला हवे होते, त्यामुळे आधी ती लाईन तपासा.

gh: Resource not accessible by integration (HTTP 403). जॉब टोकन पुल रिक्वेस्टवर लिहू शकत नाही. permissions: ब्लॉक मध्ये pull-requests: write जोडा. जर ते आधीच तिथे असेल, तर Settings मध्ये जाऊन Actions आणि त्यानंतर General तपासा, जिथे ऑर्गनायझेशन पॉलिसीमुळे वर्कफ्लो टोकनच्या अधिकारांवर मर्यादा असू शकतात.

json.decoder.JSONDecodeError. मॉडेलने पार्स करता येईल असे JSON दिलेले नाही. याचे सामान्य कारण म्हणजे रिस्पॉन्स टोकन मर्यादेपर्यंत पोहोचला आणि ऑब्जेक्ट पूर्ण होण्याआधीच थांबला. लॉग लाईन यासाठी stop_reason प्रिंट करते: max_tokens मूल्य मिळणे म्हणजे max_tokens वाढवणे किंवा MAX_COMMENTS कमी करणे आवश्यक आहे.

वर्कफ्लो कधीच रन होत नाही. gh run list मध्ये पुल रिक्वेस्टसाठी काहीही दिसत नाही. paths-ignore ने सर्व बदललेल्या फाईल्स फिल्टर केल्या नाहीत ना, हे तपासा. त्यानंतर फोर्क गार्ड आणि लेबल गार्ड तपासा, आणि शेवटी VPS वर sudo systemctl status 'actions.runner.*' वापरून रनर चालू आहे का ते पहा. रनर ऑफलाइन असल्यास जॉब रांगेत राहतो आणि पुल रिक्वेस्टमध्ये कोणताही एरर मेसेज दिसत नाही.

प्रत्येक रिव्ह्यू रिकामा येतो. एका रनसाठी MIN_SEVERITY ला low वर सेट करा. जर निष्कर्ष दिसत असतील, तर थ्रेशोल्ड योग्य काम करत आहे. जर काहीच दिसत नसेल, तर payload प्रिंट करा आणि फिल्टर्सनी संपूर्ण diff काढून टाकलेला नाही याची खात्री करा.

इतर एजंट्ससोबत हे चालवणे

रिव्ह्यूअर आकाराने लहान असल्याने, ते आधीच इतर सर्व गोष्टी चालवणाऱ्या सर्व्हरवर टाकण्याचा मोह होतो. जर रिपॉझिटरी महत्त्वाची असेल, तर हे वेगळे ठेवणेच योग्य आहे. या प्रक्रियेकडे एक टोकन असते जे तुमच्या कोडवर टिप्पणी करू शकते आणि एक की असते जी तुमचे पैसे खर्च करू शकते. सेल्फ-होस्टेड रनर हे मुळातच अशा ठिकाणी डिझाइन केलेले असते जिथे वर्कफ्लो कोड कार्यान्वित होतो. sudo अधिकार नसलेले एक समर्पित अनप्रिव्हिलेज्ड खाते, ज्यावर इतर काहीही चालत नाही, हे मूलभूत सुरक्षा मानक आहे. जर तुम्ही कोड चेकआउट करणारे इंटरअॅक्टिव्ह एजंट्स देखील चालवत असाल, तर प्रत्येक एजंटसाठी एक डिस्पोजेबल VM वापरणे हा सर्वोत्तम पर्याय आहे आणि VPS वर कोडिंग एजंट चालवणे हे सामान्य सेटअपसाठी उपयुक्त आहे. जर Anthropic API तुमच्यासाठी नवीन असेल, तर VPS वर पहिले Claude API ॲप तयार करणे ही यापेक्षा सोपी सुरुवात ठरेल.

FAQ

AI PR review agent ला माझ्या रिपॉझिटरीमध्ये write access ची गरज आहे का?

नाही. रिव्ह्यू पोस्ट करण्यासाठी त्याला pull-requests: write आणि diff मिळवण्यासाठी contents: read ची गरज असते. ही संपूर्ण यादी आहे, आणि तुम्ही ती वर्कफ्लोच्या permissions: ब्लॉक मध्ये सेट करता, जे प्रति-जॉब GITHUB_TOKEN काय करू शकते यावर मर्यादा घालते. या दोन ओळींसह, एजंट पुल रिक्वेस्टवर टिप्पणी करू शकतो परंतु कमिट पुश करू शकत नाही किंवा ब्रांच मर्ज करू शकत नाही. रिव्ह्यू पोस्ट करण्यासाठी REQUEST_CHANGES ऐवजी event: COMMENT वापरा, जेणेकरून तो मर्ज ब्लॉक करू शकणार नाही.

माझी रिव्ह्यू कमेंट HTTP 422 सह का अपयशी ठरते?

GitHub केवळ पुल रिक्वेस्ट diff चा भाग असलेल्या ओळीवरच इनलाइन रिव्ह्यू कमेंट स्वीकारते आणि तसे नसल्यास Pull request review thread line must be part of the diff रिटर्न करते. path हे रिपॉझिटरी-रिलेटिव्ह असल्याची आणि diff हेडरमधील कोणताही b/ प्रीफिक्स नसल्याची खात्री करा, तसेच ओळीचा क्रमांक त्या फाईलच्या हंक (hunk) मध्ये असावा. side हे जोडलेल्या किंवा न बदललेल्या ओळीसाठी RIGHT आणि काढलेल्या ओळीसाठी LEFT असणे आवश्यक आहे. मॉडेलला पाठवण्यापूर्वी diff च्या प्रत्येक ओळीला नवीन फाईलच्या लाईन नंबरचा प्रीफिक्स लावल्यामुळे मॉडेल स्वतःहून चुकीचे नंबर तयार करत नाही.

मी हे पब्लिक रिपॉझिटरीवर चालवू शकतो का जिथे फोर्क (fork) कडून पुल रिक्वेस्ट येतात?

या डिझाइनसह नाही. GitHub फोर्कवरून ट्रिगर झालेल्या वर्कफ्लोला सीक्रेट्स पास करत नाही, त्यामुळे मॉडेल की (key) उपलब्ध नसते आणि रन अपयशी ठरते. GitHub असेही नमूद करते की सेल्फ-होस्टेड रनर्स "पब्लिक रिपॉझिटरीसाठी कधीही वापरू नयेत", कारण कोणीही पुल रिक्वेस्ट उघडून तुमच्या मशीनवर कोड रन करू शकते. पब्लिक प्रोजेक्टसाठी, रिव्ह्यूअरला फक्त रिपॉझिटरीवर पुश केलेल्या ब्रांचपुरते मर्यादित ठेवा, जे if: गार्ड करते, किंवा रिव्ह्यू स्टेप GitHub-होस्टेड रनरवर हलवा आणि diff तुमच्या स्वतःच्या इन्फ्रास्ट्रक्चरच्या बाहेर जात असल्याचे स्वीकारा.

पुल रिक्वेस्ट रिव्ह्यूसाठी मी कोणते मॉडेल वापरावे?

Haiku 4.5 पासून सुरुवात करा. त्रुटींच्या निश्चित प्रकारांविरुद्ध मर्यादित diff वाचणे ही कठीण तर्कशक्तीची समस्या नाही, आणि सर्वात स्वस्त मॉडेलमुळे मासिक बिल कमी राहते ज्यावर कोणालाही आक्षेप नसतो. जर तुम्हाला तुमच्या लँग्वेज किंवा फ्रेमवर्कमधील वास्तविक बग्स मॉडेलला सापडत नसल्याचे आढळले, तरच Sonnet 5 वर जा आणि गृहीत धरण्याऐवजी त्याचे मोजमाप करा. Opus 5 हे तिन्हींपैकी प्रति पुल रिक्वेस्ट सर्वात महाग आहे, जे प्रत्येक फीचर ब्रांचवर पुश करण्यापेक्षा रिलीज ब्रांचवर वापरणे अधिक तर्कसंगत ठरते.