Menggabungkan infrastruktur model bahasa lokal (seperti Ollama atau vLLM) dengan provider cloud berbayar (seperti OpenAI atau Anthropic) sering kali menimbulkan tantangan konsistensi. Tanpa kontrol deterministik, sistem rentan terhadap masalah kegagalan parsial: inferensi lokal mengalami timeout di tengah proses, memicu fallback ke API cloud, dan secara tidak sengaja menghasilkan eksekusi ganda yang membebani biaya kuota dan komputasi.
Artikel ini membahas implementasi gateway router deterministik. Arsitektur ini memastikan setiap request dipetakan berdasarkan kontrak payload yang ketat, memitigasi kegagalan jaringan secara terukur, dan menjamin idempotency di setiap lapisan rute fallback.
Standardisasi Kontrak Payload Request dan Response
Langkah fundamental dalam routing deterministik adalah menetapkan satu skema kanonikal di tingkat gateway sebelum payload dialihkan ke backend tujuan. Pendekatan ini mengisolasi variasi format vendor dari kode aplikasi utama.
import { z } from "zod";
// Skema kanonikal internal gateway
export const LLMRequestSchema = z.object({
idempotency_key: z.string().uuid(),
model_tier: z.enum(["latency_critical", "cost_optimized"]),
prompt: z.string().min(1),
max_tokens: z.number().int().positive().default(512),
temperature: z.number().min(0).max(1).default(0.2)
});
export type LLMRequest = z.infer<typeof LLMRequestSchema>;
export const LLMResponseSchema = z.object({
execution_id: z.string(),
provider: z.enum(["local_vllm", "cloud_openai"]),
content: z.string(),
cached: z.boolean().default(false)
});
export type LLMResponse = z.infer<typeof LLMResponseSchema>;
Validasi skema runtime di lapisan ingress menjamin bahwa atribut penting seperti idempotency_key selalu tersedia sebelum alokasi sumber daya dimulai.
Penanganan Idempotency dan Isolasi Eksekusi Berbayar
Eksekusi ganda pada API cloud terjadi ketika request pertama mengalami kegagalan transmisi jaringan (misalnya HTTP 504) padahal upstream server telah memproses token tersebut. Fallback otomatis ke target lain tanpa mekanisme deduplikasi akan melipatgandakan biaya.
Untuk mengatasinya, gunakan status mutasi berbasis atomik pada shared cache (seperti Redis) dengan siklus status yang terdefinisi:
- PENDING: Request telah diterima router dan dikirim ke backend lokal.
- EXECUTING_FALLBACK: Backend lokal gagal secara terverifikasi (koneksi terputus atau timeout tuntas); request dialihkan ke backend cloud dengan
Idempotency-Keyyang sama pada HTTP header. - COMPLETED: Hasil inferensi berhasil diperoleh dan disimpan di cache. Request susulan dengan ID yang sama langsung membaca hasil ini.
Peringatan: Jangan pernah mengalihkan eksekusi ke cloud API jika status respons backend lokal masih ambigu, kecuali jika pembatalan token context (misalnya melalui AbortController) telah dikonfirmasi tuntas pada socket lokal.
Implementasi Middleware Routing dan Fallback Deterministik
Berikut adalah implementasi gateway minimal menggunakan Node.js dan TypeScript yang menangani timeout eksplisit, pembatalan sinyal, dan alihan rute deterministik.
import { LLMRequest, LLMResponse } from "./schemas";
interface ProviderConfig {
baseUrl: string;
timeoutMs: number;
}
const LOCAL_CONFIG: ProviderConfig = { baseUrl: "http://localhost:8000/v1", timeoutMs: 1500 };
const CLOUD_CONFIG: ProviderConfig = { baseUrl: "https://api.openai.com/v1", timeoutMs: 5000 };
async function executeInference(
url: string,
payload: LLMRequest,
timeoutMs: number,
apiKey?: string
): Promise<string> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"Idempotency-Key": payload.idempotency_key
};
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
const res = await fetch(`${url}/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify({
prompt: payload.prompt,
max_tokens: payload.max_tokens,
temperature: payload.temperature
}),
signal: controller.signal
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
return data.choices[0].text;
} finally {
clearTimeout(timeoutId);
}
}
export async function routeLLMRequest(
req: LLMRequest,
cloudApiKey: string
): Promise<LLMResponse> {
// Rute deterministik: Cost optimized mengutamakan lokal
if (req.model_tier === "cost_optimized") {
try {
const localResult = await executeInference(LOCAL_CONFIG.baseUrl, req, LOCAL_CONFIG.timeoutMs);
return {
execution_id: req.idempotency_key,
provider: "local_vllm",
content: localResult,
cached: false
};
} catch (err: unknown) {
// Hanya fallback jika error disebabkan timeout atau network down (lokal mati)
const isAbort = err instanceof Error && err.name === "AbortError";
const isConnRefused = err instanceof Error && err.message.includes("ECONNREFUSED");
if (!isAbort && !isConnRefused) {
throw err; // Lempar jika error validasi atau parameter ilegal
}
}
}
// Fallback deterministik ke API cloud
const cloudResult = await executeInference(
CLOUD_CONFIG.baseUrl,
req,
CLOUD_CONFIG.timeoutMs,
cloudApiKey
);
return {
execution_id: req.idempotency_key,
provider: "cloud_openai",
content: cloudResult,
cached: false
};
}
Menangani Edge Cases pada Kondisi Jaringan
Ketika menerapkan fallback otomatis di lingkungan produksi, perhatikan edge case berikut:
- Kelebihan Beban Bersamaan (Thundering Herd): Jika instance vLLM lokal crash, semua traffic akan langsung dialihkan ke cloud secara simultan. Pasang rate-limiter tipe leaky-bucket sebelum request masuk ke provider berbayar untuk mencegah tagihan membengkak drastis.
- Abort Signal Unhandled di Sisi Worker: Menutup koneksi HTTP client lokal dengan
AbortControllertidak otomatis menghentikan antrean pemrosesan di GPU worker jika engine inferensi tidak mendukung preemptive cancellation. Pastikan backend lokal mengaktifkan fitur continuous batching preemption. - Retry Mengambang: Terapkan algoritma exponential backoff dengan jitter hanya pada error jaringan cloud level transport (seperti HTTP 429 atau HTTP 503). Jangan mengulang payload yang menghasilkan status HTTP 400 atau 422.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!