feat(phonic): add update_options for mid-session config changes - #7089
Conversation
Add update_config() on the Phonic RealtimeModel/RealtimeSession to change config
fields mid-session (e.g. default_language, voice, boosted_keywords, no-input-poke
settings). The merged config is applied immediately by sending a Phonic reset, so
it can be called around a task advance β e.g.
model.update_config({"default_language": "es"}) β to switch the language for the
next reply. Fields left unset keep their current values; instructions and tools
remain driven by the Agent handoff. The handoff reset and update_config share a
single _send_mid_session_reset() helper.
When the default language changes (and additional_languages isn't set explicitly),
the previous default is rotated into additional_languages so the overall language
set stays intact β the API rejects a default that also appears in
additional_languages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review: use the standard `update_options` method instead of a new `update_config`. The session's `update_options` accepts `tool_choice` (the base param the framework sends each turn β ignored, Phonic has no tool_choice) plus a `config` dict of any config-level fields; the model's `update_options(config=...)` forwards it. Language rotation and the immediate reset are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Note
This report is out of date. Scroll down for Devin Review's latest report on this PR.
Devin Review found 3 potential issues.
3 flags not posted on this PR by your GitHub settings β view them in Devin Review. (Configure)
| for sess in self._sessions: | ||
| sess.update_options(config=config) |
There was a problem hiding this comment.
π‘ Only one active session resets
With multiple active sessions, the first update_options call mutates their shared options. Later sessions detect no change and keep the old server configuration.
Prompt for agents
Fix RealtimeModel.update_options and RealtimeSession option ownership in livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py. Sessions currently share realtime_model._opts, so forwarding to the first session changes the values observed by every remaining session and suppresses their resets. Give each session independent option state or separate option mutation from per-session reset scheduling, while keeping model-level updates as defaults for future sessions and applying each update to every active session.
Was this helpful? React with π or π to provide feedback.
| self._options_reset_task = asyncio.create_task( | ||
| self._apply_options_reset(), name="phonic-options-reset" | ||
| ) |
There was a problem hiding this comment.
β¦field change Address review: - Rename PhonicSessionConfigUpdate -> PhonicConfig. - Rebuild the cached _configs_for_tools/_tool_definitions when update_options changes a tool-related field (configs_for_tools/forbid_speech_after_tool_call/ phonic_tools), via a shared _rebuild_tool_definitions() helper, so the reset carries the new tool behavior instead of the previously-serialized one (Devin review). - Name the rotation locals previous_default_language / new_default_language. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
β¦fig arg) Per review (Tina, LiveKit): expose each config field as its own keyword-only NotGivenOr param on update_options β matching the openai/google realtime plugins β instead of a single `config: PhonicConfig` arg. The PhonicConfig TypedDict is removed. The model forwards each field to its sessions; the session collects the given values and applies them (rotation, change-detection, tool rebuild, coalesced reset all unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
3 flags not posted on this PR by your GitHub settings β view them in Devin Review. (Configure)
| self._options_reset_task = asyncio.create_task( | ||
| self._apply_options_reset(), name="phonic-options-reset" | ||
| ) |
There was a problem hiding this comment.
π¨ Reset exceptions can leak configuration
Unretrieved _options_reset_task failures can trigger automatic exception logging. Provider exceptions can expose prompts, endpoint headers, or customer configuration outside PII-tagged fields.
Was this helpful? React with π or π to provide feedback.
Motivation
The Phonic realtime plugin sends its config once and has no way to change config fields mid-session. A multi-step agent (e.g. an interview that advances topics via handoff) can't, for example, switch
default_languageto Spanish for the next section: the Agent handoff (_update_session) only threads instructions / chat_ctx / tools into thereset, everything else is fixed atRealtimeModelconstruction, andupdate_optionswas a no-op.Phonic's
resetmessage already accepts a full config, so the capability exists on the wire β this just exposes it via the standardupdate_optionsmethod. (Companion to the same change inagents-js: livekit/agents-js#2407.)Change
Implement
update_options(previously a no-op) on the PhonicRealtimeSession, and add a forwardingupdate_optionson theRealtimeModel(apps hold the model). It changes any config-level field mid-session and applies it immediately via a Phonicreset; unset fields keep their current values. Instructions/tools stay driven by the Agent handoff.update_optionsexposes each config-level field as its own keyword-onlyNotGivenOrparam (default_language=β¦,voice=β¦,boosted_keywords=β¦, β¦) β matching theopenai/googlerealtime plugins β so callers pass only what changes.tool_choice(the base param the framework sends each turn) is ignored: Phonic has notool_choice(same asaws/nvidia).update_optionsshare a single_send_mid_session_reset()helper.update_optionsis synchronous (like other plugins), so the reset is fired as a background task and coalesced.default_languagethat also appears inadditional_languages. When the default changes (andadditional_languagesisn't passed), the previous default is rotated intoadditional_languagesand the new one dropped β e.g.{default: en, additional: [es]}+update_options(default_language="es")β{default: es, additional: [en]}. Keeps the language set intact.Usage
Validation
uv run ruff format --check/ruff checkpass;mypyis clean on the change (the one standalonephonic.AsyncPhonicerror pre-exists onmainand resolves in CI's full env).update_options(default_language="es")switches a live session to Spanish β the server accepts theesreset and the next reply is in Spanish.