Struktur Mengatasi Serverless Cold Start dan Biaya pada Agen Vercel Eve
Ketika menerapkan agen berbasis Vercel Eve ke lingkungan produksi, Anda akan langsung berhadapan dengan sifat stateless khas dari serverless. Begitu permintaan selesai, instans ditutup dan status eksekusi hilang. Namun, memulihkan sesi dengan selalu mengakses DB seperti PostgreSQL setiap saat akan menyebabkan latensi di atas 100ms beserta lonjakan biaya DB yang drastis.
Untuk mengatasi keterbatasan serverless, berikut ringkasan tiga struktur yang digunakan di lingkungan produksi nyata.
1. Menurunkan Latensi Pemulihan Sesi di Bawah 50ms dengan Upstash Redis
Di lingkungan serverless, membuka koneksi DB baru setiap kali adalah cara tercepat yang merusak biaya infrastruktur sekaligus kecepatan respons. Masalah ini dapat diselesaikan dengan menempatkan Upstash Redis, yang berkomunikasi melalui HTTP REST API, sebagai session cache layer.
`
[User Request]
│
▼
┌──────────────┐ < 50ms (HTTP REST) ┌────────────────────────┐
│ Vercel Eve │ ────────────────────────> │ Upstash Redis │
│ Agent │ <──────────────────────── │ (Session State Storage)│
└──────────────┘ Session Context Restored└────────────────────────┘
│
│ Compress History (Sliding Window + Summary)
▼
┌──────────────┐
│ LLM Provider │
└──────────────┘
`
Data sesi diambil melalui REST API dalam waktu kurang dari 50ms. Mengganti kueri RDB langsung dengan cache akan mengurangi konsumsi Read Capacity secara signifikan.
| Item Evaluasi |
RDB Tradisional (PostgreSQL) |
DynamoDB (On-Demand) |
Upstash Redis (HTTP REST) |
| Metode Koneksi |
TCP Socket |
AWS SDK |
HTTP/REST API |
| Latensi Baca Rata-rata |
50ms - 200ms |
10ms - 20ms |
1ms - 5ms (Edge < 50ms) |
| Kesesuaian Serverless |
Rendah (Connection Exhaustion) |
Sedang (Terdapat latensi koneksi) |
Sangat Tinggi (Mendukung Scale-to-Zero) |
| Struktur Biaya |
Penagihan per jam instans ter-provisioning |
Penagihan per unit permintaan RCU/WCU |
Penagihan per unit permintaan Command ($0.20/100k) |
| Tujuan Penggunaan Utama |
Transaksi ACID, penyimpanan asli |
Penyimpanan & pencarian data permanen |
Caching sesi, Rate Limit, memori agen |
Kode untuk menyimpan dan memulihkan sesi dibuat tetap sederhana.
`typescript
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
interface AgentSessionContext {
userId: string;
currentStep: string;
intermediateThoughts: Record<string, unknown>[];
lastActiveTimestamp: number;
}
export async function restoreSessionContext(sessionId: string): Promise<AgentSessionContext | null> {
const cacheKey = session:context:${sessionId};
const cachedContext = await redis.get(cacheKey);
return cachedContext ?? null;
}
export async function saveSessionContext(
sessionId: string,
context: AgentSessionContext,
ttlSeconds: number = 3600
): Promise {
const cacheKey = session:context:${sessionId};
await redis.set(cacheKey, JSON.stringify(context), { ex: ttlSeconds });
}
`
Seiring memanjangnya percakapan, biaya token akan terus membengkak. Metode yang digunakan adalah mempertahankan 6 turn terakhir sebagai data asli, sementara percakapan sebelumnya diringkas menggunakan model ringan dan ditempatkan di bagian atas prompt.
`typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
interface Message {
role: "user" | "assistant" | "system";
content: string;
}
export async function compressConversationHistory(
messages: Message[],
recentWindowSize: number = 6
): Promise<Message[]> {
if (messages.length <= recentWindowSize) return messages;
const systemMessage = messages.find((m) => m.role === "system");
const nonSystemMessages = messages.filter((m) => m.role !== "system");
const olderMessages = nonSystemMessages.slice(0, nonSystemMessages.length - recentWindowSize);
const recentMessages = nonSystemMessages.slice(nonSystemMessages.length - recentWindowSize);
const summaryResponse = await generateText({
model: openai("gpt-4o-mini"), prompt: Ringkas fakta kunci dan keputusan dari percakapan berikut dalam waktu kurang dari 200 karakter:\n\n${JSON.stringify(olderMessages)},
});
const compressedHistory: Message[] = [];
if (systemMessage) compressedHistory.push(systemMessage);
compressedHistory.push({
role: "system",
content: [Ringkasan Percakapan Sebelumnya]: ${summaryResponse.text},
});
compressedHistory.push(...recentMessages);
return compressedHistory;
}
`
2. Pola Pertahanan Latensi dan Kegagalan API Eksternal
Jika mengalami kesalahan 429 (Rate Limit) atau 5xx saat memanggil tool eksternal, seluruh proses penalaran agen akan terhenti. Oleh karena itu, perlu dipasang exponential backoff yang dikombinasikan dengan Full Jitter serta circuit breaker.
Rumus exponential backoff menghindari kemacetan dengan menambahkan angka acak alih-alih meningkatkan waktu tunggu secara berbanding lurus.
Textdelay=minleft(Textmax,Textbaseimes2extattemptight)imesleft(0.5+extrandom(0,1.0)ight)`typescript
export interface RetryConfig {
maxRetries: number;
baseDelayMs: number;
maxDelayMs: number;
}
export async function executeWithExponentialBackoff(
fn: () => Promise,
config: RetryConfig = { maxRetries: 3, baseDelayMs: 200, maxDelayMs: 8000 }
): Promise {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (error: any) {
attempt++;
const statusCode = error?.status || error?.response?.status;
const isUnretryable = statusCode && statusCode >= 400 && statusCode < 500 && statusCode !== 429;
if (attempt > config.maxRetries || isUnretryable) throw error;
const calculatedDelay = Math.min(
config.maxDelayMs,
config.baseDelayMs * Math.pow(2, attempt)
);
const jitteredDelay = calculatedDelay * (0.5 + Math.random());
await new Promise((resolve) => setTimeout(resolve, jitteredDelay));
}
}
}
`
Jika gangguan berlangsung lama, circuit breaker akan langsung memblokir permintaan (Fail-Fast) dan menjalankan logika Fallback.
| Status Respons API Eksternal |
Status Circuit Breaker |
Mekanisme Kerja |
Hasil Pemrosesan Agen |
| HTTP 200 OK |
Closed |
Lolos secara normal & menaikkan penghitung keberhasilan |
Menyuplai data eksternal ke agen secara normal |
| HTTP 429 / 503 |
Closed $ |
|
|
| ightarrow$ Open |
Mengeksekusi exponential backoff lalu menjadi Open jika ambang tingkat kegagalan tercapai |
Sirkuit terbuka setelah penanganan ulang |
|
| Status Circuit OPEN |
Open |
Memblokir permintaan jaringan API eksternal (Fail-Fast) |
Menggunakan Tool alternatif atau menampilkan pesan Fallback |
| Setelah Cooldown Kedaluwarsa |
Half-Open |
Memverifikasi pemulihan layanan eksternal dengan permintaan Probing tunggal |
Sirkuit normal jika berhasil, sirkuit terblokir kembali jika gagal |
`typescript
export class CircuitBreaker {
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
private failureCount = 0;
private lastStateChange = Date.now();
constructor(
private failureThreshold: number = 5,
private cooldownPeriodMs: number = 30000
) {}
async execute(requestFn: () => Promise, fallbackFn: () => Promise): Promise {
const now = Date.now();
if (this.state === 'OPEN') {
if (now - this.lastStateChange > this.cooldownPeriodMs) {
this.state = 'HALF_OPEN';
this.lastStateChange = now;
} else {
return await fallbackFn();
}
}
try {
const result = await requestFn();
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
this.failureCount = 0;
this.lastStateChange = now;
}
return result;
} catch (error) {
this.failureCount++;
if (this.failureCount >= this.failureThreshold || this.state === 'HALF_OPEN') {
this.state = 'OPEN';
this.lastStateChange = now;
}
return await fallbackFn();
}
}
}
`
3. Integrasi Persetujuan Asinkron Tanpa Timeout (Human-in-the-loop)
Fungsi serverless memiliki batasan waktu eksekusi. Biarkan permintaan tetap terbuka hanya untuk menunggu persetujuan pembayaran atau penghapusan DB akan menyebabkan kesalahan timeout.
`
[Agent Action] ──> Eve Tool (needsApproval: true)
│
▼
[Checkpoint Saved & Instance Terminated]
│
├─> Slack Notification (Interactive Card)
│
[Human Approve] ───────>│ (Webhook POST Callback)
│
▼
[Resume Agent & Proceed Transaction]
`
Berikan needsApproval: true pada tool Eve, hentikan eksekusi, dan menyisakan checkpoint saja.
`typescript
import { defineTool } from "@vercel/eve";
import { z } from "zod";
export const deleteDatabaseTool = defineTool({
name: "delete_database",
description: "Menghapus rekaman basis data permanen dari penyewa tertentu.",
needsApproval: true,
input: z.object({
tenantId: z.string(),
reason: z.string(),
}),
execute: async ({ tenantId }) => {
return await db.tenant.delete({ where: { id: tenantId } });
},
});
`
Persetujuan manusia diterima melalui webhook callback untuk melanjutkan proses.
`typescript
import { createWebhook } from "@vercel/workflows";
export async function handleApprovalWorkflow(event: { approvalId: string; payload: any }) {
const webhook = createWebhook();
await sendSlackApprovalCard({
approvalId: event.approvalId,
callbackUrl: webhook.url,
payload: event.payload,
});
try {
const { approved, userReason } = await webhook.timeout("12h");
if (!approved) {
await rollbackPreviousSteps(event.payload);
return { status: "REJECTED", reason: userReason };
}
return await proceedAction(event.payload);
} catch (error) {
await rollbackPreviousSteps(event.payload);
return { status: "TIMEOUT_CANCELLED" };
}
}
`
4. Validasi Prompt dan Canary Routing pada Tahap CI/CD
Fenomena halusinasi yang terjadi setelah modifikasi prompt sulit dideteksi melalui pengujian manual. Alur pipa (pipeline) dirancang sedemikian rupa sehingga PR hanya akan digabungkan jika berhasil lolos dari indikator DeepEval.
| Indikator Evaluasi |
Ambang Penerimaan |
Kriteria Evaluasi |
| Faithfulness |
ge0.85 |
Ada/tidaknya distorsi fakta dibandingkan Context yang diberikan |
| Answer Relevancy |
ge0.75 |
Tingkat kesesuaian dengan tujuan pertanyaan pengguna |
| Hallucination Rate |
le0.10 |
Rasio terjadinya halusinasi di dalam test set |
| Tool Calling Accuracy |
ge0.90 |
Pemilihan tool spesifikasi OpenAPI yang benar dan tingkat kepatuhan tipe |
Jalankan Pytest di GitHub Actions untuk memblokir build jika berada di bawah ambang batas.
`yaml
name: Eve Agent Prompt Evaluation Pipeline
on:
pull_request:
branches: [ main ]
jobs:
evaluate-agent:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Evaluation Dependencies
run: |
pip install deepeval pytest
- name: Run DeepEval Regression Suite
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
pytest test_agent_evals.py --deepeval-metric-threshold=0.85
`
Saat deployment, hubungkan Edge Config dan middleware untuk menerapkan prompt baru hanya pada 10% dari lalu lintas terlebih dahulu.
`typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { get } from '@vercel/edge-config';
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
let variant = req.cookies.get('agent_canary_variant')?.value;
if (!variant) {
const canaryRate = (await get('canary_traffic_rate')) || 0.10;
variant = Math.random() < canaryRate ? 'canary' : 'control';
res.cookies.set('agent_canary_variant', variant, { path: '/', httpOnly: true });
}
res.headers.set('x-agent-prompt-version', variant === 'canary' ? 'v2-canary' : 'v1-stable');
return res;
}
export const config = {
matcher: '/api/agent/:path*',
};
`