1---
2title: Run Workers AI models
3description: Bind Workers AI into your Worker with Cloudflare.Workers.AI — run inference and list models directly, or turn the binding into a typed Effect LanguageModel, no gateway required.
4sidebar:
5 order: 4
6---
7
8**Workers AI** runs open-weight models (Llama, Mistral, Whisper,
9embeddings, …) on Cloudflare's GPU fleet, callable from any Worker
10through the native `env.AI` binding. There is no resource to declare
11and nothing to provision — the service is account-level, so the only
12Alchemy piece is the binding itself: `Cloudflare.Workers.AI`.
13
14This is the plain `{ type: "ai" }` Worker binding, the same one you
15would declare in `wrangler.json`. If you want requests routed through
16an **AI Gateway** — caching, rate limiting, retries, a request log —
17use [`Cloudflare.AI.Gateway`](/cloudflare/ai/ai-gateway) instead; the
18binding here talks to Workers AI directly.
19
20## Effect-style Worker
21
22`yield* Cloudflare.Workers.AI()` inside a Worker's Construction phase does two
23things: it attaches the `ai` binding to the Worker at deploy time, and
24at runtime it resolves to an Effect-native client whose `run` and
25`models` operations return Effects with a typed `WorkersAIError`
26channel. Provide `Cloudflare.Workers.AIBinding` once at the bottom of
27the constructor's layer chain, the same way every other Worker binding works:
28
29```typescript
30// src/AiWorker.ts
31import * as Cloudflare from "alchemy/Cloudflare";
32import * as Effect from "effect/Effect";
33import { HttpServerRequest } from "effect/http/HttpServerRequest";
34import * as HttpServerResponse from "effect/http/HttpServerResponse";
35
36const MODEL = "@cf/meta/llama-3.3-70b-instruct-fp8-fast";
37
38export default class AiWorker extends Cloudflare.Worker<AiWorker>()(
39 "AiWorker",
40 { main: import.meta.url },
41 Effect.gen(function* () {
42 const ai = yield* Cloudflare.Workers.AI();
43
44 return {
45 fetch: Effect.gen(function* () {
46 const request = yield* HttpServerRequest;
47 const url = new URL(request.url, "http://worker");
48
49 if (url.pathname === "/run") {
50 const result = yield* ai
51 .run(MODEL, {
52 messages: [{ role: "user", content: "Say hello in one sentence." }],
53 max_tokens: 128,
54 })
55 .pipe(Effect.orDie);
56 return yield* HttpServerResponse.json(result);
57 }
58
59 if (url.pathname === "/models") {
60 const models = yield* ai
61 .models({ search: "llama-3.3" })
62 .pipe(Effect.orDie);
63 return yield* HttpServerResponse.json({
64 names: models.map((m) => m.name),
65 });
66 }
67
68 return HttpServerResponse.text("Not Found", { status: 404 });
69 }),
70 };
71 }).pipe(Effect.provide(Cloudflare.Workers.AIBinding)),
72) {}
73```
74
75`ai.run` is typed by the model catalog from
76`@cloudflare/workers-types`: the model id narrows the `inputs` and
77output types, so `@cf/meta/...` chat models take `messages` /
78`prompt` while embedding models take `text`, checked at compile time.
79`Effect.orDie` collapses `WorkersAIError` to a defect so a model
80failure surfaces as a 500 — for typed handling use
81`Effect.catchTag("WorkersAIError", …)` instead.
82
83## Build a `LanguageModel` layer
84
85`ai.model({...})` turns the binding into an
86[Effect AI](/cloudflare/ai/effect-ai) `LanguageModel` Layer — the
87provider-agnostic service behind `generateText`, `streamText`,
88`Toolkit`, and structured outputs. It is the same adapter that AI
89Gateway's `QueryGateway` uses, minus the gateway routing, so no API
90key and no HTTP client layer are involved:
91
92```typescript
93import { LanguageModel } from "effect/ai";
94
95Effect.gen(function* () {
96 const ai = yield* Cloudflare.Workers.AI();
97
98 const languageModel = ai.model({
99 model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
100 parameters: { temperature: 0.7, maxTokens: 1024 },
101 });
102
103 return {
104 fetch: Effect.gen(function* () {
105 const response = yield* LanguageModel.generateText({
106 prompt: "Write a haiku about Effect.",
107 }).pipe(Effect.orDie);
108 return yield* HttpServerResponse.json({
109 text: response.text,
110 usage: {
111 inputTokens: response.usage.inputTokens.total,
112 outputTokens: response.usage.outputTokens.total,
113 },
114 });
115 }).pipe(Effect.provide(languageModel)),
116 };
117}).pipe(Effect.provide(Cloudflare.Workers.AIBinding));
118```
119
120Streaming works the same way as any other provider —
121`LanguageModel.streamText` returns a stream of typed response parts.
122Use `Stream.provide(languageModel)` (the stream-aware equivalent of
123`Effect.provide`) so the model stays available for the lifetime of
124the stream:
125
126```typescript
127import * as Stream from "effect/Stream";
128import * as Sse from "effect/encoding/Sse";
129
130const stream = LanguageModel.streamText({ prompt }).pipe(
131 Stream.provide(languageModel),
132 Sse.encode,
133);
134
135return HttpServerResponse.stream(stream, {
136 headers: { "content-type": "text/event-stream" },
137});
138```
139
140Because the Layer satisfies Effect's standard `LanguageModel`
141service, swapping Workers AI for OpenAI, Anthropic, or a
142gateway-fronted model later is a layer-level change — the handler
143code doesn't move. See [Effect AI](/cloudflare/ai/effect-ai) for the
144full pattern, including chat persistence.
145
146## Async Workers: bind via `env`
147
148A plain async Worker (no Effect runtime) declares the binding on
149`env` instead.
150`InferEnv` maps it to the native `Ai` runtime handle — exactly what
151`wrangler` would give you:
152
153```typescript
154// src/AiAsyncWorker.ts
155import * as Cloudflare from "alchemy/Cloudflare";
156
157export const AiAsyncWorker = Cloudflare.Worker("AiAsyncWorker", {
158 main: "./src/handler.ts",
159 env: {
160 AI: Cloudflare.Workers.AI(),
161 },
162});
163
164export type AiAsyncWorkerEnv = Cloudflare.InferEnv<typeof AiAsyncWorker>;
165// { AI: Ai }
166```
167
168```typescript
169// src/handler.ts
170import type { AiAsyncWorkerEnv } from "./AiAsyncWorker.ts";
171
172export default {
173 async fetch(request: Request, env: AiAsyncWorkerEnv): Promise<Response> {
174 const result = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
175 messages: [{ role: "user", content: "Say hello in one sentence." }],
176 max_tokens: 128,
177 });
178 return Response.json(result);
179 },
180};
181```
182
183`Cloudflare.Workers.AI()` takes an optional binding name (the `env`
184key it resolves to) and defaults to `"AI"`.
185
186## When to add an AI Gateway
187
188The plain binding is the shortest path to inference, but every
189request goes straight to Workers AI. Put an
190[AI Gateway](/cloudflare/ai/ai-gateway) in front when you want:
191
192- **Caching** — identical prompts served from cache in milliseconds.
193- **Rate limiting and retries** — protect quota, smooth over blips.
194- **A request log** — every prompt, completion, latency, and token
195 count in one dashboard.
196- **Other providers** — one endpoint fronting OpenAI, Anthropic,
197 Bedrock, … alongside Workers AI.
198
199`aiGateway.model({...})` and `ai.model({...})` produce the same
200`LanguageModel` Layer shape, so moving between them is a one-line
201change in the Construction phase.
202
203## Where to go next
204
205- [Effect AI](/cloudflare/ai/effect-ai) — the `LanguageModel` /
206 `Chat` pattern this binding plugs into.
207- [Add an AI Gateway](/cloudflare/ai/ai-gateway) — caching, rate
208 limiting, and logs in front of the same models.
209- [API reference: Cloudflare.Workers.AI](/providers/cloudflare/workers/ai)
210 — the full binding surface (`run`, `models`, `model`, `raw`).