← Přehled / ai-search / how-to
Mluvte se svou znalostní bází
Tento tutoriál sestaví hlasového agenta, se kterým si můžete povídat a který nahlas odpovídá na základě vaší AI Search znalostní báze. Používá Cloudflare Agents @cloudflare/voice balíček pro hlasový pipeline a AI Search jako znalostní bázi agenta, zpřístupněnou jako nástroj pro vyhledávání, který volá model agenta.
Co vytvoříte: Hlasový agent, který přepíše vaši řeč, zavolá AI Search, aby načetl relevantní obsah z vaší indexované znalostní báze, vygeneruje podloženou odpověď a přečte vám ji nahlas.
Jak to funguje
@cloudflare/voice balíček přidává do Cloudflare Agent kompletní hlasový pipeline: převod řeči na text (STT), handler pro "kolo", ve kterém vytváříte odpověď, a převod textu na řeč (TTS). Pipeline běží v jediném Workeru podpořeném Durable Object a prohlížeč se k němu připojuje přes WebSocket.
Jedinou metodou, kterou napíšete, je onTurn(), který přijímá přepis promluvy uživatele a vrací text, jenž se má přečíst nahlas. Právě zde přichází ke slovu AI Search: spustíte jazykový model a dáte mu AI Search k dispozici jako nástroj pro vyhledávání. Model sám rozhoduje, kdy prohledat vaši znalostní bázi, svou odpověď opře o získané výsledky a vrátí text odpovědi, který pipeline přečte nahlas.
Předpoklady
- Zaregistrujte si účet Cloudflare ↗.
- Nainstalujte
Node.js↗.
Správce verzí Node.js
Použijte správce verzí Node, jako je Volta ↗ nebo nvm ↗ abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.
Také potřebujete instanci AI Search, která již obsahuje zaindexovaný obsah. Jde o znalostní bázi, se kterou budete komunikovat. Vytvoření instance a přidání obsahu najdete v Začínáme.
1. Vytvořte hlasového agenta
Vytvořte projekt Cloudflare Agents pomocí startovací šablony pro hlas, která obsahuje propojení Durable Object a klienta React:
npm create cloudflare@latest voice-knowledge-base -- --template cloudflare/agents-starter
cd voice-knowledge-baseNainstalujte hlasový balíček:
npm i @cloudflare/voice @cloudflare/voice balíček poskytuje withVoice mixin a poskytovatele Workers AI (WorkersAIFluxSTT a WorkersAITTS). Kompletní postup pro samotného hlasového agenta, včetně klienta v prohlížeči, najdete v Příklad hlasového agenta.
2. Přidejte vazbu AI Search
Přidejte AI Search binding do vašeho Konfigurační soubor Wrangler, spolu s vazbou Workers AI a Durable Object agenta. Nahraďte my-instance s názvem vaší 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" ]Znovu vygenerujte typy vazeb, aby env.AI a env.AI_SEARCH mají typy:
npx wrangler types3. Odpovídejte na základě znalostní báze
Aktualizujte src/server.ts. Agenta sestavte pomocí withVoice mixin, nastavte poskytovatele STT a TTS, a v onTurn() spustit model Workers AI, který volá AI Search jako nástroj pro vyhledávání.
Model obdrží searchKnowledgeBase nástroj, který volá AI Search search() pro vyhledávání (retrieval). Nástroj volá ve chvíli, kdy potřebuje fakta z vaší znalostní báze, svou odpověď opírá o vrácené chunky a vy vygenerovaný text vrátíte, aby ho pipeline mohl vyslovit. Agent automaticky ukládá historii konverzace, takže můžete předat context.messages pro navazující otázky.
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>;Každá část obsahu vrácená search() obsahuje svou zdrojovou položku a skóre relevance, takže můžete zobrazit citace nebo zaznamenat, co agent načetl.
4. Sestavte klienta
Nahraďte src/client.tsx s komponentou React, která používá useVoiceAgent hook. Tento hook spravuje mikrofon, WebSocket připojení k vašemu agentovi, přehrávání zvuku a detekci přerušení, takže komponenta musí pouze vykreslit ovládací prvky. Nastavte agent k názvu třídy vašeho agenta, 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 se stará o mikrofon a přehrávání zvuku, takže není potřeba tlačítko push-to-talk. Model detekuje, kdy jste domluvili, a spustí onTurn(), a automaticky přehraje namluvenou odpověď.
5. Spusťte to lokálně
Spusťte místní vývojový server:
npm run devOtevřete aplikaci v prohlížeči a vyberte Zahájit hovor a povolte přístup k mikrofonu, poté položte otázku, na kterou váš obsah umí odpovědět. Vaše slova se v reálném čase přepisují do textu a agent odpoví hlasem na základě vaší znalostní báze. status hodnota prochází listening, thinking, a speaking tak, jak funguje.
6. Nasaďte
Nasaďte svého agenta, aby byl dostupný na internetu:
npx wrangler deployMluvte se svou znalostní bází s více lidmi
Tento tutoriál sestaví hlasového agenta pro jednoho uživatele. Pokud místo toho potřebujete, aby se znalostní bází bavilo najednou více lidí ve společné místnosti, použijte RealtimeKit pro vrstvu vícestranného zvuku a videa a ponechte tohoto hlasového agenta jako komponentu, která odpovídá pomocí AI Search. RealtimeKit poskytuje místnost schůzky a přepis řeči, ale nehostuje systém pro generování odpovědí.