TuBrief
Subscribed Channels
Videos
Community

Arquitetura para resolver o Cold Start sem servidor e reduzir custos no Vercel Eve Agent

TuBrief Editorial
July 23, 2026
0
Computing/Software

Written with AI assistance from the source video. The video is the authority.

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

Related Video

Ship 26 NYC - Workshop - Construa um Agente com Eve: O Framework de Agentes Abertos39:50

Ship 26 NYC - Workshop - Construa um Agente com Eve: O Framework de Agentes Abertos

Vercel

More from the community

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

September 13, 2026

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

September 13, 2026

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

September 13, 2026

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

September 13, 2026

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

September 12, 2026

Apple Won the AI Race

September 12, 2026

Comments (0)

Log in to leave a comment

No posts yet

© 2026 . All rights reserved.

TuBrief
Subscribed Channels
Videos
Community
Log in

Arquitetura para resolver o Cold Start sem servidor e reduzir custos no Vercel Eve Agent

Ao colocar um agente baseado no Vercel Eve em produção, você se depara imediatamente com a ausência de estado (Statelessness) típica de ambientes serverless. Quando a requisição termina, a instância é fechada e o estado de execução é perdido. Por outro lado, consultar um banco de dados como o PostgreSQL a cada execução para restaurar a sessão gera um atraso superior a 100ms e dispara os custos de DB.

Para superar as limitações do serverless, resumi três estruturas usadas na prática em ambientes de produção.

1. Reduzindo a latência de restauração de sessão para menos de 50ms com Upstash Redis

Em ambientes serverless, criar uma nova conexão com o banco de dados a cada requisição é o caminho mais rápido para arruinar tanto os custos de infraestrutura quanto o tempo de resposta. Colocar o Upstash Redis, que se comunica via HTTP REST API, como uma camada de cache de sessão resolve esse problema.

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

Os dados da sessão são recuperados em menos de 50ms por meio de uma REST API. Substituir consultas diretas ao RDB pelo cache reduz drasticamente o consumo de Read Capacity.

Item de Avaliação RDB Tradicional (PostgreSQL) DynamoDB (On-Demand) Upstash Redis (HTTP REST)
Tipo de Conexão TCP Socket AWS SDK HTTP/REST API
Latência Média de Leitura 50ms - 200ms 10ms - 20ms 1ms - 5ms (Edge < 50ms)
Adequação para Serverless Baixa (Exaustão de Conexões) Média (Existe latência de conexão) Muito Alta (Suporta Scale-to-Zero)
Estrutura de Custo Cobrança por hora da instância provisionada Cobrança por unidade de requisição RCU/WCU Cobrança por unidade de requisição de comando ($0,20/100k)
Propósito Principal Transações ACID, armazenamento primário Armazenamento e busca de dados permanentes Cache de sessão, Rate Limit, memória do agente

O código para salvar e restaurar a sessão é mantido de forma simples.

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

Conforme a conversa se estende, o custo de tokens continua aumentando. Utilizamos a abordagem de manter apenas os últimos 6 turnos em seu formato original e resumir as conversas anteriores com um modelo leve, posicionando o resumo no topo do 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. Padrões de proteção contra latência e falhas de APIs externas

Ao chamar ferramentas externas, encontrar erros 429 (Rate Limit) ou 5xx quebra toda a inferência do agente. É essencial implementar Backoff Exponencial com Full Jitter e um Circuit Breaker.

A fórmula do Backoff Exponencial evita gargalos ao misturar números aleatórios em vez de aumentar o tempo de espera de forma estritamente proporcional.

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

}
}
`

Se a falha persistir, o Circuit Breaker bloqueia imediatamente as requisições (Fail-Fast) e executa a lógica de Fallback.

Status da Resposta da API Externa Estado do Circuit Breaker Mecanismo de Ação Resultado do Processamento no Agente
HTTP 200 OK Closed Fluxo normal e incremento no contador de sucessos Fornece dados externos normalmente ao agente
HTTP 429 / 503 Closed $
ightarrow$ Open Executa Backoff Exponencial e muda para Open ao atingir o limite de taxa de falha Interrompe tentativas e abre o circuito
Estado Circuit OPEN Open Bloqueia requisições de rede para a API externa (Fail-Fast) Usa Tool alternativa ou exibe mensagem de Fallback
Após expiração do Cooldown Half-Open Valida a recuperação do serviço externo com uma única requisição de Probing Sucesso normaliza o circuito; falha bloqueia novamente

`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. Integração assíncrona para aprovação humana (Human-in-the-loop) sem Timeout

Funções serverless possuem limites de tempo de execução. Manter uma requisição aberta aguardando aprovação para pagamentos ou exclusão de banco de dados resultará em um erro 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]

Definimos needsApproval: true na ferramenta do Eve, pausamos a execução e salvamos apenas o 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 } });
},
});
`

A aprovação humana é recebida via callback de webhook para retomar o processo.

`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. Validação de prompts e roteamento Canary na etapa de CI/CD

Alucinações que ocorrem após alterar um prompt são difíceis de capturar com testes manuais. Configuramos o pipeline para que o PR só seja mesclado se passar nas métricas do DeepEval.

Métrica de Avaliação Limite Aceitável Critério de Avaliação
Faithfulness ge0.85ge 0.85ge0.85 Presença ou ausência de distorção de fatos em relação ao Contexto fornecido
Answer Relevancy ge0.75ge 0.75ge0.75 Grau de conformidade com a intenção da pergunta do usuário
Hallucination Rate le0.10le 0.10le0.10 Proporção de alucinações no conjunto de testes
Tool Calling Accuracy ge0.90ge 0.90ge0.90 Seleção correta da ferramenta da especificação OpenAPI e taxa de conformidade com tipos

Executamos o Pytest no GitHub Actions para bloquear a build caso os limites não sejam atingidos.

`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

`

No momento do deploy, integramos o Edge Config ao middleware para aplicar o novo prompt inicialmente a apenas 10% do tráfego.

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