Merge pull request #237 from acon96/release/v0.3.7

Release v0.3.7
This commit is contained in:
Alex O'Connell
2024-12-15 19:10:13 +00:00
committed by GitHub
11 changed files with 92 additions and 35 deletions

View File

@@ -20,33 +20,33 @@ jobs:
matrix:
include:
# ARM variants
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
arch: "aarch64"
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
arch: "armhf"
# Base x86
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
suffix: "-noavx"
arch: "amd64"
extra_defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF"
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3.1"
arch: "i386"
suffix: "-noavx"
extra_defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF"
# AVX2 and AVX512
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
arch: "amd64"
extra_defines: "-DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON"
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3.1"
arch: "amd64"
suffix: "-avx512"
extra_defines: "-DGGML_AVX512=ON -DGGML_FMA=ON -DGGML_F16C=ON"
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
arch: "i386"
extra_defines: "-DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON"
- home_assistant_version: "2024.2.1"
- home_assistant_version: "2024.12.3"
arch: "i386"
suffix: "-avx512"
extra_defines: "-DGGML_AVX512=ON -DGGML_FMA=ON -DGGML_F16C=ON"

View File

@@ -5,7 +5,7 @@ This project provides the required "glue" components to control your Home Assist
Please see the [Setup Guide](./docs/Setup.md) for more information on installation.
## Local LLM Conversation Integration
**The latest version of this integration requires Home Assistant 2024.8.0 or newer**
**The latest version of this integration requires Home Assistant 2024.12.3 or newer**
In order to integrate with Home Assistant, we provide a custom component that exposes the locally running LLM as a "conversation agent".
@@ -150,6 +150,7 @@ In order to facilitate running the project entirely on the system where Home Ass
## Version History
| Version | Description |
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| v0.3.7 | Update llama.cpp version to support newer models, Update minimum Home Assistant version to 2024.12.3, Add German In-Context Learning examples, Fix multi-turn use, Fix an issue with webcolors |
| v0.3.6 | Small llama.cpp backend fixes |
| v0.3.5 | Fix for llama.cpp backend installation, Fix for Home LLM v1-3 API parameters, add Polish ICL examples |
| v0.3.4 | Significantly improved language support including full Polish translation, Update bundled llama-cpp-python to support new models, various bug fixes |

View File

@@ -37,7 +37,7 @@ from homeassistant.helpers.selector import (
from homeassistant.util.package import is_installed
from importlib.metadata import version
from .utils import download_model_from_hf, install_llama_cpp_python, format_url, MissingQuantizationException
from .utils import download_model_from_hf, get_llama_cpp_python_version, install_llama_cpp_python, format_url, MissingQuantizationException
from .const import (
CONF_CHAT_MODEL,
CONF_MAX_TOKENS,
@@ -352,7 +352,9 @@ class ConfigFlow(BaseLlamaConversationConfigFlow, config_entries.ConfigFlow, dom
local_backend = is_local_backend(user_input[CONF_BACKEND_TYPE])
self.model_config.update(user_input)
if local_backend:
if is_installed("llama-cpp-python") and version("llama-cpp-python") == EMBEDDED_LLAMA_CPP_PYTHON_VERSION:
installed_version = await self.hass.async_add_executor_job(get_llama_cpp_python_version)
_LOGGER.debug(f"installed version: {installed_version}")
if installed_version == EMBEDDED_LLAMA_CPP_PYTHON_VERSION:
return await self.async_step_local_model()
else:
return await self.async_step_install_local_wheels()

View File

@@ -383,5 +383,5 @@ OPTIONS_OVERRIDES = {
},
}
INTEGRATION_VERSION = "0.3.6"
EMBEDDED_LLAMA_CPP_PYTHON_VERSION = "0.2.88"
INTEGRATION_VERSION = "0.3.7"
EMBEDDED_LLAMA_CPP_PYTHON_VERSION = "0.3.5"

View File

@@ -421,8 +421,10 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
response=intent_response, conversation_id=conversation_id
)
tool_response = None
# parse response
to_say = service_call_pattern.sub("", response.strip())
tool_response = None
for block in service_call_pattern.findall(response.strip()):
parsed_tool_call: dict = json.loads(block)
@@ -505,8 +507,11 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
)
# handle models that generate a function call and wait for the result before providing a response
if self.entry.options.get(CONF_TOOL_MULTI_TURN_CHAT, DEFAULT_TOOL_MULTI_TURN_CHAT):
conversation.append({"role": "tool", "message": json.dumps(tool_response)})
if self.entry.options.get(CONF_TOOL_MULTI_TURN_CHAT, DEFAULT_TOOL_MULTI_TURN_CHAT) and tool_response is not None:
try:
conversation.append({"role": "tool", "message": json.dumps(tool_response)})
except:
conversation.append({"role": "tool", "message": "No tools were used in this response."})
# generate a response based on the tool result
try:
@@ -527,6 +532,7 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
)
conversation.append({"role": "assistant", "message": response})
conversation.append({"role": "assistant", "message": to_say})
# generate intent response to Home Assistant
intent_response = intent.IntentResponse(language=user_input.language)
@@ -749,12 +755,14 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
value = attributes[attribute_name]
if value is not None:
if attribute_name == "temperature":
value = int(value)
if value > 50:
value = f"{value}F"
else:
value = f"{value}C"
# try to apply unit if present
unit_suffix = attributes.get(f"{attribute_name}_unit")
if unit_suffix:
value = f"{value} {unit_suffix}"
elif attribute_name == "temperature":
# try to get unit or guess otherwise
suffix = "F" if value > 50 else "C"
value = F"{int(value)} {suffix}"
elif attribute_name == "rgb_color":
value = F"{closest_color(value)} {value}"
elif attribute_name == "volume_level":

View File

@@ -0,0 +1,24 @@
type,request,tool,response
fan,Schalte den <name> ein,HassTurnOn,Ich habe den Ventilator eingeschaltet.
fan,Schalte die Ventilatoren in der <area> ein.,HassTurnOn,Ich habe den Ventilator in der <area> eingeschaltet.
fan,Schalte den <name> aus,HassTurnOff,Ich habe den Ventilator ausgeschaltet.
fan,Schalte den Ventilator <name> um,HassToggle,Ich habe den Ventilator umgeschaltet.
light,Schalte das <name> Licht an,HassTurnOn,Ich habe das Licht eingeschaltet.
light,Schalte das <name> Licht aus,HassTurnOff,Ich habe das Licht ausgeschaltet.
light,Schalte das Licht in <area> aus,HassTurnOff,Ich habe das Licht in <area> ausgeschaltet.
light,Schalte das Licht in <area> um,HassToggle,Ich habe das Licht in <area> umgeschaltet.
light,Schalte die Helligkeit von <name> auf <brightness>,HassLightSet,Ich habe die Helligkeit umgeschaltet.
light,Schalte die Lichter in <area> auf <color>,HassLightSet,Ich habe die Farbe jetzt geändert.
media_player,Schalte den <name> ein,HassTurnOn,Ich schalte den Media-Player für dich ein.
media_player,<name> sollte ausgeschaltet werden,HassTurnOff,Ich habe den Media-Player ausgeschaltet.
media_player,Drücke den Play-Knopf am <name>,HassMediaUnpause,Starte die Medienwiedergabe.
media_player,Pausiere den <name>,HassMediaPause,Pausiere die Medienwiedergabe.
media_player,Spiele das nächste auf <name>,HassMediaNext,Überspringe zum nächsten Track.
switch,Schalte den <name> ein,HassTurnOn,Ich habe den Schalter eingeschaltet.
switch,Schalte den <name> aus,HassTurnOff,Ich habe den Schalter ausgeschaltet.
switch,Schalte die Schalter in der <area> aus,HassTurnOff,Ich habe die Geräte wie gewünscht ausgeschaltet.
switch,Schalte den Schalter <name> um,HassToggle,Ich habe den Schalter umgeschaltet.
vacuum,Starte den Staubsauger namens <name>,HassVacuumStart,Ich habe den Staubsauger gestartet.
vacuum,Stoppe den Staubsauger <name>,HassVacuumReturnToBase,Ich habe den Staubsauger zurück zur Basis geschickt.
blinds, Schliesse den Rolladen <name>, HassSetPosition, Ich habe den Rolladen geschlossen.
blinds, Öffne den Rolladen <name>, HassSetPosition, Ich habe den Rolladen geöffnet.
1 type request tool response
2 fan Schalte den <name> ein HassTurnOn Ich habe den Ventilator eingeschaltet.
3 fan Schalte die Ventilatoren in der <area> ein. HassTurnOn Ich habe den Ventilator in der <area> eingeschaltet.
4 fan Schalte den <name> aus HassTurnOff Ich habe den Ventilator ausgeschaltet.
5 fan Schalte den Ventilator <name> um HassToggle Ich habe den Ventilator umgeschaltet.
6 light Schalte das <name> Licht an HassTurnOn Ich habe das Licht eingeschaltet.
7 light Schalte das <name> Licht aus HassTurnOff Ich habe das Licht ausgeschaltet.
8 light Schalte das Licht in <area> aus HassTurnOff Ich habe das Licht in <area> ausgeschaltet.
9 light Schalte das Licht in <area> um HassToggle Ich habe das Licht in <area> umgeschaltet.
10 light Schalte die Helligkeit von <name> auf <brightness> HassLightSet Ich habe die Helligkeit umgeschaltet.
11 light Schalte die Lichter in <area> auf <color> HassLightSet Ich habe die Farbe jetzt geändert.
12 media_player Schalte den <name> ein HassTurnOn Ich schalte den Media-Player für dich ein.
13 media_player <name> sollte ausgeschaltet werden HassTurnOff Ich habe den Media-Player ausgeschaltet.
14 media_player Drücke den Play-Knopf am <name> HassMediaUnpause Starte die Medienwiedergabe.
15 media_player Pausiere den <name> HassMediaPause Pausiere die Medienwiedergabe.
16 media_player Spiele das nächste auf <name> HassMediaNext Überspringe zum nächsten Track.
17 switch Schalte den <name> ein HassTurnOn Ich habe den Schalter eingeschaltet.
18 switch Schalte den <name> aus HassTurnOff Ich habe den Schalter ausgeschaltet.
19 switch Schalte die Schalter in der <area> aus HassTurnOff Ich habe die Geräte wie gewünscht ausgeschaltet.
20 switch Schalte den Schalter <name> um HassToggle Ich habe den Schalter umgeschaltet.
21 vacuum Starte den Staubsauger namens <name> HassVacuumStart Ich habe den Staubsauger gestartet.
22 vacuum Stoppe den Staubsauger <name> HassVacuumReturnToBase Ich habe den Staubsauger zurück zur Basis geschickt.
23 blinds Schliesse den Rolladen <name> HassSetPosition Ich habe den Rolladen geschlossen.
24 blinds Öffne den Rolladen <name> HassSetPosition Ich habe den Rolladen geöffnet.

View File

@@ -1,7 +1,7 @@
{
"domain": "llama_conversation",
"name": "Local LLM Conversation",
"version": "0.3.6",
"version": "0.3.7",
"codeowners": ["@acon96"],
"config_flow": true,
"dependencies": ["conversation"],
@@ -10,7 +10,7 @@
"integration_type": "service",
"iot_class": "local_polling",
"requirements": [
"huggingface-hub==0.23.0",
"webcolors<=1.13"
"huggingface-hub>=0.23.0",
"webcolors>=24.8.0"
]
}

View File

@@ -1,11 +1,13 @@
import time
import os
import re
import sys
import platform
import logging
import multiprocessing
import voluptuous as vol
import webcolors
from webcolors import CSS3
from importlib.metadata import version
from homeassistant.helpers import config_validation as cv
@@ -21,6 +23,12 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
CSS3_NAME_TO_RGB = {
name: webcolors.name_to_rgb(name, CSS3)
for name
in webcolors.names(CSS3)
}
class MissingQuantizationException(Exception):
def __init__(self, missing_quant: str, available_quants: list[str]):
self.missing_quant = missing_quant
@@ -28,8 +36,9 @@ class MissingQuantizationException(Exception):
def closest_color(requested_color):
min_colors = {}
for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
r_c, g_c, b_c = webcolors.hex_to_rgb(key)
for name, rgb in CSS3_NAME_TO_RGB.items():
r_c, g_c, b_c = rgb
rd = (r_c - requested_color[0]) ** 2
gd = (g_c - requested_color[1]) ** 2
bd = (b_c - requested_color[2]) ** 2
@@ -97,10 +106,13 @@ def download_model_from_hf(model_name: str, quantization_type: str, storage_fold
fs = HfFileSystem()
potential_files = [ f for f in fs.glob(f"{model_name}/*.gguf") ]
wanted_file = [f for f in potential_files if (f".{quantization_type.lower()}." in f or f".{quantization_type.upper()}." in f)]
wanted_file = [f for f in potential_files if (f"{quantization_type.lower()}.gguf" in f or f"{quantization_type.upper()}.gguf" in f)]
if len(wanted_file) != 1:
available_quants = [file.split(".")[-2].upper() for file in potential_files]
available_quants = [
re.split(r"\.|-", file.removesuffix(".gguf"))[-1].upper()
for file in potential_files
]
raise MissingQuantizationException(quantization_type, available_quants)
try:
os.makedirs(storage_folder, exist_ok=True)
@@ -138,6 +150,11 @@ def validate_llama_cpp_python_installation():
if process.exitcode != 0:
raise Exception(f"Failed to properly initialize llama-cpp-python. (Exit code {process.exitcode}.)")
def get_llama_cpp_python_version():
if not is_installed("llama-cpp-python"):
return None
return version("llama-cpp-python")
def install_llama_cpp_python(config_dir: str):
installed_wrong_version = False

View File

@@ -1,6 +1,6 @@
{
"name": "Local LLM Conversation",
"homeassistant": "2024.8.0",
"homeassistant": "2024.12.3",
"content_in_root": false,
"render_readme": true
}

View File

@@ -14,9 +14,8 @@ langcodes
babel==2.15.0
# integration requirements
requests>=2.31.0
huggingface-hub==0.23.0
webcolors==1.13
huggingface-hub>=0.23.0
webcolors>=24.8.0
# types from Home Assistant
homeassistant>=2024.6.1

View File

@@ -1,15 +1,21 @@
#!/bin/bash
VERSION_TO_BUILD="v0.2.88"
VERSION_TO_BUILD="v0.3.5"
# make python11 wheels
# make python 11 wheels
# docker run -it --rm \
# --entrypoint bash \
# -v $(pwd):/tmp/dist \
# homeassistant/home-assistant:2023.12.4 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD
# make python 12 wheels
# docker run -it --rm \
# --entrypoint bash \
# -v $(pwd):/tmp/dist \
# homeassistant/home-assistant:2024.2.1 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD
# make python 13 wheels
docker run -it --rm \
--entrypoint bash \
-v $(pwd):/tmp/dist \
homeassistant/home-assistant:2024.2.1 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD
homeassistant/home-assistant:2024.12.3 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD