Inferensi LLM pada AI agent beroperasi dalam skala detik hingga menit, bukan milidetik. Menjalankan proses multi-step agent—seperti analisis konteks repositori, sintesis kode, dan eksekusi tool—secara sinkronis di HTTP thread utama memicu connection timeout (HTTP 504), memblokir I/O pool, dan merusak flow state developer. Pendekatan tangguh memerlukan decoupling request menggunakan pattern queue dan worker terdistribusi berbasis Redis.
Bottleneck Inferensi LLM dan Kebutuhan Asynchronous Execution
Saat developer memicu perintah refactoring berskala besar atau PR review otomatis, agent mengeksekusi siklus Thought-Action-Observation (ReAct loop) berulang kali. Setiap iterasi menghabiskan token context window yang besar dan memicu latency hingga belasan detik. Jika API gateway menahan request tersebut secara sinkronis, beban memory server akan melonjak akibat thread pool exhaustion.
Dengan mengalihkan tugas berat ke backend worker via Redis, API gateway langsung merespons dengan HTTP 202 Accepted dan Job ID dalam waktu kurang dari 50 milidetik. Developer tetap produktif tanpa menunggu spinner UI yang membeku.
Arsitektur Antrean dengan Redis dan BullMQ
Pattern ini memisahkan API gateway (producer) dari node pemrosesan (consumer/worker). BullMQ dipilih karena berjalan di atas Redis stream/sorted sets, mendukung delayed execution, state retries, concurrency limits, dan rate limiter bawaan.
// queue.ts: Inisialisasi Queue dan Producer
import { Queue } from 'bullmq';
import IORedis from 'ioredis';
const connection = new IORedis(process.env.REDIS_URL, { maxRetriesPerRequest: null });
export const aiAgentQueue = new Queue('ai-agent-tasks', {
connection,
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
removeOnComplete: 1000,
removeOnFail: 5000,
},
});
export async function dispatchAgentJob(jobId: string, payload: { prompt: string; contextHash: string }) {
return await aiAgentQueue.add('generate-code', payload, { jobId });
}Rate Limiting Berbasis Token Bucket Terdistribusi
Lonjakan pemanggilan agent paralel berisiko menabrak rate limit penyedia LLM (TPM/Tokens Per Minute atau RPM/Requests Per Minute), yang dapat memicu error HTTP 429 dan cascading failures di seluruh worker pool. BullMQ menyediakan kontrol rate limit native yang mengisolasi eksekusi antrean menggunakan algoritma token bucket.
// worker.ts: Worker dengan BullMQ Limiter dan SSE state publisher
import { Worker, Job } from 'bullmq';
import IORedis from 'ioredis';
import { callLLMProvider } from './llmService';
const redis = new IORedis(process.env.REDIS_URL, { maxRetriesPerRequest: null });
export const agentWorker = new Worker(
'ai-agent-tasks',
async (job: Job) => {
const { prompt, contextHash } = job.data;
// 1. Publish state: Running
await redis.publish(`job-channel:${job.id}`, JSON.stringify({ state: 'RUNNING', step: 'Analyzing Context' }));
// 2. Eksekusi inferensi AI
const result = await callLLMProvider(prompt);
// 3. Update status akhir
await redis.set(`job-result:${job.id}`, JSON.stringify(result), 'EX', 3600);
await redis.publish(`job-channel:${job.id}`, JSON.stringify({ state: 'COMPLETED', result }));
return result;
},
{
connection: redis,
concurrency: 5,
limiter: {
max: 60, // Max 60 request
duration: 60000 // per 60 detik (RPM protection)
}
}
);Sinkronisasi State ke UI Menggunakan Server-Sent Events (SSE)
Mekanisme polling HTTP konvensional membebani Redis dan database secara tidak perlu. Pola yang lebih efisien menggunakan Redis Pub/Sub yang di-bridge ke koneksi HTTP streaming via Server-Sent Events (SSE) untuk mengirimkan granular task execution updates langsung ke IDE atau UI klien.
// sse-route.ts: API Endpoint untuk SSE Stream
import { Request, Response } from 'express';
import IORedis from 'ioredis';
export async function handleJobSSE(req: Request, res: Response) {
const { jobId } = req.params;
const subRedis = new IORedis(process.env.REDIS_URL);
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
await subRedis.subscribe(`job-channel:${jobId}`);
subRedis.on('message', (channel, message) => {
res.write(`data: ${message}\n\n`);
const parsed = JSON.parse(message);
if (parsed.state === 'COMPLETED' || parsed.state === 'FAILED') {
subRedis.unsubscribe();
subRedis.quit();
res.end();
}
});
req.on('close', () => {
subRedis.unsubscribe();
subRedis.quit();
});
}Memangkas Eksekusi Redundant dengan Prompt Hash Caching
Dalam alur kerja developer, agent sering menerima konteks kode dan instruksi yang identik berulang kali (misalnya, unit test generation berulang tanpa perubahan file). Menghitung cryptographic digest (SHA-256) dari kombinasi prompt dan context snapshot mencegah eksekusi ganda yang membuang kuota LLM.
// cacheMiddleware.ts: Prompt Hash Deduplication
import crypto from 'crypto';
import IORedis from 'ioredis';
const redis = new IORedis(process.env.REDIS_URL);
export async function getOrSetAgentCache(prompt: string, contextFiles: string, executeAgent: () => Promise<any>) {
const payload = JSON.stringify({ prompt, contextFiles });
const hash = crypto.createHash('sha256').update(payload).digest('hex');
const cacheKey = `agent-cache:${hash}`;
const cachedResponse = await redis.get(cacheKey);
if (cachedResponse) {
return JSON.parse(cachedResponse); // Cache hit, hindari enqueuing LLM job
}
const freshOutput = await executeAgent();
await redis.set(cacheKey, JSON.stringify(freshOutput), 'EX', 86400); // Cache 24 jam
return freshOutput;
}Trade-offs dan Failure Modes
- Redis Memory Footprint: Menyimpan context window besar di Redis memori dapat menyebabkan eviction jika policy
noevictiontidak disetel. Simpan context berukuran megabyte di object storage (S3/GCS) dan kirimkan reference URI di job payload. - Sub/Pub Dropouts: Redis Pub/Sub bersifat fire-and-forget. Jika koneksi SSE klien putus sesaat, event update bisa hilang. Simpan state incremental di Redis Hash (misalnya
HSET job-state:<id>) agar klien dapat melakukan rehidrasi state saat reconnect. - Queue Poisoning: Task dengan input rusak yang memicu crash worker berulang kali harus diisolasi ke Dead Letter Queue (DLQ) menggunakan
attemptsthreshold yang ketat untuk mencegah starvation pada job valid lainnya.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!