← Vytvářejte agenty na Cloudflare / agents / communication-channels
Hlas
Vytvářejte hlasové agenty v reálném čase s převodem řeči na text, textu na řeč a perzistencí konverzace. Zvuk streamuje přes WebSocket, není potřeba žádné SFU ani infrastruktura pro schůzky. Beta
Přehled
@cloudflare/voice poskytuje dva mixiny na straně serveru a odpovídající klientské knihovny:
| Export | Import | Účel |
|---|---|---|
withVoice |
@cloudflare/voice |
Kompletní hlasový agent: STT, LLM, TTS, perzistence |
withVoiceInput |
@cloudflare/voice |
Pouze STT: přepis bez odpovědi |
useVoiceAgent |
@cloudflare/voice/react |
React hook pro withVoice agenti |
useVoiceInput |
@cloudflare/voice/react |
React hook pro withVoiceInput agenti |
VoiceClient |
@cloudflare/voice/client |
Klient nezávislý na frameworku |
Je postaven na Cloudflare Durable Objects, takže získáte:
- Zvuk v reálném čase : zvuk z mikrofonu streamuje jako binární rámce WebSocket, zvuk TTS streamuje zpět
- Automatická perzistence konverzace : zprávy uložené v SQLite, přežijí restarty
- Streamování TTS : Tokeny LLM se rozdělují na věty a syntetizují souběžně
- Zpracování přerušení : řeč uživatele během přehrávání zruší aktuální odpověď
- Nepřetržité STT : samostatná relace přepisu pro každé volání, detekci konce kola řeší model
- Hooky pipeline : zachytává a transformuje text v každé fázi
Rychlý start
Instalovat
npm install @cloudflare/voice agentsServer
import { Agent } from "agents";
import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript, context) {
return "Hello! I heard you say: " + transcript;
}
}import { Agent } from "agents";
import {
withVoice,
WorkersAIFluxSTT,
WorkersAITTS,
type VoiceTurnContext,
} from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript: string, context: VoiceTurnContext) {
return "Hello! I heard you say: " + transcript;
}
}Klient (React)
import { useVoiceAgent } from "@cloudflare/voice/react";
function VoiceUI() {
const {
status,
transcript,
interimTranscript,
audioLevel,
isMuted,
startCall,
endCall,
toggleMute,
} = useVoiceAgent({ agent: "MyAgent" });
return (
<div>
<p>Status: {status}</p>
<button onClick={status === "idle" ? startCall : endCall}>
{status === "idle" ? "Start Call" : "End Call"}
</button>
<button onClick={toggleMute}>{isMuted ? "Unmute" : "Mute"}</button>
{interimTranscript && (
<p>
<em>{interimTranscript}</em>
</p>
)}
{transcript.map((msg, i) => (
<p key={i}>
<strong>{msg.role}:</strong> {msg.text}
</p>
))}
</div>
);
}Konfigurace Wrangleru
{
"ai": {
"binding": "AI"
},
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyAgent"]
}
]
}[ai]
binding = "AI"
[[durable_objects.bindings]]
name = "MyAgent"
class_name = "MyAgent"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyAgent" ]Jak to funguje
Browser Durable Object (withVoice)
┌──────────┐ ┌──────────────────────────┐
│ Mic │ binary PCM (16kHz) │ Transcriber session │
│ │ ──────────────────────► │ (per-call, continuous) │
│ │ │ ↓ model detects turn │
│ │ JSON: transcript │ onTurn() → your LLM code │
│ │ ◄────────────────────── │ ↓ (sentence chunking) │
│ │ binary: audio │ TTS │
│ Speaker │ ◄────────────────────── │ │
└──────────┘ └──────────────────────────┘- Klient zachytává zvuk z mikrofonu a odesílá ho jako binární rámce WebSocket (16kHz mono, 16bitové PCM).
- Zvuk nepřetržitě streamuje do relace přepisu (vytvořené na
start_call, existuje po celou dobu volání). - Model STT rozpozná, kdy uživatel dokončí promluvu, a spustí
onUtterance. Všichni poskytovatelé používají detekce kola řízená modelem : klient nemusí signalizovat konec řeči pro STT. - Váš
onTurn()metoda se spustí, obvykle jde o volání LLM. - Odpověď se rozdělí na věty a syntetizuje přes TTS.
- Zvuk streamuje zpět klientovi pro přehrání.
Klient obdrží transcript_interim zprávy s částečnými výsledky v průběhu toho, jak uživatel mluví, takže můžete v uživatelském rozhraní zobrazovat zpětnou vazbu v reálném čase.
Server API: withVoice
withVoice(Agent) přidává kompletní hlasový pipeline do třídy Agent.
Poskytovatelé
Nastavte poskytovatele jako vlastnosti třídy. Inicializátory polí třídy se spouštějí až po super(), takže this.env je k dispozici.
| Vlastnost | Typ | Povinné | Popis |
|---|---|---|---|
transcriber |
Transcriber |
Ano | Nepřetržitý STT provider pro jednotlivá volání |
tts |
TTSProvider |
Ano | Převod textu na řeč |
import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
}import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
}Pro přepínání modelu za běhu (například rozbalovací nabídku Flux vs. Nova 3) přepište createTranscriber:
export class MyAgent extends VoiceAgent {
tts = new WorkersAITTS(this.env.AI);
createTranscriber(connection) {
return new WorkersAIFluxSTT(this.env.AI);
}
}export class MyAgent extends VoiceAgent<Env> {
tts = new WorkersAITTS(this.env.AI);
createTranscriber(connection: Connection): Transcriber {
return new WorkersAIFluxSTT(this.env.AI);
}
}onTurn(transcript, context)
Povinné. Volá se, když uživatel domluví a je připraven přepis.
Vrátí string, AsyncIterable<string>, nebo ReadableStream pro streamování odpovědí.
Jednoduchá odpověď:
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript, context) {
return "You said: " + transcript;
}
}export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript: string, context: VoiceTurnContext) {
return "You said: " + transcript;
}
}Streamovaná odpověď (doporučeno pro LLM):
import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript, context) {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/moonshotai/kimi-k2.6"),
system: "You are a helpful voice assistant. Keep responses concise.",
messages: [
...context.messages.map((m) => ({
role: m.role,
content: m.content,
})),
{ role: "user", content: transcript },
],
abortSignal: context.signal,
});
return result.textStream;
}
}import { streamText } from "ai";
import { createWorkersAI } from "workers-ai-provider";
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
async onTurn(transcript: string, context: VoiceTurnContext) {
const workersai = createWorkersAI({ binding: this.env.AI });
const result = streamText({
model: workersai("@cf/moonshotai/kimi-k2.6"),
system: "You are a helpful voice assistant. Keep responses concise.",
messages: [
...context.messages.map((m) => ({
role: m.role as "user" | "assistant",
content: m.content,
})),
{ role: "user", content: transcript },
],
abortSignal: context.signal,
});
return result.textStream;
}
} context objekt poskytuje:
| Pole | Typ | Popis |
|---|---|---|
connection |
Connection |
Připojení WebSocket |
messages |
Array<{ role: string; content: string }> |
Historie konverzace ze SQLite |
signal |
AbortSignal |
Přerušeno při zásahu nebo odpojení |
Hooky životního cyklu
| Metoda | Popis |
|---|---|
beforeCallStart(connection) |
Vraťte false k zamítnutí volání |
onCallStart(connection) |
Volá se po přijetí hovoru |
onCallEnd(connection) |
Volá se při ukončení hovoru |
onInterrupt(connection) |
Volá se, když uživatel přeruší přehrávání |
Hooky pipeline
Zachyťte a transformujte data v každé fázi pipeline. Vraťte null a přeskočit aktuální promluvu.
| Metoda | Přijímá | Lze přeskočit? |
|---|---|---|
afterTranscribe(transcript, connection) |
Text STT | Ano |
beforeSynthesize(text, connection) |
Text před TTS | Ano |
afterSynthesize(audio, text, connection) |
Zvuk po TTS | Ano |
import {} from "agents";
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
afterTranscribe(transcript, connection) {
if (transcript.length < 3) return null;
return transcript;
}
beforeSynthesize(text, connection) {
return text.replace(/\bAI\b/g, "A.I.");
}
async onTurn(transcript, context) {
return transcript;
}
}import { type Connection } from "agents";
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
afterTranscribe(transcript: string, connection: Connection) {
if (transcript.length < 3) return null;
return transcript;
}
beforeSynthesize(text: string, connection: Connection) {
return text.replace(/\bAI\b/g, "A.I.");
}
async onTurn(transcript: string, context: VoiceTurnContext) {
return transcript;
}
}Pomocné metody
| Metoda | Popis |
|---|---|
speak(connection, text) |
Syntetizovat a odeslat zvuk jednomu připojení |
speakAll(text) |
Syntetizovat a odeslat zvuk všem připojením |
forceEndCall(connection) |
Programově ukončit hovor |
saveMessage(role, text) |
Uložit zprávu do historie konverzace |
getConversationHistory() |
Načíst historii konverzace z SQLite |
Možnosti konfigurace
Předejte možnosti do withVoice() jako druhý argument:
const VoiceAgent = withVoice(Agent, {
historyLimit: 20,
audioFormat: "mp3",
maxMessageCount: 1000,
});const VoiceAgent = withVoice(Agent, {
historyLimit: 20,
audioFormat: "mp3",
maxMessageCount: 1000,
});| Možnost | Typ | Výchozí | Popis |
|---|---|---|---|
historyLimit |
number |
20 |
Maximální počet zpráv načtených pro kontext |
audioFormat |
string |
"mp3" |
Formát zvuku odesílaný klientovi |
maxMessageCount |
number |
1000 |
Maximální počet zpráv uložených v SQLite |
Server API: withVoiceInput
withVoiceInput(Agent) přidává hlasový vstup pouze s STT: žádné TTS, žádné LLM, žádné generování odpovědi. Použijte to pro diktování, hlasové vyhledávání nebo jakékoli uživatelské rozhraní, kde potřebujete převod řeči na text bez konverzačního agenta.
import { Agent } from "agents";
import { withVoiceInput, WorkersAINova3STT } from "@cloudflare/voice";
const InputAgent = withVoiceInput(Agent);
export class DictationAgent extends InputAgent {
transcriber = new WorkersAINova3STT(this.env.AI);
onTranscript(text, connection) {
console.log("User said:", text);
}
}import { Agent } from "agents";
import { withVoiceInput, WorkersAINova3STT } from "@cloudflare/voice";
const InputAgent = withVoiceInput(Agent);
export class DictationAgent extends InputAgent<Env> {
transcriber = new WorkersAINova3STT(this.env.AI);
onTranscript(text: string, connection: Connection) {
console.log("User said:", text);
}
}onTranscript(text, connection)
Volá se po přepsání každé promluvy. Tuto metodu přepište, pokud chcete přepis zpracovat.
Hooky
withVoiceInput podporuje stejné hooky životního cyklu jako withVoice:
beforeCallStart(connection): vrátífalsek zamítnutíonCallStart(connection),onCallEnd(connection),onInterrupt(connection)createTranscriber(connection): přepsání pro přepínání modelu za běhuafterTranscribe(transcript, connection): filtruje nebo transformuje přepisy
To dělá ne mají TTS hooky (beforeSynthesize, afterSynthesize) nebo onTurn.
Klientské API: React hooks
useVoiceAgent
Obaluje VoiceClient pro withVoice agentů. Spravuje připojení, záznam z mikrofonu, přehrávání, detekci ticha a detekci přerušení.
import { useVoiceAgent } from "@cloudflare/voice/react";
const selectedSpeakerId = "default";
const {
status, // "idle" | "listening" | "thinking" | "speaking"
transcript, // TranscriptMessage[] — conversation history
interimTranscript, // string | null — real-time partial transcript
metrics, // VoicePipelineMetrics | null
audioLevel, // number (0–1) — current mic RMS level
isMuted, // boolean
connected, // boolean — WebSocket connected
error, // string | null
outputDeviceError, // string | null — non-fatal speaker routing issue
startCall, // () => Promise<void>
endCall, // () => void
toggleMute, // () => void
sendText, // (text: string) => void — bypass STT
sendJSON, // (data: Record<string, unknown>) => void
lastCustomMessage, // unknown — last non-voice message from server
} = useVoiceAgent({
agent: "MyAgent",
name: "default",
host: window.location.host,
outputDeviceId: selectedSpeakerId, // Optional audiooutput device ID
enabled: true,
});Použijte enabled: false když aplikace musí počkat na asynchronní předpoklady připojení, například token schopnosti vázaný na uživatele. Dokud je hook zakázán, nevytváří ani nepřipojuje VoiceClient, vrátí stav nečinného odpojení, a callbacky akcí, jako jsou startCall(), sendText(), a sendJSON() jsou bezpečně no-op.
Když enabled se změní na true, hook se připojí s aktuálními možnostmi. První povolení se považuje za počáteční připojení, takže onReconnect se spustí pouze při pozdějších změnách identity připojení, dokud zůstává hook povolený.
Výběr výstupního zařízení
Předejte outputDeviceId ke směrování přehrávání asistenta na vybraný reproduktor, pokud to prohlížeč podporuje HTMLMediaElement.setSinkId():
const [outputDeviceId, setOutputDeviceId] = useState("default");
const voice = useVoiceAgent({
agent: "MyAgent",
outputDeviceId,
});const [outputDeviceId, setOutputDeviceId] = useState("default");
const voice = useVoiceAgent({
agent: "MyAgent",
outputDeviceId,
});Použijte MediaDeviceInfo.deviceId z navigator.mediaDevices.enumerateDevices() kde kind === "audiooutput". "default" a undefined použije výchozí výstup systému. Prohlížeče bez podpory výběru výstupního zařízení pokračují v přehrávání přes výchozí výstup a nastaví outputDeviceError když je požadován jiný než výchozí výstup. Popisky zařízení mohou zůstat prázdné, dokud uživatel neudělí oprávnění k mikrofonu, proto seznamy zařízení obnovte po startCall() pokud zobrazujete výběr řečníka.
Možnosti ladění
| Možnost | Typ | Výchozí | Popis |
|---|---|---|---|
enabled |
boolean |
true |
Při hodnotě false odloží vytvoření klienta a připojení |
silenceThreshold |
number |
0.04 |
RMS pod touto úrovní se považuje za ticho |
silenceDurationMs |
number |
500 |
Doba ticha před end_of_speech (ms) |
interruptThreshold |
number |
0.05 |
RMS pro detekci řeči během přehrávání |
interruptChunks |
number |
2 |
Po sobě jdoucí chunky s vysokým RMS pro spuštění přerušení |
Změna možností ladění vyvolá opětovné připojení klienta (klíč připojení tyto možnosti zahrnuje).
useVoiceInput
Odlehčený hook pro diktování a převod řeči na text. Shromažďuje uživatelské přepisy do jediného řetězce.
import { useVoiceInput } from "@cloudflare/voice/react";
function Dictation() {
const {
transcript, // string — accumulated text from all utterances
interimTranscript, // string | null — current partial transcript
isListening, // boolean
audioLevel, // number (0–1)
isMuted, // boolean
error, // string | null
start, // () => Promise<void>
stop, // () => void
toggleMute, // () => void
clear, // () => void — clear accumulated transcript
} = useVoiceInput({ agent: "DictationAgent" });
return (
<div>
<textarea
value={transcript + (interimTranscript ? " " + interimTranscript : "")}
readOnly
/>
<button onClick={isListening ? stop : start}>
{isListening ? "Stop" : "Dictate"}
</button>
</div>
);
}Klientské API: VoiceClient
Klient nezávislý na frameworku pro prostředí bez Reactu.
import { VoiceClient } from "@cloudflare/voice/client";
const client = new VoiceClient({ agent: "MyAgent" });
client.addEventListener("statuschange", (status) => {
console.log("Status:", status);
});
client.addEventListener("transcriptchange", (messages) => {
console.log("Transcript:", messages);
});
client.addEventListener("error", (err) => {
console.error("Error:", err);
});
client.connect();
await client.startCall();
// Switch assistant playback without reconnecting the call.
await client.setOutputDevice(selectedSpeakerId);
// Later:
client.endCall();
client.disconnect();import { VoiceClient } from "@cloudflare/voice/client";
const client = new VoiceClient({ agent: "MyAgent" });
client.addEventListener("statuschange", (status) => {
console.log("Status:", status);
});
client.addEventListener("transcriptchange", (messages) => {
console.log("Transcript:", messages);
});
client.addEventListener("error", (err) => {
console.error("Error:", err);
});
client.connect();
await client.startCall();
// Switch assistant playback without reconnecting the call.
await client.setOutputDevice(selectedSpeakerId);
// Later:
client.endCall();
client.disconnect();Události
| Událost | Datový typ | Popis |
|---|---|---|
statuschange |
VoiceStatus |
Stav pipeline se změnil |
transcriptchange |
TranscriptMessage[] |
Transkript aktualizován |
interimtranscript |
string | null |
Průběžný přepis ze streamovaného STT |
metricschange |
VoicePipelineMetrics |
Metriky časování pipeline |
audiolevelchange |
number |
Úroveň zvuku mikrofonu (0 až 1) |
connectionchange |
boolean |
WebSocket připojen/odpojen |
mutechange |
boolean |
Změna stavu ztlumení |
error |
string | null |
Došlo k chybě |
outputdeviceerror |
string | null |
Nekritický problém se směrováním řečníka |
custommessage |
unknown |
Nehlasová zpráva ze serveru |
Pokročilé možnosti
| Možnost | Typ | Popis |
|---|---|---|
transport |
VoiceTransport |
Vlastní transport (výchozí: WebSocket přes PartySocket) |
audioInput |
VoiceAudioInput |
Vlastní zachytávání mikrofonu (výchozí: vestavěný AudioWorklet) |
preferredFormat |
VoiceAudioFormat |
Nápověda pro formát zvuku serveru (pouze informativní) |
outputDeviceId |
string |
Preferovaný audiooutput zařízení pro přehrávání asistenta |
Poskytovatelé
Vestavěné (Workers AI)
Nejsou potřeba žádné API klíče, stačí použít vazbu Workers AI:
| Třída | Typ | Výchozí model | Doporučeno pro |
|---|---|---|---|
WorkersAIFluxSTT |
Nepřetržité STT | @cf/deepgram/flux |
withVoice |
WorkersAINova3STT |
Nepřetržité STT | @cf/deepgram/nova-3 |
withVoiceInput |
WorkersAITTS |
TTS | @cf/deepgram/aura-1 |
Obojí |
import { Agent } from "agents";
import {
withVoice,
WorkersAIFluxSTT,
WorkersAINova3STT,
WorkersAITTS,
} from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
// Default usage
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
}
// Custom options
export class CustomAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI, {
eotThreshold: 0.8,
keyterms: ["Cloudflare", "Workers"],
});
tts = new WorkersAITTS(this.env.AI, {
model: "@cf/deepgram/aura-1",
speaker: "asteria",
});
}import { Agent } from "agents";
import {
withVoice,
WorkersAIFluxSTT,
WorkersAINova3STT,
WorkersAITTS,
} from "@cloudflare/voice";
const VoiceAgent = withVoice(Agent);
// Default usage
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new WorkersAITTS(this.env.AI);
}
// Custom options
export class CustomAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI, {
eotThreshold: 0.8,
keyterms: ["Cloudflare", "Workers"],
});
tts = new WorkersAITTS(this.env.AI, {
model: "@cf/deepgram/aura-1",
speaker: "asteria",
});
}Poskytovatelé třetích stran
| Balíček | Třída | Popis |
|---|---|---|
@cloudflare/voice-deepgram |
DeepgramSTT |
Nepřetržité STT |
@cloudflare/voice-elevenlabs |
ElevenLabsTTS |
Vysoce kvalitní TTS |
@cloudflare/voice-twilio |
TwilioAdapter |
Telefonie (telefonní hovory) |
ElevenLabs TTS:
import { ElevenLabsTTS } from "@cloudflare/voice-elevenlabs";
export class MyAgent extends VoiceAgent {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new ElevenLabsTTS({
apiKey: this.env.ELEVENLABS_API_KEY,
voiceId: "21m00Tcm4TlvDq8ikWAM",
});
}import { ElevenLabsTTS } from "@cloudflare/voice-elevenlabs";
export class MyAgent extends VoiceAgent<Env> {
transcriber = new WorkersAIFluxSTT(this.env.AI);
tts = new ElevenLabsTTS({
apiKey: this.env.ELEVENLABS_API_KEY,
voiceId: "21m00Tcm4TlvDq8ikWAM",
});
}Deepgram STT:
import { DeepgramSTT } from "@cloudflare/voice-deepgram";
export class MyAgent extends VoiceAgent {
transcriber = new DeepgramSTT({
apiKey: this.env.DEEPGRAM_API_KEY,
});
tts = new WorkersAITTS(this.env.AI);
}import { DeepgramSTT } from "@cloudflare/voice-deepgram";
export class MyAgent extends VoiceAgent<Env> {
transcriber = new DeepgramSTT({
apiKey: this.env.DEEPGRAM_API_KEY,
});
tts = new WorkersAITTS(this.env.AI);
}Telefonie (Twilio)
Připojte telefonní hovory k vašemu hlasovému agentovi pomocí adaptéru Twilio:
npm install @cloudflare/voice-twilioAdaptér propojuje Twilio Media Streams s vaším VoiceAgentem:
Phone → Twilio → WebSocket → TwilioAdapter → WebSocket → VoiceAgentWorkersAITTS vrátí MP3, které nelze v runtime Workers dekódovat na PCM. Pokud používáte adaptér Twilio, použijte poskytovatele TTS, který vrací surové PCM (například ElevenLabs s outputFormat: "pcm_16000").
Textové zprávy
withVoice agenti mohou také přijímat textové zprávy a STT úplně obejít. To se hodí pro vstup ve stylu chatu vedle hlasu.
const { sendText } = useVoiceAgent({ agent: "MyAgent" });
// Send text — goes straight to onTurn() without STT
sendText("What is the weather like today?");Textové zprávy fungují jak během aktivních hovorů, tak mimo ně. Během hovoru se odpověď přehraje nahlas prostřednictvím TTS. Mimo hovor se odpověď odesílá jako čistě textové zprávy přepisu.
Vlastní zprávy
Odesílejte a přijímejte JSON zprávy na úrovni aplikace spolu se zprávami hlasového protokolu. Nehlasové zprávy se předávají do vašeho onMessage handler na serveru a vyvolat custommessage události na klientovi.
Server:
export class MyAgent extends VoiceAgent {
onMessage(connection, message) {
const data = JSON.parse(message);
if (data.type === "kick_speaker") {
this.forceEndCall(connection);
}
}
}export class MyAgent extends VoiceAgent<Env> {
onMessage(connection: Connection, message: WSMessage) {
const data = JSON.parse(message as string);
if (data.type === "kick_speaker") {
this.forceEndCall(connection);
}
}
}Klient:
const { sendJSON, lastCustomMessage } = useVoiceAgent({ agent: "MyAgent" });
sendJSON({ type: "kick_speaker" });
useEffect(() => {
if (lastCustomMessage) {
console.log("Custom message:", lastCustomMessage);
}
}, [lastCustomMessage]);Vynucení jediného mluvčího
Použijte beforeCallStart k omezení toho, kdo může zahájit hovor. Tento příklad vynucuje jediného mluvčího, přičemž aktivním mluvčím může být vždy jen jedno připojení:
import {} from "agents";
export class MyAgent extends VoiceAgent {
#speakerId = null;
beforeCallStart(connection) {
if (this.#speakerId !== null) {
return false;
}
this.#speakerId = connection.id;
return true;
}
onCallEnd(connection) {
if (this.#speakerId === connection.id) {
this.#speakerId = null;
}
}
}import { type Connection } from "agents";
export class MyAgent extends VoiceAgent<Env> {
#speakerId: string | null = null;
beforeCallStart(connection: Connection) {
if (this.#speakerId !== null) {
return false;
}
this.#speakerId = connection.id;
return true;
}
onCallEnd(connection: Connection) {
if (this.#speakerId === connection.id) {
this.#speakerId = null;
}
}
}Metriky pipeline
withVoice agenti po každém tahu vysílají metriky časování:
const { metrics } = useVoiceAgent({ agent: "MyAgent" });
// metrics: {
// llm_ms: 850,
// tts_ms: 200,
// first_audio_ms: 950,
// total_ms: 1200,
// }Historie konverzace
withVoice automaticky ukládá zprávy konverzace do SQLite. K historii přistupte ve svém onTurn prostřednictvím context.messages, nebo přímo:
const history = this.getConversationHistory(20);
this.saveMessage("assistant", "Welcome! How can I help?");const history = this.getConversationHistory(20);
this.saveMessage("assistant", "Welcome! How can I help?");Historie přetrvává restarty Durable Object i opětovná připojení klienta. Hlasoví agenti používají keepAlive k zamezení vyřazení z paměti během aktivních volání.