From 442fc334ff9f6487094b264acdb6401d805a1e09 Mon Sep 17 00:00:00 2001 From: Luke Oliff Date: Fri, 25 Jul 2025 21:08:01 +0100 Subject: [PATCH] fix(agent): move mip_opt_out field to root level of SettingsOptions The mip_opt_out field was incorrectly placed inside the agent object, but according to the Deepgram API specification, it should be at the root level of the Settings message payload. This was causing the API to return: 'Error parsing client message. Check the agent.mip_opt_out field against the API spec.' - Moved mip_opt_out from Agent class to SettingsOptions class - Updated all unit tests to reference the new location - Verified serialization now matches API spec: mip_opt_out at $message.payload#/mip_opt_out --- .../clients/agent/v1/websocket/options.py | 6 +- .../unit_test/test_unit_agent_mip_opt_out.py | 98 ++++++++----------- tests/unit_test/test_unit_agent_tags.py | 8 +- 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/deepgram/clients/agent/v1/websocket/options.py b/deepgram/clients/agent/v1/websocket/options.py index ffb30b18..f3e99ae4 100644 --- a/deepgram/clients/agent/v1/websocket/options.py +++ b/deepgram/clients/agent/v1/websocket/options.py @@ -271,9 +271,6 @@ class Agent(BaseResponse): greeting: Optional[str] = field( default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) - mip_opt_out: Optional[bool] = field( - default=False, metadata=dataclass_config(exclude=lambda f: f is None) - ) tags: Optional[List[str]] = field( default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) @@ -355,6 +352,9 @@ class SettingsOptions(BaseResponse): type: str = str(AgentWebSocketEvents.Settings) audio: Audio = field(default_factory=Audio) agent: Agent = field(default_factory=Agent) + mip_opt_out: Optional[bool] = field( + default=False, metadata=dataclass_config(exclude=lambda f: f is None) + ) def __getitem__(self, key): _dict = self.to_dict() diff --git a/tests/unit_test/test_unit_agent_mip_opt_out.py b/tests/unit_test/test_unit_agent_mip_opt_out.py index 39db1ce5..59347120 100644 --- a/tests/unit_test/test_unit_agent_mip_opt_out.py +++ b/tests/unit_test/test_unit_agent_mip_opt_out.py @@ -11,32 +11,28 @@ class TestAgentMipOptOut: - """Unit tests for mip_opt_out agent setting""" + """Unit tests for mip_opt_out setting at the root level of SettingsOptions""" def test_default_mip_opt_out_value(self): """Test that mip_opt_out defaults to False""" options = SettingsOptions() - # Default should be False - assert options.agent.mip_opt_out == False - - # Verify it's accessible through the agent directly - agent = Agent() - assert agent.mip_opt_out == False + # Default should be False at root level + assert options.mip_opt_out == False def test_set_mip_opt_out_true(self): """Test setting mip_opt_out to True""" options = SettingsOptions() - options.agent.mip_opt_out = True + options.mip_opt_out = True - assert options.agent.mip_opt_out == True + assert options.mip_opt_out == True def test_set_mip_opt_out_false_explicitly(self): """Test explicitly setting mip_opt_out to False""" options = SettingsOptions() - options.agent.mip_opt_out = False + options.mip_opt_out = False - assert options.agent.mip_opt_out == False + assert options.mip_opt_out == False def test_mip_opt_out_serialization_default(self): """Test that mip_opt_out with default value is excluded from serialization""" @@ -47,57 +43,55 @@ def test_mip_opt_out_serialization_default(self): # With default False and exclude=lambda f: f is None metadata, # the field should be excluded from serialization when it's False/default - # But let's verify the structure exists - assert "agent" in result - # The default exclude behavior might not serialize False values, let's test both cases + # mip_opt_out should not be in the root level when it's the default value + assert "mip_opt_out" not in result or result.get( + "mip_opt_out") == False def test_mip_opt_out_serialization_true(self): """Test that mip_opt_out=True is included in serialization""" options = SettingsOptions() - options.agent.mip_opt_out = True + options.mip_opt_out = True result = options.to_dict() json_str = options.to_json() parsed_json = json.loads(json_str) - # Should be included when True - assert result["agent"]["mip_opt_out"] == True - assert parsed_json["agent"]["mip_opt_out"] == True + # Should be included at root level when True + assert result["mip_opt_out"] == True + assert parsed_json["mip_opt_out"] == True def test_mip_opt_out_serialization_false_explicit(self): """Test that mip_opt_out=False (explicit) behavior in serialization""" options = SettingsOptions() - options.agent.mip_opt_out = False + options.mip_opt_out = False result = options.to_dict() json_str = options.to_json() # The field might be excluded due to dataclass_config exclude logic # Let's verify the actual behavior - if "mip_opt_out" in result.get("agent", {}): - assert result["agent"]["mip_opt_out"] == False + if "mip_opt_out" in result: + assert result["mip_opt_out"] == False def test_mip_opt_out_deserialization(self): """Test deserializing mip_opt_out from dict""" - # Test with True value + # Test with True value at root level data_true = { - "agent": { - "mip_opt_out": True - } + "mip_opt_out": True, + "agent": {} } options_true = SettingsOptions.from_dict(data_true) - assert options_true.agent.mip_opt_out == True + assert options_true.mip_opt_out == True - # Test with False value + # Test with False value at root level data_false = { - "agent": { - "mip_opt_out": False - } + "mip_opt_out": False, + "agent": {} } options_false = SettingsOptions.from_dict(data_false) - assert options_false.agent.mip_opt_out == False + assert options_false.mip_opt_out == False def test_mip_opt_out_deserialization_missing(self): """Test deserializing when mip_opt_out is not present (should default to False)""" @@ -108,43 +102,43 @@ def test_mip_opt_out_deserialization_missing(self): } options = SettingsOptions.from_dict(data) - assert options.agent.mip_opt_out == False + assert options.mip_opt_out == False def test_mip_opt_out_round_trip(self): """Test serialization and deserialization round-trip""" # Test with True original_true = SettingsOptions() - original_true.agent.mip_opt_out = True + original_true.mip_opt_out = True serialized_true = original_true.to_dict() restored_true = SettingsOptions.from_dict(serialized_true) - assert restored_true.agent.mip_opt_out == True + assert restored_true.mip_opt_out == True # Test with False (if it gets serialized) original_false = SettingsOptions() - original_false.agent.mip_opt_out = False + original_false.mip_opt_out = False serialized_false = original_false.to_dict() restored_false = SettingsOptions.from_dict(serialized_false) - assert restored_false.agent.mip_opt_out == False + assert restored_false.mip_opt_out == False - def test_mip_opt_out_with_other_agent_settings(self): - """Test mip_opt_out works correctly with other agent settings""" + def test_mip_opt_out_with_other_settings(self): + """Test mip_opt_out works correctly with other settings""" options = SettingsOptions() options.agent.language = "en" - options.agent.mip_opt_out = True + options.mip_opt_out = True options.agent.greeting = "Hello, I have opted out of MIP" assert options.agent.language == "en" - assert options.agent.mip_opt_out == True + assert options.mip_opt_out == True assert options.agent.greeting == "Hello, I have opted out of MIP" # Test serialization with multiple fields result = options.to_dict() assert result["agent"]["language"] == "en" - assert result["agent"]["mip_opt_out"] == True + assert result["mip_opt_out"] == True assert result["agent"]["greeting"] == "Hello, I have opted out of MIP" def test_mip_opt_out_type_validation(self): @@ -152,20 +146,14 @@ def test_mip_opt_out_type_validation(self): options = SettingsOptions() # Should accept boolean values - options.agent.mip_opt_out = True - assert options.agent.mip_opt_out == True + options.mip_opt_out = True + assert options.mip_opt_out == True - options.agent.mip_opt_out = False - assert options.agent.mip_opt_out == False + options.mip_opt_out = False + assert options.mip_opt_out == False - def test_agent_direct_instantiation(self): - """Test mip_opt_out when creating Agent directly""" + def test_legacy_agent_mip_opt_out_removed(self): + """Test that mip_opt_out is no longer on the Agent class""" agent = Agent() - assert agent.mip_opt_out == False - - agent.mip_opt_out = True - assert agent.mip_opt_out == True - - # Test serialization of direct Agent instance - result = agent.to_dict() - assert result["mip_opt_out"] == True \ No newline at end of file + # mip_opt_out should not exist on Agent anymore + assert not hasattr(agent, 'mip_opt_out') diff --git a/tests/unit_test/test_unit_agent_tags.py b/tests/unit_test/test_unit_agent_tags.py index 6ca40021..a84abc0d 100644 --- a/tests/unit_test/test_unit_agent_tags.py +++ b/tests/unit_test/test_unit_agent_tags.py @@ -162,19 +162,19 @@ def test_tags_with_other_agent_settings(self): options.agent.language = "en" options.agent.tags = ["integration", "test"] options.agent.greeting = "Hello, this is a tagged conversation" - options.agent.mip_opt_out = True + options.mip_opt_out = True assert options.agent.language == "en" assert options.agent.tags == ["integration", "test"] assert options.agent.greeting == "Hello, this is a tagged conversation" - assert options.agent.mip_opt_out == True + assert options.mip_opt_out == True # Test serialization with multiple fields result = options.to_dict() assert result["agent"]["language"] == "en" assert result["agent"]["tags"] == ["integration", "test"] assert result["agent"]["greeting"] == "Hello, this is a tagged conversation" - assert result["agent"]["mip_opt_out"] == True + assert result["mip_opt_out"] == True def test_tags_type_validation(self): """Test that tags accepts list of strings""" @@ -190,4 +190,4 @@ def test_tags_type_validation(self): # Should accept None options.agent.tags = None - assert options.agent.tags is None \ No newline at end of file + assert options.agent.tags is None