Skip to content

feat(agent): add update_models() to swap STT/VAD/LLM/TTS at runtime#6440

Merged
longcw merged 4 commits into
mainfrom
longc/agent-update-models
Jul 17, 2026
Merged

feat(agent): add update_models() to swap STT/VAD/LLM/TTS at runtime#6440
longcw merged 4 commits into
mainfrom
longc/agent-update-models

Conversation

@longcw

@longcw longcw commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Agent.update_models(*, stt, vad, llm, tts) swaps a model in place. Only the models passed are changed; strings resolve to inference models like the constructor, and None disables a model (overriding the session), matching Agent(stt=None).

When the agent isn't running it replaces the stored model, applied on the next start. When running, it applies to the live pipeline: the STT and VAD streams are rewired, the LLM and TTS take effect on the next generation and synthesis respectively, and each model's metrics_collected/error listeners follow the new instance. The call is synchronous and atomic.

The Agent is the source of truth — the activity resolves agent.<model> if given else session.<model> live — so update_models just writes to the agent. This is distinct from AgentSession.update_agent (full handoff, new instance) and from a model's own update_options (same instance, provider options).

Alternative to #6235

Realtime models

A RealtimeModel owns a live session created when the agent starts, so it can't be swapped in place. While running, passing a RealtimeModel as llm (or swapping away from one) raises RuntimeError pointing to AgentSession.update_agent. Validation happens before any mutation, so the call stays all-or-nothing.

Agent.update_models(*, stt, vad, llm, tts) swaps a pipeline model in place, for cases like changing the STT language or TTS voice mid-call without a full agent handoff.

When the agent isn't running it replaces the stored model; when running it applies to the live pipeline: STT/VAD streams are rewired, LLM/TTS take effect on the next generation/synthesis, and metrics/error listeners follow the new model. None disables a model, matching Agent(stt=None).

Swapping to or from a RealtimeModel while running raises (its session is created at start); use AgentSession.update_agent instead. Adds KeytermDetector.swap_stt to rebind the recognizer in place so the swap stays synchronous.
@longcw
longcw requested a review from a team as a code owner July 15, 2026 12:58

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

chat_ctx, exclude_invalid_function_calls=exclude_invalid_function_calls
)

def update_models(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about naming it update_options to keep it consistent with how we do it in other parts of the codebase?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to update_options for Agent, agent activity keeps _update_models

@theomonnom

Copy link
Copy Markdown
Member

Just double checking, when switching to another TTS mid-conversation? Will an "active" generation still use the old TTS, or the new one?

…ive swap

AudioRecognition._update_stt now optionally refreshes _stt_model/_stt_provider (used for user_turn trace-span attributes) and resets stt_context, so a mid-call STT swap doesn't keep the previous provider's trace labels or stale speaker metadata. Existing callers (_start, _stop, _clear_user_turn) reuse the same STT and are unaffected.
devin-ai-integration[bot]

This comment was marked as resolved.

@longcw

longcw commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Just double checking, when switching to another TTS mid-conversation? Will an "active" generation still use the old TTS, or the new one?

update the TTS won't cancel the active generation, the active one still uses the old TTS.

longcw added 2 commits July 17, 2026 14:08
Aligns with update_options elsewhere in the codebase (AgentSession, AgentActivity, stt/tts) and leaves room to add turn_handling/tool_handling to the same method later. The internal AgentActivity._update_models helper keeps its name (private, models-only, and avoids colliding with the activity's existing update_options).
… atomic

_update_models previously mutated STT before the VAD branch, where _update_vad's streaming-turn-detector min_silence check can raise ValueError, leaving the pipeline half-swapped despite the documented all-or-nothing contract. _check_vad_silence_requirement now accepts a candidate vad so the check runs up front alongside the RealtimeModel check, before any mutation.
@longcw
longcw merged commit a663a53 into main Jul 17, 2026
25 checks passed
@longcw
longcw deleted the longc/agent-update-models branch July 17, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants