Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/update-agent-models-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@livekit/agents': patch
---

Add `Agent.updateOptions()` for swapping STT, VAD, LLM, and TTS models at runtime, including
explicit `null` values to disable session fallback.
64 changes: 52 additions & 12 deletions agents/src/voice/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,26 @@ export interface ModelSettings {
toolChoice?: ToolChoice;
}

export interface AgentUpdateOptions {
/** New STT model. Pass `null` to disable the agent STT and override any session STT. */
stt?: STT | STTModelString | null;
/** New VAD model. Pass `null` to disable the agent VAD and override any session VAD. */
vad?: VAD | null;
/** New LLM model. Pass `null` to disable the agent LLM and override any session LLM. */
llm?: LLM | RealtimeModel | LLMModels | null;
/** New TTS model. Pass `null` to disable the agent TTS and override any session TTS. */
tts?: TTS | TTSModelString | null;
}

export interface AgentOptions<UserData> {
id?: string;
instructions: string | Instructions;
chatCtx?: ChatContext;
tools?: ToolContextLike<UserData>;
stt?: STT | STTModelString;
vad?: VAD;
llm?: LLM | RealtimeModel | LLMModels;
tts?: TTS | TTSModelString;
stt?: STT | STTModelString | null;
vad?: VAD | null;
llm?: LLM | RealtimeModel | LLMModels | null;
tts?: TTS | TTSModelString | null;
turnHandling?: TurnHandlingOptions;
toolHandling?: ToolHandlingOptions;
minConsecutiveSpeechDelay?: number;
Expand All @@ -153,10 +164,14 @@ export interface AgentOptions<UserData> {

export class Agent<UserData = any> {
private _id: string;
private _stt?: STT;
private _vad?: VAD;
private _llm?: LLM | RealtimeModel;
private _tts?: TTS;
/** @internal */
_stt?: STT | null;
/** @internal */
_vad?: VAD | null;
/** @internal */
_llm?: LLM | RealtimeModel | null;
/** @internal */
_tts?: TTS | null;
private _turnHandling?: Partial<TurnHandlingOptions>;

private _minConsecutiveSpeechDelay?: number;
Expand Down Expand Up @@ -255,19 +270,19 @@ export class Agent<UserData = any> {
}

get vad(): VAD | undefined {
return this._vad;
return this._vad ?? undefined;
}

get stt(): STT | undefined {
return this._stt;
return this._stt ?? undefined;
}

get llm(): LLM | RealtimeModel | undefined {
return this._llm;
return this._llm ?? undefined;
}

get tts(): TTS | undefined {
return this._tts;
return this._tts ?? undefined;
}

get useTtsAlignedTranscript(): boolean | undefined {
Expand Down Expand Up @@ -383,6 +398,31 @@ export class Agent<UserData = any> {
await this._agentActivity.updateInstructions(instructions);
}

async updateOptions(options: AgentUpdateOptions): Promise<void> {
const resolved: AgentUpdateOptions = { ...options };
if (typeof resolved.stt === 'string') {
resolved.stt = InferenceSTT.fromModelString(resolved.stt);
}
if (typeof resolved.llm === 'string') {
resolved.llm = InferenceLLM.fromModelString(resolved.llm);
}
if (typeof resolved.tts === 'string') {
resolved.tts = InferenceTTS.fromModelString(resolved.tts);
}

if (!this._agentActivity) {
if (Object.hasOwn(resolved, 'stt')) this._stt = resolved.stt as STT | null;
if (Object.hasOwn(resolved, 'vad')) this._vad = resolved.vad as VAD | null;
if (Object.hasOwn(resolved, 'llm')) {
this._llm = resolved.llm as LLM | RealtimeModel | null;
}
if (Object.hasOwn(resolved, 'tts')) this._tts = resolved.tts as TTS | null;
return;
}

await this._agentActivity.updateModels(resolved);
}

// TODO(parity): Add when AgentConfigUpdate is ported to ChatContext.
async updateTools(tools: ToolContextLike<UserData>): Promise<void> {
if (!this._agentActivity) {
Expand Down
29 changes: 29 additions & 0 deletions agents/src/voice/agent.type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import { describe, expectTypeOf, it } from 'vitest';
import { Agent } from './agent.js';

describe('Agent model getters', () => {
it('preserves undefined-based narrowing for existing callers', () => {
const agent = new Agent({ instructions: 'test' });

expectTypeOf(agent.stt).not.toEqualTypeOf<null>();
expectTypeOf(agent.vad).not.toEqualTypeOf<null>();
expectTypeOf(agent.llm).not.toEqualTypeOf<null>();
expectTypeOf(agent.tts).not.toEqualTypeOf<null>();

if (agent.stt !== undefined) {
agent.stt.stream();
}
if (agent.vad !== undefined) {
agent.vad.stream();
}
if (agent.llm !== undefined) {
agent.llm.label();
}
if (agent.tts !== undefined) {
agent.tts.stream();
}
});
});
Loading
Loading