← Обзор / ai-search / how-to
Общение с базой знаний
В этом руководстве вы создадите голосового агента, с которым можно разговаривать и который отвечает вслух на основе AI Search база знаний. Она использует Cloudflare Agents @cloudflare/voice пакет для голосового пайплайна и AI Search в качестве базы знаний агента, доступной как инструмент поиска, который вызывает модель агента.
Что вы создадите: Голосовой агент, который распознаёт вашу речь, обращается к AI Search за релевантным контентом из вашей проиндексированной базы знаний, формирует ответ на основе найденного контента и озвучивает его.
Как это работает
@cloudflare/voice пакет добавляет в Cloudflare Agent полноценный голосовой пайплайн: преобразование речи в текст (STT), обработчик "хода", в котором вы формируете ответ, и преобразование текста в речь (TTS). Пайплайн выполняется в одном Worker на основе Durable Object, а браузер подключается к нему через WebSocket.
Единственный метод, который нужно написать: onTurn(), который получает транскрипт пользователя и возвращает текст для озвучивания. Именно здесь в дело вступает AI Search: вы запускаете языковую модель и предоставляете ей AI Search в качестве инструмента поиска. Модель сама решает, когда обратиться к вашей базе знаний, строит ответ на основе полученных результатов и возвращает текст ответа, который затем озвучивает пайплайн.
Предварительные требования
- Зарегистрируйтесь для получения Аккаунт Cloudflare ↗.
- Установка
Node.js↗.
менеджер версий Node.js
Используйте менеджер версий Node, например Volta ↗ или nvm ↗ чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.
Вам также потребуется экземпляр AI Search, уже содержащий проиндексированный контент. Это база знаний, к которой вы будете обращаться с запросами. Чтобы создать её и добавить контент, см. Начало работы.
1. Создайте голосового агента
Создайте проект Cloudflare Agents на основе стартового голосового шаблона, который уже включает связку с Durable Object и клиент на React:
npm create cloudflare@latest voice-knowledge-base -- --template cloudflare/agents-starter
cd voice-knowledge-baseУстановите голосовой пакет:
npm i @cloudflare/voice @cloudflare/voice пакет предоставляет withVoice mixin и провайдеров Workers AI (WorkersAIFluxSTT и WorkersAITTS). Полное описание самого голосового агента, включая клиент в браузере, см. в Пример голосового агента.
2. Добавьте привязку AI Search
Добавьте Привязка AI Search в ваш конфигурационный файл Wrangler, вместе с привязкой Workers AI и Durable Object агента. Замените my-instance с именем вашего экземпляра.
{
"name": "voice-knowledge-base",
"main": "src/server.ts",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": ["nodejs_compat"],
"ai": {
"binding": "AI",
"remote": true
},
"ai_search": [
{
"binding": "AI_SEARCH",
"instance_name": "my-instance",
"remote": true
}
],
"durable_objects": {
"bindings": [
{
"name": "TalkToDocs",
"class_name": "TalkToDocs"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["TalkToDocs"]
}
]
}name = "voice-knowledge-base"
main = "src/server.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = [ "nodejs_compat" ]
[ai]
binding = "AI"
remote = true
[[ai_search]]
binding = "AI_SEARCH"
instance_name = "my-instance"
remote = true
[[durable_objects.bindings]]
name = "TalkToDocs"
class_name = "TalkToDocs"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "TalkToDocs" ]Заново сгенерируйте типы своих привязок, чтобы env.AI и env.AI_SEARCH типизированы:
npx wrangler types3. Отвечайте на основе базы знаний
Обновите src/server.ts. Соберите агента с помощью withVoice mixin, задайте провайдеров STT и TTS, а в onTurn() запустить модель Workers AI, которая вызывает AI Search как инструмент поиска.
Модель получает searchKnowledgeBase инструмент, который вызывает search() для поиска. Он вызывает инструмент, когда ему нужны факты из вашей базы знаний, обосновывает ответ полученными чанками, а вы возвращаете сгенерированный текст, чтобы конвейер его озвучил. Агент автоматически сохраняет историю переписки, поэтому вы можете передать context.messages для уточняющих вопросов.
import { Agent, routeAgentRequest } from "agents";
import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "@cloudflare/voice";
import { generateText, tool, stepCountIs } from "ai";
import { createWorkersAI } from "workers-ai-provider";
import { z } from "zod";
const VoiceAgent = withVoice(Agent);
export class TalkToDocs extends VoiceAgent {
// Workers AI powers speech-to-text and text-to-speech (no API keys needed).
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
// Called each time the user finishes speaking. The agent's model generates
// the reply, calling AI Search as a retrieval tool when it needs facts from
// your knowledge base.
async onTurn(transcript, context) {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = await generateText({
// Use a Workers AI model that supports function calling.
model: workersai("@cf/zai-org/glm-5.2"),
system:
"You are a helpful voice assistant that answers from a Cloudflare AI Search knowledge base. " +
"For questions about the product, call the searchKnowledgeBase tool first and answer using the results. " +
"Skip the tool for greetings and small talk. Keep replies short and conversational.",
messages: [
...context.messages.map((message) => ({
role: message.role,
content: message.content,
})),
{ role: "user", content: transcript },
],
tools: {
searchKnowledgeBase: tool({
description:
"Search the knowledge base for information to answer the user's question.",
inputSchema: z.object({
query: z.string().describe("A focused search query"),
}),
execute: async ({ query }) => {
// search() runs retrieval only and returns the matching chunks.
const res = await this.env.AI_SEARCH.search({
query,
ai_search_options: { retrieval: { max_num_results: 5 } },
});
return res.chunks.map((chunk) => chunk.text).join("\n\n");
},
}),
},
// Let the model call the tool, then answer from the results.
stopWhen: stepCountIs(4),
});
// Return the generated answer for the pipeline to speak.
return result.text;
}
}
export default {
async fetch(request, env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};import { Agent, routeAgentRequest } from "agents";
import {
withVoice,
WorkersAIFluxSTT,
WorkersAITTS,
type VoiceTurnContext,
} from "@cloudflare/voice";
import { generateText, tool, stepCountIs } from "ai";
import { createWorkersAI } from "workers-ai-provider";
import { z } from "zod";
const VoiceAgent = withVoice(Agent);
export class TalkToDocs extends VoiceAgent<Env> {
// Workers AI powers speech-to-text and text-to-speech (no API keys needed).
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
// Called each time the user finishes speaking. The agent's model generates
// the reply, calling AI Search as a retrieval tool when it needs facts from
// your knowledge base.
async onTurn(transcript: string, context: VoiceTurnContext) {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = await generateText({
// Use a Workers AI model that supports function calling.
model: workersai("@cf/zai-org/glm-5.2"),
system:
"You are a helpful voice assistant that answers from a Cloudflare AI Search knowledge base. " +
"For questions about the product, call the searchKnowledgeBase tool first and answer using the results. " +
"Skip the tool for greetings and small talk. Keep replies short and conversational.",
messages: [
...context.messages.map((message) => ({
role: message.role as "user" | "assistant",
content: message.content,
})),
{ role: "user" as const, content: transcript },
],
tools: {
searchKnowledgeBase: tool({
description:
"Search the knowledge base for information to answer the user's question.",
inputSchema: z.object({
query: z.string().describe("A focused search query"),
}),
execute: async ({ query }) => {
// search() runs retrieval only and returns the matching chunks.
const res = await this.env.AI_SEARCH.search({
query,
ai_search_options: { retrieval: { max_num_results: 5 } },
});
return res.chunks.map((chunk) => chunk.text).join("\n\n");
},
}),
},
// Let the model call the tool, then answer from the results.
stopWhen: stepCountIs(4),
});
// Return the generated answer for the pipeline to speak.
return result.text;
}
}
export default {
async fetch(request: Request, env: Env) {
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;Каждый чанк, возвращённый search() включает исходный элемент и оценку релевантности, поэтому вы можете показывать источники или регистрировать, что именно нашёл агент.
4. Создайте клиент
Замените src/client.tsx компонентом React, который использует useVoiceAgent хук. Он управляет микрофоном, WebSocket-соединением с вашим агентом, воспроизведением аудио и определением прерываний, поэтому компоненту остаётся только отрисовать элементы управления. Задайте agent к имени класса вашего агента, TalkToDocs.
import { useVoiceAgent } from "@cloudflare/voice/react";
function App() {
// useVoiceAgent connects to your agent over WebSocket, captures the
// microphone, plays the spoken response, and exposes the live call state.
// `agent` matches your Durable Object class name.
const {
// Pipeline state: "idle" | "listening" | "thinking" | "speaking".
status,
// Finalized conversation turns (your speech and the agent's replies).
transcript,
// Live partial transcription of what you are currently saying.
interimTranscript,
// Whether the WebSocket connection to the agent is open.
connected,
startCall,
endCall,
toggleMute,
isMuted,
} = useVoiceAgent({ agent: "TalkToDocs" });
const inCall = status !== "idle";
return (
<div>
<h1>Talk to your knowledge base</h1>
{/* Shows "thinking" while the agent searches the KB and generates a reply. */}
<p>Status: {status}</p>
{/* Toggle the call. Disabled until the agent connection is open. */}
<button
onClick={inCall ? endCall : startCall}
disabled={!connected && !inCall}
>
{!connected ? "Connecting…" : inCall ? "End call" : "Start call"}
</button>
{/* Mute only applies once a call is active. */}
{inCall && (
<button onClick={toggleMute}>{isMuted ? "Unmute" : "Mute"}</button>
)}
{/* Lightweight loading state while the agent works on a reply. */}
{status === "thinking" && <p>Thinking…</p>}
{/* Live partial transcript, updated as you speak. */}
{interimTranscript && (
<p>
<em>{interimTranscript}</em>
</p>
)}
{/* Finalized turns from both you and the agent. */}
{transcript.map((message, index) => (
<p key={index}>
<strong>{message.role}:</strong> {message.text}
</p>
))}
</div>
);
}
export default App;Hook управляет микрофоном и воспроизведением, поэтому кнопка push-to-talk не нужна. Модель определяет, когда вы закончили говорить, и запускает onTurn(), и автоматически воспроизводит устный ответ.
5. Запустите локально
Запустите локальный сервер разработки:
npm run devОткройте приложение в браузере, выберите Начать звонок и разрешите доступ к микрофону, затем задайте вопрос, на который может ответить ваш контент. Ваши слова будут расшифровываться в текст в реальном времени, а агент озвучит ответ на основе вашей базы знаний. status значение проходит через listening, thinking, а также speaking как есть, если это работает.
6. Разверните
Разверните своего агента, чтобы сделать его доступным в интернете:
npx wrangler deployГрупповое общение с базой знаний
В этом руководстве вы создадите голосового агента для одного пользователя. Если вместо этого вам нужно, чтобы несколько человек одновременно общались с базой знаний в одной комнате, используйте RealtimeKit для уровня многостороннего аудио и видео, а этот голосовой агент оставьте компонентом, который отвечает от имени AI Search. RealtimeKit предоставляет комнату для встреч и транскрипцию, но не размещает у себя движок ответов.