← Обзор / ai-search / agent-sdks
AI SDK
Vercel AI SDK ↗ представляет собой набор инструментов на TypeScript для создания приложений с большими языковыми моделями. ai-search-provider ↗ пакет связывает AI Search с AI SDK, поэтому из одного и того же API вы можете генерировать ответы, основанные на вашем индексированном контенте, получать чанки и управлять документами.
В этом руководстве вы создадите Worker, который создаёт экземпляр AI Search, загружает и индексирует документ, а затем отправляет к нему запрос через AI SDK.
Предварительные требования
- Зарегистрируйтесь для получения Аккаунт Cloudflare ↗.
- Установка
Node.js↗.
менеджер версий Node.js
Используйте менеджер версий Node, например Volta ↗ или nvm ↗ чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.
1. Создайте проект Worker
Создайте новый проект Worker с помощью create-cloudflare CLI (C3). C3 ↗ это инструмент командной строки, который помогает настраивать и развёртывать новые приложения в Cloudflare.
Создайте новый проект с именем ai-search-ai-sdk выполнив:
npm create cloudflare@latest -- ai-search-ai-sdkДля настройки выберите следующие параметры:
- Для С чего вы хотите начать?, выберите
Hello World example. - Для Какой шаблон вы хотите использовать?, выберите
Worker only. - Для Какой язык вы хотите использовать?, выберите
TypeScript. - Для Хотите использовать git для контроля версий?, выберите
Yes. - Для Хотите развернуть приложение?, выберите
No(мы внесём некоторые изменения перед развёртыванием).
Перейдите в каталог вашего приложения:
cd ai-search-ai-sdk2. Установите AI SDK и провайдер
Установите AI SDK и провайдер AI Search. Провайдеру требуется AI SDK v6 (ai@^6):
npm i ai ai-search-provider3. Привяжите Worker к AI Search
Создайте привязку между своим Worker и AI Search. Bindings позволяют вашему Worker взаимодействовать с ресурсами на Cloudflare Developer Platform.
Добавьте следующее в свой конфигурационный файл Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"ai_search_namespaces": [
{
"binding": "AI_SEARCH",
"namespace": "default",
"remote": true
}
]
}[[ai_search_namespaces]]
binding = "AI_SEARCH"
namespace = "default"
remote = trueЭто привязывает default пространство имён к env.AI_SEARCH. remote опция позволяет wrangler dev перенаправляют запросы к вашему развёрнутому экземпляру, поскольку AI Search не выполняется локально. ai_search_namespaces привязка требует compatibility_date 2026-03-27 или новее, чему новые проекты C3 уже соответствуют.
4. Создайте экземпляр и проиндексируйте контент
Добавьте /setup маршрут, который создаёт экземпляр и загружает документ. Включите гибридный поиск при создании, задав index_method для индексирования как векторов, так и ключевых слов.
create() метод относится к привязке пространства имён (env.AI_SEARCH), а не на клиенте провайдера. Создание экземпляра, который уже существует, вызывает ошибку, поэтому следующий код создаёт его, а при следующем запуске просто обновляет.
import { createAISearchNamespace } from "ai-search-provider";
import { generateText, streamText } from "ai";
const INSTANCE_NAME = "knowledge-base";
const SAMPLE_DOC = `# Caching on Cloudflare
Cloudflare caches static assets at the edge. Use Cache Rules to control what is
cached, set an Edge Cache TTL to control how long objects stay in cache, and
purge the cache after a deploy.`;
// Create the instance with hybrid search, or update it if it already exists.
async function ensureInstance(env) {
// index_method with both vector and keyword enables hybrid search.
const hybrid = { index_method: { vector: true, keyword: true } };
try {
await env.AI_SEARCH.create({ id: INSTANCE_NAME, ...hybrid });
} catch {
await env.AI_SEARCH.get(INSTANCE_NAME).update(hybrid);
}
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
// createAISearchNamespace adapts the binding to the AI SDK provider API.
const aiSearch = createAISearchNamespace({ binding: env.AI_SEARCH });
// Visit /setup once to create the instance and index a document.
if (url.pathname === "/setup") {
await ensureInstance(env);
const instance = aiSearch.get(INSTANCE_NAME);
// upload() queues the file and returns immediately. Indexing runs in
// the background, so poll the item's status until it is searchable.
const { id, key } = await instance.items.upload("caching.md", SAMPLE_DOC);
let info = await instance.items.get(id).info();
while (info.status === "queued" || info.status === "running") {
await new Promise((resolve) => setTimeout(resolve, 2_000));
info = await instance.items.get(id).info();
}
return Response.json({ key, status: info.status });
}
// Query the instance (see the next step).
return new Response("Visit /setup first, then query with ?q=");
},
};import { createAISearchNamespace } from "ai-search-provider";
import { generateText, streamText } from "ai";
interface Env {
AI_SEARCH: AiSearchNamespace;
}
const INSTANCE_NAME = "knowledge-base";
const SAMPLE_DOC = `# Caching on Cloudflare
Cloudflare caches static assets at the edge. Use Cache Rules to control what is
cached, set an Edge Cache TTL to control how long objects stay in cache, and
purge the cache after a deploy.`;
// Create the instance with hybrid search, or update it if it already exists.
async function ensureInstance(env: Env) {
// index_method with both vector and keyword enables hybrid search.
const hybrid = { index_method: { vector: true, keyword: true } };
try {
await env.AI_SEARCH.create({ id: INSTANCE_NAME, ...hybrid });
} catch {
await env.AI_SEARCH.get(INSTANCE_NAME).update(hybrid);
}
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
// createAISearchNamespace adapts the binding to the AI SDK provider API.
const aiSearch = createAISearchNamespace({ binding: env.AI_SEARCH });
// Visit /setup once to create the instance and index a document.
if (url.pathname === "/setup") {
await ensureInstance(env);
const instance = aiSearch.get(INSTANCE_NAME);
// upload() queues the file and returns immediately. Indexing runs in
// the background, so poll the item's status until it is searchable.
const { id, key } = await instance.items.upload("caching.md", SAMPLE_DOC);
let info = await instance.items.get(id).info();
while (info.status === "queued" || info.status === "running") {
await new Promise((resolve) => setTimeout(resolve, 2_000));
info = await instance.items.get(id).info();
}
return Response.json({ key, status: info.status });
}
// Query the instance (see the next step).
return new Response("Visit /setup first, then query with ?q=");
},
} satisfies ExportedHandler<Env>;AiSearchNamespace это ambient-тип, доступный после выполнения wrangler types.
5. Запросите свой экземпляр
Опросить экземпляр из AI SDK можно тремя способами. Выберите тот, который подходит вашему приложению:
- Сгенерировать ответ возвращает полный ответ одним вызовом.
- Потоковая передача ответа отправляет токены по мере их генерации, что подходит для длинных ответов и чат-интерфейсов.
- Поиск как инструмент позволяет модели самой решать, когда выполнять поиск, а это именно то, что нужно в агенте.
Сгенерировать ответ
Передайте instance.chat() к generateText, и AI Search за один вызов находит релевантный контент и генерирует ответ.
Замените заполнитель запроса в своём fetch обработчик следующим образом:
const query = url.searchParams.get("q") ?? "How does caching work?";
// chat() returns an AI SDK model that retrieves matching chunks and generates
// a grounded answer in one call, so there is no separate search step.
const { text, sources } = await generateText({
model: aiSearch.get(INSTANCE_NAME).chat({
ai_search_options: {
// "hybrid" ranks results from both the vector and keyword indexes.
retrieval: { retrieval_type: "hybrid", max_num_results: 5 },
},
}),
messages: [{ role: "user", content: query }],
});
// `sources` holds the retrieved chunks, so you can cite them alongside `text`.
return Response.json({ text, sources });const query = url.searchParams.get("q") ?? "How does caching work?";
// chat() returns an AI SDK model that retrieves matching chunks and generates
// a grounded answer in one call, so there is no separate search step.
const { text, sources } = await generateText({
model: aiSearch.get(INSTANCE_NAME).chat({
ai_search_options: {
// "hybrid" ranks results from both the vector and keyword indexes.
retrieval: { retrieval_type: "hybrid", max_num_results: 5 },
},
}),
messages: [{ role: "user", content: query }],
});
// `sources` holds the retrieved chunks, so you can cite them alongside `text`.
return Response.json({ text, sources });AI Search возвращает найденные чанки в виде source parts AI SDK в sources, чтобы вы могли ссылаться на них рядом со сгенерированным текстом. Поскольку экземпляр индексирует и векторы, и ключевые слова, retrieval_type: "hybrid" использует оба.
Потоковая передача ответа
Для более длинных ответов используйте streamText вместо generateText. AI Search отправляет найденные чанки в виде частей источника ещё до первой текстовой части.
// streamText returns right away; tokens stream in as they are generated.
const result = streamText({
model: aiSearch.get(INSTANCE_NAME).chat(),
messages: [{ role: "user", content: query }],
});
// toTextStreamResponse() streams the generated text only.
return result.toTextStreamResponse();// streamText returns right away; tokens stream in as they are generated.
const result = streamText({
model: aiSearch.get(INSTANCE_NAME).chat(),
messages: [{ role: "user", content: query }],
});
// toTextStreamResponse() streams the generated text only.
return result.toTextStreamResponse();toTextStreamResponse() отправляет сгенерированный текст и отбрасывает источники. Чтобы также передавать полученные чанки потоком, верните поток сообщений UI с sendSources включено, или ознакомьтесь с result.fullStream напрямую:
// sendSources forwards each retrieved chunk as a source-url part. They arrive
// before the first text part, so you can render citations as the answer streams.
return result.toUIMessageStreamResponse({ sendSources: true });// sendSources forwards each retrieved chunk as a source-url part. They arrive
// before the first text part, so you can render citations as the answer streams.
return result.toUIMessageStreamResponse({ sendSources: true });Поиск как инструмент
С chat(), AI Search выполняет поиск в вашем экземпляре при каждом запросе. Чтобы решение о том, когда искать, принимала модель, предоставьте instance.search() как AI SDK инструмент ↗ и передайте её модели, которая поддерживает вызов функций, например Workers AI модель. Это тот паттерн, который стоит использовать в агенте, где модель сама выбирает между поиском и другими инструментами.
Установите провайдер Workers AI и Zod. Используйте версию 3 workers-ai-provider: последняя версия 4 требует AI SDK v7, но ai-search-provider требует v6, поэтому npm не может установить их вместе.
npm i workers-ai-provider@^3 zodДобавьте привязку Workers AI в конфигурацию Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"ai": {
"binding": "AI"
}
}[ai]
binding = "AI"Затем определите инструмент поиска. Модель вызывает его, когда ей нужно получить контент:
import { createWorkersAI } from "workers-ai-provider";
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
const instance = aiSearch.get(INSTANCE_NAME);
// The tool-caller must support function calling. Use a dedicated model here
// rather than the AI Search chat model, which retrieves on every request.
const workersai = createWorkersAI({ binding: env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-5.2"),
messages: [{ role: "user", content: query }],
tools: {
search_knowledge_base: tool({
description: "Search the indexed knowledge base for relevant content.",
inputSchema: z.object({
query: z.string().describe("The search query"),
}),
// The model decides when to call this; it searches the instance.
execute: async ({ query }) =>
instance.search({
query,
ai_search_options: { retrieval: { max_num_results: 5 } },
}),
}),
},
// Cap the tool-call loop so the model cannot invoke tools indefinitely.
stopWhen: stepCountIs(5),
});import { createWorkersAI } from "workers-ai-provider";
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
const instance = aiSearch.get(INSTANCE_NAME);
// The tool-caller must support function calling. Use a dedicated model here
// rather than the AI Search chat model, which retrieves on every request.
const workersai = createWorkersAI({ binding: env.AI });
const { text } = await generateText({
model: workersai("@cf/zai-org/glm-5.2"),
messages: [{ role: "user", content: query }],
tools: {
search_knowledge_base: tool({
description: "Search the indexed knowledge base for relevant content.",
inputSchema: z.object({
query: z.string().describe("The search query"),
}),
// The model decides when to call this; it searches the instance.
execute: async ({ query }) =>
instance.search({
query,
ai_search_options: { retrieval: { max_num_results: 5 } },
}),
}),
},
// Cap the tool-call loop so the model cannot invoke tools indefinitely.
stopWhen: stepCountIs(5),
});6. Протестируйте локально
Перед развёртыванием убедитесь, что весь процесс работает с вашим экземпляром, с помощью wrangler dev, который проксирует удалённую привязку AI Search. Вывод ниже получен от сформировать ответ опцию.
Запустите локальный сервер разработки:
npx wrangler devСначала проиндексируйте образец документа, перейдя по /setup:
curl http://localhost:8787/setupПервые /setup может занять пару минут, пока документ индексируется. По завершении вы получите ключ элемента и completed статус:
{ "key": "caching.md", "status": "completed" }Затем отправьте запрос к экземпляру:
curl "http://localhost:8787/?q=How+does+caching+work%3F"Работающая интеграция возвращает сгенерированный text основанный на вашем документе, а также sources массив со ссылкой на найденный файл (поля сокращены):
{
"text": "Cloudflare caches static assets at the edge...",
"sources": [{ "sourceType": "url", "url": "caching.md" }]
}Если sources возвращается пустым, значит документ ещё не завершил индексацию. Выполните /setup ещё раз и повторите запрос.
7. Разверните
Войдите в свою учётную запись Cloudflare:
npx wrangler loginРазверните свой Worker, чтобы сделать его доступным в интернете:
npx wrangler deployОсобенности
- Чат-модель поддерживает только текст. Части сообщений с файлами и изображениями не поддерживаются.
- Параметры генерации, например
temperatureиmaxOutputTokensпередаются напрямую в модель генерации экземпляра.maxOutputTokensобрезает ответ и устанавливаетfinishReasonк"length". Если модель игнорирует опцию, у результатаwarningsмассив остаётся пустым, поэтому неподдерживаемая опция игнорируется без явной ошибки. - По умолчанию AI Search использует модель генерации, настроенную для экземпляра. Передайте
instance.chat({ model: "..." })и переопределить его для конкретного запроса.