mirror of
https://github.com/acon96/home-llm.git
synced 2026-01-08 21:28:05 -05:00
16
.github/workflows/create-release.yml
vendored
16
.github/workflows/create-release.yml
vendored
@@ -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"
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
@@ -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":
|
||||
|
||||
@@ -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,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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Local LLM Conversation",
|
||||
"homeassistant": "2024.8.0",
|
||||
"homeassistant": "2024.12.3",
|
||||
"content_in_root": false,
|
||||
"render_readme": true
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user