AI
Workers AI
Run Workers AI text generation and embedding models with OpenOrange through an OpenAI-compatible provider.
Status: Partial. Needs a service: an OpenAI-compatible provider.
Choose a provider
| Provider | Set in .dev.vars | Notes |
|---|---|---|
| OpenRouter (default) | OPENROUTER_API_KEY=<API key> | The built-in model mappings use OpenRouter model IDs. |
| OpenAI | ORANGE_AI_BASE_URL=https://api.openai.com/v1ORANGE_AI_API_KEY=<API key> | Map each model with ORANGE_AI_MODELS. |
| Groq | ORANGE_AI_BASE_URL=https://api.groq.com/openai/v1ORANGE_AI_API_KEY=<API key> | Map each model with ORANGE_AI_MODELS. |
| Together AI | ORANGE_AI_BASE_URL=https://api.together.ai/v1ORANGE_AI_API_KEY=<API key> | Map each model with ORANGE_AI_MODELS. |
| Fireworks AI | ORANGE_AI_BASE_URL=https://api.fireworks.ai/inference/v1ORANGE_AI_API_KEY=<API key> | Map each model with ORANGE_AI_MODELS. |
| Any OpenAI-compatible API | ORANGE_AI_BASE_URL=<base URL>ORANGE_AI_API_KEY=<API key> | The API must serve /chat/completions, and /embeddings for embedding models. |
| A model on your machine | ORANGE_AI_BASE_URL=http://127.0.0.1:11434/v1 | See Run a model locally. |
The built-in mappings send OpenRouter model IDs, such as meta-llama/llama-3.3-70b-instruct.
Another provider uses its own IDs, so add a mapping in ORANGE_AI_MODELS for each model that
the Worker calls. See Models.
Configuration
OpenOrange reads the standard Wrangler AI binding:
{
"ai": { "binding": "AI" }
}
OpenOrange sends model calls to an OpenAI-compatible provider. The default is
OpenRouter. Put the provider settings in .dev.vars
or the process environment:
| Variable | Default | Meaning |
|---|---|---|
ORANGE_AI_API_KEY | OPENROUTER_API_KEY | Bearer token for the provider. Required for OpenRouter. |
ORANGE_AI_BASE_URL | https://openrouter.ai/api/v1 | Base URL of the OpenAI-compatible API. |
ORANGE_AI_MODELS | none | JSON that adds or replaces model mappings. |
OPENROUTER_API_KEY=sk-or-...
Without ORANGE_AI_BASE_URL, a key is required. If no key is set, orange dev
prints one warning at startup, and env.AI.run() throws an
InferenceUpstreamError that names both variables.
With ORANGE_AI_BASE_URL set, the key is optional. Without a key, OpenOrange
sends no Authorization header.
Models
Each Workers AI model name maps to one provider model. A text model also has the result shape that Cloudflare returns for it:
legacy:{ response, tool_calls, usage }. Streams senddata: {"response":"..."}events.openai: an OpenAI chat completion,{ id, object, created, model, choices, usage }. Streams send OpenAIchat.completion.chunkevents.
| Workers AI model | Provider model | Task | Shape |
|---|---|---|---|
@cf/meta/llama-3.3-70b-instruct-fp8-fast | meta-llama/llama-3.3-70b-instruct | Text | legacy |
@cf/meta/llama-3.1-8b-instruct | meta-llama/llama-3.1-8b-instruct | Text | legacy |
@cf/meta/llama-3.1-8b-instruct-fast | meta-llama/llama-3.1-8b-instruct | Text | legacy |
@cf/zai-org/glm-4.7-flash | z-ai/glm-4.7-flash | Text | openai |
@cf/moonshotai/kimi-k2.7-code | moonshotai/kimi-k2.7-code | Text | openai |
@cf/openai/gpt-oss-120b | openai/gpt-oss-120b | Text | openai |
@cf/openai/gpt-oss-20b | openai/gpt-oss-20b | Text | openai |
@cf/baai/bge-base-en-v1.5 | baai/bge-base-en-v1.5 | Embeddings | |
@cf/baai/bge-large-en-v1.5 | baai/bge-large-en-v1.5 | Embeddings | |
@cf/baai/bge-m3 | baai/bge-m3 | Embeddings |
The shapes come from workerd’s model types. GLM 4.7 Flash and Kimi K2.7 Code
return ChatCompletionsOutput. gpt-oss returns it for messages input.
Add or replace mappings with ORANGE_AI_MODELS. Each entry needs a provider
model and a task, text or embeddings. shape is optional and defaults to
legacy:
ORANGE_AI_MODELS='{"@cf/meta/llama-3-8b-instruct":{"model":"meta-llama/llama-3-8b-instruct","task":"text"},"@cf/zai-org/glm-5.2":{"model":"z-ai/glm-5.2","task":"text","shape":"openai"}}'
A model without a mapping never falls back to another model. run() throws:
InferenceUpstreamError: 5007: Model "@cf/baai/bge-small-en-v1.5" has no OpenOrange mapping. Add it to ORANGE_AI_MODELS.
Map an embedding model only to the same model on the provider. Another model returns vectors that do not match vectors you stored before.
Run a model locally
A local server with an OpenAI-compatible API needs no key. Set only its base URL, and map each model to a model that the server has:
| Server | ORANGE_AI_BASE_URL |
|---|---|
| Ollama | http://127.0.0.1:11434/v1 |
| LM Studio | http://127.0.0.1:1234/v1 |
ORANGE_AI_BASE_URL=http://127.0.0.1:11434/v1
ORANGE_AI_MODELS='{"@cf/meta/llama-3.1-8b-instruct":{"model":"llama3.1:8b","task":"text"}}'
How it works
env.AI is a port of workerd’s AI client. Its requests go to an in-process
backend that translates them to the provider:
| Call | Provider request | Result |
|---|---|---|
run() with a legacy text model | POST {base}/chat/completions | { response, tool_calls, usage } |
run() with an openai text model | POST {base}/chat/completions | The provider’s chat completion |
run() with stream: true | Same, with stream: true | text/event-stream |
run() with an embedding model | POST {base}/embeddings | { shape, data } |
models() | none | The mapped models |
- A
promptbecomes one user message. - For a
legacymodel,messages,max_tokens,temperature,top_p,top_k,seed,frequency_penalty,presence_penalty,repetition_penalty,tools, andresponse_formatare forwarded. - For an
openaimodel, every input field is forwarded unchanged. - A
legacystream sendsdata: {"response":"..."}events. One last event holds the tool calls and usage, thendata: [DONE]. - An
openaistream forwards each provider chunk, thendata: [DONE]. - Libraries that read Workers AI streams, such as
workers-ai-provider, parse both shapes. - A provider error throws
InferenceUpstreamErrorwith<HTTP status>: <provider message>.returnRawResponse: truereturns the failedResponseinstead. orange devlogs each call with the model, the provider model, the status, and the latency.
Conformance
An end-to-end orange dev test runs a Worker against a mock provider, which is
a real HTTP server. It covers both result shapes, tool calls, streaming in both
shapes, provider 429 and 500 errors, a provider error during a stream,
embeddings, the gateway option, unmapped models, a missing OpenRouter key, a
local provider without a key, and overlapping calls with different options. The generated Ai type is not registered in the
type-conformance checks. No test calls a real provider.
Limits and deviations
- The text comes from a different host, so it differs from Cloudflare. The result shape follows the Workers AI schema.
- An
openaimodel returns the provider’s chat completion as it is. Itsidandmodelfields are the provider’s values. - gpt-oss with Responses API input (
inputandinstructions) throws. Passmessagesinstead. - In the
legacyshape, each tool call has both the{ name, arguments }shape and the{ id, type, function }shape. - The stream framing is not checked against recordings from Cloudflare.
- With
response_format,responseis the model’s JSON text. - Cloudflare’s default
max_tokensis not applied. The provider’s default applies. - The
gatewayoption works, but requests go to the provider directly. There is no gateway caching or logging, andaiGatewayLogIdisnull.orange devwarns once. env.AI.gateway(),autorag(),aiSearch(), andtoMarkdown()throwNotSupportedError.websocket: trueandReadableStreamorFormDatainputs throwNotSupportedError.- Image, speech, translation, and reranker models have no mapping.
models()lists only mapped models. It appliessearchandtaskand ignores the other filters.lastRequestIdisnull.new Ai()fromcloudflare:aistill throws. Use theaibinding.
Compatibility
From the latest conformance run. Scores on the compatibility page; every binding on the status page.
Workers AI + AI Gateway
ai Runs on Any OpenAI-compatible provider (OpenRouter by default, or ORANGE_AI_BASE_URL).
Text generation and embeddings run. The gateway option is accepted, but env.AI.gateway(), streaming inputs, and toMarkdown throw.
Works with
Locally: Ollama
Any OpenAI-compatible endpoint, set in ORANGE_AI_BASE_URL. Text and embedding models.
Recorded results
WorksWorkers AIModels on Cloudflare's network.Any OpenAI-compatible provider. It needs your key, and @cf/ model names need a mapping in ORANGE_AI_MODELS. 1/1 pass
- Results
- example: workers-ai Pass
In the feature matrix
AlternativeaiRuns models on Workers AI.A port of workerd's Ai client sends each run() to an OpenAI-compatible provider, OpenRouter by default, set with ORANGE_AI_BASE_URL and ORANGE_AI_API_KEY. Text generation, streams, tool calls, and embeddings work for the built-in @cf model table and for models added with ORANGE_AI_MODELS. 5 files
- Notes
- The text comes from another host. A model without a mapping, and image, speech, translation, and reranker models, throw an error that names ORANGE_AI_MODELS. The gateway option goes to the provider directly. env.AI.gateway(), autorag(), aiSearch(), and toMarkdown() throw NotSupportedError. See docs/modules/workers-ai.mdx.
- Source
- src/config/load-wrangler.ts:811-812packages/runtime-node/src/runner.ts:451-459packages/runtime-node/src/bindings/ai/ai.ts:61-188packages/runtime-node/src/bindings/ai/backend.ts:401-559src/runtime/ai-binding.test.ts:243-438