VPS-এ কীভাবে নিজের AI PR Review Agent সেটআপ করবেন
আপনার নিজস্ব VPS-এ AI PR reviewer সেটআপ করার সম্পূর্ণ নির্দেশিকা। এতে diff-scoped প্রম্পট, পাথ ফিল্টার, ইনলাইন কমেন্ট এবং প্রতিটি PR-এর খরচসহ বিস্তারিত তথ্য দেওয়া হয়েছে।
একটি self-hosted PR review agent যা করে
আপনার নিজস্ব সার্ভারে চালানো PR review agent হলো একটি ছোট প্রোগ্রাম। এটি একটি pull request (PR)-এর diff পড়ে এবং শুধু পরিবর্তিত লাইনগুলো একটি model-এ পাঠায়। model থেকে পাওয়া ফলাফল inline review comment হিসেবে পোস্ট করা হয়। এটি কখনো আপনার branch checkout করে না। pull request-এ পরিবর্তন করা হয়নি, এমন কোনো file-ও এটি কখনো পড়ে না। এটির কাছে থাকা একমাত্র credential হলো একটি model API (application programming interface) key এবং comment করার অনুমতিসহ অন্য কোনো কাজের অনুমতি নেই—এমন একটি token। এটি agent-এর একটি সীমিত সংজ্ঞা। এটি autonomous কোনো ব্যবস্থার চেয়ে সামনে filter বসানো একটি prompt-এর কাছাকাছি। আপনি এটি তৈরি করার আগে সম্পূর্ণ ধারণা পেতে চাইলে ধারণা থেকে নিজে লেখা loop পর্যন্ত ধাপে ধাপে পথটি এর ভিত্তিগত বিষয়গুলো ব্যাখ্যা করে।
একটি মডেল diff পড়তে পারে। সেই সমস্যার সমাধান হয়ে গেছে। গুরুত্বপূর্ণ বিষয় হলো diff কোথায় যাচ্ছে এবং key কার কাছে আছে। একটি hosted review bot ব্যবহার করার অর্থ হলো প্রতিটি private repository থেকে আসা প্রতিটি diff আপনার নেটওয়ার্কের বাইরে চলে যায়, তৃতীয় পক্ষের লগে জমা হয় এবং তাদের retention policy-র অধীনে থাকে। আপনার নিজস্ব একটি VPS (virtual private server)-এ, diff-টি GitHub থেকে আপনার সার্ভারে আসে এবং সেখান থেকে model API-তে যায়। আপনি সেই চল্লিশ লাইন কোড পড়তে পারেন যা নির্ধারণ করে কী পাঠানো হবে।
শুরু করার আগে আপনার যা প্রয়োজন
- Ubuntu 24.04 চালিত একটি VPS, যেখানে একটি self-hosted GitHub Actions runner আগে থেকেই রিপোজিটরিতে রেজিস্টার করা আছে। রেজিস্টার করার সময় এটিকে
pr-reviewঅতিরিক্ত লেবেল দিন, কারণ নিচের ওয়ার্কফ্লোটি সেই লেবেলের ওপর ভিত্তি করেই কাজ করবে। - Claude Console থেকে পাওয়া একটি Anthropic API key।
- এমন একটি রিপোজিটরি যেখানে কারা pull request খুলতে পারবে তা আপনি নিয়ন্ত্রণ করেন। প্রাইভেট রিপোজিটরির ক্ষেত্রে এটি সহজ। নিচের ফর্ক সেকশনে পাবলিক রিপোজিটরির বিষয়টি আলোচনা করা হয়েছে, তবে সেখানে সমাধানটি কিছুটা জটিল।
VPS-এ reviewer ইনস্টল করা
runner service-টি সেই unprivileged account-এ চলে যা আপনি ./svc.sh install চালানোর সময় তৈরি করেছিলেন। reviewer-টিকে সেই একই account-এর অধীনে ইনস্টল করুন যাতে job-টি sudo ছাড়াই এটি চালাতে পারে। নিচে runner-এর পরিবর্তে আপনার account-এর নাম ব্যবহার করুন।
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 --versiongh --version কমান্ডটি 2026 সালের আগস্ট মাস অনুযায়ী Ubuntu 24.04-এ gh version 2.45.0 প্রদর্শন করে। 2.20 সংস্করণ বা তার পরবর্তী যেকোনো release-এ নিচে ব্যবহৃত --input flag-টি রয়েছে। Command 'gh' not found বার্তাটির অর্থ হলো universe component-টি enabled করা নেই, তাই sudo add-apt-repository universe চালিয়ে পুনরায় চেষ্টা করুন।
কী এবং টোকেন যেখানে থাকে
দুটি গোপন তথ্য, দুটি ভিন্ন মেয়াদ। কোনোটিই রিপোজিটরিতে রাখা যাবে না।
ANTHROPIC_API_KEY হলো একটি রিপোজিটরি সিক্রেট, যা Settings-এর অধীনে Secrets and variables এবং তারপর Actions থেকে সেট করতে হয়। GitHub এটিকে এনক্রিপ্ট করে এবং রানটাইমে স্টেপের এনভায়রনমেন্টে ইনজেক্ট করে। এটি কখনোই ডিস্কে ফাইল হিসেবে থাকে না এবং গিট হিস্ট্রিতেও থাকে না।
GITHUB_TOKEN ভিন্নভাবে কাজ করে। Actions প্রতিটি জবের জন্য একটি নতুন টোকেন তৈরি করে এবং জব শেষ হলে তা ধ্বংস করে দেয়। সেই টোকেন কী করতে পারবে তা ওয়ার্কফ্লোর permissions: ব্লকের মাধ্যমে নির্ধারণ করা হয়, তাই এখানেই মূলত 'least privilege' বা ন্যূনতম অনুমোদনের বিষয়টি কার্যকর হয়:
permissions:
contents: read
pull-requests: writeএই টোকেনটি একটি রিভিউ পোস্ট করতে পারে। এটি কোনো কমিট পুশ করতে, ব্রাঞ্চ মার্জ করতে, ওয়ার্কফ্লো ফাইল এডিট করতে বা অন্য কোনো রিপোজিটরি স্পর্শ করতে পারে না। যে এজেন্ট মন্তব্য করতে পারে, সে একজন রিভিউয়ার। যে এজেন্ট পুশ করতে পারে, সে একজন কমিটার, এবং কেউ এর অনুমতি দেয়নি। মডেল কী-এর ক্ষেত্রেও একই সতর্কতা অবলম্বন করুন, কারণ এটি আপনার অ্যাকাউন্টের অর্থ খরচ করে। এই ধরনের সমস্যা সম্পর্কে আরও জানতে দেখুন keeping secrets out of an AI agent's reach।
Actions জব লগে গোপন তথ্যের মূল স্ট্রিংটিকে *** দিয়ে প্রতিস্থাপন করে। এটি শুধুমাত্র হুবহু স্ট্রিংটির সাথেই মেলে, তাই আপনি যদি কোনো কী-কে base64 এনকোড করেন, দুই লাইনে ভাগ করেন বা প্রতিবার একটি করে ক্যারেক্টার প্রিন্ট করেন, তবে তা লগে পরিষ্কার দেখা যাবে। এমন কোনো ডিবাগ স্টেপ যোগ করবেন না যা এনভায়রনমেন্টের সব তথ্য ডাম্প করে।
কেন fork থেকে আসা pull request আপনার API key দেখতে পায় না
GitHub-এর নিয়মটি সংক্ষিপ্ত: GITHUB_TOKEN ছাড়া, যখন কোনো forked repository থেকে workflow ট্রিগার করা হয়, তখন কোনো secret রানারে পাঠানো হয় না। তাই fork থেকে চালানো একটি pull_request আপনার স্ক্রিপ্টকে কোনো ANTHROPIC_API_KEY ছাড়াই শুরু করে, এবং প্রথম API কলটি invalid x-api-key এর কারণে ব্যর্থ হয়।
এর একটি সহজ সমাধান হলো ট্রিগারটিকে pull_request_target এ পরিবর্তন করা, যা মূল repository-এর কনটেক্সটে চলে এবং secret-গুলো পায়। এখানে এমনটি করবেন না। GitHub-এর নিজস্ব নিরাপত্তা নির্দেশনায় বলা হয়েছে যে, এই ধরনের workflow "privileged বা বিশেষ সুবিধাপ্রাপ্ত, যার অর্থ হলো এগুলো অন্যান্য privileged workflow ট্রিগারের সাথে main branch-এর cache শেয়ার করে এবং এগুলোর repository-তে লেখার ক্ষমতা ও সংরক্ষিত secret-গুলোতে অ্যাক্সেস থাকতে পারে", এবং এর ফলাফল "repository দখল করার কাজে ব্যবহার করা যেতে পারে"।
একই নির্দেশনায় রানার সম্পর্কে সরাসরি বলা হয়েছে: "GitHub-এ পাবলিক repository-এর জন্য self-hosted runner প্রায় কখনোই ব্যবহার করা উচিত নয়, কারণ যেকোনো ব্যবহারকারী repository-তে pull request খুলতে পারে এবং পরিবেশটিকে compromised বা ঝুঁকিপূর্ণ করে তুলতে পারে।"
এটি দুটি ডিজাইন সিদ্ধান্তকে প্রভাবিত করে। এই job-টিতে একটি guard বা সুরক্ষা ব্যবস্থা আছে যাতে এটি শুধুমাত্র আপনার নিজস্ব repository-তে push করা branch-গুলোতে চলে। এবং workflow-টিতে কোনো actions/checkout ধাপ নেই। agent-এর কাছে ডিস্কে কখনোই branch-টি থাকে না, তাই একটি ক্ষতিকারক pull request শুধুমাত্র টেক্সট হিসেবে মডেলে পাঠানো হয়। এটি আপনার VPS-এ কোনো build script চালাতে পারে না, কারণ আপনার VPS-এ কিছুই এটি রান করে না। তবে টেক্সট মানেই যে ক্ষতিকর নয় তা নয়: অপরিচিত কারো লেখা একটি diff হলো মডেলে আসা একটি untrusted input, যা আপনি agent-কে ওয়েব সার্চ করতে দেওয়ার সময় যে trust boundary বা বিশ্বাসের সীমানার মুখোমুখি হন, এটিও তাই। এখানে এটিকে নিয়ন্ত্রণ করার একমাত্র উপায় হলো এই agent-এর শুধুমাত্র একটি comment পোস্ট করা ছাড়া আর কিছুই করার ক্ষমতা নেই।
পুরো রিপোজিটরি নয়, শুধুমাত্র diff সংগ্রহ করুন
একটি অনুরোধের মাধ্যমেই পুরো diff-কে plain text হিসেবে পাওয়া সম্ভব।
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 media type-টি ব্যবহার করলে response-টি pull request-এর বিবরণ সম্বলিত JSON object-এর পরিবর্তে সরাসরি unified diff হিসেবে আসে এবং gh api সেই body-টিকে কোনো পরিবর্তন ছাড়াই প্রদর্শন করে। আপনার দেখা প্রথম লাইনটি অবশ্যই diff --git a/ দিয়ে শুরু হতে হবে। gh: Not Found (HTTP 404) আসার অর্থ হলো token-টির রিপোজিটরি দেখার অনুমতি নেই; fine-grained personal token-এর ক্ষেত্রে এর মানে সাধারণত Pull requests permission-টি সক্রিয় করা হয়নি।
টোকেন খরচ করার আগে ফিল্টার করুন
এই অংশটি একটি বটকে এমনভাবে তৈরি করে যা মানুষ পড়তে পছন্দ করে, অন্যথায় তারা এটিকে মিউট করে দেয়। নিচে উল্লিখিত প্রতিটি ফিল্টার মডেলের কাছে কোনো তথ্য পৌঁছানোর আগেই কাজ করে।
- Path filters. লক ফাইল, ভেন্ডর ডিরেক্টরি, মিনিফাইড বান্ডেল এবং জেনারেট করা কোড বাদ দিন।
package-lock.json-এ মডেলের কোনো মন্তব্য কেবল নয়েজ তৈরি করে, তাছাড়া একটি ডিফের বেশিরভাগ বাইট এই ফাইলগুলো থেকেই আসে। - A size cap. নির্ধারিত সীমার বেশি হলে রিভিউ এড়িয়ে যান এবং সফলভাবে প্রস্থান করুন। 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) অংশ আলাদা করা হলে তবেই পাথ ফিল্টারিং সম্ভব হয়। প্রতিটি লাইনে নম্বর দেওয়া হলে তবেই রিভিউ কমেন্টগুলো সঠিক জায়গায় যুক্ত হয়। GitHub শুধুমাত্র ডিফের অন্তর্ভুক্ত লাইনেই ইনলাইন কমেন্ট গ্রহণ করে, তাই মডেলটিকে অবশ্যই একটি প্রকৃত লাইন নম্বর উল্লেখ করতে হয়। নম্বরগুলো প্রদান করলে মডেলটি নতুন কোনো নম্বর তৈরি না করে বিদ্যমান নম্বরটি কপি করতে পারে।
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 @@ নির্দেশ করে যে নতুন ফাইলের হাঙ্ক 12 নম্বর লাইন থেকে শুরু হয়েছে, তাই কাউন্টার সেখান থেকে শুরু হয় এবং শুধুমাত্র নতুন যোগ করা ও অপরিবর্তিত লাইনগুলোর ক্ষেত্রে বৃদ্ধি পায়। মুছে ফেলা লাইনগুলো নম্বরবিহীন থাকে, কারণ নতুন ফাইলে সেগুলোর অস্তিত্ব নেই। ব্যাকস্ল্যাশ দিয়ে শুরু হওয়া লাইনগুলোর জন্য রাখা গার্ডটি git-এর ফাইলের শেষে লেখা no-newline মার্কারটিকে এড়িয়ে যায়, যা অন্যথায় পরবর্তী প্রতিটি নম্বরকে এক ঘর পিছিয়ে দিত।
উভয় এক্সিটেই 1-এর পরিবর্তে 0 স্ট্যাটাস ব্যবহার করা হয়েছে। একটি ফিল্টার করা বা অতিরিক্ত বড় পুল রিকোয়েস্টের ক্ষেত্রে সবুজ চেক চিহ্ন দেখানো উচিত। মানুষ ব্যবস্থা নিতে পারে না এমন লাল চেক চিহ্নকে উপেক্ষা করা হয়, এবং একবার একটি চেক উপেক্ষা করা হলে, বাকিগুলোও উপেক্ষা করা হয়।
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-টিকে প্রথম { এবং শেষ }-এর মধ্যে স্লাইস করা হয়েছে, কারণ মডেল অনেক সময় তার উত্তরকে কোড ফেন্সের মধ্যে রাখে এবং json.loads সেই ফেন্সের কারণে বাধাগ্রস্ত হয়। path থেকে শুরুর b/ সরিয়ে ফেলা হয়েছে, কারণ এই প্রিফিক্সটি ডিফের হেডার থেকে আসে এবং GitHub-এর রিপোজিটরি-রিলেটিভ পাথ প্রয়োজন হয়। এবং --input - পুরো রিভিউটিকে একটি API কল হিসেবে পাঠায়, ফলে দশটি ফলাফল দশটি আলাদা নোটিফিকেশনের পরিবর্তে একটি নোটিফিকেশন হিসেবে পৌঁছায়।
রিপোর্ট করার মতো কিছু না থাকলে, স্ক্রিপ্টটি কিছুই পোস্ট করে না। যে বট প্রতিটি পুল রিকোয়েস্টে "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.pyGITHUB_REPOSITORY সেই env: ব্লকে নেই কারণ Actions প্রতিটি জবের জন্য এটি আগেই সেট করে রাখে। concurrency গ্রুপটি বিলের জন্য গুরুত্বপূর্ণ: এটি ছাড়া, একটি ব্রাঞ্চে তিনটি দ্রুত ফিক্স পুশ করলে তিনটি পূর্ণ রিভিউ চলে এবং আপনাকে তিনটির জন্যই মূল্য দিতে হয়, কিন্তু এটি থাকলে শুধুমাত্র সর্বশেষটি কার্যকর থাকে।
if: লাইনটি দুটি কাজ করে। প্রথমার্ধটি ফর্ক থেকে আসা pull request-গুলোকে এড়িয়ে যায়, যা কোনো key ছাড়া এমনিতেই ব্যর্থ হতো। দ্বিতীয়ার্ধটি আপনার টিমকে একটি অফ সুইচ দেয়: একটি pull request-এ no-ai-review লেবেল যোগ করলে জবটি আর চলে না।
একটি pull request খুলুন এবং দেখুন কী ঘটে:
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 সহ কয়েক সেকেন্ডের মধ্যে শেষ হয়, সেটি সঠিকভাবে কাজ করছে। একটি ছোট এবং পরিচ্ছন্ন pull request-এর ক্ষেত্রে এটিই প্রত্যাশিত ফলাফল।
স্বয়ংক্রিয় পুল রিকোয়েস্ট রিভিউয়ের খরচ কত?
ডিফ (diff) মূলত পুরো ইনপুটের সমান, তাই ডিফ-এর আকারই খরচ নির্ধারণ করে। নিচে 500 লাইনের একটি ডিফ এবং সিস্টেম প্রম্পট-এর পরিমাপ দেওয়া হলো, যা অনুমানের পরিবর্তে টোকেন কাউন্টিং এন্ডপয়েন্ট ব্যবহার করে গণনা করা হয়েছে।
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"
}
]এই ডিফ-এর জন্য Haiku 4.5 মডেলে 8,000 ইনপুট টোকেন এবং Sonnet 5 মডেলে 10,400 ইনপুট টোকেন খরচ হয়েছে। একই টেক্সট, কিন্তু টোকেন সংখ্যা ভিন্ন। 4.7 সংস্করণ থেকে Claude মডেলগুলো নতুন টোকেনাইজার ব্যবহার করে, যা একই ইনপুটের জন্য প্রায় 30% বেশি টোকেন তৈরি করে; Anthropic তাদের প্রাইসিং পেজে এটি উল্লেখ করেছে। নতুন মডেলের সাথে পুরনো মডেলের তুলনা করার সময় প্রতি মিলিয়ন টোকেনের মূল্যের পাশাপাশি এই বিষয়টিও মাথায় রাখুন।
আগস্ট 2026 অনুযায়ী তালিকাভুক্ত মূল্য: Haiku 4.5-এর জন্য প্রতি মিলিয়ন ইনপুট টোকেন 1 ডলার এবং আউটপুট টোকেন 5 ডলার। Sonnet 5-এর জন্য 31 আগস্ট 2026 পর্যন্ত প্রারম্ভিক মূল্য প্রতি মিলিয়ন ইনপুট টোকেন 2 ডলার এবং আউটপুট টোকেন 10 ডলার, এরপর তা বেড়ে 3 ডলার এবং 15 ডলার হবে। Opus 5-এর জন্য এই মূল্য 5 ডলার এবং 25 ডলার।
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 প্রতিটি পুশ-এ রিভিউ ট্রিগার করে, তাই একটি সক্রিয় ব্রাঞ্চে আটটি পুশ থাকলে আটবার রিভিউয়ের খরচ হবে এবং কনকারেন্সি রুল শুধুমাত্র তখনই কাজে আসে যখন পুশগুলো খুব কাছাকাছি সময়ে আসে। এই হিসাবগুলো পাথ ফিল্টার সঠিকভাবে কাজ করছে ধরে নিয়ে করা হয়েছে: একটি ফিল্টার না করা লক ফাইল একাই ইনপুট টোকেন দ্বিগুণ করে দিতে পারে।
প্রম্পট ক্যাশিং এখানে কোনো কাজে আসে না। ক্যাশ করা প্রিফিক্সকে প্রতিটি কলের ক্ষেত্রে বাইট-টু-বাইট অভিন্ন হতে হয়, কিন্তু ডিফ প্রতিবারই পরিবর্তিত হয়। সিস্টেম প্রম্পটই একমাত্র স্থিতিশীল অংশ, এবং এটি ন্যূনতম ক্যাশযোগ্য দৈর্ঘ্যের চেয়ে অনেক ছোট। সাধারণ নিয়মের জন্য দেখুন কখন প্রম্পট ক্যাশিং সাশ্রয়ী হয়, এবং উপরের তিনটি মডেলের মধ্যে কোনটি বেছে নেবেন তা জানতে দেখুন কোন কাজের জন্য কোন Claude মডেল ব্যবহার করবেন।
এটি চালু করার আগে আপনার নিজের ডিফগুলো পরিমাপ করুন
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 হিসেবে নয়। একটি মডেলের মতামত মার্জ (merge) আটকে দেওয়ার ক্ষমতা রাখা উচিত নয়। যদি তা হয়, তবে ডেডলাইনের চাপে থাকা কেউ এর সাথে তর্ক না করে পুরো ওয়ার্কফ্লোটিই সরিয়ে ফেলবে।
ব্যর্থতার ধরন এবং যে বার্তাগুলো আপনি দেখবেন
রিভিউ পোস্ট করার সময় 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 মুছে ফেলেনি।
আপনার অন্যান্য এজেন্টের পাশাপাশি এটি চালানো
রিভিউয়ারটি ছোট আকারের হওয়ায়, এটিকে অন্য সব কিছু চলছে এমন সার্ভারে বসিয়ে দেওয়া লোভনীয় মনে হতে পারে। তবে রিপোজিটরি যদি গুরুত্বপূর্ণ হয়, তবে এটিকে আলাদা রাখুন। এই প্রসেসটি এমন একটি টোকেন ধারণ করে যা আপনার কোডে মন্তব্য করতে পারে এবং এমন একটি কি (key) ব্যবহার করে যা আপনার অর্থ খরচ করতে পারে। একটি self-hosted রানার মূলত এমন একটি জায়গা যেখানে workflow কোড নির্বাহ হয়। কোনো sudo অধিকার নেই এমন একটি ডেডিকেটেড আনপ্রিভিলেজড অ্যাকাউন্ট, যেখানে অন্য কিছু চলছে না, সেটিই হলো প্রাথমিক নিরাপত্তা মান। আপনি যদি এমন ইন্টারঅ্যাক্টিভ এজেন্টও চালান যা কোড চেক আউট করে, তবে প্রতি এজেন্টের জন্য একটি ডিসপোজেবল VM ব্যবহার করাই হলো কার্যকর পদ্ধতি, এবং একটি VPS-এ কোডিং এজেন্ট চালানো সাধারণ সেটআপের বিষয়টি কভার করে। Anthropic API আপনার কাছে নতুন হলে, একটি VPS-এ প্রথম Claude API অ্যাপ তৈরি করা এখান থেকে শুরু করার চেয়ে সহজ হবে।
FAQ
একটি AI PR review agent-এর কি আমার রিপোজিটরিতে write access প্রয়োজন?
না। রিভিউ পোস্ট করার জন্য এটির pull-requests: write এবং diff সংগ্রহ করার জন্য contents: read প্রয়োজন। এটিই সম্পূর্ণ তালিকা, এবং আপনি এটি workflow-এর permissions: ব্লকে সেট করেন, যা প্রতি-জব GITHUB_TOKEN-এর ক্ষমতা সীমাবদ্ধ করে। এই দুটি লাইন থাকলে এজেন্ট একটি pull request-এ মন্তব্য করতে পারে কিন্তু কোনো commit push বা branch merge করতে পারে না। রিভিউ পোস্ট করার জন্য REQUEST_CHANGES-এর পরিবর্তে event: COMMENT ব্যবহার করুন, যাতে এটি কোনো merge-কে বাধা দিতে না পারে।
আমার রিভিউ মন্তব্য কেন HTTP 422 এর সাথে ব্যর্থ হয়?
GitHub শুধুমাত্র pull request diff-এর অন্তর্ভুক্ত লাইনেই একটি inline review মন্তব্য গ্রহণ করে, অন্যথায় এটি Pull request review thread line must be part of the diff রিটার্ন করে। নিশ্চিত করুন যে path রিপোজিটরি-রিলেটিভ এবং এতে diff হেডার থেকে কোনো b/ প্রিফিক্স নেই, এবং লাইন নম্বরটি সেই ফাইলের একটি hunk-এর ভেতরে রয়েছে। side অবশ্যই একটি নতুন বা অপরিবর্তিত লাইনের জন্য RIGHT এবং একটি মুছে ফেলা লাইনের জন্য LEFT হতে হবে। মডেলে পাঠানোর আগে diff-এর প্রতিটি লাইনের শুরুতে নতুন ফাইলের লাইন নম্বর যুক্ত করলে মডেলটি নিজে থেকে কোনো নম্বর তৈরি করতে পারে না।
আমি কি এটি fork থেকে আসা pull request সহ পাবলিক রিপোজিটরিতে চালাতে পারি?
এই ডিজাইনের সাথে এটি সম্ভব নয়। GitHub fork থেকে ট্রিগার হওয়া workflow-এ কোনো secret পাঠায় না, তাই মডেল কি (model key) অনুপস্থিত থাকে এবং রান ব্যর্থ হয়। GitHub আরও উল্লেখ করে যে, self-hosted runner "পাবলিক রিপোজিটরির জন্য কখনোই ব্যবহার করা উচিত নয়", কারণ যে কেউ একটি pull request ওপেন করে আপনার মেশিনে কোড রান করাতে পারে। পাবলিক প্রজেক্টের ক্ষেত্রে, রিভিউয়ারকে শুধুমাত্র রিপোজিটরিতে push করা branch-এর মধ্যে সীমাবদ্ধ রাখুন, যা if: গার্ড করে থাকে, অথবা রিভিউ ধাপটি GitHub-hosted runner-এ সরিয়ে নিন এবং মেনে নিন যে diff আপনার নিজস্ব ইনফ্রাস্ট্রাকচার থেকে বাইরে চলে যাচ্ছে।
pull request রিভিউয়ের জন্য আমার কোন মডেল ব্যবহার করা উচিত?
Haiku 4.5 দিয়ে শুরু করুন। একটি নির্দিষ্ট ত্রুটির তালিকার বিপরীতে সীমাবদ্ধ diff পড়া খুব জটিল কোনো বিষয় নয়, এবং সবচেয়ে সস্তা মডেলটি ব্যবহার করলে মাসিক খরচ এমন একটি পর্যায়ে থাকে যা নিয়ে কারো আপত্তি থাকে না। যদি দেখেন এটি আপনার ভাষা বা ফ্রেমওয়ার্কের প্রকৃত বাগগুলো ধরতে পারছে না, তবেই Sonnet 5-এ উন্নীত হন এবং অনুমানের ভিত্তিতে নয়, বরং পরিমাপ করে সিদ্ধান্ত নিন। Opus 5 এই তিনটির মধ্যে সবচেয়ে ব্যয়বহুল, যা প্রতিটি feature branch-এর প্রতিটি push-এর চেয়ে release branch-এর ক্ষেত্রে ব্যবহার করা বেশি যুক্তিযুক্ত।