Trade-off Arsitektur Agentic PR Reviewer

Mengintegrasikan Large Language Model (LLM) ke dalam pipeline CI/CD untuk otomatisasi peninjauan Pull Request (PR) menghadirkan dilema teknis: kedalaman analisis berbanding lurus dengan latensi build dan biaya komputasi token. Terdapat dua pola arsitektur utama untuk tugas ini: Single-pass Linier dan Autonomous Multi-turn Loop.

1. Single-pass Linier

Alur kerja mengekstrak git diff, menyusun prompt terstruktur, memanggil LLM satu kali, dan mengirim komentar balik ke Git provider. Pola ini membatasi latensi pada durasi satu network trip LLM (umumnya 3–10 detik) dan penggunaan token dapat diprediksi secara deterministik melalui batas ukuran payload masukan.

2. Autonomous Multi-turn Loop

Agent berjalan dengan runtime ReAct (Reasoning + Acting) atau tool-calling. Agent dapat membaca file referensi di luar diff, mengeksekusi test suite, atau menelusuri definisi fungsi. Akurasi konteks meningkat, namun konsumsi token meningkat secara eksponensial karena riwayat chat terakumulasi di setiap turn. Latensi melonjak hingga puluhan detik atau beberapa menit, berpotensi memblokir runner CI/CD.

Metrik Pipeline, Biaya Token, dan Context Drift

Saat merancang sistem ini, perhatikan tiga metrik operasional:

  • Pipeline Latency: Panggilan LLM yang berjalan sinkron di CI runner mengonsumsi durasi runner berbayar. Solusinya, decoupling eksekusi review menggunakan async webhook orchestrator di luar jalur kritis testing dan deployment.
  • Token Amplification: Pada alur autonomous, mengirim ulang riwayat turn yang sama beserta tool outputs menyebabkan konsumsi token membesar secara kuadratik ($O(n^2)$) terhadap jumlah turn.
  • Context Window Drift & Hallucination: Menjejalkan ribuan baris kode mentah ke context window menyebabkan model kehilangan fokus pada chunk krusial (needle in a haystack problem), memicu false positive pada ulasan kode.

Strategi Optimasi Biaya dan Konteks

Diff Chunking Berbasis File Boundary

Hindari mengirim output git diff utuh. Filter file non-kritis (seperti lockfiles, minified assets, atau auto-generated migration schema) sebelum masuk ke payload. Pecah diff per modul atau file dan evaluasi secara paralel jika diperlukan.

Local AST Caching

Gunakan Abstract Syntax Tree (AST) parser lokal (seperti Tree-sitter) di sisi orchestrator untuk mengekstrak hanya signature fungsi yang berubah dan call graph terkait. Kirim representasi ringkas ini sebagai konteks tambahan ke prompt, alih-alih membaca keseluruhan isi file target dari repositori.

Fail-safe Gate

PR review adalah proses non-blocking untuk build passing. Jika kuota LLM habis (HTTP 429), token habis, atau endpoint timeout, orchestrator harus mengeksekusi degradasi graceful: log kegagalan, poskan komentar informatif di PR bahwa ulasan otomatis dilewati, lalu set status commit check menjadi neutral atau success agar developer tidak terblokir untuk merge.

Topologi Sistem

[Git Provider (Webhook)]
         |
         v
[Webhook Orchestrator]
   ├── 1. Filter files (.lock, generated, etc.)
   ├── 2. Parse AST / local symbol resolution
   ├── 3. Token budget evaluation
   └── 4. Queue / Async worker
              |
              v
      [LLM Review Engine] <--- Fail-safe on 429/timeout
              |
              v
[Git Provider API (Post Review Comments)]

Implementasi Webhook Orchestrator

Contoh implementasi webhook handler menggunakan Node.js dan standard HTTP library untuk menerima webhook, memvalidasi ukuran diff, dan membatasi pemanggilan LLM secara aman:

import { createServer } from 'node:http';

const MAX_DIFF_BYTES = 50_000; // Batasi ukuran payload diff

async function handlePRWebhook(payload) {
  const { action, pull_request } = payload;
  if (!['opened', 'synchronize'].includes(action)) {
    return { status: 'skipped', reason: 'unsupported action' };
  }

  // Fail-safe check 1: Hindari parsing payload raksasa
  if (pull_request.changed_files > 20) {
    return { status: 'skipped', reason: 'exceeds file change limit' };
  }

  const diffResponse = await fetch(pull_request.diff_url);
  const rawDiff = await diffResponse.text();

  if (Buffer.byteLength(rawDiff, 'utf8') > MAX_DIFF_BYTES) {
    // Fail-safe: Diff terlalu besar, degradasi tanpa call LLM
    await postPRComment(pull_request.comments_url, 'Automated Review: Diff melebihi kuota analisis langsung.');
    return { status: 'bypassed', reason: 'payload too large' };
  }

  const filteredDiff = filterDiff(rawDiff);

  try {
    const review = await callLLMSinglePass(filteredDiff);
    await postPRComment(pull_request.comments_url, review);
    return { status: 'completed' };
  } catch (err) {
    // Fail-safe check 2: Error LLM tidak boleh memutus workflow CI
    console.error('LLM Review failed:', err.message);
    await postPRComment(pull_request.comments_url, 'Automated Review: Sistem sedang tidak tersedia (fallback triggered).');
    return { status: 'failed_gracefully' };
  }
}

function filterDiff(diff) {
  // Buang diff file lock atau generated file
  return diff
    .split('diff --git')
    .filter(chunk => !chunk.includes('package-lock.json') && !chunk.includes('pnpm-lock.yaml'))
    .join('diff --git');
}

async function callLLMSinglePass(diffContext) {
  // ponytail: implementasi agentic multi-turn loop dapat menggantikan ini
  // jika membutuhkan penelusuran referensi dependensi multi-file.
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.LLM_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: 'Review diff berikut secara ringkas. Fokus pada bug logika dan security issue.' },
        { role: 'user', content: diffContext }
      ],
      temperature: 0.1
    })
  });

  if (!response.ok) {
    throw new Error(`Upstream LLM error: ${response.statusText}`);
  }

  const data = await response.json();
  return data.choices[0].message.content;
}

async function postPRComment(url, body) {
  await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ body })
  });
}

Panduan Pemilihan Arsitektur

Gunakan pendekatan Single-pass Linier untuk PR harian dengan volume tinggi guna menjaga latensi build di bawah 1 menit dan biaya API terkontrol. Gunakan Autonomous Multi-turn Loop secara selektif, misalnya hanya pada PR yang diberi label khusus (seperti security-audit) atau perubahan pada core module yang membutuhkan penelusuran context graph secara mendalam.