Created
September 6, 2025 22:39
-
-
Save ericclemmons/d939830c7375c2f143645906cfb3fa62 to your computer and use it in GitHub Desktop.
Cloudflare AutoRAG for https://x.com/sunglassesface/status/1964122155622293749
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import alchemy from "alchemy"; | |
import { CloudflareStateStore } from "alchemy/state"; | |
const { AutoRag } = await import('./AutoRag.ts'); | |
const app = await alchemy("orlie", { | |
stateStore: (scope) => new CloudflareStateStore(scope), | |
}); | |
export const BUCKET = await R2Bucket('autorag-bucket'); | |
export const GATEWAY = await AiGateway(`autorag-gateway`); | |
export const AUTO_RAG = await AutoRag(`autorag`, { | |
aiGateway: GATEWAY, | |
bucket: BUCKET, | |
}); | |
await app.finalize(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://alchemy.run/concepts/resource/ | |
import alchemy, { Resource, type Context } from "alchemy"; | |
import { | |
CloudflareApi, | |
createCloudflareApi, | |
type AiGatewayResource, | |
type R2BucketResource, | |
} from "alchemy/cloudflare"; | |
export interface AutoRagProps { | |
adopt?: boolean; | |
aiGateway: AiGatewayResource; | |
bucket: R2BucketResource; | |
enabled?: boolean; | |
name?: string; | |
// https://developers.cloudflare.com/api/resources/autorag/subresources/rags/methods/ai_search/#(params)%20default%20%3E%20(param)%20account_id%20%3E%20(schema) | |
aiSearchModel?: | |
| "" | |
| "@cf/meta/llama-3.3-70b-instruct-fp8-fast" | |
| "@cf/meta/llama-3.1-8b-instruct-fast" | |
| "@cf/meta/llama-3.1-8b-instruct-fp8" | |
| "@cf/meta/llama-4-scout-17b-16e-instruct" | |
| "@cf/qwen/qwen3-30b-a3b-fp8"; | |
} | |
interface AutoRagResult { | |
ai_gateway_id: AiGatewayResource["id"]; | |
ai_search_model: AutoRagProps["aiSearchModel"]; | |
cache: boolean; | |
cache_threshold: "close_enough" | "exact_match" | null; | |
chunk: boolean; | |
chunk_overlap: number; | |
chunk_size: number; | |
created_at: string; | |
created_by: string; | |
embedding_model: string; | |
enabled?: boolean; | |
id: string; | |
max_num_results: number; | |
modified_at: string; | |
modified_by: string; | |
paused: boolean; | |
rewrite_model: string; | |
rewrite_query: boolean; | |
score_threshold: number; | |
source: R2BucketResource["name"]; | |
source_params: { r2_jurisdiction: "default" }; | |
status: "waiting" | "running" | "paused" | "error"; | |
summarization: boolean; | |
summarization_model: string; | |
token_id: string; | |
type: "r2"; | |
vectorize_name: string; | |
} | |
export interface AutoRagResource | |
extends Resource<"custom::cloudflare:autorag">, | |
AutoRagProps, | |
AutoRagResult {} | |
interface JsonResponse<T> { | |
success: boolean; | |
errors: { code: number; message: string }[]; | |
messages: unknown[]; | |
result: T; | |
} | |
interface PermissionGroup { | |
description: string; | |
id: string; | |
label: string; | |
name: string; | |
scopes: string[]; | |
} | |
// ⚠️ Creating tokens requires special `API Tokens Write` permissions that aren't available in the UI! | |
// @see https://developers.cloudflare.com/api/resources/user/subresources/tokens/subresources/permission_groups/methods/list/ | |
// | |
// As a workaround, we can use a global API token to get permission groups & create tokens. | |
// There's a template in the UI, but I haven't tested it: | |
// @see https://developers.cloudflare.com/fundamentals/api/reference/template/ | |
// | |
// In theory, the user could call `await AutoRag({ tokenKey })` | |
const createTokenApi = async (api: CloudflareApi) => { | |
return new CloudflareApi({ | |
accountId: api.accountId, | |
authOptions: { | |
apiKey: await alchemy.secret.env("CLOUDFLARE_GLOBAL_API_KEY"), | |
apiEmail: await alchemy.env("CLOUDFLARE_GLOBAL_EMAIL"), | |
type: "api-key", | |
}, | |
}); | |
}; | |
const getPermissionGroups = async (api: CloudflareApi): Promise<PermissionGroup[]> => { | |
const globalApi = await createTokenApi(api); | |
const response = await globalApi.get(`/user/tokens/permission_groups`); | |
if (!response.ok) { | |
throw new Error("Failed to get permission groups", { cause: await response.json() }); | |
} | |
const json = (await response.json()) as JsonResponse<PermissionGroup[]>; | |
if (!json.success) { | |
throw new Error("Failed to get permission groups", { cause: json }); | |
} | |
return json.result; | |
}; | |
type ResourceKey = `com.cloudflare.api.account.${string}`; | |
type Resources = Record<ResourceKey, string>; | |
interface Policy { | |
effect: "allow" | "deny"; | |
id: string; | |
resources: Resources; | |
permission_groups: Array<Pick<PermissionGroup, "id" | "name">>; | |
} | |
interface UserToken { | |
id: string; | |
issued_on: string; | |
last_used_on: string | null; | |
modified_on: string; | |
name: string; | |
policies: Array<Policy>; | |
status: "active"; | |
value: string; | |
} | |
const createUserToken = async (api: CloudflareApi, id: string, props: AutoRagProps) => { | |
const permissionGroups = await getPermissionGroups(api); | |
const condition = {} as const; | |
const name = `AutoRag Token - ${props.name ?? id}`; | |
const permissionGroupKeys = [] as const; | |
const scopes = [] as const; | |
const policies = [ | |
{ | |
effect: "allow", | |
resources: { | |
[`com.cloudflare.api.account.${api.accountId}`]: "*", | |
} satisfies Resources, | |
permission_groups: permissionGroups | |
.filter((group) => | |
[ | |
"AI Gateway Write", | |
"Vectorize Write", | |
"Workers R2 Storage Write", | |
"Workers Scripts Write", | |
"Account Settings Read", | |
].includes(group.name) | |
) | |
.map((group) => ({ id: group.id })), | |
}, | |
]; | |
const body = { name, scopes, permissionGroupKeys, condition, policies }; | |
const tokenApi = await createTokenApi(api); | |
const response = await tokenApi.post(`/accounts/${api.accountId}/tokens`, body); | |
if (!response.ok) { | |
throw new Error("Failed to create user token", { cause: await response.json() }); | |
} | |
const json = (await response.json()) as JsonResponse<UserToken>; | |
if (!json.success) { | |
throw new Error("Failed to create user token", { cause: json }); | |
} | |
return json.result; | |
}; | |
interface AutoRagToken { | |
cf_api_id: string; | |
cf_api_key: string; | |
created_at: string; | |
created_by: string; | |
id: string; | |
modified_at: string; | |
modified_by: string; | |
name: string; | |
} | |
const createAutoRagToken = async ( | |
api: CloudflareApi, | |
_id: string, | |
_props: AutoRagProps, | |
userToken: UserToken | |
) => { | |
const body = { | |
cf_api_id: userToken.id, | |
cf_api_key: userToken.value, | |
name: userToken.name, | |
}; | |
const response = await api.post(`/accounts/${api.accountId}/autorag/tokens`, body); | |
if (!response.ok) { | |
throw new Error("Failed to create AutoRag token", { cause: await response.json() }); | |
} | |
const json = (await response.json()) as JsonResponse<AutoRagToken>; | |
if (!json.success) { | |
throw new Error("Failed to create AutoRag token", { cause: json }); | |
} | |
return json.result; | |
}; | |
const createAutoRag = async (api: CloudflareApi, id: string, props: AutoRagProps) => { | |
const userToken = await createUserToken(api, id, props); | |
const autoRagToken = await createAutoRagToken(api, id, props, userToken); | |
const body = { | |
ai_gateway_id: props.aiGateway.id, | |
ai_search_model: props.aiSearchModel ?? "", // Auto | |
cache: true, | |
cache_threshold: "close_enough", | |
chunk_overlap: 10, | |
chunk_size: 265, | |
embedding_model: "", | |
enabled: props.enabled ?? true, | |
id: props.name ?? id, | |
max_num_results: 10, // 1-10 | |
rewrite_model: "", | |
rewrite_query: true, | |
score_threshold: 0.4, | |
source: props.bucket.name, | |
source_params: { | |
r2_jurisdiction: "default", | |
}, | |
token_id: autoRagToken.id, | |
type: "r2", | |
} satisfies Omit< | |
AutoRagResult, | |
| "chunk" | |
| "created_at" | |
| "created_by" | |
| "modified_at" | |
| "modified_by" | |
| "paused" | |
| "status" | |
| "summarization_model" | |
| "summarization" | |
| "vectorize_name" | |
>; | |
// Proactively delete any previous vectorize indexes that got left behind by a previous AutoRag | |
const vectorizeResponse = await api.delete( | |
`/accounts/${api.accountId}/vectorize/v2/indexes/autorag-${body.id}` | |
); | |
if (vectorizeResponse.ok) { | |
console.info( | |
`Deleted previous Cloudflare AutoRag Vector Index "autorag-${body.id}" through API.` | |
); | |
} | |
const response = await api.post(`/accounts/${api.accountId}/autorag/rags`, body); | |
if (!response.ok) { | |
const cause = (await response.json()) as JsonResponse<unknown>; | |
if ( | |
props.adopt && | |
cause.errors.some((error) => error.message.includes("autorag_with_this_name_already_exist")) | |
) { | |
const existing = await api.get(`/accounts/${api.accountId}/autorag/rags/${body.id}`); | |
const json = (await existing.json()) as JsonResponse<AutoRagResource>; | |
if (!json.success) { | |
throw new Error("Failed to get existing AutoRag", { cause: json }); | |
} | |
return json.result; | |
} | |
throw new Error("Failed to create AutoRag", { cause }); | |
} | |
const json = (await response.json()) as JsonResponse<AutoRagResource>; | |
if (!json.success) { | |
throw new Error( | |
`Failed to create AutoRag. There may be previous resources conflicting at https://dash.cloudflare.com/${api.accountId}/ai/autorag`, | |
{ cause: json } | |
); | |
} | |
return json.result; | |
}; | |
const updateAutoRag = async ( | |
api: CloudflareApi, | |
_id: string, | |
_props: AutoRagProps, | |
output: AutoRagResource | |
) => { | |
console.warn("Don't feel like supporting AutoRag updates yet 😘. Returning existing value..."); | |
const existing = await api.get(`/accounts/${api.accountId}/autorag/rags/${output.id}`); | |
const json = (await existing.json()) as JsonResponse<AutoRagResource>; | |
if (!json.success) { | |
throw new Error("Failed to get existing AutoRag", { cause: json }); | |
} | |
return json.result; | |
}; | |
const deleteAutoRag = async ( | |
api: CloudflareApi, | |
id: string, | |
_props: AutoRagProps, | |
output: AutoRagResource | |
) => { | |
const response = await api.delete( | |
`/accounts/${api.accountId}/autorag/rags/${id}/${output.name}?force=true` | |
); | |
if (!response.ok) { | |
console.warn( | |
`Could not delete Cloudflare AutoRag through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/ai/autorag`, | |
await response.json() | |
); | |
} | |
if (output.vectorize_name) { | |
const vectorizeResponse = await api.delete( | |
`/accounts/${api.accountId}/vectorize/v2/indexes/${output.vectorize_name}` | |
); | |
if (!vectorizeResponse.ok) { | |
console.warn( | |
`Could not delete Cloudflare AutoRag Vector Index "${output.vectorize_name}" through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/ai/vectorize`, | |
await vectorizeResponse.json() | |
); | |
} | |
} | |
if (output.token_id) { | |
const tokenApi = await createTokenApi(api); | |
const tokenResponse = await tokenApi.delete(`/user/tokens/${output.token_id}`); | |
if (!tokenResponse.ok) { | |
console.warn( | |
`Could not delete Cloudflare AutoRag User Token "${output.token_id}" through API. Do it manually at:\n\thttps://dash.cloudflare.com/${api.accountId}/api-tokens`, | |
await tokenResponse.json() | |
); | |
} | |
} | |
}; | |
export const AutoRag = Resource( | |
"custom::cloudflare:autorag", | |
async function ( | |
this: Context<AutoRagResource>, | |
id: string, | |
props: AutoRagProps | |
): Promise<AutoRagResource> { | |
const api = await createCloudflareApi({}); | |
switch (this.phase) { | |
case "create": { | |
const output = await createAutoRag(api, id, props); | |
return this.create({ ...props, ...output }); | |
} | |
case "update": { | |
const output = await updateAutoRag(api, id, props, this.output); | |
return this.create({ ...props, ...output }); | |
} | |
case "delete": { | |
await deleteAutoRag(api, id, props, this.output); | |
return this.destroy(); | |
} | |
default: | |
// @ts-expect-error Property 'phase' does not exist on type 'never'.ts(2339) | |
const phase: never = this.phase; | |
throw new Error(`Unhandled phase: ${phase}`); | |
} | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment