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:
- Task ID Deterministik: Gunakan
cache_keysebagai 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. - Pemisahan Dead-Letter Queue (DLQ): Route task yang gagal melampaui
max_retrieske antrian khusus (DLQ) untuk analisis error tanpa memblokir pipeline utama. - Penyesuaian Concurrency Worker: Batasi parameter
--concurrencypada worker Celery agar selaras dengan batas maksimum RPM akun LLM, mencegah burst tak terkontrol sejak level client pool.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!