
Agents Ts
- 32 installs
- 3 repo stars
- Updated January 22, 2026
- codestackr/livekit-skills
Build voice AI agent backends in TypeScript with LiveKit's Node.js Agents SDK, covering AgentSession, zod tools, and STT/LLM/TTS models.
About
Builds LiveKit voice AI agent backends in TypeScript or JavaScript using @livekit/agents-js. A developer uses it to create voice assistants or realtime AI apps with AgentSession, zod function tools, and STT/LLM/TTS models.
- Builds voice AI agents with LiveKit's TypeScript/Node.js Agents SDK
- Covers AgentSession, zod function tools, STT/LLM/TTS models, and realtime models
Agents Ts by the numbers
- 32 all-time installs (skills.sh)
- Ranked #9,084 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/codestackr/livekit-skills --skill agents-tsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 32 |
|---|---|
| repo stars | ★ 3 |
| Last updated | January 22, 2026 |
| Repository | codestackr/livekit-skills ↗ |
What it does
Build voice AI agent backends in TypeScript with LiveKit's Node.js Agents SDK, covering AgentSession, zod tools, and STT/LLM/TTS models.
Files
LiveKit Agents TypeScript SDK
Build voice AI agents with LiveKit's TypeScript/Node.js Agents SDK.
LiveKit MCP server tools
This skill works alongside the LiveKit MCP server, which provides direct access to the latest LiveKit documentation, code examples, and changelogs. Use these tools when you need up-to-date information that may have changed since this skill was created.
Available MCP tools:
docs_search- Search the LiveKit docs siteget_pages- Fetch specific documentation pages by pathget_changelog- Get recent releases and updates for LiveKit packagescode_search- Search LiveKit repositories for code examplesget_python_agent_example- Browse 100+ Python agent examples
When to use MCP tools:
- You need the latest API documentation or feature updates
- You're looking for recent examples or code patterns
- You want to check if a feature has been added in recent releases
- The local references don't cover a specific topic
When to use local references:
- You need quick access to core concepts covered in this skill
- You're working offline or want faster access to common patterns
- The information in the references is sufficient for your needs
Use MCP tools and local references together for the best experience.
References
Consult these resources as needed:
- ./references/livekit-overview.md -- LiveKit ecosystem overview and how these skills work together
- ./references/agent-session.md -- AgentSession lifecycle, events, and configuration
- ./references/tools.md -- Function tools with zod schemas
- ./references/models.md -- STT, LLM, TTS plugins and realtime models
Installation
pnpm add @livekit/agents@1.x \
@livekit/agents-plugin-silero@1.x \
@livekit/agents-plugin-livekit@1.x \
@livekit/noise-cancellation-node@0.x \
dotenvEnvironment variables
Use the LiveKit CLI to load your credentials into a .env.local file:
lk app env -wOr manually create a .env.local file:
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
LIVEKIT_URL=wss://your-project.livekit.cloudQuick start
Basic agent with STT-LLM-TTS pipeline
import {
type JobContext,
type JobProcess,
WorkerOptions,
cli,
defineAgent,
voice,
} from '@livekit/agents';
import * as livekit from '@livekit/agents-plugin-livekit';
import * as silero from '@livekit/agents-plugin-silero';
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
import { fileURLToPath } from 'node:url';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
export default defineAgent({
prewarm: async (proc: JobProcess) => {
proc.userData.vad = await silero.VAD.load();
},
entry: async (ctx: JobContext) => {
const vad = ctx.proc.userData.vad! as silero.VAD;
const assistant = new voice.Agent({
instructions: `You are a helpful voice AI assistant.
Keep responses concise, 1-3 sentences. No markdown or emojis.`,
});
const session = new voice.AgentSession({
vad,
stt: "assemblyai/universal-streaming:en",
llm: "openai/gpt-4.1-mini",
tts: "cartesia/sonic-3:9626c31c-bec5-4cca-baa8-f8ba9e84c8bc",
turnDetection: new livekit.turnDetector.MultilingualModel(),
});
await session.start({
agent: assistant,
room: ctx.room,
inputOptions: {
// For standard web/mobile participants use BackgroundVoiceCancellation()
// For telephony/SIP applications use TelephonyBackgroundVoiceCancellation()
noiseCancellation: BackgroundVoiceCancellation(),
},
});
await ctx.connect();
const handle = session.generateReply({
instructions: 'Greet the user and offer your assistance.',
});
await handle.waitForPlayout();
},
});
cli.runApp(new WorkerOptions({ agent: fileURLToPath(import.meta.url) }));Basic agent with realtime model
import {
type JobContext,
WorkerOptions,
cli,
defineAgent,
voice,
} from '@livekit/agents';
import * as openai from '@livekit/agents-plugin-openai';
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
import { fileURLToPath } from 'node:url';
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
export default defineAgent({
entry: async (ctx: JobContext) => {
const assistant = new voice.Agent({
instructions: 'You are a helpful voice AI assistant.',
});
const session = new voice.AgentSession({
llm: new openai.realtime.RealtimeModel({
voice: 'coral',
}),
});
await session.start({
agent: assistant,
room: ctx.room,
inputOptions: {
// For standard web/mobile participants use BackgroundVoiceCancellation()
// For telephony/SIP applications use TelephonyBackgroundVoiceCancellation()
noiseCancellation: BackgroundVoiceCancellation(),
},
});
await ctx.connect();
const handle = session.generateReply({
instructions: 'Greet the user and offer your assistance.',
});
await handle.waitForPlayout();
},
});
cli.runApp(new WorkerOptions({ agent: fileURLToPath(import.meta.url) }));Core concepts
defineAgent
The entry point for defining your agent:
import { defineAgent, type JobContext, type JobProcess } from '@livekit/agents';
export default defineAgent({
// Optional: Preload models before jobs start
prewarm: async (proc: JobProcess) => {
proc.userData.vad = await silero.VAD.load();
},
// Required: Main entry point for each job
entry: async (ctx: JobContext) => {
// Your agent logic here
},
});voice.Agent
Define agent behavior. You can use the voice.Agent constructor directly or extend the class:
import { voice, llm } from '@livekit/agents';
import { z } from 'zod';
// Option 1: Direct instantiation
const assistant = new voice.Agent({
instructions: 'Your system prompt here',
tools: {
getWeather: llm.tool({
description: 'Get the current weather for a location',
parameters: z.object({
location: z.string().describe('The city name'),
}),
execute: async ({ location }) => {
return `The weather in ${location} is sunny and 72°F`;
},
}),
},
});
// Option 2: Class extension (recommended for complex agents)
class Assistant extends voice.Agent {
constructor() {
super({
instructions: 'Your system prompt here',
tools: {
getWeather: llm.tool({
description: 'Get the current weather for a location',
parameters: z.object({
location: z.string().describe('The city name'),
}),
execute: async ({ location }) => {
return `The weather in ${location} is sunny and 72°F`;
},
}),
},
});
}
}voice.AgentSession
The session orchestrates the voice pipeline:
const session = new voice.AgentSession({
stt: "assemblyai/universal-streaming:en",
llm: "openai/gpt-4.1-mini",
tts: "cartesia/sonic-3:voice_id",
vad: await silero.VAD.load(),
turnDetection: new livekit.turnDetector.MultilingualModel(),
});Key methods:
session.start({ agent, room })- Start the sessionsession.say(text)- Speak text directlysession.generateReply({ instructions })- Generate LLM responsesession.interrupt()- Stop current speechsession.updateAgent(newAgent)- Switch to different agent
Running the agent
Add scripts to package.json:
{
"scripts": {
"dev": "tsx agent.ts dev",
"build": "tsc",
"start": "node agent.js start",
"download-files": "tsc && node agent.js download-files"
}
}# Development mode with auto-reload
pnpm dev
# Production mode
pnpm build && pnpm start
# Download required model files
pnpm download-filesLiveKit Inference model strings
Use model strings for simple configuration without API keys:
STT (Speech-to-Text):
"assemblyai/universal-streaming:en"- AssemblyAI streaming"deepgram/nova-3:en"- Deepgram Nova"cartesia/ink"- Cartesia STT
LLM (Large Language Model):
"openai/gpt-4.1-mini"- GPT-4.1 mini (recommended)"openai/gpt-4.1"- GPT-4.1"openai/gpt-5"- GPT-5"gemini/gemini-3-flash"- Gemini 3 Flash
TTS (Text-to-Speech):
"cartesia/sonic-3:{voice_id}"- Cartesia Sonic 3"elevenlabs/eleven_turbo_v2_5:{voice_id}"- ElevenLabs"deepgram/aura:{voice}"- Deepgram Aura
Package structure
@livekit/agents # Core framework
@livekit/agents-plugin-openai # OpenAI (LLM, STT, TTS, Realtime)
@livekit/agents-plugin-deepgram # Deepgram (STT, TTS)
@livekit/agents-plugin-elevenlabs # ElevenLabs (TTS)
@livekit/agents-plugin-silero # Silero (VAD)
@livekit/agents-plugin-livekit # Turn detector
@livekit/agents-plugin-gemini # Google Gemini
@livekit/agents-plugin-groq # Groq
@livekit/noise-cancellation-node # Noise cancellationBest practices
1. Always use LiveKit Inference model strings as the default for STT, LLM, and TTS. This eliminates the need to manage individual provider API keys. Only use plugins when you specifically need custom models, voice cloning, or self-hosted models. 2. Use defineAgent pattern for proper lifecycle management. 3. Prewarm VAD models in the prewarm function for faster job startup. 4. Use the appropriate noise cancellation for your use case:
BackgroundVoiceCancellation()for standard web/mobile participantsTelephonyBackgroundVoiceCancellation()for SIP/telephony applications
5. Call ctx.connect() after session.start() to connect to the room. 6. Await generateReply with waitForPlayout() when you need to wait for the greeting to complete. 7. Use `lk app env -w` to load LiveKit Cloud credentials into your environment.
AgentSession reference
The voice.AgentSession is the main orchestrator for your voice AI app.
Constructor options
import { voice } from '@livekit/agents';
import * as silero from '@livekit/agents-plugin-silero';
import * as livekit from '@livekit/agents-plugin-livekit';
const session = new voice.AgentSession({
// Models (use inference strings or plugin instances)
stt: "assemblyai/universal-streaming:en",
llm: "openai/gpt-4.1-mini",
tts: "cartesia/sonic-3:voice_id",
// Voice activity detection
vad: await silero.VAD.load(),
// Turn detection
turnDetection: new livekit.turnDetector.MultilingualModel(),
// Voice options
voiceOptions: {
allowInterruptions: true,
minInterruptionDuration: 500,
minInterruptionWords: 0,
minEndpointingDelay: 500,
maxEndpointingDelay: 6000,
preemptiveGeneration: false,
},
// User data
userData: { key: 'value' },
});Starting the session
import {
BackgroundVoiceCancellation,
TelephonyBackgroundVoiceCancellation
} from '@livekit/noise-cancellation-node';
await session.start({
agent: myAgent,
room: ctx.room,
inputOptions: {
// Use BackgroundVoiceCancellation() for standard web/mobile participants
// Use TelephonyBackgroundVoiceCancellation() for SIP/telephony applications
noiseCancellation: BackgroundVoiceCancellation(),
},
});
// Connect to room after starting session
await ctx.connect();
// Optionally wait for the greeting to complete
const handle = session.generateReply({
instructions: 'Greet the user and offer your assistance.',
});
await handle.waitForPlayout();For telephony applications (SIP calls), use the telephony-optimized noise cancellation:
await session.start({
agent: myAgent,
room: ctx.room,
inputOptions: {
noiseCancellation: TelephonyBackgroundVoiceCancellation(),
},
});Key methods
Generate speech
// Generate LLM response
const handle = session.generateReply({
instructions: 'Greet the user warmly',
userInput: 'Hello!', // Optional user message
allowInterruptions: true,
});
await handle.waitForPlayout();
// Speak text directly
const handle = session.say('Hello! How can I help you today?', {
allowInterruptions: true,
});
await handle.waitForPlayout();Interrupt and control
// Stop current speech
session.interrupt();
// Commit user turn manually (when turnDetection="manual")
session.commitUserTurn();
// Clear user turn
session.clearUserTurn();Switch agents
// Switch to a different agent
session.updateAgent(newAgent);Access state
// Chat context
const chatCtx = session.chatCtx;
// Current agent state
const state = session.agentState; // "initializing", "listening", "thinking", "speaking"
// User data
const data = session.userData;
// Current agent
const agent = session.currentAgent;Events
import { voice } from '@livekit/agents';
session.on(voice.AgentSessionEventTypes.UserStateChanged, (ev) => {
// ev.newState: "speaking", "listening", "away"
console.log(`User state: ${ev.newState}`);
});
session.on(voice.AgentSessionEventTypes.AgentStateChanged, (ev) => {
// ev.newState: "initializing", "listening", "thinking", "speaking"
console.log(`Agent state: ${ev.newState}`);
});
session.on(voice.AgentSessionEventTypes.ConversationItemAdded, (ev) => {
console.log(`New message:`, ev.item);
});
session.on(voice.AgentSessionEventTypes.MetricsCollected, (ev) => {
console.log(`Metrics:`, ev.metrics);
});
session.on(voice.AgentSessionEventTypes.UserInputTranscribed, (ev) => {
console.log(`User said: ${ev.transcript}`);
});
session.on(voice.AgentSessionEventTypes.SpeechCreated, (ev) => {
console.log(`Speech created:`, ev);
});
session.on(voice.AgentSessionEventTypes.Close, (ev) => {
console.log(`Session closed:`, ev.reason);
});Turn detection modes
import * as livekit from '@livekit/agents-plugin-livekit';
import * as silero from '@livekit/agents-plugin-silero';
// Recommended: Turn detector model
const session = new voice.AgentSession({
turnDetection: new livekit.turnDetector.MultilingualModel(),
vad: await silero.VAD.load(),
});
// English only (faster)
const session = new voice.AgentSession({
turnDetection: new livekit.turnDetector.EnglishModel(),
vad: await silero.VAD.load(),
});
// VAD only
const session = new voice.AgentSession({
turnDetection: 'vad',
vad: await silero.VAD.load(),
});
// STT endpointing
const session = new voice.AgentSession({
turnDetection: 'stt',
stt: "assemblyai/universal-streaming:en",
vad: await silero.VAD.load(),
});
// Manual control
const session = new voice.AgentSession({
turnDetection: 'manual',
});Voice options
| Option | Default | Description |
|---|---|---|
allowInterruptions | true | Allow user to interrupt agent |
discardAudioIfUninterruptible | true | Drop buffered audio when uninterruptible |
minInterruptionDuration | 500 | Minimum speech duration (ms) before interruption |
minInterruptionWords | 0 | Minimum words before interruption |
minEndpointingDelay | 500 | Wait time (ms) before considering turn complete |
maxEndpointingDelay | 6000 | Maximum wait time (ms) for turn completion |
maxToolSteps | 3 | Maximum chained tool calls |
preemptiveGeneration | false | Start LLM response while user still speaking |
userAwayTimeout | 15.0 | Seconds before marking user as away |
Closing the session
// Graceful close
await session.close();
// Shutdown with options
session.shutdown({ drain: true, reason: 'user_initiated' });Input/Output control
// Access input/output objects
const input = session.input;
const output = session.output;
// Enable/disable audio input
session.input.setAudioEnabled(false);
session.input.setAudioEnabled(true);LiveKit overview
LiveKit is a realtime communication platform for building AI-native applications with audio, video, and data streaming. This overview helps you understand the LiveKit ecosystem and how to use these skills effectively.
Platform components
LiveKit Cloud
LiveKit Cloud is a fully managed platform for building, deploying, and operating AI agent applications. It includes:
- Realtime media infrastructure - Global mesh of servers for low-latency audio, video, and data streaming
- Managed agent hosting - Deploy agents without managing servers or orchestration
- LiveKit Inference - Run AI models directly within LiveKit Cloud without API keys
- Native telephony - Provision phone numbers and connect PSTN calls directly to rooms
- Observability - Built-in analytics, logs, and quality metrics
Agents framework
The Agents framework lets you build Python or Node.js programs that join LiveKit rooms as realtime participants. Key capabilities:
- Voice pipelines - Stream audio through STT-LLM-TTS pipelines
- Realtime models - Use models like OpenAI Realtime API that handle speech directly
- Tool calling - Define functions the LLM can invoke during conversations
- Multi-agent workflows - Hand off between specialized agents
- Turn detection - State-of-the-art model for natural conversation flow
Architecture
┌─────────────┐ WebRTC ┌─────────────┐ HTTP/WS ┌─────────────┐
│ Frontend │ ◄─────────────► │ LiveKit │ ◄──────────────► │ Agent │
│ (Web/App) │ │ Room │ │ Server │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Telephony │ │ AI Models │
│ (SIP) │ │ (STT/LLM/TTS)│
└─────────────┘ └─────────────┘How these skills work together
The LiveKit skills cover the full stack for building voice AI applications:
| Skill | Purpose | Language |
|---|---|---|
agents-py | Build agent backends | Python |
agents-ts | Build agent backends | TypeScript/Node.js |
agents-ui | Build agent frontends | React |
Typical workflow:
1. Choose your backend - Use agents-py or agents-ts based on your team's preference 2. Build the frontend - Use agents-ui for React-based web interfaces 3. Connect via LiveKit - Both connect to the same LiveKit room for realtime communication
Using the skills effectively
When to use each skill
- Building a new voice agent? Start with
agents-pyoragents-tsfor the backend logic - Need a web interface? Add
agents-uifor pre-built React components - Full-stack project? Use both a backend skill and
agents-uitogether
Combining skills
The skills are designed to work together. A typical project structure:
my-voice-app/
├── agent/ # Use agents-py or agents-ts skill
│ └── agent.py # or agent.ts
├── frontend/ # Use agents-ui skill
│ └── src/
│ └── app/
└── .env.local # Shared LiveKit credentialsEnvironment setup
All skills require LiveKit credentials:
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
LIVEKIT_URL=wss://your-project.livekit.cloudGet these from your LiveKit Cloud dashboard or self-hosted deployment.
Resources
Models reference
LiveKit Inference is the recommended way to use AI models with LiveKit Agents. It provides access to leading models without managing individual provider API keys. LiveKit Cloud handles authentication, billing, and optimal provider selection automatically.
LiveKit Inference (recommended)
Use model strings to configure STT, LLM, and TTS in your AgentSession.
STT (speech-to-text)
const session = new voice.AgentSession({
stt: "deepgram/nova-3:en",
});| Provider | Model | String |
|---|---|---|
| AssemblyAI | Universal Streaming | "assemblyai/universal-streaming:en" |
| AssemblyAI | Universal Multilingual | "assemblyai/universal-streaming-multilingual:en" |
| Cartesia | Ink Whisper | "cartesia/ink" |
| Deepgram | Flux | "deepgram/flux-general:en" |
| Deepgram | Nova 3 | "deepgram/nova-3:en" |
| Deepgram | Nova 3 (multilingual) | "deepgram/nova-3:multi" |
| Deepgram | Nova 2 | "deepgram/nova-2:en" |
| ElevenLabs | Scribe V2 | "elevenlabs/scribe_v1:en" |
Automatic model selection: Use "auto:language" to let LiveKit choose the best STT model for a language:
const session = new voice.AgentSession({
stt: "auto:en", // Best available English STT
});LLM (large language model)
const session = new voice.AgentSession({
llm: "openai/gpt-4.1-mini",
});| Provider | Model | String |
|---|---|---|
| OpenAI | GPT-4.1 mini | "openai/gpt-4.1-mini" |
| OpenAI | GPT-4.1 | "openai/gpt-4.1" |
| OpenAI | GPT-4.1 nano | "openai/gpt-4.1-nano" |
| OpenAI | GPT-5 | "openai/gpt-5" |
| OpenAI | GPT-5 mini | "openai/gpt-5-mini" |
| OpenAI | GPT-5 nano | "openai/gpt-5-nano" |
| OpenAI | GPT-5.1 | "openai/gpt-5.1" |
| OpenAI | GPT-5.2 | "openai/gpt-5.2" |
| OpenAI | GPT OSS 120B | "openai/gpt-oss-120b" |
| Gemini 3 Pro | "gemini/gemini-3-pro" | |
| Gemini 3 Flash | "gemini/gemini-3-flash" | |
| Gemini 2.5 Pro | "gemini/gemini-2.5-pro" | |
| Gemini 2.5 Flash | "gemini/gemini-2.5-flash" | |
| Gemini 2.0 Flash | "gemini/gemini-2.0-flash" | |
| DeepSeek | DeepSeek V3 | "deepseek/deepseek-v3" |
| DeepSeek | DeepSeek V3.2 | "deepseek/deepseek-v3.2" |
TTS (text-to-speech)
const session = new voice.AgentSession({
tts: "cartesia/sonic-3:9626c31c-bec5-4cca-baa8-f8ba9e84c8bc",
});| Provider | Model | String format |
|---|---|---|
| Cartesia | Sonic 3 | "cartesia/sonic-3:{voice_id}" |
| Cartesia | Sonic 2 | "cartesia/sonic-2:{voice_id}" |
| Deepgram | Aura 2 | "deepgram/aura-2:{voice}" |
| ElevenLabs | Turbo v2.5 | "elevenlabs/eleven_turbo_v2_5:{voice_id}" |
| Inworld | Inworld TTS | "inworld/inworld-tts-1:{voice_name}" |
| Rime | Arcana | "rime/arcana:{voice}" |
| Rime | Mist | "rime/mist:{voice}" |
Popular voices:
| Provider | Voice | String |
|---|---|---|
| Cartesia | Jacqueline (American female) | "cartesia/sonic-3:9626c31c-bec5-4cca-baa8-f8ba9e84c8bc" |
| Cartesia | Blake (American male) | "cartesia/sonic-3:a167e0f3-df7e-4d52-a9c3-f949145efdab" |
| Deepgram | Apollo (casual male) | "deepgram/aura-2:apollo" |
| Deepgram | Athena (professional female) | "deepgram/aura-2:athena" |
| ElevenLabs | Jessica (playful female) | "elevenlabs/eleven_turbo_v2_5:cgSgspJ2msm6clMCkdW9" |
| Rime | Luna (excitable female) | "rime/arcana:luna" |
Realtime models
For speech-to-speech without separate STT/TTS pipelines:
OpenAI Realtime
import * as openai from '@livekit/agents-plugin-openai';
const session = new voice.AgentSession({
llm: new openai.realtime.RealtimeModel({
voice: 'coral',
model: 'gpt-4o-realtime-preview',
}),
});Gemini Live
import * as google from '@livekit/agents-plugin-google';
const session = new voice.AgentSession({
llm: new google.realtime.RealtimeModel({
voice: 'Puck',
}),
});Advanced configuration
Use the inference module when you need additional parameters while still using LiveKit Inference:
import { voice, inference } from '@livekit/agents';
const session = new voice.AgentSession({
llm: new inference.LLM({
model: "openai/gpt-5-mini",
provider: "openai",
modelOptions: { reasoning_effort: "low" }
}),
stt: new inference.STT({
model: "deepgram/nova-3",
language: "en",
}),
tts: new inference.TTS({
model: "cartesia/sonic-3",
voice: "9626c31c-bec5-4cca-baa8-f8ba9e84c8bc",
language: "en",
modelOptions: { speed: 1.2, emotion: "cheerful" }
}),
});VAD and turn detection
These components are configured separately from model providers:
import * as silero from '@livekit/agents-plugin-silero';
import * as livekit from '@livekit/agents-plugin-livekit';
// In prewarm
proc.userData.vad = await silero.VAD.load();
// In entry
const session = new voice.AgentSession({
vad: ctx.proc.userData.vad as silero.VAD,
turnDetection: new livekit.turnDetector.MultilingualModel(),
});Turn detection options:
new livekit.turnDetector.MultilingualModel()- Recommended for natural conversation flow"vad"- VAD-only turn detection"stt"- STT endpointing (works with Deepgram Flux)"manual"- Manual control withsession.commitUserTurn()
Noise cancellation
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
await session.start({
agent: assistant,
room: ctx.room,
inputOptions: {
noiseCancellation: BackgroundVoiceCancellation(),
},
});---
Using plugins (when needed)
Use plugins directly only when you need features not available in LiveKit Inference:
- Custom or fine-tuned models not available in LiveKit Inference
- Voice cloning with your own provider account
- Self-hosted models via Ollama
- Provider-specific features not exposed through inference module
OpenAI (direct)
import * as openai from '@livekit/agents-plugin-openai';
const session = new voice.AgentSession({
llm: new openai.LLM({ model: 'gpt-4o' }),
stt: new openai.STT(),
tts: new openai.TTS({ voice: 'alloy' }),
});Requires: OPENAI_API_KEY
Deepgram (direct)
import * as deepgram from '@livekit/agents-plugin-deepgram';
const session = new voice.AgentSession({
stt: new deepgram.STT({ model: 'nova-2' }),
tts: new deepgram.TTS({ model: 'aura-asteria-en' }),
});Requires: DEEPGRAM_API_KEY
ElevenLabs (direct)
import * as elevenlabs from '@livekit/agents-plugin-elevenlabs';
const session = new voice.AgentSession({
tts: new elevenlabs.TTS({
voiceId: '21m00Tcm4TlvDq8ikWAM',
model: 'eleven_turbo_v2_5',
}),
});Requires: ELEVENLABS_API_KEY
Groq (direct)
import * as groq from '@livekit/agents-plugin-groq';
const session = new voice.AgentSession({
llm: new groq.LLM({ model: 'llama-3.3-70b-versatile' }),
stt: new groq.STT(),
tts: new groq.TTS(),
});Requires: GROQ_API_KEY
Other plugins
Additional plugins are available for: Gemini, Ollama, and more. Each requires its own API key and account setup.
See the LiveKit Agents documentation for the full list.
Package installation
# Core
pnpm add @livekit/agents@1.x
# Plugins (only install if needed)
pnpm add @livekit/agents-plugin-openai@1.x
pnpm add @livekit/agents-plugin-deepgram@1.x
pnpm add @livekit/agents-plugin-elevenlabs@1.x
pnpm add @livekit/agents-plugin-silero@1.x
pnpm add @livekit/agents-plugin-livekit@1.x
pnpm add @livekit/agents-plugin-gemini@1.x
pnpm add @livekit/agents-plugin-groq@1.x
# Noise cancellation
pnpm add @livekit/noise-cancellation-node@0.xFunction tools reference
Function tools let your agent call external functions during conversations.
Basic function tool with zod
import { voice, llm } from '@livekit/agents';
import { z } from 'zod';
const assistant = new voice.Agent({
instructions: 'You are a helpful assistant.',
tools: {
getWeather: llm.tool({
description: 'Get the current weather for a location',
parameters: z.object({
location: z.string().describe('The city name to get weather for'),
}),
execute: async ({ location }) => {
return `The weather in ${location} is sunny and 72°F`;
},
}),
},
});Tool with multiple parameters
const bookAppointment = llm.tool({
description: 'Book an appointment',
parameters: z.object({
date: z.string().describe('The date in YYYY-MM-DD format'),
time: z.string().describe('The time in HH:MM format'),
service: z.enum(['haircut', 'coloring', 'styling']).describe('Type of service'),
notes: z.string().optional().describe('Optional additional notes'),
}),
execute: async ({ date, time, service, notes }) => {
return `Booked ${service} for ${date} at ${time}`;
},
});Tool with enum values
const roomNameSchema = z.enum(['bedroom', 'living room', 'kitchen', 'bathroom', 'office']);
const toggleLight = llm.tool({
description: 'Turn a light on or off in a room',
parameters: z.object({
room: roomNameSchema.describe('The room to control'),
switchTo: z.enum(['on', 'off']).describe('The desired state'),
}),
execute: async ({ room, switchTo }) => {
return `The light in the ${room} is now ${switchTo}`;
},
});Raw parameter schema (without zod)
const openGate = llm.tool({
description: 'Opens a specified gate from a predefined set of access points',
parameters: {
type: 'object',
properties: {
gateId: {
type: 'string',
description: 'The ID of the gate to open',
enum: ['main_entrance', 'north_parking', 'loading_dock'],
},
},
required: ['gateId'],
additionalProperties: false,
},
execute: async ({ gateId }) => {
return `The gate ${gateId} is now open`;
},
});Tools in Agent class
class MyAgent extends voice.Agent {
constructor() {
super({
instructions: 'You are a helpful assistant.',
tools: {
getWeather: llm.tool({
description: 'Get weather for a location',
parameters: z.object({
location: z.string(),
}),
execute: async ({ location }) => {
return `Weather in ${location}: Sunny`;
},
}),
calculateTip: llm.tool({
description: 'Calculate tip for a bill',
parameters: z.object({
amount: z.number(),
percentage: z.number().default(18),
}),
execute: async ({ amount, percentage }) => {
const tip = amount * (percentage / 100);
return `Tip: $${tip.toFixed(2)}`;
},
}),
},
});
}
}Agent handoff via tools
Tools can return a new Agent to transfer control using llm.handoff():
class TriageAgent extends voice.Agent {
constructor() {
super({
instructions: 'You are a triage agent.',
tools: {
transferToSales: llm.tool({
description: 'Transfer to the sales department',
parameters: z.object({}),
execute: async () => {
// Return handoff with optional message for the LLM
return llm.handoff({
agent: new SalesAgent(),
returns: 'Transferring the user to the sales department',
});
},
}),
},
});
}
}
class SalesAgent extends voice.Agent {
constructor() {
super({
instructions: 'You are a sales representative.',
});
}
}Chaining tool calls
Enable multiple tool calls in sequence:
const session = new voice.AgentSession({
llm: "openai/gpt-4.1-mini",
voiceOptions: {
maxToolSteps: 5, // Allow up to 5 chained tool calls
},
});Tool execution events
Listen for tool execution:
session.on(voice.AgentSessionEventTypes.FunctionToolsExecuted, (ev) => {
console.log('Tools executed:', ev);
});Error handling
Use llm.ToolError to return errors to the LLM:
import { llm } from '@livekit/agents';
import { z } from 'zod';
const lookupWeather = llm.tool({
description: 'Look up weather for a location',
parameters: z.object({
location: z.string(),
}),
execute: async ({ location }) => {
if (location === 'mars') {
throw new llm.ToolError('This location is not supported yet.');
}
return `Weather in ${location}: Sunny, 72°F`;
},
});Best practices
1. Write clear descriptions - The LLM uses them to decide when to call the tool. 2. Use zod for type safety - Provides validation and better type inference. 3. Keep parameters simple - Prefer flat objects over deeply nested structures. 4. Return strings - Tool results are added to conversation context. 5. Handle errors with ToolError - Use llm.ToolError to return meaningful errors to the LLM. 6. Use enums for fixed values - Helps the LLM choose valid options. 7. Use llm.handoff() for agent transfers - Return a handoff object when transitioning to another agent.