fix(homeassistant): support return_response for HA response-only services (#27256)#27270
Open
xxxigm wants to merge 3 commits into
Open
fix(homeassistant): support return_response for HA response-only services (#27256)#27270xxxigm wants to merge 3 commits into
xxxigm wants to merge 3 commits into
Conversation
…ices
`todo.get_items`, `calendar.get_events`, `weather.get_forecasts` and
`conversation.process` are registered in Home Assistant with
SupportsResponse.ONLY. Calling them via the REST API without
`?return_response=true` makes HA reject the request with HTTP 400
("Service ... requires response data"), so `ha_call_service` silently
400'd on `todo.get_items` while `add_item` / `remove_item` (non-response
services using the same flat payload) kept working — exactly the
symptom reported in NousResearch#27256.
The fix adds three small layers:
1. A `return_response` parameter on `_async_call_service` /
`ha_call_service` that toggles `?return_response=true`.
2. A static allowlist (`_RESPONSE_ONLY_SERVICES`) of well-known
response-only services that opt in automatically so the model
doesn't have to know the rule.
3. A defensive auto-retry-once when HA's 400 body literally mentions
"return_response" / "requires response" — covers custom
integrations and future HA versions not in the static list.
`_parse_service_response` now also recognises the new response shape
(`{changed_states, service_response}`) and surfaces the `service_response`
payload back to the caller, while keeping the legacy list shape
unchanged for non-response services.
…ch#27256 48 regression tests for the new return_response plumbing: * `_service_url` query-string construction. * `_is_response_required_hint` accept/reject matrix (only 400s with return_response / requires response wording are retried). * `_RESPONSE_ONLY_SERVICES` membership for todo / calendar / weather / conversation, and absence for ordinary services. * `_parse_service_response` handles both legacy list and new `{changed_states, service_response}` shapes, including empty, None and malformed entries. * `_async_call_service` over a fake aiohttp session: no query for regular services, auto-opt-in for known response services, explicit-flag path, auto-retry-once on 400 hint, no retry on unrelated 400s, no double-retry when we already opted in, non-JSON / empty bodies. * Handler-level wiring: bool / string coercion for return_response, default false, AsyncMock-based to avoid leaking unawaited coroutines. * Schema surface: field present, not required, description mentions response services. * End-to-end repro: `todo.get_items` returns parsed items, while `todo.add_item` keeps its original wire compat.
Update the tools reference page so the description matches the new schema text and documents that `todo.get_items`, `calendar.get_events` and `weather.get_forecasts` need `return_response=true` (well-known response services opt in automatically). Issue NousResearch#27256.
This comment was marked as spam.
This comment was marked as spam.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes
homeassistant_tool.ha_call_servicesilently returning HTTP 400 on Home Assistant response services such astodo.get_items,calendar.get_events,weather.get_forecastsandconversation.process.HA registers these with
SupportsResponse.ONLY, meaning a REST call without?return_response=trueis rejected with400 — "Service ... requires response data". The previous implementation never appended that query parameter, so every response service 400'd while non-response services on the same domain (e.g.todo.add_item,todo.remove_item) kept working — exactly the symptom in the bug report.The fix is defense-in-depth so the model rarely has to think about the rule:
return_responseparameter on_async_call_service/ha_call_servicethat toggles?return_response=true._RESPONSE_ONLY_SERVICESfor the well-known response services (auto-opt-in).return_response/requires response— covers user HA versions and custom integrations not on the static list. Unrelated 400s (auth, unknown service, invalid entity_id) are not retried._parse_service_responserecognises the new shape{changed_states, service_response}and surfacesservice_responseto the caller; the legacy list shape (all non-response services) is unchanged.Related Issue
Closes #27256.
Type of Change
Changes Made
tools/homeassistant_tool.py— newreturn_responseparameter on_async_call_serviceand theha_call_serviceschema; new helpers_service_url,_is_response_required_hint; new constant_RESPONSE_ONLY_SERVICES; auto-retry-once logic in_async_call_service; response-shape-aware_parse_service_response; bool/string coercion forreturn_responsein the handler; updatedHA_CALL_SERVICE_SCHEMAdescription + newreturn_responsefield with examples.tests/tools/test_homeassistant_response_services.py— 48 new regression tests:TestServiceURL— 2 cases pinning query-string construction.TestIsResponseRequiredHint— 10 cases (4 positive wordings + 6 negatives for unrelated 400s / non-400s / empty body / success).TestResponseOnlyAllowlist— 5 cases (4 known services + regular-services-not-listed).TestParseServiceResponseNewShape— 6 cases (new shape, legacy list, empty dict, None, malformed entries skipped).TestAsyncCallServiceQueryString— 8 cases over a scripted aiohttp double (no query for regular service, auto-opt-in for known, explicit flag, auto-retry on 400 hint, no retry on unrelated 400s, no double-retry when already opted in, non-JSON / empty bodies).TestHandlerReturnResponseWiring— 12 cases (bool true/false, string coercion across 9 values, default false). UsesAsyncMockto avoid leaking unawaited coroutines.TestSchemaSurface— 3 cases (field present, not required, description mentions response services +todo.get_items).TestIssue27256Repro— 2 end-to-end cases:todo.get_itemsreturns parsed items,todo.add_itemkeeps original wire format.website/docs/reference/tools-reference.md—ha_call_servicedescription aligned with the new schema text.How to Test
.venvis set up:python3 -m venv .venv && source .venv/bin/activate && pip install -e ".[all,dev]"HASS_TOKEN/HASS_URL):ha_call_servicewithdomain=todo, service=get_items, entity_id=todo.<your_list>should now return the items inresult.service_response.ha_call_servicewithdomain=todo, service=add_item, entity_id=todo.<your_list>, data='{"item":"buy milk"}'should keep working (no behavioural change for non-response services).Checklist
Code
fix(homeassistant): ...,test(homeassistant): ...,docs(homeassistant): ...)scripts/run_tests.sh tests/tools/test_homeassistant_response_services.py tests/tools/test_homeassistant_tool.pyand all tests passDocumentation & Housekeeping
website/docs/reference/tools-reference.md—ha_call_servicedescription now mentions response services andreturn_response)cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AHA_CALL_SERVICE_SCHEMAdescription and the newreturn_responsefield cover the new behaviourScreenshots / Logs