Integrasi API Large Language Model (LLM) seperti Cohere Command atau Claude untuk tugas coding sering terbentur restriksi ketat Rate Limit (Requests Per Minute dan Tokens Per Minute) serta latensi inferensi yang tinggi. Tanpa arsitektur kontrol konkurensi di sisi backend, lonjakan request serentak memicu error 429 Too Many Requests yang berujung pada kegagalan eksekusi di sisi klien.

Solusi arsitektural untuk problem ini bertumpu pada dua layer utama: Queue Buffer untuk meratakan lonjakan beban (smoothing out bursts) via antrian asinkron, dan Semantic Normalization Cache untuk memotong konsumsi token pada instruksi kode yang berulang atau identik secara struktural.

Arsitektur Sistem: Buffer dan Deduplikasi

Pola komunikasi langsung (synchronous direct call) dari client ke endpoint LLM rentan menghasilkan bottleneck. Desain yang lebih stabil memposisikan message broker sebagai buffer idempotensi.

  • Klien/API Gateway: Mengirim payload instruksi, menghasilkan hash deterministik sebagai Job ID, lalu mengecek cache.
  • Semantic Cache (Redis Key-Value): Menyimpan hasil inferensi berdasarkan hash AST/normalisasi whitespace kode input. Hit cache langsung merespons klien tanpa inferensi upstream.
  • Queue Worker (Celery): Membatasi konkurensi worker via token bucket lokal. Jika upstream mengembalikan status 429, worker mengaktifkan distributed lock untuk menghentikan request lain sementara waktu seraya mengeksekusi exponential backoff.

Implementasi Normalisasi & Cache Hashing

Snippet kode sering kali hanya berbeda pada identasi, format whitespace, atau penamaan variabel sementara. Menggunakan hash teks mentah (SHA-256 langsung) memiliki rasio cache hit yang rendah. Normalisasi kode sebelum hashing meningkatkan efisiensi cache secara signifikan.

import hashlib
import re

def normalize_code_snippet(code: str) -> str:
    # Hapus inline dan block comments sederhana
    code = re.sub(r'#.*$', '', code, flags=re.MULTILINE)
    # Standarisasi baris kosong dan whitespace trailing
    lines = [line.rstrip() for line in code.splitlines() if line.strip()]
    return "\n".join(lines)

def generate_semantic_cache_key(prompt: str, code: str, model: str) -> str:
    clean_code = normalize_code_snippet(code)
    payload = f"{model}:{prompt.strip()}:{clean_code}"
    return f"llm_cache:{hashlib.sha256(payload.encode('utf-8')).hexdigest()}"

Implementasi Worker Celery dengan Distributed Backoff

Worker berikut mengeksekusi inferensi upstream, memeriksa cache Redis, dan menangani error 429 dengan memanfaatkan Redis lock agar worker lain tidak memperburuk status token bucket upstream.

import time
import redis
from celery import Celery
from celery.exceptions import MaxRetriesExceededError

app = Celery('llm_tasks', broker='redis://localhost:6379/0', backend='redis://localhost:6379/1')
redis_client = redis.Redis(host='localhost', port=6379, db=0)

RATE_LIMIT_LOCK_KEY = "lock:llm:rate_limit"

class UpstreamRateLimitException(Exception):
    pass

def mock_call_llm_api(prompt: str, code: str) -> str:
    # Simulasi interaksi API LLM
    # Raise UpstreamRateLimitException jika API merespons HTTP 429
    return f"Refactored: {code}"

@app.task(bind=True, max_retries=5, default_retry_delay=5)
def process_code_llm_task(self, prompt: str, code: str, model: str = "command-r"):
    cache_key = generate_semantic_cache_key(prompt, code, model)
    
    # 1. Cek Semantic Cache
    cached_response = redis_client.get(cache_key)
    if cached_response:
        return {"result": cached_response.decode('utf-8'), "source": "cache"}

    # 2. Cek apakah upstream sedang dalam status backoff global
    if redis_client.get(RATE_LIMIT_LOCK_KEY):
        retry_sec = self.default_retry_delay * (2 ** self.request.retries)
        raise self.retry(countdown=retry_sec)

    try:
        # 3. Eksekusi Inferensi
        response_text = mock_call_llm_api(prompt, code)
        
        # Simpan ke cache (TTL 24 jam)
        redis_client.setex(cache_key, 86400, response_text)
        return {"result": response_text, "source": "inference"}

    except UpstreamRateLimitException as exc:
        # Pasang distributed lock selama masa cooldown (misal 30 detik)
        redis_client.set(RATE_LIMIT_LOCK_KEY, "blocked", ex=30)
        
        # Exponential backoff: 5s, 10s, 20s, 40s...
        countdown = self.default_retry_delay * (2 ** self.request.retries)
        try:
            raise self.retry(exc=exc, countdown=countdown)
        except MaxRetriesExceededError:
            return {"error": "Rate limit upstream habis. Coba beberapa saat lagi.", "source": "exhausted"}

Menjaga Idempotensi dan Penanganan Dead Letter Queue

Tugas inferensi LLM memakan waktu dan komputasi. Kegagalan worker atau koneksi terputus tidak boleh menghasilkan eksekusi ganda yang memboroskan kuota token:

  1. Task ID Deterministik: Gunakan cache_key sebagai ID Celery task (task_id=cache_key). Dengan pola ini, pemanggilan berulang dengan payload yang sama pada jendela waktu aktif tidak akan menduplikasi antrian di broker.
  2. Pemisahan Dead-Letter Queue (DLQ): Route task yang gagal melampaui max_retries ke antrian khusus (DLQ) untuk analisis error tanpa memblokir pipeline utama.
  3. Penyesuaian Concurrency Worker: Batasi parameter --concurrency pada worker Celery agar selaras dengan batas maksimum RPM akun LLM, mencegah burst tak terkontrol sejak level client pool.