createSpekoComponents
Build a { stt, llm, tts } bundle ready for voice.AgentSession.
createSpekoComponents is the one-call wiring helper for voice.AgentSession. It constructs SpekoSTT, SpekoLLM, SpekoTTS from a single options object and wraps STT and TTS with LiveKit's StreamAdapter so Speko's streaming REST proxy can drive a streaming session.
import { createSpekoComponents } from '@spekoai/adapter-livekit';
const { stt, llm, tts } = createSpekoComponents({
speko,
vad,
intent: { language: 'en-US', optimizeFor: 'balanced' },
});
const session = new voice.AgentSession({ vad, stt, llm, tts });Signature
function createSpekoComponents(
options: CreateSpekoComponentsOptions,
): SpekoComponents;CreateSpekoComponentsOptions
| Field | Type | Required | Description |
|---|---|---|---|
speko | Speko | ✅ | Initialised @spekoai/sdk client. |
intent | Intent | ✅ | Routing hint shared by STT, LLM, and TTS. |
vad | VAD | ✅ | VAD instance used by the stt.StreamAdapter. Typically await silero.VAD.load(). |
voice | string? | Voice id passed to SpekoTTS (maps to the Speko proxy's voice param). | |
constraints | PipelineConstraints? | Allow-list constraints applied to all three modalities. | |
sentenceTokenizer | tokenize.SentenceTokenizer? | Tokenizer for chunking LLM output before TTS. Defaults to tokenize.basic.SentenceTokenizer. | |
llm | { temperature?, maxTokens? }? | Tuning forwarded to /v1/complete. | |
ttsOptions | { sampleRate?, speed? }? | Output sample rate and speech speed forwarded to SpekoTTS. | |
agentId | string? | Enables the registered-tools loader. When set, the adapter calls speko.agents.tools.listChatTools(agentId) once per session — using the speko client you pass for auth and base URL — and merges the result with LiveKit's runtime ToolContext. Registered tools win on name collision. Omit to keep runtime-only behavior. | |
apiBaseUrl | string? | Deprecated and ignored — the loader reads the base URL from the speko client. Safe to omit. | |
apiKey | string? | Deprecated and ignored — the loader reads the API key from the speko client. Safe to omit. | |
onRegisteredToolsError | (err: Error) => void? | Called once if the registered-tools fetch fails. Voice session keeps running with runtime-only tools — this is a soft degradation, not a crash. |
Registered tools
When agentId is set, createSpekoComponents constructs a RegisteredToolsLoader for the underlying SpekoLLM. The loader lazily calls speko.agents.tools.listChatTools(agentId) on the first chat() of each session — reusing the Speko client you pass for auth and base URL — and caches the result for the LLM's lifetime. Voice sessions live for seconds-to-minutes and chat() is called many times — re-fetching every turn would be wasteful. (apiBaseUrl/apiKey are deprecated and ignored; the speko client carries both.)
On collision with a runtime tool of the same name, the registered tool wins (it's the customer's authoritative declaration). Fetch failures are non-fatal — the loader returns undefined and the agent continues with runtime tools only, calling onRegisteredToolsError once.
listChatTools returns every source kind — inline, webhook, builtin, and integration — already in the ChatTool[] shape /v1/complete accepts.
See the tool calling guide for the full picture.
Returns — SpekoComponents
interface SpekoComponents {
stt: stt.StreamAdapter; // wraps SpekoSTT + vad
llm: SpekoLLM; // used directly
tts: tts.StreamAdapter; // wraps SpekoTTS + sentenceTokenizer
}Drop the returned object straight into a voice.AgentSession.
Custom sentence tokenizer
import { tokenize } from '@livekit/agents';
const { stt, llm, tts } = createSpekoComponents({
speko,
vad,
intent,
sentenceTokenizer: new tokenize.basic.SentenceTokenizer({ minSentenceLength: 20 }),
});Use a longer minimum sentence length if you want fewer, longer TTS calls at the cost of latency before the first audio chunk.
Constraints shared across modalities
createSpekoComponents({
speko,
vad,
intent: { language: 'en' },
constraints: {
allowedProviders: {
stt: ['deepgram'],
llm: ['anthropic'],
tts: ['cartesia'],
},
},
});Every underlying call (/v1/transcribe, /v1/complete, /v1/synthesize) receives the same constraints object.
Opting out — use classes directly
If you need finer control, construct the classes yourself. createSpekoComponents is a convenience wrapper; nothing stops you from building the pipeline manually.
import { SpekoSTT, SpekoLLM, SpekoTTS } from '@spekoai/adapter-livekit';
import { stt, tts, tokenize } from '@livekit/agents';
const spekoSTT = new SpekoSTT({ speko, intent });
const wrappedSTT = new stt.StreamAdapter(spekoSTT, vad);
const spekoLLM = new SpekoLLM({ speko, intent, temperature: 0.7 });
const spekoTTS = new SpekoTTS({ speko, intent, voice: 'sonic-english' });
const wrappedTTS = new tts.StreamAdapter(spekoTTS, new tokenize.basic.SentenceTokenizer());