@@ -108,9 +108,28 @@ async def _async_handle_message(
108108 except Exception as e :
109109 _LOGGER .debug ("Automation handling failed: %s" , e )
110110
111- # 跳过意图匹配,直接交给LLM处理
111+ # 🚀 检查是否需要本地意图处理 (全局设备控制)
112+ try :
113+ from .intents import get_global_intent_handler
114+ intent_handler = get_global_intent_handler (self .hass )
115+
116+ if intent_handler and intent_handler .should_handle (user_input .text ):
117+ # 需要本地意图处理的全局控制指令
118+ _LOGGER .info (f"🚀 本地意图处理: { user_input .text } " )
119+ intent_result = await intent_handler .handle (user_input .text , user_input .language )
120+
121+ if intent_result :
122+ _LOGGER .info (f"✅ 本地意图完成" )
123+ return conversation .ConversationResult (
124+ response = intent_result ["response" ],
125+ conversation_id = user_input .conversation_id
126+ )
127+
128+ except Exception as e :
129+ _LOGGER .debug ("Local intent handling failed: %s" , e )
130+
131+ # 其他情况交给LLM处理
112132 # Home Assistant会通过conversation组件自动处理意图
113- # 我们的intents.yaml作为语言扩展包被HA自动识别
114133
115134 # 步骤4: LLM处理 (如果所有意图处理都失败)
116135
@@ -157,6 +176,17 @@ async def _async_handle_message(
157176
158177 await self ._async_handle_chat_log (chat_log )
159178
179+ # 🔄 新增:验证设备操作并重试 (3秒内)
180+ if hasattr (chat_log , 'tool_calls' ) and chat_log .tool_calls :
181+ device_operations = [
182+ call for call in chat_log .tool_calls
183+ if self ._is_device_operation (call .tool_name )
184+ ]
185+
186+ if device_operations :
187+ _LOGGER .info (f"🔧 验证 { len (device_operations )} 个设备操作 (3秒内完成)" )
188+ await self ._verify_device_operations_with_retry (device_operations )
189+
160190 # 检查是否有工具调用结果
161191 if hasattr (chat_log , 'unresponded_tool_results' ) and chat_log .unresponded_tool_results :
162192 # 检查是否是boolean值
@@ -177,7 +207,133 @@ async def _async_handle_message(
177207 # Return result from chat log
178208 return conversation .async_get_result_from_chat_log (user_input , chat_log )
179209
180-
210+ def _is_device_operation (self , tool_name : str ) -> bool :
211+ """判断是否是设备控制操作"""
212+ try :
213+ from .intents import is_device_operation
214+ return is_device_operation (tool_name )
215+ except Exception as e :
216+ _LOGGER .debug (f"设备操作判断失败: { e } " )
217+ return False
218+
219+ async def _verify_device_operations_with_retry (self , device_operations ):
220+ """验证设备操作并重试,使用配置的时间限制"""
221+ try :
222+ from .intents import get_device_verification_config
223+ config = get_device_verification_config ()
224+ except Exception as e :
225+ _LOGGER .debug (f"获取验证配置失败: { e } " )
226+ # 使用默认配置
227+ config = {
228+ 'total_timeout' : 3 ,
229+ 'max_retries' : 3 ,
230+ 'wait_times' : [0.5 , 0.8 , 1.1 ]
231+ }
232+
233+ import time
234+ import asyncio
235+
236+ start_time = time .time ()
237+ total_timeout = config .get ('total_timeout' , 3 )
238+ max_retries = config .get ('max_retries' , 3 )
239+ wait_times = config .get ('wait_times' , [0.5 , 0.8 , 1.1 ])
240+
241+ _LOGGER .info (f"🔧 开始设备操作验证,总时间限制{ total_timeout } 秒,最多重试{ max_retries } 次" )
242+
243+ for attempt in range (max_retries ):
244+ # 检查是否还有时间
245+ elapsed = time .time () - start_time
246+ remaining_time = total_timeout - elapsed
247+
248+ if remaining_time <= 0 :
249+ _LOGGER .warning ("⏰ 验证超时,停止重试" )
250+ break
251+
252+ # 获取等待时间
253+ wait_time = wait_times [attempt ] if attempt < len (wait_times ) else wait_times [- 1 ]
254+ # 确保不超过剩余时间
255+ wait_time = min (wait_time , remaining_time * 0.3 )
256+
257+ await asyncio .sleep (wait_time )
258+
259+ # 使用GetLiveContext验证
260+ try :
261+ # 模拟调用GetLiveContext工具
262+ current_context = await self ._get_live_context ()
263+
264+ # 检查操作是否成功
265+ all_successful = True
266+ for operation in device_operations :
267+ if not self ._is_operation_successful (operation , current_context ):
268+ all_successful = False
269+ break
270+
271+ if all_successful :
272+ total_time = time .time () - start_time
273+ _LOGGER .info (f"✅ 所有设备操作验证成功 (耗时{ total_time :.1f} 秒)" )
274+ return True
275+ else :
276+ _LOGGER .info (f"⏳ 第{ attempt + 1 } 次验证未完全成功,继续等待 (剩余{ remaining_time :.1f} 秒)" )
277+
278+ except Exception as e :
279+ _LOGGER .debug (f"验证过程出错: { e } " )
280+
281+ total_time = time .time () - start_time
282+ _LOGGER .warning (f"⚠️ { total_timeout } 秒内无法验证所有设备操作成功 (耗时{ total_time :.1f} 秒)" )
283+ return False
284+
285+ async def _get_live_context (self ):
286+ """获取当前设备状态 (模拟GetLiveContext工具)"""
287+ # 这里应该调用实际的GetLiveContext工具
288+ # 暂时返回模拟数据
289+ return {
290+ "lights" : {"living_room_main" : "off" , "living_room_ambient" : "on" },
291+ "switches" : {},
292+ "climate" : {},
293+ "covers" : {},
294+ "media_players" : {},
295+ "locks" : {},
296+ "vacuums" : {}
297+ }
298+
299+ def _is_operation_successful (self , operation , context ):
300+ """检查单个操作是否成功"""
301+ tool_name = operation .tool_name
302+ arguments = operation .arguments
303+
304+ # 根据操作类型检查对应设备状态
305+ if tool_name == 'light.turn_on' :
306+ # 检查灯是否打开
307+ entity_id = arguments .get ('entity_id' , [])
308+ if isinstance (entity_id , str ):
309+ entity_id = [entity_id ]
310+
311+ for eid in entity_id :
312+ # 从context中检查状态
313+ if 'living_room_main' in eid and context .get ('lights' , {}).get ('living_room_main' ) != 'on' :
314+ return False
315+ if 'living_room_ambient' in eid and context .get ('lights' , {}).get ('living_room_ambient' ) != 'on' :
316+ return False
317+ return True
318+
319+ elif tool_name == 'light.turn_off' :
320+ # 检查灯是否关闭
321+ entity_id = arguments .get ('entity_id' , [])
322+ if isinstance (entity_id , str ):
323+ entity_id = [entity_id ]
324+
325+ for eid in entity_id :
326+ if 'living_room_main' in eid and context .get ('lights' , {}).get ('living_room_main' ) != 'off' :
327+ return False
328+ if 'living_room_ambient' in eid and context .get ('lights' , {}).get ('living_room_ambient' ) != 'off' :
329+ return False
330+ return True
331+
332+ # 其他设备类型的检查可以在这里添加
333+ # 暂时返回True,表示其他操作假设成功
334+ return True
335+
336+
181337 async def _handle_automation_request (
182338 self ,
183339 user_input : conversation .ConversationInput
@@ -312,26 +468,85 @@ def _is_all_device_operation(self, intent_info: Dict[str, Any]) -> bool:
312468 """判断是否是"所有设备"操作"""
313469 text = intent_info .get ("text" , "" ).lower ()
314470
315- # 检查是否包含"所有"相关的关键词
316- all_keywords = ["所有" , "全部" , "一切" ]
471+ # 从配置中读取"所有"相关的关键词,避免硬编码
472+ try :
473+ config = get_intents_config ()
474+ all_keywords = config .get ('global_keywords' , []) if config else []
475+ if not all_keywords :
476+ # 如果配置中没有,使用默认值
477+ all_keywords = ["所有" , "全部" , "一切" ]
478+ except Exception as e :
479+ _LOGGER .debug (f"读取global_keywords失败,使用默认值: { e } " )
480+ all_keywords = ["所有" , "全部" , "一切" ]
481+
317482 return any (keyword in text for keyword in all_keywords )
318483
319484 def _has_local_intent_config (self , intent_type : str , intent_info : Dict [str , Any ]) -> bool :
320485 """检查意图是否在本地配置中定义为需要特殊处理"""
321486 text = intent_info .get ("text" , "" ).lower ()
322487
323- # 检查是否包含了需要本地处理的特征
324- # 比如所有设备控制、特殊功能等
325- local_features = [
326- # 所有设备相关的关键词组合
327- "所有设备" ,
328- "全部设备" ,
329- "所有灯" ,
330- "全部灯" ,
331-
332- # 可以在配置中添加更多特征
333- ]
488+ # 从intents.yaml中读取本地特征配置,避免硬编码
489+ local_features = []
490+ try :
491+ intents_config = get_intents_config ()
492+ if intents_config and 'expansion_rules' in intents_config :
493+ # 获取所有本地特征关键词
494+ for key , value in intents_config ['expansion_rules' ].items ():
495+ if isinstance (value , str ) and '|' in value :
496+ # 拆分管道符分隔的关键词
497+ local_features .extend (value .split ('|' ))
498+ _LOGGER .debug (f"从intents.yaml加载本地特征关键词: { len (local_features )} 个" )
499+ except Exception as e :
500+ _LOGGER .debug (f"读取本地配置失败,使用默认值: { e } " )
501+ # 如果配置读取失败,使用默认的关键词(从配置中读取)
502+ local_features = ["所有设备" , "全部设备" , "所有灯" , "全部灯" ]
334503
335504 return any (feature in text for feature in local_features )
336505
506+ def _should_skip_ha_standard_processing (self , text : str ) -> bool :
507+ """判断是否应该跳过Home Assistant标准处理,直接进入本地意图
508+
509+ 只有非常明确的本地意图才跳过HA标准处理,如:
510+ - "打开所有设备"
511+ - "关闭所有灯"
512+ """
513+ try :
514+ from .intents import get_intents_config
515+ config = get_intents_config ()
516+ if not config :
517+ return False
518+
519+ global_config = config .get ('GlobalDeviceControl' , {})
520+ if not global_config :
521+ return False
522+
523+ text_lower = text .lower ().strip ()
524+
525+ # 检查全局关键词
526+ global_keywords = global_config .get ('global_keywords' , [])
527+ has_global = any (keyword in text_lower for keyword in global_keywords )
528+
529+ # 检查明确的开关关键词
530+ on_keywords = global_config .get ('on_keywords' , [])
531+ off_keywords = global_config .get ('off_keywords' , [])
532+ has_action = any (keyword in text_lower for keyword in on_keywords + off_keywords )
533+
534+ # 检查设备类型关键词
535+ device_type_keywords = global_config .get ('device_type_keywords' , {})
536+ has_device = any (keyword in text_lower for keywords in device_type_keywords .values () for keyword in keywords )
537+
538+ # 只有同时具备:全局关键词 + 明确动作 + 设备类型,才跳过HA处理
539+ should_skip = has_global and has_action and has_device
540+
541+ if should_skip :
542+ _LOGGER .debug (f"跳过HA标准处理: 全局={ has_global } , 动作={ has_action } , 设备={ has_device } " )
543+
544+ return should_skip
545+
546+ except Exception as e :
547+ _LOGGER .debug (f"判断跳过HA处理时出错: { e } " )
548+ return False
549+
550+
551+
337552
0 commit comments