INTEGRITY Dokumentace

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:

Rychlý start

Instalovat

npm install @cloudflare/voice agents

Server

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  │ ◄────────────────────── │                          │
└──────────┘                         └──────────────────────────┘
  1. Klient zachytává zvuk z mikrofonu a odesílá ho jako binární rámce WebSocket (16kHz mono, 16bitové PCM).
  2. Zvuk nepřetržitě streamuje do relace přepisu (vytvořené na start_call, existuje po celou dobu volání).
  3. 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.
  4. Váš onTurn() metoda se spustí, obvykle jde o volání LLM.
  5. Odpověď se rozdělí na věty a syntetizuje přes TTS.
  6. 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:

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-twilio

Adaptér propojuje Twilio Media Streams s vaším VoiceAgentem:

Phone → Twilio → WebSocket → TwilioAdapter → WebSocket → VoiceAgent

WorkersAITTS 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í.