Skip to content

Port initial realtime session configuration from agents-js - #8474

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
unmasked-paroxysm-typhoons
Open

rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
unmasked-paroxysm-typhoons

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Ports livekit/agents-js#2325.

Realtime sessions now accept their initial instructions, chat context, and tools at creation time. AgentActivity supplies that configuration before providers connect, avoiding Gemini's redundant startup connection while keeping OpenAI-compatible, LiveKit Inference, xAI, and Phonic sessions configured from their first request.

Google and Phonic session state is isolated per session, and Google model option changes are forwarded only to open sessions.

Source diff coverage
  • .changeset/google-realtime-single-connect.md: not applicable. The Python repository does not use Changesets; versions are changed only by release PRs.
  • agents/etc/agents.api.md: not applicable. Python does not commit generated API Extractor snapshots.
  • agents/src/inference/realtime.ts: adapted to livekit-agents/livekit/agents/inference/realtime/openai.py; Python keyword arguments are forwarded to the inference session.
  • agents/src/llm/index.ts: adapted through the public RealtimeModel.session() signature in livekit-agents/livekit/agents/llm/realtime.py; Python needs no separate type export.
  • agents/src/llm/openai_realtime/realtime_model.test.ts: adapted to tests/test_realtime/test_openai_realtime_model.py with the same three initial-option scenarios.
  • agents/src/llm/openai_realtime/realtime_model.ts: adapted to livekit-agents/livekit/agents/llm/_realtime/openai.py; initial instructions/tools are queued before connection and initial chat context uses the standard update path.
  • agents/src/llm/realtime.ts: adapted to livekit-agents/livekit/agents/llm/realtime.py using idiomatic keyword-only arguments and NOT_GIVEN sentinels.
  • agents/src/voice/agent_activity.ts: adapted to livekit-agents/livekit/agents/voice/agent_activity.py; instruction messages are removed before session creation and the initial configuration is passed atomically.
  • agents/src/voice/realtime_tool_output_commit.test.ts: adapted to tests/fake_realtime.py and tests/test_realtime_auto_disable_turn_detection.py; the fake records creation arguments and the voice test asserts the same configuration.
  • plugins/google/etc/agents-plugin-google.api.md: not applicable. Python has no generated API snapshot.
  • plugins/google/src/realtime/realtime_api.ts: adapted to livekit-plugins/livekit-plugins-google/livekit/plugins/google/realtime/realtime_api.py; sessions copy model options, seed configuration before connecting, receive model updates, and unregister when closed or their connection loop ends.
  • plugins/google/src/realtime/realtime_session_options.test.ts: adapted to tests/test_plugin_google_realtime.py with all five source scenarios.
  • plugins/openai/etc/agents-plugin-openai.api.md: not applicable. Python has no generated API snapshot; the plugin inherits the core OpenAI implementation.
  • plugins/phonic/etc/agents-plugin-phonic.api.md: not applicable. Python has no generated API snapshot.
  • plugins/phonic/src/realtime/realtime_model.test.ts: adapted to livekit-plugins/livekit-plugins-phonic/tests/test_realtime_session_options.py with all five source scenarios.
  • plugins/phonic/src/realtime/realtime_model.ts: adapted to livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py; initial configuration is seeded before connect and instructions remain session-local.
  • plugins/xai/etc/agents-plugin-xai.api.md: not applicable. Python has no generated API snapshot.
  • plugins/xai/src/realtime/realtime_model.ts: adapted to livekit-plugins/livekit-plugins-xai/livekit/plugins/xai/realtime/realtime_model.py; initial configuration is forwarded to the OpenAI-compatible base session.

The expanded signature is also carried through target-only realtime providers and adapters so AgentActivity can use the API uniformly. Providers that do not require handshake-time configuration retain their existing _update_session() behavior.

Validation:

  • make check
  • 186 focused post-review tests passed
  • 34 package-local tests passed
  • 1,226 tests passed in the broader touched-area run; credential-gated Azure cases could not run, and two pre-existing Google tests fail because they construct RealtimeSession via __new__ without required existing fields
  • Full unit gate: 7,188 passed; remaining failures require valid inference credentials, a local LiveKit server, or a missing audio fixture
  • Built livekit-agents, livekit-plugins-google, livekit-plugins-phonic, livekit-plugins-xai, livekit-plugins-aws, livekit-plugins-azure, livekit-plugins-nvidia, livekit-plugins-rime, livekit-plugins-thunderphone, and livekit-plugins-ultravox

cue-cli runtime validation could not be run because the binary is unavailable in the environment and no cue-cli package is published for uvx; the hermetic voice-pipeline propagation test passes.


Ported from livekit/agents-js#2325

Original PR description

Google Realtime requires instructions/tools to be passed in during initial connection. if they are not passed in, setting them for the first time would cause WS to be re-established, wasting time in the beginning.

updating realtime session establishment to include these parameters when available.

fixes: #2324

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner November 9, 2026 16:04

@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 found 2 potential issues.

Devin Review

Comment on lines +979 to +981
self._initial_chat_ctx_task: asyncio.Task[None] | None = None
if is_given(chat_ctx) and chat_ctx.items:
self._initial_chat_ctx_task = asyncio.create_task(self.update_chat_ctx(chat_ctx))

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.

🟑 First response omits initial history

Calling generate_reply() immediately after session(chat_ctx=...) queues the reply before _initial_chat_ctx_task runs. The first response therefore omits the supplied history across OpenAI-compatible sessions.

Learn more

The constructor queues instructions and tools synchronously, but schedules the initial chat context as a separate task. generate_reply() synchronously queues response.create, so a caller can enqueue it before the event loop runs the context task. The send loop preserves that order and the provider receives the reply request first. This affects the core OpenAI implementation and subclasses such as xAI, ThunderPhone, and LiveKit Inference.

Example: A caller creates model.session(chat_ctx=history) and immediately calls session.generate_reply(). The outgoing queue contains session.update, then response.create; conversation items from history arrive later. The model answers without that history.

Recommended fix: Ensure initial conversation-item events are queued before the constructor returns, or gate generate_reply() and other turn-starting operations until initial context synchronization completes. Preserve acknowledgement and reconnect bookkeeping when moving the initial synchronization.

Devin Review


Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Comment on lines +535 to 541
instructions: NotGivenOr[str] = NOT_GIVEN,
chat_ctx: NotGivenOr[llm.ChatContext] = NOT_GIVEN,
tools: NotGivenOr[list[llm.Tool]] = NOT_GIVEN,
) -> RealtimeSession:
"""Return a new RealtimeSession bound to this model instance."""
# disabling server-side turn detection is unsupported (can_disable_turn_detection=False)
sess = RealtimeSession(self)

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.

🟑 Initial configuration discarded by providers

Passing initial configuration to session() on AWS leaves the new session empty. Duplex, Azure, NVIDIA, Rime, and Ultravox do the same, breaking direct API callers.

Learn more

The expanded public RealtimeModel.session() contract accepts initial instructions, chat context, and tools. This implementation accepts all three values but constructs RealtimeSession(self), so none reaches the session. The same incomplete transformation appears in DuplexRealtimeAdapter.session, Azure RealtimeModel.session, NVIDIA RealtimeModel.session, Rime RealtimeModel.session, and Ultravox RealtimeModel.session. AgentActivity masks this by subsequently calling the private _update_session(), but direct users of the new public arguments receive an unconfigured session.

Example: aws_model.session(instructions="Answer briefly", tools=[lookup]) creates a Nova session with its default prompt and no lookup tool. No later public operation applies those arguments automatically.

Recommended fix: Pass the values into each concrete session and seed its state before connection, or explicitly apply them through a provider-supported initialization path before returning. For providers that cannot support an initial field, reject it rather than silently discarding it.

Devin Review


Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

This branch has not been deployed

No deployments
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.

0 participants