From 1b99ffe61bd8c4a6845e6f61972f1a97df103678 Mon Sep 17 00:00:00 2001 From: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:13:23 +0200 Subject: [PATCH] Pass satellite id through assist pipeline (#151992) --- .../components/assist_pipeline/__init__.py | 2 ++ .../components/assist_pipeline/pipeline.py | 26 ++++++++++++++----- .../components/assist_satellite/entity.py | 1 + .../components/conversation/agent_manager.py | 2 ++ .../components/conversation/default_agent.py | 1 + homeassistant/components/conversation/http.py | 1 + .../components/conversation/models.py | 4 +++ .../components/conversation/trigger.py | 1 + homeassistant/helpers/intent.py | 5 ++++ .../assist_pipeline/snapshots/test_init.ambr | 4 +++ .../snapshots/test_pipeline.ambr | 6 +++++ .../snapshots/test_websocket.ambr | 9 +++++++ .../assist_pipeline/test_pipeline.py | 2 ++ tests/components/conversation/conftest.py | 1 + .../conversation/test_default_agent.py | 8 ++++++ tests/components/conversation/test_init.py | 3 +++ tests/components/conversation/test_trigger.py | 22 ++++++++++++++-- 17 files changed, 90 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/assist_pipeline/__init__.py b/homeassistant/components/assist_pipeline/__init__.py index 8f4c6efd355..e9394a8ac5d 100644 --- a/homeassistant/components/assist_pipeline/__init__.py +++ b/homeassistant/components/assist_pipeline/__init__.py @@ -103,6 +103,7 @@ async def async_pipeline_from_audio_stream( wake_word_settings: WakeWordSettings | None = None, audio_settings: AudioSettings | None = None, device_id: str | None = None, + satellite_id: str | None = None, start_stage: PipelineStage = PipelineStage.STT, end_stage: PipelineStage = PipelineStage.TTS, conversation_extra_system_prompt: str | None = None, @@ -115,6 +116,7 @@ async def async_pipeline_from_audio_stream( pipeline_input = PipelineInput( session=session, device_id=device_id, + satellite_id=satellite_id, stt_metadata=stt_metadata, stt_stream=stt_stream, wake_word_phrase=wake_word_phrase, diff --git a/homeassistant/components/assist_pipeline/pipeline.py b/homeassistant/components/assist_pipeline/pipeline.py index 0cd593e9666..ad291b3427b 100644 --- a/homeassistant/components/assist_pipeline/pipeline.py +++ b/homeassistant/components/assist_pipeline/pipeline.py @@ -583,6 +583,9 @@ class PipelineRun: _device_id: str | None = None """Optional device id set during run start.""" + _satellite_id: str | None = None + """Optional satellite id set during run start.""" + _conversation_data: PipelineConversationData | None = None """Data tied to the conversation ID.""" @@ -636,9 +639,12 @@ class PipelineRun: return pipeline_data.pipeline_debug[self.pipeline.id][self.id].events.append(event) - def start(self, conversation_id: str, device_id: str | None) -> None: + def start( + self, conversation_id: str, device_id: str | None, satellite_id: str | None + ) -> None: """Emit run start event.""" self._device_id = device_id + self._satellite_id = satellite_id self._start_debug_recording_thread() data: dict[str, Any] = { @@ -646,6 +652,8 @@ class PipelineRun: "language": self.language, "conversation_id": conversation_id, } + if satellite_id is not None: + data["satellite_id"] = satellite_id if self.runner_data is not None: data["runner_data"] = self.runner_data if self.tts_stream: @@ -1057,7 +1065,6 @@ class PipelineRun: self, intent_input: str, conversation_id: str, - device_id: str | None, conversation_extra_system_prompt: str | None, ) -> str: """Run intent recognition portion of pipeline. Returns text to speak.""" @@ -1088,7 +1095,8 @@ class PipelineRun: "language": input_language, "intent_input": intent_input, "conversation_id": conversation_id, - "device_id": device_id, + "device_id": self._device_id, + "satellite_id": self._satellite_id, "prefer_local_intents": self.pipeline.prefer_local_intents, }, ) @@ -1099,7 +1107,8 @@ class PipelineRun: text=intent_input, context=self.context, conversation_id=conversation_id, - device_id=device_id, + device_id=self._device_id, + satellite_id=self._satellite_id, language=input_language, agent_id=self.intent_agent.id, extra_system_prompt=conversation_extra_system_prompt, @@ -1269,6 +1278,7 @@ class PipelineRun: text=user_input.text, conversation_id=user_input.conversation_id, device_id=user_input.device_id, + satellite_id=user_input.satellite_id, context=user_input.context, language=user_input.language, agent_id=user_input.agent_id, @@ -1567,10 +1577,15 @@ class PipelineInput: device_id: str | None = None """Identifier of the device that is processing the input/output of the pipeline.""" + satellite_id: str | None = None + """Identifier of the satellite that is processing the input/output of the pipeline.""" + async def execute(self) -> None: """Run pipeline.""" self.run.start( - conversation_id=self.session.conversation_id, device_id=self.device_id + conversation_id=self.session.conversation_id, + device_id=self.device_id, + satellite_id=self.satellite_id, ) current_stage: PipelineStage | None = self.run.start_stage stt_audio_buffer: list[EnhancedAudioChunk] = [] @@ -1656,7 +1671,6 @@ class PipelineInput: tts_input = await self.run.recognize_intent( intent_input, self.session.conversation_id, - self.device_id, self.conversation_extra_system_prompt, ) if tts_input.strip(): diff --git a/homeassistant/components/assist_satellite/entity.py b/homeassistant/components/assist_satellite/entity.py index 3d562544c68..05c0db776a3 100644 --- a/homeassistant/components/assist_satellite/entity.py +++ b/homeassistant/components/assist_satellite/entity.py @@ -522,6 +522,7 @@ class AssistSatelliteEntity(entity.Entity): pipeline_id=self._resolve_pipeline(), conversation_id=session.conversation_id, device_id=device_id, + satellite_id=self.entity_id, tts_audio_output=self.tts_options, wake_word_phrase=wake_word_phrase, audio_settings=AudioSettings( diff --git a/homeassistant/components/conversation/agent_manager.py b/homeassistant/components/conversation/agent_manager.py index 6203525ac01..fb050397061 100644 --- a/homeassistant/components/conversation/agent_manager.py +++ b/homeassistant/components/conversation/agent_manager.py @@ -71,6 +71,7 @@ async def async_converse( language: str | None = None, agent_id: str | None = None, device_id: str | None = None, + satellite_id: str | None = None, extra_system_prompt: str | None = None, ) -> ConversationResult: """Process text and get intent.""" @@ -97,6 +98,7 @@ async def async_converse( context=context, conversation_id=conversation_id, device_id=device_id, + satellite_id=satellite_id, language=language, agent_id=agent_id, extra_system_prompt=extra_system_prompt, diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 4e07fd0135f..f7b3562fe81 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -470,6 +470,7 @@ class DefaultAgent(ConversationEntity): language, assistant=DOMAIN, device_id=user_input.device_id, + satellite_id=user_input.satellite_id, conversation_agent_id=user_input.agent_id, ) except intent.MatchFailedError as match_error: diff --git a/homeassistant/components/conversation/http.py b/homeassistant/components/conversation/http.py index 290e3aab955..077201fca6e 100644 --- a/homeassistant/components/conversation/http.py +++ b/homeassistant/components/conversation/http.py @@ -201,6 +201,7 @@ async def websocket_hass_agent_debug( context=connection.context(msg), conversation_id=None, device_id=msg.get("device_id"), + satellite_id=None, language=msg.get("language", hass.config.language), agent_id=agent.entity_id, ) diff --git a/homeassistant/components/conversation/models.py b/homeassistant/components/conversation/models.py index dac1fb862ec..96c245d4b27 100644 --- a/homeassistant/components/conversation/models.py +++ b/homeassistant/components/conversation/models.py @@ -37,6 +37,9 @@ class ConversationInput: device_id: str | None """Unique identifier for the device.""" + satellite_id: str | None + """Unique identifier for the satellite.""" + language: str """Language of the request.""" @@ -53,6 +56,7 @@ class ConversationInput: "context": self.context.as_dict(), "conversation_id": self.conversation_id, "device_id": self.device_id, + "satellite_id": self.satellite_id, "language": self.language, "agent_id": self.agent_id, "extra_system_prompt": self.extra_system_prompt, diff --git a/homeassistant/components/conversation/trigger.py b/homeassistant/components/conversation/trigger.py index 752e294a8b3..ccf3868c212 100644 --- a/homeassistant/components/conversation/trigger.py +++ b/homeassistant/components/conversation/trigger.py @@ -100,6 +100,7 @@ async def async_attach_trigger( entity_name: entity["value"] for entity_name, entity in details.items() }, "device_id": user_input.device_id, + "satellite_id": user_input.satellite_id, "user_input": user_input.as_dict(), } diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index de6f98527c5..a412d475acf 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -114,6 +114,7 @@ async def async_handle( language: str | None = None, assistant: str | None = None, device_id: str | None = None, + satellite_id: str | None = None, conversation_agent_id: str | None = None, ) -> IntentResponse: """Handle an intent.""" @@ -138,6 +139,7 @@ async def async_handle( language=language, assistant=assistant, device_id=device_id, + satellite_id=satellite_id, conversation_agent_id=conversation_agent_id, ) @@ -1276,6 +1278,7 @@ class Intent: "intent_type", "language", "platform", + "satellite_id", "slots", "text_input", ] @@ -1291,6 +1294,7 @@ class Intent: language: str, assistant: str | None = None, device_id: str | None = None, + satellite_id: str | None = None, conversation_agent_id: str | None = None, ) -> None: """Initialize an intent.""" @@ -1303,6 +1307,7 @@ class Intent: self.language = language self.assistant = assistant self.device_id = device_id + self.satellite_id = satellite_id self.conversation_agent_id = conversation_agent_id @callback diff --git a/tests/components/assist_pipeline/snapshots/test_init.ambr b/tests/components/assist_pipeline/snapshots/test_init.ambr index 4ae4b5dce4c..56ca8bde0ba 100644 --- a/tests/components/assist_pipeline/snapshots/test_init.ambr +++ b/tests/components/assist_pipeline/snapshots/test_init.ambr @@ -45,6 +45,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -145,6 +146,7 @@ 'intent_input': 'test transcript', 'language': 'en-US', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -245,6 +247,7 @@ 'intent_input': 'test transcript', 'language': 'en-US', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -369,6 +372,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), diff --git a/tests/components/assist_pipeline/snapshots/test_pipeline.ambr b/tests/components/assist_pipeline/snapshots/test_pipeline.ambr index b6354b2342b..7a51eddf8d6 100644 --- a/tests/components/assist_pipeline/snapshots/test_pipeline.ambr +++ b/tests/components/assist_pipeline/snapshots/test_pipeline.ambr @@ -23,6 +23,7 @@ 'intent_input': 'Set a timer', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -178,6 +179,7 @@ 'intent_input': 'Set a timer', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -411,6 +413,7 @@ 'intent_input': 'Set a timer', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -634,6 +637,7 @@ 'intent_input': 'test input', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -687,6 +691,7 @@ 'intent_input': 'test input', 'language': 'en-US', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), @@ -740,6 +745,7 @@ 'intent_input': 'test input', 'language': 'en-us', 'prefer_local_intents': False, + 'satellite_id': None, }), 'type': , }), diff --git a/tests/components/assist_pipeline/snapshots/test_websocket.ambr b/tests/components/assist_pipeline/snapshots/test_websocket.ambr index 4f29fd79568..5e0d915a77e 100644 --- a/tests/components/assist_pipeline/snapshots/test_websocket.ambr +++ b/tests/components/assist_pipeline/snapshots/test_websocket.ambr @@ -44,6 +44,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_audio_pipeline.4 @@ -136,6 +137,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_audio_pipeline_debug.4 @@ -240,6 +242,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_audio_pipeline_with_enhancements.4 @@ -354,6 +357,7 @@ 'intent_input': 'test transcript', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_audio_pipeline_with_wake_word_no_timeout.6 @@ -575,6 +579,7 @@ 'intent_input': 'Are the lights on?', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_intent_failed.2 @@ -599,6 +604,7 @@ 'intent_input': 'Are the lights on?', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_intent_timeout.2 @@ -635,6 +641,7 @@ 'intent_input': 'never mind', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_pipeline_empty_tts_output.2 @@ -785,6 +792,7 @@ 'intent_input': 'Are the lights on?', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_text_only_pipeline[extra_msg0].2 @@ -833,6 +841,7 @@ 'intent_input': 'Are the lights on?', 'language': 'en', 'prefer_local_intents': False, + 'satellite_id': None, }) # --- # name: test_text_only_pipeline[extra_msg1].2 diff --git a/tests/components/assist_pipeline/test_pipeline.py b/tests/components/assist_pipeline/test_pipeline.py index 0cb67302700..75234122368 100644 --- a/tests/components/assist_pipeline/test_pipeline.py +++ b/tests/components/assist_pipeline/test_pipeline.py @@ -1707,6 +1707,7 @@ async def test_chat_log_tts_streaming( language: str | None = None, agent_id: str | None = None, device_id: str | None = None, + satellite_id: str | None = None, extra_system_prompt: str | None = None, ): """Mock converse.""" @@ -1715,6 +1716,7 @@ async def test_chat_log_tts_streaming( context=context, conversation_id=conversation_id, device_id=device_id, + satellite_id=satellite_id, language=language, agent_id=agent_id, extra_system_prompt=extra_system_prompt, diff --git a/tests/components/conversation/conftest.py b/tests/components/conversation/conftest.py index 19d8434fc5a..8fefcdf7f01 100644 --- a/tests/components/conversation/conftest.py +++ b/tests/components/conversation/conftest.py @@ -43,6 +43,7 @@ def mock_conversation_input(hass: HomeAssistant) -> conversation.ConversationInp conversation_id=None, agent_id="mock-agent-id", device_id=None, + satellite_id=None, language="en", ) diff --git a/tests/components/conversation/test_default_agent.py b/tests/components/conversation/test_default_agent.py index a90cd1b55c1..64457300dad 100644 --- a/tests/components/conversation/test_default_agent.py +++ b/tests/components/conversation/test_default_agent.py @@ -2534,6 +2534,7 @@ async def test_non_default_response(hass: HomeAssistant, init_components) -> Non context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -2884,6 +2885,7 @@ async def test_intent_cache_exposed(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -2923,6 +2925,7 @@ async def test_intent_cache_all_entities(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -2958,6 +2961,7 @@ async def test_intent_cache_fuzzy(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -3000,6 +3004,7 @@ async def test_entities_filtered_by_input(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -3026,6 +3031,7 @@ async def test_entities_filtered_by_input(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -3166,6 +3172,7 @@ async def test_handle_intents_with_response_errors( context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) @@ -3203,6 +3210,7 @@ async def test_handle_intents_filters_results( context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, agent_id=None, ) diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index e757c56042b..7cec3543fab 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -281,6 +281,7 @@ async def test_async_handle_sentence_triggers( conversation_id=None, agent_id=conversation.HOME_ASSISTANT_AGENT, device_id=device_id, + satellite_id=None, language=hass.config.language, ), ) @@ -318,6 +319,7 @@ async def test_async_handle_intents(hass: HomeAssistant) -> None: agent_id=conversation.HOME_ASSISTANT_AGENT, conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, ), ) @@ -335,6 +337,7 @@ async def test_async_handle_intents(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id=None, + satellite_id=None, language=hass.config.language, ), ) diff --git a/tests/components/conversation/test_trigger.py b/tests/components/conversation/test_trigger.py index a01f4cd8112..4531e5857e2 100644 --- a/tests/components/conversation/test_trigger.py +++ b/tests/components/conversation/test_trigger.py @@ -50,6 +50,7 @@ async def test_if_fires_on_event( "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -81,11 +82,13 @@ async def test_if_fires_on_event( "slots": {}, "details": {}, "device_id": None, + "satellite_id": None, "user_input": { "agent_id": HOME_ASSISTANT_AGENT, "context": context.as_dict(), "conversation_id": None, "device_id": None, + "satellite_id": None, "language": "en", "text": "Ha ha ha", "extra_system_prompt": None, @@ -185,6 +188,7 @@ async def test_response_same_sentence( "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -230,11 +234,13 @@ async def test_response_same_sentence( "slots": {}, "details": {}, "device_id": None, + "satellite_id": None, "user_input": { "agent_id": HOME_ASSISTANT_AGENT, "context": context.as_dict(), "conversation_id": None, "device_id": None, + "satellite_id": None, "language": "en", "text": "test sentence", "extra_system_prompt": None, @@ -376,6 +382,7 @@ async def test_same_trigger_multiple_sentences( "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -408,11 +415,13 @@ async def test_same_trigger_multiple_sentences( "slots": {}, "details": {}, "device_id": None, + "satellite_id": None, "user_input": { "agent_id": HOME_ASSISTANT_AGENT, "context": context.as_dict(), "conversation_id": None, "device_id": None, + "satellite_id": None, "language": "en", "text": "hello", "extra_system_prompt": None, @@ -449,6 +458,7 @@ async def test_same_sentence_multiple_triggers( "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -474,6 +484,7 @@ async def test_same_sentence_multiple_triggers( "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -590,6 +601,7 @@ async def test_wildcards(hass: HomeAssistant, service_calls: list[ServiceCall]) "slots": "{{ trigger.slots }}", "details": "{{ trigger.details }}", "device_id": "{{ trigger.device_id }}", + "satellite_id": "{{ trigger.satellite_id }}", "user_input": "{{ trigger.user_input }}", } }, @@ -636,11 +648,13 @@ async def test_wildcards(hass: HomeAssistant, service_calls: list[ServiceCall]) }, }, "device_id": None, + "satellite_id": None, "user_input": { "agent_id": HOME_ASSISTANT_AGENT, "context": context.as_dict(), "conversation_id": None, "device_id": None, + "satellite_id": None, "language": "en", "text": "play the white album by the beatles", "extra_system_prompt": None, @@ -660,7 +674,7 @@ async def test_trigger_with_device_id(hass: HomeAssistant) -> None: "command": ["test sentence"], }, "action": { - "set_conversation_response": "{{ trigger.device_id }}", + "set_conversation_response": "{{ trigger.device_id }} - {{ trigger.satellite_id }}", }, } }, @@ -675,8 +689,12 @@ async def test_trigger_with_device_id(hass: HomeAssistant) -> None: context=Context(), conversation_id=None, device_id="my_device", + satellite_id="assist_satellite.my_satellite", language=hass.config.language, agent_id=None, ) ) - assert result.response.speech["plain"]["speech"] == "my_device" + assert ( + result.response.speech["plain"]["speech"] + == "my_device - assist_satellite.my_satellite" + )