diff --git a/custom_components/powerllm/api.py b/custom_components/powerllm/api.py index ccb74a5..5d54431 100644 --- a/custom_components/powerllm/api.py +++ b/custom_components/powerllm/api.py @@ -33,7 +33,7 @@ DOMAIN, ) from .llm_tools import PowerIntentTool, PowerLLMTool, PowerScriptTool -from .tools.duckduckgo import DDGMapsSearchTool, DDGNewsTool, DDGTextSearchTool +from .tools.duckduckgo import DDGNewsTool, DDGTextSearchTool from .tools.memory import MemoryTool from .tools.script import DynamicScriptTool @@ -241,7 +241,6 @@ def _async_get_tools( DDGTextSearchTool(self.config_entry.options[CONF_DUCKDUCKGO_REGION]) ) tools.append(DDGNewsTool(self.config_entry.options[CONF_DUCKDUCKGO_REGION])) - tools.append(DDGMapsSearchTool()) tools.append(MemoryTool(self.config_entry)) tools.extend(self.hass.data.get(DOMAIN, {}).values()) diff --git a/custom_components/powerllm/manifest.json b/custom_components/powerllm/manifest.json index 9961a15..8f599b3 100644 --- a/custom_components/powerllm/manifest.json +++ b/custom_components/powerllm/manifest.json @@ -8,6 +8,6 @@ "integration_type": "service", "iot_class": "local_push", "issue_tracker": "https://github.com/Shulyaka/powerllm/issues", - "requirements": ["duckduckgo-search==6.2.9", "RestrictedPython>=7.4", "trafilatura==2.0.0"], + "requirements": ["duckduckgo-search==7.2.0", "RestrictedPython>=7.4", "trafilatura==2.0.0"], "version": "0.0.1" } diff --git a/custom_components/powerllm/tools/duckduckgo.py b/custom_components/powerllm/tools/duckduckgo.py index 2bfd85f..c5219e1 100644 --- a/custom_components/powerllm/tools/duckduckgo.py +++ b/custom_components/powerllm/tools/duckduckgo.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from duckduckgo_search import AsyncDDGS +from duckduckgo_search import DDGS from duckduckgo_search.exceptions import DuckDuckGoSearchException from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -21,7 +21,7 @@ class DDGBaseTool(PowerLLMTool): def __init__(self, region: str = "wt-wt"): """Initialize the tool.""" - self._ddg = AsyncDDGS() + self._ddg = DDGS() self._region = region @@ -44,10 +44,13 @@ async def async_call( ) -> JsonObjectType: """Execute text search.""" try: - return await self._ddg.atext( - tool_input.tool_args["query"], - region=self._region, - max_results=tool_input.tool_args.get("max_results"), + return await hass.loop.run_in_executor( + None, + lambda: self._ddg.text( + tool_input.tool_args["query"], + region=self._region, + max_results=tool_input.tool_args.get("max_results"), + ), ) or {"error": "No results returned"} except DuckDuckGoSearchException as e: raise HomeAssistantError(str(e)) from e @@ -72,73 +75,13 @@ async def async_call( ) -> JsonObjectType: """Execute news search.""" try: - return await self._ddg.anews( - tool_input.tool_args["keywords"], - region=self._region, - max_results=tool_input.tool_args.get("max_results"), + return await hass.loop.run_in_executor( + None, + lambda: self._ddg.news( + tool_input.tool_args["keywords"], + region=self._region, + max_results=tool_input.tool_args.get("max_results"), + ), ) or {"error": "No results returned"} except DuckDuckGoSearchException as e: raise HomeAssistantError(str(e)) from e - - -class DDGMapsSearchTool(DDGBaseTool): - """DuckDuckGo maps search tool.""" - - name = "maps_search" - description = "Search for places on the map" - parameters = vol.Schema( - { - vol.Required("keywords", description="keywords for the search"): cv.string, - vol.Optional( - "place", description="if set, the other parameters are not used" - ): cv.string, - vol.Optional("street", description="house number/street"): cv.string, - vol.Optional("city", description="city of search"): cv.string, - vol.Optional("county", description="county of search"): cv.string, - vol.Optional("state", description="state of search"): cv.string, - vol.Optional("country", description="country of search"): cv.string, - vol.Optional("postalcode", description="postalcode of search"): cv.string, - vol.Optional( - "latitude", description="geographic coordinate (north-south position)" - ): cv.string, - vol.Optional( - "longitude", - description="geographic coordinate (east-west position); if latitude " - "and longitude are set, the other parameters are not used", - ): cv.string, - vol.Optional( - "radius", - description="expand the search square by the distance in kilometers", - ): vol.Coerce(int), - vol.Optional( - "max_results", default=5, description="Number of results requested" - ): vol.Coerce(int), - } - ) - - async def async_call( - self, hass: HomeAssistant, tool_input: ToolInput, llm_context: LLMContext - ) -> JsonObjectType: - """Execute news search.""" - kwargs = tool_input.tool_args.copy() - if all( - kwargs.get(x) is None - for x in [ - "place", - "street", - "city", - "county", - "state", - "country", - "postalcode", - "latitude", - "longitude", - ] - ): - kwargs["latitude"] = str(hass.config.latitude) - kwargs["longitude"] = str(hass.config.longitude) - - try: - return await self._ddg.amaps(**kwargs) or {"error": "No results returned"} - except DuckDuckGoSearchException as e: - raise HomeAssistantError(str(e)) from e diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f64b24b..10b8b9e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -19,6 +19,6 @@ hassil mutagen ha-ffmpeg pymicro-vad -duckduckgo-search +duckduckgo-search==7.2.0 RestrictedPython trafilatura diff --git a/tests/const.py b/tests/const.py index f406c69..cbfc44e 100644 --- a/tests/const.py +++ b/tests/const.py @@ -29,7 +29,6 @@ "HassTurnOff": True, "HassTurnOn": True, "homeassistant_script": True, - "maps_search": True, "memory": True, "news": True, "websearch": True, diff --git a/tests/test_api.py b/tests/test_api.py index 12c48c5..079545e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -21,6 +21,33 @@ from custom_components.powerllm.const import CONF_PROMPT_ENTITIES +INTENT_TOOLS = [ + "HassTurnOn", + "HassTurnOff", + "HassGetState", + "HassSetPosition", +] + +TIMER_TOOLS = [ + "HassStartTimer", + "HassCancelTimer", + "HassCancelAllTimers", + "HassIncreaseTimer", + "HassDecreaseTimer", + "HassPauseTimer", + "HassUnpauseTimer", + "HassTimerStatus", +] + +POWERLLM_TOOLS = [ + "homeassistant_script", + "websearch", + "news", + "memory", + "python_code_execute", + "web_scrape", +] + def test_test(hass): """Workaround for https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/discussions/160.""" @@ -70,19 +97,19 @@ class MyIntentHandler(intent.IntentHandler): assert len(llm.async_get_apis(hass)) == 2 api = await llm.async_get_api(hass, "powerllm", llm_context) - assert len(api.tools) == 11 + assert len(api.tools) == len(INTENT_TOOLS) + len(POWERLLM_TOOLS) # Match all intent_handler.platforms = None api = await llm.async_get_api(hass, "powerllm", llm_context) - assert len(api.tools) == 12 + assert len(api.tools) == len(INTENT_TOOLS) + len(POWERLLM_TOOLS) + 1 # Match specific domain intent_handler.platforms = {"light"} api = await llm.async_get_api(hass, "powerllm", llm_context) - assert len(api.tools) == 12 + assert len(api.tools) == len(INTENT_TOOLS) + len(POWERLLM_TOOLS) + 1 tool = api.tools[4] assert tool.name == "test_intent" assert tool.description == "Execute Home Assistant test_intent intent" @@ -278,26 +305,10 @@ class MyIntentHandler(intent.IntentHandler): api = await llm.async_get_api(hass, "powerllm", llm_context) assert [tool.name for tool in api.tools] == [ - "HassTurnOn", - "HassTurnOff", - "HassGetState", - "HassSetPosition", - "HassStartTimer", - "HassCancelTimer", - "HassCancelAllTimers", - "HassIncreaseTimer", - "HassDecreaseTimer", - "HassPauseTimer", - "HassUnpauseTimer", - "HassTimerStatus", + *INTENT_TOOLS, + *TIMER_TOOLS, "Super_crazy_intent_with_unique_name", - "homeassistant_script", - "websearch", - "news", - "maps_search", - "memory", - "python_code_execute", - "web_scrape", + *POWERLLM_TOOLS, ] @@ -314,8 +325,8 @@ class MyIntentHandler(intent.IntentHandler): assert len(llm.async_get_apis(hass)) == 2 api = await llm.async_get_api(hass, "powerllm", llm_context) - assert len(api.tools) == 13 - tool = api.tools[5] + assert len(api.tools) == len(INTENT_TOOLS) + len(POWERLLM_TOOLS) + 2 + tool = api.tools[len(INTENT_TOOLS) + 1] assert tool.name == "test_intent" assert tool.description == "my intent handler"