Workers AI and Cloudflare Agents
Cloudflare’s AI stack has four products that fit together: Workers AI (managed inference for 50+ open-weight models — Llama, Phi, Mistral, DeepSeek, Stable Diffusion, Whisper, etc. — billed per request, no infra to manage), Vectorize (managed vector database for embeddings + semantic search), AI Gateway (analytics + caching + rate limiting + fallback chains across both Workers AI and external providers like OpenAI / Anthropic / Google), and the Cloudflare Agents SDK (durable AI agents backed by Durable Objects — state, tool use, scheduled execution, WebSocket-streaming UI, MCP server hosting). Together these let you build a complete AI application on Cloudflare without leaving the edge.
See also
- workers-bindings-and-storage —
[ai]and[[vectorize]]bindings - durable-objects-and-sqlite-storage — Agents SDK is built on DOs
- _index — Claude is accessible via AI Gateway as an upstream
- _index — Bedrock is accessible via AI Gateway as an upstream
1. Workers AI — catalog and API
Per developers.cloudflare.com/workers-ai/. Open-weight models hosted on Cloudflare’s GPU fleet. Pay-per-request, no instance management.
Model categories
| Category | Notable models | Use |
|---|---|---|
| Text generation (LLM) | @cf/meta/llama-3.3-70b-instruct, @cf/meta/llama-3.1-8b-instruct, @cf/microsoft/phi-3-medium-4k-instruct, @cf/mistral/mistral-small-3.1-24b-instruct, @cf/deepseek-ai/deepseek-r1-distill-qwen-32b | Chat, completion, instruction-following |
| Embeddings | @cf/baai/bge-base-en-v1.5 (768d), @cf/baai/bge-large-en-v1.5 (1024d), @cf/baai/bge-m3 (1024d, multilingual) | Vector search, semantic similarity |
| Reranking | @cf/baai/bge-reranker-base | RAG post-retrieval reranking |
| Image generation | @cf/black-forest-labs/flux-1-schnell, @cf/stabilityai/stable-diffusion-xl-base-1.0, @cf/lykon/dreamshaper-8-lcm | Text-to-image |
| Image understanding | @cf/llava-hf/llava-1.5-7b-hf, @cf/meta/llama-3.2-11b-vision-instruct | Image captioning, VQA |
| Speech-to-text | @cf/openai/whisper, @cf/openai/whisper-large-v3-turbo | Audio transcription |
| Text-to-speech | @cf/myshell-ai/melotts | Audio synthesis |
| Translation | @cf/meta/m2m100-1.2b | 100-language translation |
| Object detection | @cf/facebook/detr-resnet-50 | Bounding-box detection |
| Text classification | @cf/huggingface/distilbert-sst-2-int8 | Sentiment, intent |
Full catalog (50+ models): developers.cloudflare.com/workers-ai/models/.
Binding
[ai]
binding = "AI"Inference
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Text generation
const chat = await env.AI.run("@cf/meta/llama-3.3-70b-instruct", {
messages: [
{ role: "system", content: "You are a coding tutor." },
{ role: "user", content: "Explain async/await." },
],
max_tokens: 512,
temperature: 0.3,
stream: false,
});
// chat.response is the generated text
// Embedding
const embed = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
text: ["Hello world", "Goodbye world"],
});
// embed.data[0] is the first embedding (768-dim array)
// Image generation
const img = await env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
prompt: "A cat in a wizard hat",
steps: 4, // FLUX schnell uses few steps
});
return new Response(img, { headers: { "Content-Type": "image/jpeg" } });
},
};Streaming
const stream = await env.AI.run("@cf/meta/llama-3.3-70b-instruct", {
messages: [...],
stream: true,
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
});The SSE stream contains data: { ... } JSON events with incremental tokens.
Tool use
const result = await env.AI.run("@cf/meta/llama-3.3-70b-instruct", {
messages: [
{ role: "user", content: "What's the weather in Paris?" },
],
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Get current weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string" },
},
required: ["city"],
},
},
},
],
});
if (result.tool_calls) {
for (const tc of result.tool_calls) {
const args = JSON.parse(tc.function.arguments);
const weather = await getWeather(args.city);
// Continue conversation with tool result...
}
}Pricing
Per developers.cloudflare.com/workers-ai/platform/pricing/. Different unit per model category:
| Category | Unit | Example |
|---|---|---|
| Text generation | Per-million tokens | Llama 3.3 70B: $0.59/MTok in, $0.79/MTok out |
| Embeddings | Per-million tokens | bge-base-en-v1.5: $0.012/MTok |
| Image generation | Per-megapixel-step | Flux schnell: $0.0001 / megapixel-step |
| Whisper | Per-audio-second | $0.000012 / sec |
Free plan: 10,000 “neurons” per day (Cloudflare’s normalized AI unit) — enough for hundreds of LLM calls or thousands of embeddings.
2. Vectorize — managed vector database
Per developers.cloudflare.com/vectorize/. Vector index with metadata filtering and namespacing. GA in early 2024.
Setup
wrangler vectorize create my-index --dimensions=768 --metric=cosine[[vectorize]]
binding = "VECTORS"
index_name = "my-index"Insert / upsert
await env.VECTORS.insert([
{
id: "doc-1",
values: new Array(768).fill(0).map(() => Math.random()), // 768d vector
metadata: {
source: "blog",
url: "https://example.com/post-1",
published_at: 1716508800,
},
},
]);
// Upsert (overwrites if ID exists)
await env.VECTORS.upsert([{ id: "doc-1", values: [...], metadata: {...} }]);Query
const results = await env.VECTORS.query(queryEmbedding, {
topK: 5,
returnValues: false,
returnMetadata: true,
filter: {
source: { "$eq": "blog" },
published_at: { "$gt": 1700000000 },
},
namespace: "tenant-123", // optional logical separation within an index
});
for (const match of results.matches) {
console.log(match.id, match.score, match.metadata);
}Metadata indexes (re:Invent 2024)
Add specific metadata fields to a searchable index for faster filtered queries:
wrangler vectorize create-metadata-index my-index --property-name source --type string
wrangler vectorize create-metadata-index my-index --property-name published_at --type numberLimits
- Max dimensions: 1536
- Max vectors per index: 5M (Paid)
- Max indexes per account: 100 (Paid)
- Max metadata per vector: 10 KB
- Distance metrics: cosine, euclidean, dot-product
Pricing
- Stored vector dimensions: $0.05 / 100M dimensions / month
- Queried vector dimensions: $0.01 / 1M dimensions
For a 1M-vector index at 768 dimensions:
- Storage: 1M × 768 = 768M dimensions = $0.38/mo
- 1M queries against it: 1M × 768 = 768M = $7.68
Significantly cheaper than Pinecone for similar workloads.
3. AI Gateway — observability and resilience across providers
Per developers.cloudflare.com/ai-gateway/. A proxy that sits between your Worker (or any client) and your AI providers (Workers AI, OpenAI, Anthropic, Google, Mistral, Replicate, Bedrock, Azure, Cohere, Groq, Together, HuggingFace, Perplexity, etc.).
Setup
Create a Gateway in the dashboard → AI → Gateway. You get a base URL like https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/<provider>.
Use via direct HTTP
const resp = await fetch(
"https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/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: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
}),
}
);Use via the AI binding (Workers AI Gateway)
const response = await env.AI.run(
"@cf/meta/llama-3.3-70b-instruct",
{ messages: [...] },
{
gateway: {
id: "my-gateway",
cacheTtl: 3600,
skipCache: false,
collectLog: true,
},
}
);Features
- Logging — every request + response is logged with token counts, latency, cost. Searchable via dashboard.
- Caching — exact-match request cache. TTL configurable. Especially valuable for embedding requests (deterministic queries).
- Rate limiting — per-IP, per-API-key. Block runaway calls before they hit upstream.
- Fallbacks — if Anthropic 429s, automatically retry on OpenAI.
- Cost tracking — aggregated spend by provider, model, token usage.
- Real-time analytics — request volumes, error rates, token usage curves.
Pricing
Free for first 100k requests/day; Paid usage-based. The savings on cached requests typically pay for the Gateway many times over.
4. Cloudflare Agents SDK
Per developers.cloudflare.com/agents/ and github.com/cloudflare/agents. A TypeScript framework for building stateful AI agents on Cloudflare. Built on:
- Durable Objects — for per-agent state (each agent instance is a DO)
- WebSocket Hibernation — for cost-effective real-time UI
- SQLite-in-DO — for built-in storage
- Workers AI / AI Gateway — for LLM inference
- Cron triggers + alarms — for scheduled / proactive agent execution
Install
npx create-cloudflare@latest --template cloudflare/agents-starterThis scaffolds an agent project with React frontend + Worker backend + DO-backed agent class.
Basic agent
import { Agent, callable } from "agents";
interface State {
count: number;
messages: { role: string; content: string }[];
}
export class CounterAgent extends Agent<Env, State> {
initialState: State = { count: 0, messages: [] };
@callable()
async increment(): Promise<number> {
this.setState({ ...this.state, count: this.state.count + 1 });
return this.state.count;
}
@callable()
async chat(message: string): Promise<string> {
const messages = [
{ role: "system", content: "You are a helpful assistant." },
...this.state.messages,
{ role: "user", content: message },
];
const result = await this.env.AI.run("@cf/meta/llama-3.3-70b-instruct", {
messages,
});
this.setState({
...this.state,
messages: [
...messages.slice(1),
{ role: "assistant", content: result.response },
],
});
return result.response;
}
}AIChatAgent — multi-turn chat with persistence built in
import { AIChatAgent } from "agents/ai-chat-agent";
import { streamText } from "ai";
import { workersai } from "workers-ai-provider";
export class ChatAgent extends AIChatAgent<Env> {
async onChatMessage(onFinish: any): Promise<Response> {
const model = workersai(this.env.AI)("@cf/meta/llama-3.3-70b-instruct");
const result = streamText({
model,
messages: this.messages, // pre-populated history
tools: { // SDK-provided tools
getWeather: { ... },
},
onFinish,
});
return result.toDataStreamResponse();
}
}The AIChatAgent class automatically:
- Persists conversation history in the DO’s SQL storage.
- Syncs state to connected WebSocket clients in real time.
- Plays well with
useAgentReact hook for client-side chat UIs.
React client hook
"use client";
import { useAgent } from "agents/react";
export function ChatUI() {
const { messages, sendMessage, state } = useAgent({
agent: "chat-agent",
name: "user-7281-session", // DO addressing — same name = same agent
});
return (
<div>
{messages.map((m, i) => <div key={i}>{m.role}: {m.content}</div>)}
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); }}>
<input ... />
</form>
</div>
);
}Tools — server-side
import { Agent, tool } from "agents";
import { z } from "zod";
export class TripPlannerAgent extends Agent<Env> {
async runTrip(destination: string) {
const result = await this.askAI({
messages: [{ role: "user", content: `Plan a trip to ${destination}` }],
tools: {
searchFlights: tool({
description: "Search for flights",
parameters: z.object({
from: z.string(),
to: z.string(),
date: z.string(),
}),
execute: async ({ from, to, date }) => {
// Hit your flight API
return { flights: [...] };
},
}),
},
});
return result;
}
}Scheduled execution
Agents can self-schedule via alarms (inherited from DO):
async checkSubscription() {
const expires = this.state.subscriptionExpires;
if (expires - Date.now() < 86400000) { // < 1 day
await this.sendReminderEmail();
}
// Schedule next check in 1 day
await this.ctx.storage.setAlarm(Date.now() + 86400000);
}
async alarm() {
await this.checkSubscription();
}Human-in-the-loop
Pattern: agent emits a requiresApproval event, the UI shows it to the user, user approves → agent continues. Built-in helpers:
async approveAction(action: Action): Promise<boolean> {
this.setState({
...this.state,
pendingApproval: action,
});
// Wait for approval (resolves via DO alarms or external trigger)
return new Promise((resolve) => {
this.approvalResolvers.set(action.id, resolve);
});
}MCP server hosting
Agents can expose themselves as MCP (Model Context Protocol) servers — other AI agents (Claude Code, Cursor, etc.) can call them as tools. Configure in the agent class:
import { McpAgent } from "agents/mcp";
export class MyMcpAgent extends McpAgent<Env> {
async initializeMcp(server: McpServer) {
server.tool("search-docs", { description: "Search internal docs" }, async ({ query }) => {
// ... search logic
return { content: [{ type: "text", text: results }] };
});
}
}Then any MCP-aware client (Claude Code, Cursor, MCP Inspector) can connect to your agent via the MCP transport (Streamable HTTP).
5. The AI SDK Workers AI provider
Per npm.cloudflare.com/workers-ai-provider. Use Vercel’s AI SDK syntax with Workers AI:
import { createWorkersAI } from "workers-ai-provider";
import { generateText } from "ai";
const workersai = createWorkersAI({ binding: env.AI });
const { text } = await generateText({
model: workersai("@cf/meta/llama-3.3-70b-instruct"),
prompt: "Hello!",
});Lets you write Vercel-AI-SDK-compatible code that runs on Cloudflare with Workers AI instead of an external provider.
6. RAG pattern — end-to-end on Cloudflare
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { query } = await request.json();
// 1. Embed query
const embed = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: [query] });
const queryVec = embed.data[0];
// 2. Search Vectorize
const matches = await env.VECTORS.query(queryVec, {
topK: 5,
returnMetadata: true,
});
// 3. Rerank (optional)
const rerankInput = matches.matches.map(m => m.metadata.text as string);
const reranked = await env.AI.run("@cf/baai/bge-reranker-base", {
query,
contexts: rerankInput.map(text => ({ text })),
});
// 4. Build context
const context = reranked.response
.sort((a, b) => b.score - a.score)
.slice(0, 3)
.map(r => rerankInput[r.id])
.join("\n\n---\n\n");
// 5. Generate answer
const answer = await env.AI.run("@cf/meta/llama-3.3-70b-instruct", {
messages: [
{ role: "system", content: `Answer based on:\n${context}` },
{ role: "user", content: query },
],
});
return Response.json({ answer: answer.response });
},
};All four stages (embed, search, rerank, generate) run on Cloudflare. No external API keys, no egress fees, single-vendor billing.
7. Costs at scale — Workers AI vs OpenAI/Anthropic
Comparing Llama 3.3 70B on Workers AI vs Claude Sonnet 4.6 on Anthropic for a typical workload:
| Provider | Input | Output | Note |
|---|---|---|---|
| Workers AI Llama 3.3 70B | $0.59/MTok | $0.79/MTok | ~6× cheaper |
| Anthropic Claude Sonnet 4.6 | $3/MTok | $15/MTok | ~10× more capable on many tasks |
Workers AI wins on price; Claude wins on quality. For agent-style workflows where Llama 3.3 is “good enough” (classification, extraction, basic tool-calling), Workers AI’s price advantage compounds rapidly. For nuanced reasoning, stick with Claude (via AI Gateway).
8. Limits
- Workers AI requests per minute: 720 default per account on Paid; higher quotas available.
- Max tokens per generation: model-dependent (typically 4-32k).
- Max image size for vision: 4 MB.
- Vectorize ingestion rate: 100 vectors/sec/index sustained; bursts higher.