feat(openai): add LLM.with_cloudflare() for Cloudflare AI Gateway#6131
feat(openai): add LLM.with_cloudflare() for Cloudflare AI Gateway#6131mcdgavin wants to merge 3 commits into
Conversation
Adds a with_cloudflare() convenience constructor that routes LLM traffic through the Cloudflare AI Gateway unified OpenAI-compatible endpoint, mirroring with_openrouter / with_azure. - Builds the gateway URL from account_id (env CLOUDFLARE_ACCOUNT_ID) and gateway_id (default "default"), with a base_url override, mirroring with_azure. - Dual auth: cf_aig_token -> cf-aig-authorization, api_key -> Authorization (BYOK); requires at least one. Placeholder key for gateway-stored-keys mode (with_ollama precedent). - gateway_options (CloudflareGatewayOptions TypedDict) maps caching/retry/timeout/metadata/ custom-cost to cf-aig-* request headers. - Lists Cloudflare in the plugin's provider docstring; hermetic unit tests for URL building, auth modes, and the gateway-options header mapping. Closes livekit#6101
| "Cloudflare account_id is required, either as argument or set " | ||
| "CLOUDFLARE_ACCOUNT_ID environment variable (or pass base_url directly)" | ||
| ) | ||
| base_url = f"https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat" |
There was a problem hiding this comment.
The Unified API (OpenAI compat) endpoint is being deprecated, we should use the rest-api instead.
There was a problem hiding this comment.
Switched to the REST API (https://api.cloudflare.com/client/v4/accounts/<id>/ai/v1) in 3a9f400. It's OpenAI-SDK compatible — Cloudflare's own example uses new OpenAI({ baseURL: ".../ai/v1" }) — so the wrapper is unchanged apart from the base URL, and streaming is supported on that endpoint. Gateway selection moved to the cf-aig-gateway-id header (no gateway path segment; defaults to the account's default gateway).
One caveat I want to call out: the REST-API docs don't explicitly mention tool/function calling. It's the OpenAI schema so tools/tool_calls pass through at the protocol level, but whether the gateway+model honors them is model-dependent and I haven't verified it against a live gateway.
Address review on livekit#6131 — the OpenAI-compat /compat endpoint is deprecated. Switch to the REST API (/accounts/<id>/ai/v1), which is OpenAI-SDK compatible: - auth via Authorization: Bearer <Cloudflare API token> (api_key); drop cf_aig_token, the cf-aig-authorization header, and the placeholder-key logic - route through a specific gateway via the cf-aig-gateway-id header (no gateway path segment); defaults to the account's default gateway - drop custom_cost (not supported on the REST endpoint) and CloudflareCustomCost - metadata now also accepts a pre-serialized JSON string
Completes the per-request cf-aig-* header surface for the REST endpoint.
Summary
Closes #6101. Adds
openai.LLM.with_cloudflare(...), a convenience constructor that routesLLM traffic through the Cloudflare AI Gateway
unified OpenAI-compatible endpoint — mirroring the existing
with_openrouter/with_azurehelpers.
Design notes
cf-aig-authorization(gateway token) andAuthorization(BYO downstream provider key); at least one is required. In gateway-stored-keysmode the OpenAI SDK still needs a non-empty
Authorization, so a"cloudflare"placeholder isused — the same pattern as
with_ollama.Cloudflare's controls are
cf-aig-*HTTP headers.gateway_optionsis aCloudflareGatewayOptionsTypedDict covering caching, retries, timeout, metadata, and customcost; set fields map to the corresponding headers (
metadata/custom_costJSON-encoded). Agrouped TypedDict (over loose kwargs) keeps the signature to one extra param while typing the
structured values. Validation is type-level only; numeric ranges pass through to Cloudflare.
account_id(envCLOUDFLARE_ACCOUNT_ID) andgateway_id(default"default"), with abase_urloverride.Testing
tests/test_openai_with_cloudflare.py(hermetic,--unit): URL building fromaccount_id/gateway_id,
CLOUDFLARE_ACCOUNT_IDenv fallback,base_urloverride, BYOK vsgateway-stored-keys auth, the full
gateway_options → cf-aig-*header mapping (incl. JSONencoding and the
skip_cache-only-when-true rule), and allValueErrorpaths.make checkpasses (ruff format + lint, strict mypy).
Out of scope
STT/TTS via Cloudflare Workers AI (#3163), tracked separately.