TuBrief
구독 채널
비디오
커뮤니티

Architektur zur Bewältigung von Serverless Cold Starts und Kosten beim Vercel Eve Agenten

TuBrief 편집팀
2026년 7월 23일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Deutsch한국어EnglishEspañol中文हिन्दीFrançaisРусскийPortuguês日本語Bahasa Indonesiaالعربية

관련 영상

Ship 26 NYC – Workshop – Einen Agenten bauen mit Eve: Das Open-Agent-Framework39:50

Ship 26 NYC – Workshop – Einen Agenten bauen mit Eve: Das Open-Agent-Framework

Vercel

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Architektur zur Bewältigung von Serverless Cold Starts und Kosten beim Vercel Eve Agenten

Bringt man Vercel Eve-basierte Agenten in Produktion, wird man direkt mit der für Serverless typischen Zustandslosigkeit (Statelessness) konfrontiert. Nach Abschluss einer Anfrage wird die Instanz geschlossen und der Ausführungszustand geht verloren. Greift man jedoch zur Wiederherstellung der Sitzung jedes Mal auf eine Datenbank wie PostgreSQL zu, führt dies zu Latenzen von über 100 ms und rapide steigenden Datenbankkosten.

Um diese Serverless-Einschränkungen zu überwinden, sind hier drei Architekturen zusammengefasst, die in echten Produktionsumgebungen eingesetzt werden.

1. Sitzungswiederherstellungs-Latenz mit Upstash Redis auf unter 50 ms senken

Das jedesmalige Neuaufbauen von Datenbankverbindungen in einer Serverless-Umgebung ist der schnellste Weg, sowohl Infrastrukturkosten als auch Antwortzeiten zu ruinieren. Wenn man Upstash Redis, das über eine HTTP REST API kommuniziert, als Sitzungscache-Layer einsetzt, lässt sich dieses Problem lösen.

`
[User Request]
│
▼
┌──────────────┐ < 50ms (HTTP REST) ┌────────────────────────┐
│ Vercel Eve │ ────────────────────────> │ Upstash Redis │
│ Agent │ <──────────────────────── │ (Session State Storage)│
└──────────────┘ Session Context Restored└────────────────────────┘
│
│ Compress History (Sliding Window + Summary)
▼
┌──────────────┐
│ LLM Provider │
└──────────────┘

`

Sitzungsdaten werden über die REST API in unter 50 ms abgerufen. Das Ersetzen direkter RDB-Abfragen durch Cache-Zugriffe reduziert den Verbrauch der Read Capacity erheblich.

Bewertungskriterium Traditionelle RDB (PostgreSQL) DynamoDB (On-Demand) Upstash Redis (HTTP REST)
Verbindungsart TCP Socket AWS SDK HTTP/REST API
Durchschnittliche Lese-Latenz 50ms - 200ms 10ms - 20ms 1ms - 5ms (Edge < 50ms)
Serverless-Eignung Niedrig (Connection Exhaustion) Mittel (Verbindungsverzögerung vorhanden) Sehr hoch (Scale-to-Zero-Unterstützung)
Kostenstruktur Abrechnung pro Stunde der bereitgestellten Instanz Abrechnung pro RCU/WCU-Anfrageeinheit Abrechnung pro Command-Anfrageeinheit ($0.20/100k)
Haupteinsatzzweck ACID-Transaktionen, Originalspeicherung Permanente Datenspeicherung und -suche Sitzungscaching, Rate Limit, Agentenspeicher

Der Code zum Speichern und Wiederherstellen von Sitzungen wird einfach gehalten.

`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 });
}

`

Wenn Gespräche länger werden, steigen die Token-Kosten kontinuierlich. Es wird ein Ansatz gewählt, bei dem nur die letzten 6 Turns im Original belassen und ältere Gespräche mit einem leichtgewichtigen Modell zusammengefasst und oben im Prompt platziert werden.

`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: `Fassen Sie nur die Kernfakten und Entscheidungen der folgenden Konversation in unter 200 Zeichen zusammen:

${JSON.stringify(olderMessages)}`,
});

const compressedHistory: Message[] = [];
if (systemMessage) compressedHistory.push(systemMessage);
compressedHistory.push({
role: "system",
content: [Zusammenfassung der bisherigen Konversation]: ${summaryResponse.text},
});
compressedHistory.push(...recentMessages);

return compressedHistory;
}

`

2. Schutzmuster gegen Latenz und Ausfälle externer APIs

Treffen Aufrufe externer Tools auf 429- (Rate Limit) oder 5xx-Fehler, bricht die gesamte Inferenz des Agenten zusammen. Es müssen exponentieller Backoff mit Full Jitter und Circuit Breaker implementiert werden.

Die Formel für den exponentiellen Backoff erhöht die Wartezeit nicht rein proportional, sondern mischt Zufallswerte ein, um Engpässe zu vermeiden.

Textdelay=minleft(Textmax,Textbaseimes2extattemptight)imesleft(0.5+extrandom(0,1.0)ight)T_{ ext{delay}} = minleft(T_{ ext{max}}, T_{ ext{base}} imes 2^{ ext{attempt}} ight) imes left(0.5 + ext{random}(0, 1.0) ight)Textdelay​=minleft(Textmax​,Textbase​imes2extattemptight)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));
}

}
}

`

Dauern Störungen länger an, blockiert der Circuit Breaker Anfragen sofort (Fail-Fast) und führt eine Fallback-Logik aus.

Antwortstatus der externen API Circuit-Breaker-Status Funktionsmechanismus Agenten-Verarbeitungsergebnis
HTTP 200 OK Closed Normaler Durchlass und Erhöhung des Erfolgszählers Normale Einspeisung externer Daten in den Agenten
HTTP 429 / 503 Closed $
ightarrow$ Open Ausführung des exponentiellen Backoffs; bei Erreichen der Fehlerratenschwelle $
ightarrow$ Open Abbrechen nach Wiederholungsversuchen und Öffnen des Schaltkreises
Circuit OPEN Status Open Blockieren von Netzwerkanfragen an externe APIs (Fail-Fast) Verwendung alternativer Tools oder Ausgabe einer Fallback-Nachricht
Nach Ablaufen des Cooldowns Half-Open Überprüfung der Wiederherstellung des externen Dienstes durch eine einzelne Probing-Anfrage Bei Erfolg Normalisierung des Schaltkreises, bei Misserfolg erneutes Blockieren

`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. Asynchrone Genehmigungsanbindung ohne Timeouts (Human-in-the-loop)

Serverless-Funktionen unterliegen Ausführungszeitbegrenzungen. Läst man Anfragen offen stehen, während man auf Zahlungs- oder DB-Löschgenehmigungen wartet, kommt es zu Timeout-Fehlern.

`
[Agent Action] ──> Eve Tool (needsApproval: true)
│
▼
[Checkpoint Saved & Instance Terminated]
│
├─> Slack Notification (Interactive Card)
│
[Human Approve] ───────>│ (Webhook POST Callback)
│
▼
[Resume Agent & Proceed Transaction]

`

Dem Eve-Tool wird needsApproval: true zugewiesen, um die Ausführung zu stoppen und nur einen Checkpoint zu hinterlassen.

`typescript
import { defineTool } from "@vercel/eve";
import { z } from "zod";

export const deleteDatabaseTool = defineTool({
name: "delete_database",
description: "Löscht permanente Datenbankeinträge eines bestimmten Mandanten.",
needsApproval: true,
input: z.object({
tenantId: z.string(),
reason: z.string(),
}),
execute: async ({ tenantId }) => {
return await db.tenant.delete({ where: { id: tenantId } });
},
});

`

Menschliche Genehmigungen werden über Webhook-Callbacks empfangen, um den Prozess fortzusetzen.

`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. Prompt-Validierung in der CI/CD-Phase und Canary-Routing

Halluzinationsphänomene, die nach Änderungen an Prompts auftreten, lassen sich durch manuelles Testen nur schwer erfassen. Die Pipeline wird so aufgebaut, dass ein PR erst gemergt werden kann, wenn die DeepEval-Metriken bestanden wurden.

Bewertungsmetrik Akzeptanzschwelle Bewertungskriterium
Faithfulness ge0.85ge 0.85ge0.85 Vorhandensein von Faktenverzerrungen im Vergleich zum bereitgestellten Kontext
Answer Relevancy ge0.75ge 0.75ge0.75 Übereinstimmung mit dem Zweck der Benutzerfrage
Hallucination Rate le0.10le 0.10le0.10 Anteil von Halluzinationen im Test-Set
Tool Calling Accuracy ge0.90ge 0.90ge0.90 Auswahltreue korrekter OpenAPI-Spezifikations-Tools und Typeneinhaltung

In GitHub Actions wird Pytest ausgeführt, um den Build zu blockieren, wenn die Schwellenwerte nicht erreicht werden.

`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

`

Bei der Bereitstellung werden Edge Config und Middleware gekoppelt, um den neuen Prompt zunächst nur auf 10 % des Traffics anzuwenden.

`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*',
};

`