0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Workers AI を binding 経由で無料枠フル活用する — 複数プロバイダーをまとめる抽象化レイヤー

0
Posted at

はじめに

Cloudflare Workers AI には 無料枠 10,000 Neurons/日 がある。個人レベルなら十分すぎる量だが、唯一のクセは API キーではなく Worker binding 経由で呼び出す ところにある。

OpenAI / Anthropic / Google のような AI Gateway 経由で呼べるプロバイダーと混ぜて使う場合、抽象化レイヤーが必要になる。本記事は、11 プロバイダーを単一の callProvider() でまとめた実装をコード付きで共有する。

完成イメージ

const result = await callProvider(env, 'cloudflare', '@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
  system: 'You are a poet.',
  user: 'Describe wind through cedar leaves.',
});
console.log(result.text);

呼び出し側は providermodel を文字列で渡すだけ。中で binding か HTTP かを切り替える。

ステップ 1: wrangler.toml に binding を追加

# wrangler.toml
name = "my-worker"
main = "worker/index.ts"
compatibility_date = "2025-10-01"

[ai]
binding = "AI"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxxx"

[ai] binding = "AI"env.AI 経由で Workers AI が呼べるようになる。API キー不要、無料枠が自動で適用される

ステップ 2: 型定義

// worker/types.ts
export interface Env {
  AI: Ai;                  // Cloudflare 提供の型
  DB: D1Database;
  OPENAI_API_KEY: string;
  ANTHROPIC_API_KEY: string;
  GOOGLE_API_KEY: string;
  AI_GATEWAY_URL: string;  // https://gateway.ai.cloudflare.com/v1/<account>/<gateway>
}

export type Provider = 'openai' | 'anthropic' | 'google' | 'cloudflare';

export interface ChatRequest {
  system: string;
  user: string;
  maxTokens?: number;
}

export interface ChatResponse {
  text: string;
  provider: Provider;
  model: string;
}

ステップ 3: プロバイダー別ディスパッチャ

// worker/lib/providers.ts
export async function callProvider(
  env: Env,
  provider: Provider,
  model: string,
  req: ChatRequest,
): Promise<ChatResponse> {
  switch (provider) {
    case 'cloudflare': return callCloudflareAI(env, model, req);
    case 'openai':     return callOpenAI(env, model, req);
    case 'anthropic':  return callAnthropic(env, model, req);
    case 'google':     return callGoogle(env, model, req);
    default:           throw new Error(`unknown provider: ${provider}`);
  }
}

Cloudflare Workers AI(binding 経由)

async function callCloudflareAI(
  env: Env,
  model: string,
  req: ChatRequest,
): Promise<ChatResponse> {
  const result = await env.AI.run(model as any, {
    messages: [
      { role: 'system', content: req.system },
      { role: 'user',   content: req.user },
    ],
    max_tokens: req.maxTokens ?? 400,
  });

  // モデルにより response 形状が違う:
  //  - chat 系: { response: "..." }
  //  - 一部:    string をそのまま返す
  //  - 一部:    { result: { response: "..." } }
  const text =
    typeof result === 'string'
      ? result
      : (result as any).response ?? (result as any).result?.response ?? '';

  return { text, provider: 'cloudflare', model };
}

ポイント:

  • env.AI.run(model, payload) で完結。fetch 不要。
  • レスポンス形状はモデルにより微妙に違う。実モデルで確認してから flatten する。

OpenAI / Anthropic / Google(AI Gateway 経由)

async function callOpenAI(env: Env, model: string, req: ChatRequest): Promise<ChatResponse> {
  const r = await fetch(`${env.AI_GATEWAY_URL}/openai/chat/completions`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
      'Content-Type':  'application/json',
    },
    body: JSON.stringify({
      model,
      messages: [
        { role: 'system', content: req.system },
        { role: 'user',   content: req.user },
      ],
      max_tokens: req.maxTokens ?? 400,
    }),
  });
  const data = await r.json<any>();
  return { text: data.choices?.[0]?.message?.content ?? '', provider: 'openai', model };
}

async function callAnthropic(env: Env, model: string, req: ChatRequest): Promise<ChatResponse> {
  const r = await fetch(`${env.AI_GATEWAY_URL}/anthropic/v1/messages`, {
    method: 'POST',
    headers: {
      'x-api-key':         env.ANTHROPIC_API_KEY,
      'anthropic-version': '2023-06-01',
      'Content-Type':      'application/json',
    },
    body: JSON.stringify({
      model,
      system:   req.system,
      messages: [{ role: 'user', content: req.user }],
      max_tokens: req.maxTokens ?? 400,
    }),
  });
  const data = await r.json<any>();
  return { text: data.content?.[0]?.text ?? '', provider: 'anthropic', model };
}

AI Gateway を通すメリット:

  • 全プロバイダーのログ・コスト・レイテンシが 1 ダッシュボードで見られる
  • キャッシュ・レート制限を Gateway 側で設定できる
  • フォールバック設定も可能

ステップ 4: 無料枠とコストの整理

Provider 無料枠 認証 ストリーミング
Cloudflare Workers AI 10,000 Neurons/日 binding(キー不要) あり
Google Gemini Flash 無料枠あり API key あり
OpenAI なし API key あり
Anthropic なし API key あり

Workers AI は 1 call ≈ 200〜500 Neurons(モデルによる)。1 日 20〜50 calls までは完全無料で動く。

cron で 2 時間ごとに 1 リクエスト × 12 回/日 ≈ 240 calls/日 の運用でも、推定 60,000〜120,000 Neurons でちょっと枠を超えるくらい。サイズの小さいモデルを混ぜれば余裕で収まる。

ステップ 5: 実運用での落とし穴

落とし穴 1: モデル名がいきなり deprecated になる

私の運用では @cf/google/gemma-7b-it が突然 404 を返すようになった(Gemma 3 12B に置き換わっていた)。Workers AI のモデルカタログは静的ではないので、定期的にチェックが必要。

→ 対策: 週次 cron でプロバイダー API を叩いて pool と diff し、メールで通知。

落とし穴 2: env.AI.run の型が any 寄り

// 動くが型情報がほぼない
const r = await env.AI.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast' as any, { /* ... */ });

→ 対策: provider ごとに型付き wrapper を書いて、呼び出し側からは型安全にする。

落とし穴 3: Reasoning model はストリーミングで content が空になる

gpt-oss-120bo3deepseek-reasoner 系は SSE ストリームで delta.content が空、reasoning_content のみが流れることがある。これを content || reasoning で fallback すると 思考プロセスが意図せずユーザーに見える

→ 対策: reasoning model は専用の non-streaming パスでハンドルし、最終出力 message.content だけを返す。

const REASONING_MODELS = new Set([
  'gpt-oss-120b',
  'o3',
  'o4-mini',
  'deepseek-reasoner',
]);

if (REASONING_MODELS.has(model)) {
  return callProviderNonStream(env, provider, model, req);
}

落とし穴 4: max_tokens の意味がプロバイダーで違う

  • OpenAI: completion トークン数の上限
  • Anthropic: 出力トークン数の上限(必須パラメータ)
  • Workers AI: モデル依存。一部モデルでは無視される

→ 対策: provider ごとに「適切なデフォルト値」を抽象化レイヤーで決め打ちにする。私の運用ではデフォルト 400、ストリーミングのみ 800 に上げている。

まとめ

  • Cloudflare Workers AI は 無料枠 10,000 Neurons/日 で個人運用には十分
  • API キーではなく binding(env.AI で呼び出す
  • 複数プロバイダーを混ぜるなら AI Gateway で一元化(ログ・レート制限・キャッシュ)
  • レスポンス形状はプロバイダーごとに違うので flatten レイヤー を 1 枚噛ませる
  • Reasoning model は non-streaming のみ で扱う

実プロダクトでは、上記抽象化に モデル健全性追跡AI Gateway 経由の自動リトライ を組み合わせて、11 社のモデルを混ぜて運用している。


796f75617265686f6d65.com — AI のためのハビタット

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?