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

Une architecture pour surmonter le démarrage à froid serverless et réduire les coûts des agents Vercel Eve

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

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

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

관련 영상

Ship 26 NYC - Atelier - Créez un agent avec Eve : Le framework d'agents open source39:50

Ship 26 NYC - Atelier - Créez un agent avec Eve : Le framework d'agents open source

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
구독 채널
비디오
커뮤니티
로그인

Une architecture pour surmonter le démarrage à froid serverless et réduire les coûts des agents Vercel Eve

Lorsqu'on déploie un agent basé sur Vercel Eve en production, on se heurte immédiatement à l'absence d'état (statelessness) propre au serverless. Une fois la requête terminée, l'instance se ferme et l'état d'exécution est perdu. Pour autant, interroger une base de données comme PostgreSQL à chaque fois afin de restaurer la session entraîne un temps de latence de plus de 100 ms et une explosion des coûts de base de données.

Pour dépasser les limites du serverless, voici le récapitulatif de trois architectures utilisées en production réelle.

1. Réduire la latence de restauration de session sous les 50 ms avec Upstash Redis

Dans un environnement serverless, ouvrir une nouvelle connexion à la base de données à chaque fois est le moyen le plus rapide de gâcher à la fois les coûts d'infrastructure et le temps de réponse. Placer Upstash Redis, qui communique via une API REST HTTP, comme couche de cache de session permet de résoudre ce problème.

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

`

Les données de session sont récupérées en moins de 50 ms via l'API REST. Le fait de remplacer les requêtes directes à la BDR par du cache réduit considérablement la consommation de capacité de lecture (Read Capacity).

Critère d'évaluation BDR traditionnelle (PostgreSQL) DynamoDB (On-Demand) Upstash Redis (HTTP REST)
Mode de connexion Socket TCP AWS SDK API HTTP/REST
Latence moyenne de lecture 50ms - 200ms 10ms - 20ms 1ms - 5ms (Edge < 50ms)
Adaptabilité au Serverless Faible (Connection Exhaustion) Moyenne (latence de connexion présente) Très haute (Support de Scale-to-Zero)
Structure des coûts Facturation à l'heure d'instance provisionnée Facturation à la requête RCU/WCU Facturation à la commande ($0.20/100k)
Usage principal Transactions ACID, stockage source Stockage permanent et recherche Cache de session, Rate Limit, mémoire de l'agent

Le code de stockage et de restauration de session reste simple.

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

`

À mesure que la conversation s'allonge, les coûts en tokens augmentent régulièrement. On utilise une approche consistant à conserver uniquement les 6 derniers tours de parole sous leur forme originale, tout en résumant la conversation antérieure à l'aide d'un modèle léger placé en haut du 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: 다음 대화의 핵심 사실과 결정 사항만 200자 이내로 요약하세요:\n\n${JSON.stringify(olderMessages)},
});

const compressedHistory: Message[] = [];
if (systemMessage) compressedHistory.push(systemMessage);
compressedHistory.push({
role: "system",
content: [이전 대화 요약]: ${summaryResponse.text},
});
compressedHistory.push(...recentMessages);

return compressedHistory;
}

`

2. Pattern de protection contre les latences et défaillances des API externes

Si l'appel d'un outil externe rencontre une erreur 429 (Rate Limit) ou 5xx, c'est l'ensemble du raisonnement de l'agent qui s'effondre. Il faut mettre en place un backoff exponentiel intégrant du Full Jitter ainsi qu'un disjoncteur (circuit breaker).

La formule du backoff exponentiel évite les engorgements en combinant une augmentation proportionnelle du temps d'attente avec une valeur aléatoire.

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

}
}

`

Si la panne se prolonge, le disjoncteur bloque immédiatement les requêtes (Fail-Fast) et déclenche la logique de fallback.

État de réponse de l'API externe État du disjoncteur Mécanisme de fonctionnement Résultat de traitement de l'agent
HTTP 200 OK Closed Passage normal et incrémentation du compteur de succès Fournit normalement les données externes à l'agent
HTTP 429 / 503 Closed $
ightarrow$ Open Exécution du backoff exponentiel, puis passage en Open si le seuil d'échec est atteint Ouverture du circuit après tentatives
État Circuit OPEN Open Blocage des requêtes réseau vers l'API externe (Fail-Fast) Utilisation d'un outil alternatif ou affichage d'un message de fallback
Après expiration du Cooldown Half-Open Vérification du rétablissement du service externe via une requête unique de probing Normalisation du circuit en cas de succès, re-blocage en cas d'échec

`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. Intégration de la validation humaine (Human-in-the-loop) asynchrone sans timeout

Les fonctions serverless ont une limite de temps d'exécution. Laisser la requête ouverte en attendant une validation de paiement ou de suppression de base de données provoque une erreur de timeout.

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

`

On applique needsApproval: true à l'outil Eve pour interrompre l'exécution et ne sauvegarder qu'un checkpoint.

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

export const deleteDatabaseTool = defineTool({
name: "delete_database",
description: "특정 테넌트의 영구 데이터베이스 레코드를 삭제합니다.",
needsApproval: true,
input: z.object({
tenantId: z.string(),
reason: z.string(),
}),
execute: async ({ tenantId }) => {
return await db.tenant.delete({ where: { id: tenantId } });
},
});

`

La validation humaine est reçue via un callback webhook afin de reprendre le processus.

`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. Validation de prompt au niveau CI/CD et routage canary

Les phénomènes d'hallucination qui surviennent après la modification d'un prompt sont difficiles à détecter par des tests meuels. Le pipeline est conçu de manière à ce que la PR ne puisse être fusionnée que si elle valide les indicateurs DeepEval.

Indicateur d'évaluation Seuil d'acceptation Critère d'évaluation
Faithfulness ge0.85ge 0.85ge0.85 Présence ou absence de déformation des faits par rapport au contexte fourni
Answer Relevancy ge0.75ge 0.75ge0.75 Niveau d'adéquation avec l'objectif de la question de l'utilisateur
Hallucination Rate le0.10le 0.10le0.10 Proportion d'hallucinations dans le jeu de test
Tool Calling Accuracy ge0.90ge 0.90ge0.90 Sélection de l'outil conforme à la spécification OpenAPI et respect des types

Pytest est exécuté dans GitHub Actions pour bloquer le build si le seuil n'est pas atteint.

`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

`

Lors du déploiement, Edge Config et un middleware sont associés pour n'appliquer le nouveau prompt qu'à 10 % du trafic dans un premier temps.

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

`