Skip to content

Commit 5c8c55a

Browse files
brucearctorwuliang229
authored andcommitted
fix: use Modality enum for RunConfig.response_modalities
Merge #4889 ## Description Fixes #4869 `RunConfig.response_modalities` is typed as `Optional[list[str]]`, but it gets assigned to `LiveConnectConfig.response_modalities` which expects `Optional[list[Modality]]` (an enum from `google-genai`). This type mismatch causes Pydantic to emit a `PydanticSerializationUnexpectedValue` warning on every live streaming session, polluting production logs. ## Changes - **`run_config.py`**: Changed `response_modalities` type from `Optional[list[str]]` to `Optional[list[types.Modality]]` - **`runners.py`**: Updated two string literal `'AUDIO'` usages to `types.Modality.AUDIO` for consistency This is backwards compatible — `Modality` is a string enum, so Pydantic automatically coerces string values like `'AUDIO'` to `Modality.AUDIO`. ## Testing Plan Added 4 new unit tests in `test_run_config.py`: - `test_response_modalities_accepts_enum` — enum values are stored correctly - `test_response_modalities_coerces_string_to_enum` — string values still work (backwards compat) - `test_response_modalities_coerces_lowercase_string_to_enum` — lowercase string values also work (backwards compat) - `test_response_modalities_serialization_no_warning` — `LiveConnectConfig.model_dump()` produces no Pydantic warnings Co-authored-by: Liang Wu <wuliang@google.com> COPYBARA_INTEGRATE_REVIEW=#4889 from brucearctor:fix/response-modalities-type e67f715 PiperOrigin-RevId: 934659835
1 parent 2f729b5 commit 5c8c55a

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/google/adk/agents/run_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class RunConfig(BaseModel):
196196
speech_config: Optional[types.SpeechConfig] = None
197197
"""Speech configuration for the live agent."""
198198

199-
response_modalities: Optional[list[str]] = None
199+
response_modalities: Optional[list[types.Modality]] = None
200200
"""The output modalities. If not set, it's default to AUDIO."""
201201

202202
avatar_config: Optional[types.AvatarConfig] = None

src/google/adk/runners.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,7 @@ async def run_live(
15541554
# Some native audio models requires the modality to be set. So we set it to
15551555
# AUDIO by default.
15561556
if run_config.response_modalities is None:
1557-
run_config.response_modalities = ['AUDIO']
1557+
run_config.response_modalities = [types.Modality.AUDIO]
15581558
if session is None and (user_id is None or session_id is None):
15591559
raise ValueError(
15601560
'Either session or user_id and session_id must be provided.'
@@ -2021,7 +2021,7 @@ def _new_invocation_context_for_live(
20212021
# For live multi-agents system, we need model's text transcription as
20222022
# context for the transferred agent.
20232023
if hasattr(self.agent, 'sub_agents') and self.agent.sub_agents:
2024-
if 'AUDIO' in run_config.response_modalities:
2024+
if types.Modality.AUDIO in run_config.response_modalities:
20252025
if not run_config.output_audio_transcription:
20262026
run_config.output_audio_transcription = (
20272027
types.AudioTranscriptionConfig()

tests/unittests/agents/test_run_config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import sys
1616
from unittest.mock import ANY
1717
from unittest.mock import patch
18+
import warnings
1819

1920
from google.adk.agents.run_config import RunConfig
2021
from google.genai import types
@@ -67,6 +68,37 @@ def test_audio_transcription_configs_are_not_shared_between_instances():
6768
)
6869

6970

71+
def test_response_modalities_accepts_enum():
72+
config = RunConfig(response_modalities=[types.Modality.AUDIO])
73+
assert config.response_modalities == [types.Modality.AUDIO]
74+
assert isinstance(config.response_modalities[0], types.Modality)
75+
76+
77+
def test_response_modalities_coerces_string_to_enum():
78+
config = RunConfig(response_modalities=["AUDIO"])
79+
assert config.response_modalities == [types.Modality.AUDIO]
80+
assert isinstance(config.response_modalities[0], types.Modality)
81+
82+
83+
def test_response_modalities_coerces_lowercase_string_to_enum():
84+
config = RunConfig(response_modalities=["audio"])
85+
assert config.response_modalities == [types.Modality.AUDIO]
86+
assert isinstance(config.response_modalities[0], types.Modality)
87+
88+
89+
def test_response_modalities_serialization_no_warning():
90+
config = RunConfig(response_modalities=[types.Modality.AUDIO])
91+
live_config = types.LiveConnectConfig()
92+
live_config.response_modalities = config.response_modalities
93+
with warnings.catch_warnings(record=True) as w:
94+
warnings.simplefilter("always")
95+
live_config.model_dump()
96+
pydantic_warnings = [
97+
x for x in w if "PydanticSerializationUnexpectedValue" in str(x.message)
98+
]
99+
assert len(pydantic_warnings) == 0
100+
101+
70102
def test_avatar_config_initialization():
71103
custom_avatar = types.CustomizedAvatar(
72104
image_mime_type="image/jpeg", image_data=b"image_bytes"

0 commit comments

Comments
 (0)