TuBrief
Subscribed Channels
Videos
Community

Vercel Eve エージェントのサーバーレスコールドスタートとコストを抑える構成

TuBrief Editorial
July 23, 2026
0
Computing/Software

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

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

Related Video

Ship 26 NYC - ワークショップ - Eveでエージェントを構築する:オープンエージェントフレームワーク39:50

Ship 26 NYC - ワークショップ - Eveでエージェントを構築する:オープンエージェントフレームワーク

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

Vercel Eve エージェントのサーバーレスコールドスタート과コストを抑える構成

Vercel Eve ベースのエージェントをプロダクションに投入すると、サーバーレス特有の無状態性(Statelessness)にすぐさま直面する。リクエストが終わるとインスタンスが終了し、実行状態は消失する。だからといって、セッションを復元するために毎回 PostgreSQL のような DB にアクセスすると、100ms を超えるレイテンシとともに DB コストが急増してしまう。

サーバーレスの限界を超えるために、実際のプロダクションで採用されている3つの構成をまとめた。

1. Upstash Redis でセッション復元レイテンシを 50ms 未満に抑える

サーバーレス環境で DB 接続を毎回新しく確立する行為は、インフラコストと応答速度の両方を悪化させる近道だ。HTTP REST API で通信する Upstash Redis をセッションキャッシュレイヤーとして配置することで、この問題を解決できる。

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

`

セッションデータは REST API 経由で 50ms 以内に取得する。RDB への直接照会をキャッシュに置き換えることで、Read Capacity の消費を大幅に削減できる。

評価項目 従来の RDB (PostgreSQL) DynamoDB (On-Demand) Upstash Redis (HTTP REST)
接続方式 TCP Socket AWS SDK HTTP/REST API
平均読み取りレイテンシ 50ms - 200ms 10ms - 20ms 1ms - 5ms (Edge < 50ms)
サーバーレス適合性 低い (Connection Exhaustion) 普通 (接続レイテンシが存在) 非常に高い (Scale-to-Zero 対応)
コスト構造 プロビジョニングインスタンスの時間課金 RCU/WCU リクエスト単位課金 Command リクエスト単位 ($0.20/100k)
主な活用目的 ACID トランザクション、マスター保存 永続データの保存および検索 セッションキャッシュ、Rate Limit、エージェントメモリ

セッションの保存および復元コードはシンプルに保持する。

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

`

会話が長くなるとトークンコストが膨らみ続ける。直近の 6 ターンのみを原文のまま残し、それ以前の会話は軽量モデルで要約してプロンプトの先頭に配置する手法をとる。

`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. 外部 API のレイテンシと失敗を防護するパターン

外部ツールを呼び出す際に 429 (Rate Limit) や 5xx エラーに遭遇すると、エージェントの推論全体が破損してしまう。Full Jitter を組み込んだ指数バックオフとサーキットブレーカーを導入しておく必要がある。

指数バックオフの計算式は、待機時間を単純な正比例で増やすのではなく乱数を混ぜることでボトルネックを回避する。

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

}
}

`

障害が長期化する場合は、サーキットブレーカーによって即座にリクエストを遮断 (Fail-Fast) し、Fallback ロジックへ切り替える。

外部 API 応答状態 サーキットブレーカー状態 動作メカニズム エージェント処理結果
HTTP 200 OK Closed 正常通過および成功カウンター増加 外部データを正常にエージェントへ供給
HTTP 429 / 503 Closed $
ightarrow$ Open 指数バックオフ実行後、失敗率の閾値に達すると Open 再試行後にサーキットを開放
Circuit OPEN 状態 Open 外部 API へのネットワークリクエストを遮断 (Fail-Fast) 代替 Tool の使用または Fallback メッセージを出力
Cooldown 満了後 Half-Open 単一の Probing リクエストで外部サービスの復旧状態を検証 成功時はサーキットを正常化、失敗時はサーキットを再遮断

`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. タイムアウトのない非同期承認 (Human-in-the-loop) 連携

サーバーレス関数には実行時間の制限が存在する。決済や DB 削除の承認を待つためにリクエストを開いたまま保持すると、タイムアウトエラーが発生してしまう。

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

`

Eve ツールに needsApproval: true を設定して実行を中断し、チェックポイントのみを保存する。

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

`

人間による承認は Webhook コールバックとして受け取り、プロセスを再開する。

`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. CI/CD ステージでのプロンプト検証とカナリアルーティング

プロンプトの変更後に生じるハルシネーション(幻覚)現象は、手動テストで検出するのが困難だ。DeepEval の指標をクリアしなければ PR がマージされないようパイプラインを構築する。

評価指標 許容閾値 評価基準
Faithfulness ge0.85ge 0.85ge0.85 提供された Context に対するファクトの歪みの有無
Answer Relevancy ge0.75ge 0.75ge0.75 ユーザーの質問目的との適合度
Hallucination Rate le0.10le 0.10le0.10 テストセットにおけるハルシネーション発生率
Tool Calling Accuracy ge0.90ge 0.90ge0.90 正しい OpenAPI スペックツールの選択および型準拠率

GitHub Actions 上で Pytest を実行し、閾値に満たない場合はビルドをブロックする。

`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

`

デプロイ時には Edge Config とミドルウェアを連携させ、トラフィックの 10% にのみ新規プロンプトを先行適用する。

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

`