AI Search
Learn how to deploy a knowledge base with Cloudflare AI Search and a Worker using Alchemy.
This guide walks you through creating an AI Search knowledge base with a Cloudflare Worker that provides semantic search and RAG-powered chat completions.
-
Install Dependencies
Terminal window bun add alchemyTerminal window npm install alchemyTerminal window pnpm add alchemyTerminal window yarn add alchemy -
Credentials
Set your Cloudflare API token in your environment:
Terminal window export CLOUDFLARE_API_TOKEN=your-api-tokenYou can create a user API token at dash.cloudflare.com/profile/api-tokens with the AI Search Edit and Workers Scripts Edit permissions. (This is the token Alchemy uses to manage AI Search instances; it is separate from the service token Cloudflare provisions internally to grant AI Search access to an R2 bucket — see Service Token in the AiSearch reference.)
-
Create
alchemy.run.tsCreate a deployment script that provisions an AI Search instance and a Worker:
alchemy.run.ts import alchemy from "alchemy";import { AiSearch, AiSearchNamespace, Worker } from "alchemy/cloudflare";const app = await alchemy("ai-search-app");// Create a namespace for organizing instancesconst ns = await AiSearchNamespace("docs", {name: "my-docs",adopt: true,});// Create an AI Search instance (built-in storage, no R2 needed)const search = await AiSearch("knowledge-base", {name: "knowledge-base",adopt: true,});// Deploy a Worker with both binding types.export const worker = await Worker("api", {entrypoint: "./src/worker.ts",url: true,bindings: {SEARCH: search, // Single instance bindingDOCS: ns, // Namespace binding for dynamic access},});console.log({ url: worker.url });await app.finalize(); -
Implement the Worker
Create a Worker that handles search queries, file uploads, and chat completions:
src/worker.ts import type { worker } from "../alchemy.run";export default {async fetch(request: Request, env: typeof worker.Env): Promise<Response> {const url = new URL(request.url);// Upload a documentif (url.pathname === "/upload" && request.method === "POST") {const formData = await request.formData();const file = formData.get("file") as File;if (!file) return new Response("No file", { status: 400 });const item = await env.SEARCH.items.upload(file.name, file);return Response.json({ uploaded: item });}// Search — simple lookup uses `query`.if (url.pathname === "/search") {const query = url.searchParams.get("q");if (!query) return new Response("?q= required", { status: 400 });const results = await env.SEARCH.search({ query });return Response.json(results);}// Chat completions (streaming) — chat-style context uses `messages`.if (url.pathname === "/chat") {const query = url.searchParams.get("q");if (!query) return new Response("?q= required", { status: 400 });const stream = await env.SEARCH.chatCompletions({messages: [{ role: "user", content: query }],stream: true,});return new Response(stream, {headers: { "content-type": "text/event-stream" },});}// List instances in namespaceif (url.pathname === "/instances") {const list = await env.DOCS.list();return Response.json(list);}return new Response("Endpoints:\n" +" POST /upload — Upload a document\n" +" GET /search?q= — Search documents\n" +" GET /chat?q= — Chat with documents (streaming)\n" +" GET /instances — List AI Search instances\n",);},}; -
Deploy
Run the
alchemy.run.tsscript to deploy:Terminal window bun ./alchemy.runTerminal window npx tsx ./alchemy.runTerminal window pnpm tsx ./alchemy.runTerminal window yarn tsx ./alchemy.runIt should log out the Worker URL:
Terminal window { url: "https://api.your-subdomain.workers.dev" } -
Upload and Search
Upload a document and search it:
Terminal window # Upload a documentcurl -F "file=@guide.pdf" https://api.your-subdomain.workers.dev/upload# Search (wait a moment for indexing)curl "https://api.your-subdomain.workers.dev/search?q=how+does+caching+work"# Chat with streaming responsecurl "https://api.your-subdomain.workers.dev/chat?q=explain+caching" -
Tear Down
Terminal window bun ./alchemy.run --destroyTerminal window npx tsx ./alchemy.run --destroyTerminal window pnpm tsx ./alchemy.run --destroyTerminal window yarn tsx ./alchemy.run --destroy