From af2ad692d0aefbe47f9fac9591f9413a32ba8496 Mon Sep 17 00:00:00 2001 From: michiot05 Date: Fri, 24 Jul 2026 23:28:12 +0200 Subject: [PATCH] fix(quick-router): abstain on an outdoor temperature query instead of thermostat status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "what's the temperature outside" is a weather question, not an indoor thermostat reading, but home_status_target matched "temperature" and emitted a garbled home_status{entity:"temperature outside"} — there is no such device. Abstain when a temperature/climate status query is outdoor-qualified ("outside"/"outdoor") so the LLM grounds it as weather, mirroring the branch's existing indoor-only scoping (the attic arm). --- crates/genie-core/src/tools/quick.rs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/genie-core/src/tools/quick.rs b/crates/genie-core/src/tools/quick.rs index d1f2fb61..45e87020 100644 --- a/crates/genie-core/src/tools/quick.rs +++ b/crates/genie-core/src/tools/quick.rs @@ -2590,6 +2590,14 @@ fn home_status_target(text: &str) -> Option { &target, &["thermostat", "thermostats", "temperature", "climate"], ) { + // "temperature outside" / "outdoor temperature" is a weather question, + // not an indoor thermostat reading — there is no "temperature outside" + // device, so emitting home_status{entity:"temperature outside"} is a + // garbled misroute. Abstain so the LLM grounds it as weather. The attic + // arm below already shows this branch is scoped to indoor climate. + if contains_any(&target, &["outside", "outdoor"]) { + return None; + } if target.contains("attic") { return Some("attic temperature".into()); } @@ -5837,6 +5845,35 @@ mod tests { assert_eq!(contracted.arguments["entity"], spelled.arguments["entity"]); } + #[test] + fn outdoor_temperature_query_abstains_instead_of_thermostat_status() { + // "what's the temperature outside" is a weather question, not an indoor + // thermostat reading — there is no "temperature outside" device, so the + // router emitted a garbled home_status{entity:"temperature outside"}. An + // outdoor-qualified temperature/climate query must abstain so the LLM + // grounds it (as weather). + for utterance in [ + "what's the temperature outside", + "what is the temperature outdoors", + "what's the outdoor temperature", + "how's the climate outside", + ] { + assert!( + route(utterance).is_none(), + "{utterance:?} must abstain, not report a thermostat status" + ); + } + + // Indoor thermostat/climate queries still resolve. + let call = route("what's the temperature").unwrap(); + assert_eq!(call.name, "home_status"); + assert_eq!(call.arguments["entity"], "thermostat"); + + let call = route("is the climate control on").unwrap(); + assert_eq!(call.name, "home_status"); + assert_eq!(call.arguments["entity"], "climate control"); + } + #[test] fn status_entity_drops_both_state_word_and_time_qualifier() { // A status query can trail a state word AND a time qualifier. The entity