Skip to content

Commit 2565134

Browse files
Patch76crs2007
andauthored
feat: Add process action to ha_manage_pipeline (#2215)
* feat: Add process action to ha_manage_pipeline Runs a sentence through Home Assistant's Assist conversation layer via POST /api/conversation/process, so a matched intent executes rather than being described. An optional pipeline_id resolves the conversation agent and the language that pipeline is configured with. When its conversation_language is "*", the STT, TTS and pipeline languages are tried in that order, mirroring how core itself resolves that case for intent recognition. Adds "sentence" to the usage logger's mask set: the tool decorator records kwargs verbatim into mcp_usage.jsonl, which ha_report_issue attaches to bug reports, and an utterance carries whatever the user said. Supersedes #2108, whose separate ha_intent_process tool the review asked to fold into the existing Assist tool. The result keys and the speech extraction follow that PR. Co-authored-by: Sharon Rimer <crs2007@gmail.com> * fix: reject malformed Assist conversation responses An empty or undecodable 2xx body reaches the tool as {} (the REST client normalizes a JSON decode failure that way), and the process action turned that into success=True with a null response_type and no speech. It now raises a structured SERVICE_CALL_FAILED instead. The parametrized unit test covers the three shapes that reach the guard. Three further review findings, all on the same action: - openWorldHint is now True. action='process' returns whatever the conversation agent says, and that agent can be a cloud LLM, so the tool carries externally-authored content back to the client. - The docstring claimed the sentence takes the same path as a voice command, which its own closing sentence then contradicted. It goes straight to the conversation agent. - The e2e process test skipped itself whenever the call failed. The conversation integration ships with default_config, which the test container loads, so a failure there is a regression and now fails. * test: assert the Assist process call with the success helper The unmatched-sentence test expects the call to succeed, and tests/AGENTS.md reserves safe_call_tool for expected failures and availability probes. It now goes through MCPAssertions.call_tool_success, which fails the test on a ToolError instead of handing back an error dict the assertions would have to re-check. * fix: report partial Assist intent failures, reject malformed responses action='process' left failed targets nested inside service_calls, so a partial intent failure came back with the intent's ordinary speech and nothing at the top level pointing at it. HA raises only when no target succeeded (helpers/intent.py, DynamicServiceIntentHandler.async_handle_states), so this is reachable, not theoretical. Populate the top-level warnings list, and warn on failed_to_handle, which is HA reporting an intent it matched and could not run. success stays True either way: Assist answering is not a tool failure. A non-mapping data was silently replaced with {}, one line below a guard that raises for the same class of malformation. IntentResponse.as_dict always writes both response_type and data, so a body missing either is malformed rather than a variant; raise for all of them. An empty conversation_language was returned as "" instead of falling through to the STT/TTS/language chain. HA's async_converse substitutes its default for a missing language but not for an empty string, so "" reached the conversation agent verbatim. The docstring claimed sentence triggers cannot fire. They do when the built-in agent answers: DefaultAgent._async_handle_message checks them before it matches intents. They do not fire for any other agent, since the direct endpoint call skips the pipeline step that applies triggers to non-default agents. Say both halves. Tests pin the failed and targets keys to values distinct from the extraction's defaults, the "*" fallback ladder rung by rung including STT-over-TTS precedence, every guard in the speech extraction, the sentence staying out of the second raise site's payload, and a bare process call not fetching a pipeline. Two E2E cases cover the pipeline-resolved agent id against a real instance and a matched intent flipping a throwaway input_boolean. * refactor: move Assist pipeline tools into their own module tools_voice_assistant.py had grown to 1078 lines carrying two unrelated concerns. ha_manage_pipeline and its helpers move verbatim into tools_assist_pipeline.py as AssistPipelineTools; entity exposure keeps the old module, so KNOWN_ASSISTANTS and every patch target in the exposure tests stay where they are. The registry discovers the new module by convention, so nothing is wired by hand. Moving the pipeline half rather than the exposure half is what keeps this small: it touches four files instead of the six the other direction needs. * test: remove the E2E helper by hand and retry the first match The cleanup_tracker fixture only logs what it would delete, so the other suites that create a helper remove it explicitly; this one now does the same, in a finally block so a failed assertion does not leak the entity. A newly exposed entity reaches the conversation agent asynchronously, so the first sentence can legitimately come back unmatched. Retry within a budget rather than race it — a real regression still fails once the budget is spent. * test: probe the helper with safe_call_tool and widen the cleanup The creation probe skipped on a falsy `success`, but `parse_mcp_result` catches nothing, so a `ha_config_set_helper` that raises `ToolError` errored the test instead of skipping it. `safe_call_tool` catches it and returns the dict the check already expects, which is also what `tests/CLAUDE.md` prescribes for an availability probe. The readiness assertion sat outside the `try`, so a helper that never became available leaked for the rest of the session: `cleanup_tracker` only logs its entries, and the hand-written removal lives in the `finally`. The `try` now opens right after `entity_id` resolves. The exposure call expects success rather than probing, so it goes through `MCPAssertions.call_tool_success` like the process call below it. --------- Co-authored-by: Sharon Rimer <crs2007@gmail.com>
1 parent 64fb173 commit 2565134

7 files changed

Lines changed: 1460 additions & 548 deletions

File tree

0 commit comments

Comments
 (0)