AI 推論がエッジに移行

2023 年まで、大規模言語モデルの実行はほぼ必須でした 外部 API (OpenAI、Anthropic、Google) と通信するか、高価な GPU を導入する 専用のインフラストラクチャ上で。これらの集中エンドポイントへのネットワーク遅延 各リクエストに 200 ~ 800 ミリ秒が追加され、GPU コストが法外に高くなりました。 少量のアプリケーション。

ワーカーAI がこのシナリオを変更しました。 Cloudflareが配布しています 世界中の数十のデータセンターにある AI 推論ハードウェア (専用 GPU)。 モデルはワーカーを実行するのと同じハードウェア上で実行されるため、 外部プロバイダーへのネットワークの往復。結果はレイテンシ推論です 削減され、インフラストラクチャ管理なしで消費量に応じて請求されます。

何を学ぶか

  • Workers AI で利用可能なテンプレート: LLM、ビジョン、スピーチ、埋め込み
  • Llama 3.1 によるテキスト生成と応答ストリーミング
  • 視覚モデル: LLaVA による画像解析
  • ウィスパーによる音声テキスト変換
  • セマンティック検索のためのテキスト埋め込み
  • AI ゲートウェイ: AI リクエストのキャッシュ、レート制限、可観測性
  • 制限、コスト、最適化戦略

利用可能なモデルの概要

Workers AI は、推論用に最適化されたオープンソース モデルのセレクションを提供します Cloudflareハードウェア上で。モデルは接頭辞で示されます @cf/ o @hf/ (ハグフェイス主催):

カテゴリ 主要機種 使用事例
テキストの生成 @cf/meta/llama-3.1-8b-instruct、@cf/mistral/mistral-7b-instruct-v0.2 チャットボット、概要、Q&A、コード生成
テキスト生成(大) @cf/meta/llama-3.3-70b-instruct-fp8-fast 複雑な推論、高度な分析
ビジョン @cf/llava-hf/llava-1.5-7b-hf、@cf/unum/uform-gen2-qwen-500m 画像キャプション、ビジュアル Q&A
音声からテキストへの変換 @cf/openai/whisper、@cf/openai/whisper-large-v3-turbo 音声転写
テキストの埋め込み @cf/baai/bge-base-en-v1.5、@cf/baai/bge-large-en-v1.5 セマンティック検索、類似性、RAG
画像の分類 @cf/microsoft/resnet-50 画像分類
翻訳 @cf/meta/m2m100-1.2b 翻訳 100 以上の言語
画像生成 @cf/stabilityai/stable-diffusion-xl-base-1.0 テキストから画像へ

構成: wrangler.toml の AI バインディング

Workers AI を使用するには、バインディングを追加するだけです [ai] 構成内:

# wrangler.toml
name = "ai-worker"
main = "src/worker.ts"
compatibility_date = "2024-09-23"

# Binding per Workers AI
[ai]
binding = "AI"

バインディングの TypeScript タイプはインターフェイスで宣言する必要があります Env:

// types.ts - dichiarazione del binding AI
interface Env {
  AI: Ai; // Tipo fornito da @cloudflare/workers-types
}

Llama 3.1 によるテキスト生成

最も一般的な使用例は、テンプレートを使用してテキストを生成することです 指示と応答。チャット エンドポイントを実装する方法を見てみましょう。

// src/worker.ts - endpoint di chat con Llama 3.1

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST' || new URL(request.url).pathname !== '/chat') {
      return new Response('POST /chat required', { status: 400 });
    }

    const { messages, stream = false } = await request.json<ChatRequest>();

    // Valida l'input
    if (!Array.isArray(messages) || messages.length === 0) {
      return Response.json({ error: 'messages array required' }, { status: 400 });
    }

    if (stream) {
      // Streaming response: il modello restituisce token man mano
      const aiStream = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
        messages,
        stream: true,
        max_tokens: 1024,
        temperature: 0.7,
      });

      // Trasforma lo stream AI in Server-Sent Events
      return new Response(aiStream, {
        headers: {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
          'Connection': 'keep-alive',
        },
      });
    }

    // Risposta sincrona: attende il completion completo
    const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
      messages,
      max_tokens: 1024,
      temperature: 0.7,
    });

    return Response.json({
      response: (result as AiTextGenerationOutput).response,
      usage: {
        // Workers AI non espone ancora i token counts nella risposta base
        model: '@cf/meta/llama-3.1-8b-instruct',
      },
    });
  },
};

interface ChatRequest {
  messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;
  stream?: boolean;
}

interface Env {
  AI: Ai;
}

システム プロンプト アシスタントを実装するより完全な例 堅牢なエラー処理:

// src/assistant-worker.ts

const SYSTEM_PROMPT = `Sei un assistente tecnico esperto in cloud computing e edge computing.
Rispondi in modo conciso e tecnico. Se non conosci la risposta, dillo chiaramente.
Non inventare informazioni. Rispondi sempre in italiano a meno che l'utente non scriva in un'altra lingua.`;

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    let body: AssistantRequest;
    try {
      body = await request.json<AssistantRequest>();
    } catch {
      return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
    }

    if (!body.question?.trim()) {
      return Response.json({ error: 'question field is required' }, { status: 400 });
    }

    try {
      const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
        messages: [
          { role: 'system', content: SYSTEM_PROMPT },
          { role: 'user', content: body.question },
        ],
        max_tokens: 2048,
        temperature: 0.3, // Bassa temperatura per risposte piu deterministiche
      }) as AiTextGenerationOutput;

      return Response.json({
        answer: result.response,
        model: '@cf/meta/llama-3.1-8b-instruct',
        timestamp: new Date().toISOString(),
      });
    } catch (err) {
      console.error('AI inference error:', err);
      return Response.json(
        { error: 'AI inference failed', details: (err as Error).message },
        { status: 500 }
      );
    }
  },
};

interface AssistantRequest {
  question: string;
}

interface Env {
  AI: Ai;
}

視覚モデル: 画像解析

ビジョンモデルを使用すると、質問とともに入力画像を分析できます テキスト的な。これはコンテンツの管理や情報抽出に役立ちます スキャンされたドキュメントとアクセシビリティ機能から:

// src/vision-worker.ts - analisi immagini con LLaVA

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    // Accetta immagine come Base64 o URL
    const body = await request.json<VisionRequest>();

    let imageData: number[];

    if (body.imageUrl) {
      // Scarica l'immagine e converti in array di byte
      const imgResponse = await fetch(body.imageUrl);
      if (!imgResponse.ok) {
        return Response.json({ error: 'Failed to fetch image' }, { status: 400 });
      }
      const buffer = await imgResponse.arrayBuffer();
      imageData = Array.from(new Uint8Array(buffer));
    } else if (body.imageBase64) {
      // Decodifica Base64
      const binaryString = atob(body.imageBase64);
      imageData = Array.from({ length: binaryString.length }, (_, i) =>
        binaryString.charCodeAt(i)
      );
    } else {
      return Response.json({ error: 'imageUrl or imageBase64 required' }, { status: 400 });
    }

    const prompt = body.prompt ?? 'Descrivi questa immagine in dettaglio in italiano.';

    const result = await env.AI.run('@cf/llava-hf/llava-1.5-7b-hf', {
      image: imageData,
      prompt,
      max_tokens: 512,
    }) as AiTextGenerationOutput;

    return Response.json({
      description: result.response,
      prompt,
      model: '@cf/llava-hf/llava-1.5-7b-hf',
    });
  },
};

interface VisionRequest {
  imageUrl?: string;
  imageBase64?: string;
  prompt?: string;
}

interface Env {
  AI: Ai;
}

ささやき声によるテキスト読み上げ

Workers AI には、音声転写用の Whisper が含まれています。モデルは音声を受け入れます フォーマットで ArrayBuffer そしてタイムスタンプ付きのトランスクリプトを返します オプション:

// src/transcribe-worker.ts - Speech-to-text con Whisper

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    const contentType = request.headers.get('Content-Type') ?? '';

    // Accetta audio come multipart/form-data o application/octet-stream
    let audioBuffer: ArrayBuffer;

    if (contentType.includes('multipart/form-data')) {
      const formData = await request.formData();
      const audioFile = formData.get('audio') as File | null;
      if (!audioFile) {
        return Response.json({ error: 'audio field required in form data' }, { status: 400 });
      }
      audioBuffer = await audioFile.arrayBuffer();
    } else {
      // Raw binary audio
      audioBuffer = await request.arrayBuffer();
    }

    if (audioBuffer.byteLength === 0) {
      return Response.json({ error: 'Empty audio data' }, { status: 400 });
    }

    // Limita a 25MB (limite Whisper)
    if (audioBuffer.byteLength > 25 * 1024 * 1024) {
      return Response.json({ error: 'Audio file too large (max 25MB)' }, { status: 413 });
    }

    const result = await env.AI.run('@cf/openai/whisper', {
      audio: Array.from(new Uint8Array(audioBuffer)),
    }) as AiSpeechRecognitionOutput;

    return Response.json({
      text: result.text,
      wordCount: result.text.split(/\s+/).filter(Boolean).length,
      model: '@cf/openai/whisper',
    });
  },
};

interface Env {
  AI: Ai;
}

セマンティック検索のためのテキスト埋め込み

埋め込みは意味論的な意味を表す数値ベクトルです テキストの。 Workers AI には、セマンティック検索用に最適化された BGE モデルが含まれています。 Vectorize (Cloudflare のベクター データベース) と組み合わせることで、 RAG パイプラインは完全にエッジにあります。

// src/embedding-worker.ts - generazione embeddings + ricerca semantica

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/embed' && request.method === 'POST') {
      const { texts } = await request.json<EmbedRequest>();

      if (!Array.isArray(texts) || texts.length === 0) {
        return Response.json({ error: 'texts array required' }, { status: 400 });
      }

      // BGE genera embedding di 768 dimensioni (base) o 1024 (large)
      const result = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
        text: texts,
      }) as AiTextEmbeddingsOutput;

      return Response.json({
        embeddings: result.data,
        dimensions: result.data[0]?.length ?? 0,
        count: result.data.length,
        model: '@cf/baai/bge-base-en-v1.5',
      });
    }

    if (url.pathname === '/search' && request.method === 'POST') {
      const { query, topK = 5 } = await request.json<SearchRequest>();

      // 1. Genera l'embedding per la query
      const queryEmbed = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
        text: [query],
      }) as AiTextEmbeddingsOutput;

      // 2. Ricerca semantica su Vectorize
      const results = await env.VECTORIZE.query(queryEmbed.data[0], {
        topK,
        returnMetadata: 'all',
      });

      return Response.json({
        query,
        results: results.matches.map((match) => ({
          id: match.id,
          score: match.score,
          metadata: match.metadata,
        })),
      });
    }

    return new Response('Not Found', { status: 404 });
  },
};

interface EmbedRequest {
  texts: string[];
}

interface SearchRequest {
  query: string;
  topK?: number;
}

interface Env {
  AI: Ai;
  VECTORIZE: VectorizeIndex;
}

AI ゲートウェイ: 可観測性とキャッシュ

Cloudflare AI ゲートウェイ それは透明なプロキシです。 AI コール (Workers AI と OpenAI などの外部プロバイダーの両方) よりも上位にあります。 セマンティック キャッシュ、レート制限、ロギング、自動フォールバックを追加します。

// src/worker-with-gateway.ts - Workers AI via AI Gateway

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { prompt } = await request.json<{ prompt: string }>();

    // Usa il gateway invece del binding diretto
    // Il gateway aggiunge: caching, retry, logging, rate limiting
    const response = await fetch(
      `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/workers-ai/@cf/meta/llama-3.1-8b-instruct`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.CF_API_TOKEN}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [{ role: 'user', content: prompt }],
          max_tokens: 512,
        }),
      }
    );

    if (!response.ok) {
      const error = await response.text();
      return Response.json({ error }, { status: response.status });
    }

    const result = await response.json();
    return Response.json(result);
  },
};

interface Env {
  AI: Ai;
  CF_ACCOUNT_ID: string;
  CF_API_TOKEN: string;
  AI_GATEWAY_ID: string;
}

あるいは、wrangler.toml の AI バインディングでゲートウェイを直接構成することもできます。

# wrangler.toml con AI Gateway
[ai]
binding = "AI"
# Il gateway viene usato automaticamente per tutte le chiamate
# Configurato nella dashboard Cloudflare

制限とコストに関する考慮事項

モデル 無料(ニューロンユニット) 有料 (1,000 ニューロンあたりのドル) 一般的な遅延
ラマ 3.1 8B 10,000 NU/日無料 $0.011 / 1K NU ~500ms-2s (トークンによって異なります)
ラマ 3.3 70B FP8 有料プランに含まれる $0.050 / 1K NU ~1~5秒
ささやき 10,000 NU/日無料 $0.011 / 1K NU 音声は 1 分あたり約 1 ~ 3 秒
BGE エンベディング 10,000 NU/日無料 $0.011 / 1K NU ~50~200ミリ秒
安定拡散XL 10,000 NU/日無料 $0.020/画像 ~3~10秒

タイムアウトとCPU制限

Worker AI は、Worker の通常の CPU 予算 (30 秒の有料プラン) の範囲外で動作します。 ただし、Llama 70B のような大型モデルの場合は、5 ~ 15 秒かかる場合があります。 応答します。このような場合には、必ず ストリーミング クライアントの HTTP タイムアウトを超えずに、実行中にトークンを返すようにします。 長い推論の場合は、キュー (ワーカー キュー) の使用を検討し、通知します。 終了したらクライアント。

生産パターン: RAG at the Edge

ますます一般的なパターンは、 RAG (検索拡張生成) 完全にエッジで: 取得にはベクトル化、埋め込みと生成には Workers AI を使用します。

// src/rag-worker.ts - RAG completo all'edge

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') return new Response('POST only', { status: 405 });

    const { question } = await request.json<{ question: string }>();

    // Step 1: Genera l'embedding della domanda
    const queryEmbedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
      text: [question],
    }) as AiTextEmbeddingsOutput;

    // Step 2: Recupera i chunk rilevanti dal vector store
    const relevant = await env.DOCS.query(queryEmbedding.data[0], {
      topK: 3,
      returnMetadata: 'all',
    });

    // Step 3: Costruisce il contesto dai chunk recuperati
    const context = relevant.matches
      .map((m) => m.metadata?.['text'] as string ?? '')
      .filter(Boolean)
      .join('\n\n---\n\n');

    // Step 4: Genera la risposta con il contesto
    const answer = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
      messages: [
        {
          role: 'system',
          content: `Rispondi alla domanda basandoti SOLO sul contesto fornito.
Se il contesto non contiene informazioni sufficienti, dillo esplicitamente.
Contesto:
${context}`,
        },
        { role: 'user', content: question },
      ],
      max_tokens: 1024,
      temperature: 0.1,
    }) as AiTextGenerationOutput;

    return Response.json({
      question,
      answer: answer.response,
      sources: relevant.matches.map((m) => ({
        id: m.id,
        score: m.score,
        title: m.metadata?.['title'],
      })),
    });
  },
};

interface Env {
  AI: Ai;
  DOCS: VectorizeIndex;
}

結論と次のステップ

Workers AI は、AI 推論へのアクセスにおけるパラダイム シフトを表しています。 管理する GPU や統合する外部プロバイダーがなく、課金も必要ありません 豊富な無料利用枠で消費できます。 2026 年第 1 四半期までの前年比 4000% の成長を反映 よりシンプルな方法を求める開発者による迅速な導入 製品における AI に向けて。

Workers AI + Vectorize + Durable Objects (管理用) の組み合わせ 会話履歴)を使用して、完全な AI アシスタントを完全に構築できます Cloudflareプラットフォーム上で、外部依存関係なし。

シリーズの次の記事

  • 第6条: Vercel Edge ランタイム — アドバンスト ミドルウェア、 地理位置情報と A/B テスト: Vercel が Next.js でエッジ ランタイムを使用する方法 カスタマイズと機能フラグ用。
  • 第7条: エッジでの地理的ルーティング — パーソナライゼーション GDPR コンテンツとコンプライアンス: メイン サーバーに触れることなく、地理ベースのロジックを構築します。
  • 第8条: CloudflareのキャッシュAPIと無効化戦略 ワーカー: TTL、再検証中の失効、およびキーごとのパージを備えたプログラム可能な CDN。