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
20 changes: 13 additions & 7 deletions src/google/adk/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2379,18 +2379,24 @@ def _new_invocation_context_for_live(
run_config = run_config or RunConfig()

# For live multi-agents system, we need model's text transcription as
# context for the transferred agent.
# context for the transferred agent. Only fill fields the caller left
# unset: an explicit None is an opt-out and stays None. Both fields
# default to an AudioTranscriptionConfig() via default_factory, so
# `model_fields_set` is what separates "unset" from "explicitly off".
# Writes go on a copy, never the caller's own RunConfig (see the same
# treatment for response_modalities in run_live).
if hasattr(self.agent, 'sub_agents') and self.agent.sub_agents:
updates: dict[str, Any] = {}
if (
run_config.response_modalities
and types.Modality.AUDIO in run_config.response_modalities
and 'output_audio_transcription' not in run_config.model_fields_set
):
if not run_config.output_audio_transcription:
run_config.output_audio_transcription = (
types.AudioTranscriptionConfig()
)
if not run_config.input_audio_transcription:
run_config.input_audio_transcription = types.AudioTranscriptionConfig()
updates['output_audio_transcription'] = types.AudioTranscriptionConfig()
if 'input_audio_transcription' not in run_config.model_fields_set:
updates['input_audio_transcription'] = types.AudioTranscriptionConfig()
if updates:
run_config = run_config.model_copy(update=updates)
return self._new_invocation_context(
session,
live_request_queue=live_request_queue,
Expand Down
110 changes: 110 additions & 0 deletions tests/unittests/test_runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2983,5 +2983,115 @@ async def test_base_agent_run_live_does_not_leak_context():
otel_context.detach(token)


def _live_runner(agent) -> Runner:
return Runner(
app_name=TEST_APP_ID,
agent=agent,
session_service=InMemorySessionService(),
artifact_service=InMemoryArtifactService(),
)


def _multi_agent() -> LlmAgent:
return LlmAgent(
name="live_root",
model="gemini-2.0-flash-live-001",
sub_agents=[
LlmAgent(name="live_child", model="gemini-2.0-flash-live-001")
],
)


def _live_session() -> Session:
return Session(id=TEST_SESSION_ID, app_name=TEST_APP_ID, user_id=TEST_USER_ID)


def test_live_context_respects_explicit_transcription_opt_out():
"""An explicit None for transcription stays None for the run."""
from google.adk.agents.live_request_queue import LiveRequestQueue

runner = _live_runner(_multi_agent())
config = RunConfig(
response_modalities=[types.Modality.AUDIO],
input_audio_transcription=None,
output_audio_transcription=None,
)

ic = runner._new_invocation_context_for_live(
_live_session(),
live_request_queue=LiveRequestQueue(),
run_config=config,
)

assert ic.run_config.input_audio_transcription is None
assert ic.run_config.output_audio_transcription is None
# The caller's own RunConfig is never written to.
assert config.input_audio_transcription is None
assert config.output_audio_transcription is None


def test_live_context_keeps_default_transcription_enabled():
"""A default RunConfig keeps its default-on transcription configs."""
from google.adk.agents.live_request_queue import LiveRequestQueue

runner = _live_runner(_multi_agent())
config = RunConfig(response_modalities=[types.Modality.AUDIO])

ic = runner._new_invocation_context_for_live(
_live_session(),
live_request_queue=LiveRequestQueue(),
run_config=config,
)

assert ic.run_config.input_audio_transcription is not None
assert ic.run_config.output_audio_transcription is not None


def test_live_context_without_sub_agents_passes_config_through():
"""Without sub_agents the config is passed through untouched."""
from google.adk.agents.live_request_queue import LiveRequestQueue

runner = _live_runner(
LlmAgent(name="live_solo", model="gemini-2.0-flash-live-001")
)
config = RunConfig(
response_modalities=[types.Modality.AUDIO],
input_audio_transcription=None,
output_audio_transcription=None,
)

ic = runner._new_invocation_context_for_live(
_live_session(),
live_request_queue=LiveRequestQueue(),
run_config=config,
)

assert ic.run_config is config
assert config.input_audio_transcription is None
assert config.output_audio_transcription is None


def test_live_context_opt_out_survives_unset_modalities():
"""Explicit None is respected even when response_modalities is unset."""
from google.adk.agents.live_request_queue import LiveRequestQueue

runner = _live_runner(_multi_agent())
config = RunConfig(
input_audio_transcription=None,
output_audio_transcription=None,
)

ic = runner._new_invocation_context_for_live(
_live_session(),
live_request_queue=LiveRequestQueue(),
run_config=config,
)

assert ic.run_config.input_audio_transcription is None
assert ic.run_config.output_audio_transcription is None
assert config.input_audio_transcription is None
assert config.output_audio_transcription is None


if __name__ == "__main__":
pytest.main([__file__])
Loading