Skip to content

Commit 30e50d2

Browse files
authored
Made Google Search enable dependent on Assist availability (#141712)
* Made Google Search enable dependent on Assist availability * Show error instead of rendering again * Cleanup test code
1 parent 3b2ff38 commit 30e50d2

File tree

3 files changed

+150
-33
lines changed

3 files changed

+150
-33
lines changed

homeassistant/components/google_generative_ai_conversation/config_flow.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -179,28 +179,30 @@ async def async_step_init(
179179
) -> ConfigFlowResult:
180180
"""Manage the options."""
181181
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
182+
errors: dict[str, str] = {}
182183

183184
if user_input is not None:
184185
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
185186
if user_input[CONF_LLM_HASS_API] == "none":
186187
user_input.pop(CONF_LLM_HASS_API)
187-
return self.async_create_entry(title="", data=user_input)
188+
if not (
189+
user_input.get(CONF_LLM_HASS_API, "none") != "none"
190+
and user_input.get(CONF_USE_GOOGLE_SEARCH_TOOL, False) is True
191+
):
192+
# Don't allow to save options that enable the Google Seearch tool with an Assist API
193+
return self.async_create_entry(title="", data=user_input)
194+
errors[CONF_USE_GOOGLE_SEARCH_TOOL] = "invalid_google_search_option"
188195

189196
# Re-render the options again, now with the recommended options shown/hidden
190197
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
191198

192-
options = {
193-
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
194-
CONF_PROMPT: user_input[CONF_PROMPT],
195-
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
196-
}
199+
options = user_input
197200

198201
schema = await google_generative_ai_config_option_schema(
199202
self.hass, options, self._genai_client
200203
)
201204
return self.async_show_form(
202-
step_id="init",
203-
data_schema=vol.Schema(schema),
205+
step_id="init", data_schema=vol.Schema(schema), errors=errors
204206
)
205207

206208

homeassistant/components/google_generative_ai_conversation/strings.json

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
"prompt": "Instruct how the LLM should respond. This can be a template."
4444
}
4545
}
46+
},
47+
"error": {
48+
"invalid_google_search_option": "Google Search cannot be enabled alongside any Assist capability, this can only be used when Assist is set to \"No control\"."
4649
}
4750
},
4851
"services": {

tests/components/google_generative_ai_conversation/test_config_flow.py

+137-25
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@
3939
from tests.common import MockConfigEntry
4040

4141

42-
@pytest.fixture
43-
def mock_models():
44-
"""Mock the model list API."""
42+
def get_models_pager():
43+
"""Return a generator that yields the models."""
4544
model_20_flash = Mock(
4645
display_name="Gemini 2.0 Flash",
4746
supported_actions=["generateContent"],
@@ -72,11 +71,7 @@ async def models_pager():
7271
yield model_15_pro
7372
yield model_10_pro
7473

75-
with patch(
76-
"google.genai.models.AsyncModels.list",
77-
return_value=models_pager(),
78-
):
79-
yield
74+
return models_pager()
8075

8176

8277
async def test_form(hass: HomeAssistant) -> None:
@@ -119,8 +114,13 @@ async def test_form(hass: HomeAssistant) -> None:
119114
assert len(mock_setup_entry.mock_calls) == 1
120115

121116

117+
def will_options_be_rendered_again(current_options, new_options) -> bool:
118+
"""Determine if options will be rendered again."""
119+
return current_options.get(CONF_RECOMMENDED) != new_options.get(CONF_RECOMMENDED)
120+
121+
122122
@pytest.mark.parametrize(
123-
("current_options", "new_options", "expected_options"),
123+
("current_options", "new_options", "expected_options", "errors"),
124124
[
125125
(
126126
{
@@ -147,6 +147,7 @@ async def test_form(hass: HomeAssistant) -> None:
147147
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
148148
CONF_USE_GOOGLE_SEARCH_TOOL: RECOMMENDED_USE_GOOGLE_SEARCH_TOOL,
149149
},
150+
None,
150151
),
151152
(
152153
{
@@ -157,6 +158,7 @@ async def test_form(hass: HomeAssistant) -> None:
157158
CONF_TOP_P: RECOMMENDED_TOP_P,
158159
CONF_TOP_K: RECOMMENDED_TOP_K,
159160
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
161+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
160162
},
161163
{
162164
CONF_RECOMMENDED: True,
@@ -168,42 +170,152 @@ async def test_form(hass: HomeAssistant) -> None:
168170
CONF_LLM_HASS_API: "assist",
169171
CONF_PROMPT: "",
170172
},
173+
None,
174+
),
175+
(
176+
{
177+
CONF_RECOMMENDED: False,
178+
CONF_PROMPT: "Speak like a pirate",
179+
CONF_TEMPERATURE: 0.3,
180+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
181+
CONF_TOP_P: RECOMMENDED_TOP_P,
182+
CONF_TOP_K: RECOMMENDED_TOP_K,
183+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
184+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
185+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
186+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
187+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
188+
CONF_USE_GOOGLE_SEARCH_TOOL: RECOMMENDED_USE_GOOGLE_SEARCH_TOOL,
189+
},
190+
{
191+
CONF_RECOMMENDED: False,
192+
CONF_PROMPT: "Speak like a pirate",
193+
CONF_TEMPERATURE: 0.3,
194+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
195+
CONF_TOP_P: RECOMMENDED_TOP_P,
196+
CONF_TOP_K: RECOMMENDED_TOP_K,
197+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
198+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
199+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
200+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
201+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
202+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
203+
},
204+
{
205+
CONF_RECOMMENDED: False,
206+
CONF_PROMPT: "Speak like a pirate",
207+
CONF_TEMPERATURE: 0.3,
208+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
209+
CONF_TOP_P: RECOMMENDED_TOP_P,
210+
CONF_TOP_K: RECOMMENDED_TOP_K,
211+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
212+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
213+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
214+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
215+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
216+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
217+
},
218+
None,
219+
),
220+
(
221+
{
222+
CONF_RECOMMENDED: False,
223+
CONF_PROMPT: "Speak like a pirate",
224+
CONF_TEMPERATURE: 0.3,
225+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
226+
CONF_TOP_P: RECOMMENDED_TOP_P,
227+
CONF_TOP_K: RECOMMENDED_TOP_K,
228+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
229+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
230+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
231+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
232+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
233+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
234+
},
235+
{
236+
CONF_RECOMMENDED: False,
237+
CONF_PROMPT: "Speak like a pirate",
238+
CONF_LLM_HASS_API: "assist",
239+
CONF_TEMPERATURE: 0.3,
240+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
241+
CONF_TOP_P: RECOMMENDED_TOP_P,
242+
CONF_TOP_K: RECOMMENDED_TOP_K,
243+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
244+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
245+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
246+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
247+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
248+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
249+
},
250+
{
251+
CONF_RECOMMENDED: False,
252+
CONF_PROMPT: "Speak like a pirate",
253+
CONF_TEMPERATURE: 0.3,
254+
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
255+
CONF_TOP_P: RECOMMENDED_TOP_P,
256+
CONF_TOP_K: RECOMMENDED_TOP_K,
257+
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
258+
CONF_HARASSMENT_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
259+
CONF_HATE_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
260+
CONF_SEXUAL_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
261+
CONF_DANGEROUS_BLOCK_THRESHOLD: RECOMMENDED_HARM_BLOCK_THRESHOLD,
262+
CONF_USE_GOOGLE_SEARCH_TOOL: True,
263+
},
264+
{CONF_USE_GOOGLE_SEARCH_TOOL: "invalid_google_search_option"},
171265
),
172266
],
173267
)
174268
@pytest.mark.usefixtures("mock_init_component")
175269
async def test_options_switching(
176270
hass: HomeAssistant,
177271
mock_config_entry: MockConfigEntry,
178-
mock_models,
179272
current_options,
180273
new_options,
181274
expected_options,
275+
errors,
182276
) -> None:
183277
"""Test the options form."""
184278
with patch("google.genai.models.AsyncModels.get"):
185279
hass.config_entries.async_update_entry(
186280
mock_config_entry, options=current_options
187281
)
188282
await hass.async_block_till_done()
189-
options_flow = await hass.config_entries.options.async_init(
190-
mock_config_entry.entry_id
191-
)
192-
if current_options.get(CONF_RECOMMENDED) != new_options.get(CONF_RECOMMENDED):
193-
options_flow = await hass.config_entries.options.async_configure(
283+
with patch(
284+
"google.genai.models.AsyncModels.list",
285+
return_value=get_models_pager(),
286+
):
287+
options_flow = await hass.config_entries.options.async_init(
288+
mock_config_entry.entry_id
289+
)
290+
if will_options_be_rendered_again(current_options, new_options):
291+
retry_options = {
292+
**current_options,
293+
CONF_RECOMMENDED: new_options[CONF_RECOMMENDED],
294+
}
295+
with patch(
296+
"google.genai.models.AsyncModels.list",
297+
return_value=get_models_pager(),
298+
):
299+
options_flow = await hass.config_entries.options.async_configure(
300+
options_flow["flow_id"],
301+
retry_options,
302+
)
303+
with patch(
304+
"google.genai.models.AsyncModels.list",
305+
return_value=get_models_pager(),
306+
):
307+
options = await hass.config_entries.options.async_configure(
194308
options_flow["flow_id"],
195-
{
196-
**current_options,
197-
CONF_RECOMMENDED: new_options[CONF_RECOMMENDED],
198-
},
309+
new_options,
199310
)
200-
options = await hass.config_entries.options.async_configure(
201-
options_flow["flow_id"],
202-
new_options,
203-
)
204311
await hass.async_block_till_done()
205-
assert options["type"] is FlowResultType.CREATE_ENTRY
206-
assert options["data"] == expected_options
312+
if errors is None:
313+
assert options["type"] is FlowResultType.CREATE_ENTRY
314+
assert options["data"] == expected_options
315+
316+
else:
317+
assert options["type"] is FlowResultType.FORM
318+
assert options.get("errors", None) == errors
207319

208320

209321
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)