mirror of
https://github.com/acon96/home-llm.git
synced 2026-01-09 21:58:00 -05:00
16
.github/workflows/create-release.yml
vendored
16
.github/workflows/create-release.yml
vendored
@@ -20,33 +20,33 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# ARM variants
|
# ARM variants
|
||||||
- home_assistant_version: "2024.2.1"
|
- home_assistant_version: "2024.12.3"
|
||||||
arch: "aarch64"
|
arch: "aarch64"
|
||||||
- home_assistant_version: "2024.2.1"
|
- home_assistant_version: "2024.12.3"
|
||||||
arch: "armhf"
|
arch: "armhf"
|
||||||
|
|
||||||
# Base x86
|
# Base x86
|
||||||
- home_assistant_version: "2024.2.1"
|
- home_assistant_version: "2024.12.3"
|
||||||
suffix: "-noavx"
|
suffix: "-noavx"
|
||||||
arch: "amd64"
|
arch: "amd64"
|
||||||
extra_defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF"
|
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"
|
arch: "i386"
|
||||||
suffix: "-noavx"
|
suffix: "-noavx"
|
||||||
extra_defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF"
|
extra_defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DGGML_F16C=OFF"
|
||||||
|
|
||||||
# AVX2 and AVX512
|
# AVX2 and AVX512
|
||||||
- home_assistant_version: "2024.2.1"
|
- home_assistant_version: "2024.12.3"
|
||||||
arch: "amd64"
|
arch: "amd64"
|
||||||
extra_defines: "-DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON"
|
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"
|
arch: "amd64"
|
||||||
suffix: "-avx512"
|
suffix: "-avx512"
|
||||||
extra_defines: "-DGGML_AVX512=ON -DGGML_FMA=ON -DGGML_F16C=ON"
|
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"
|
arch: "i386"
|
||||||
extra_defines: "-DGGML_AVX=ON -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON"
|
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"
|
arch: "i386"
|
||||||
suffix: "-avx512"
|
suffix: "-avx512"
|
||||||
extra_defines: "-DGGML_AVX512=ON -DGGML_FMA=ON -DGGML_F16C=ON"
|
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.
|
Please see the [Setup Guide](./docs/Setup.md) for more information on installation.
|
||||||
|
|
||||||
## Local LLM Conversation Integration
|
## 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".
|
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 History
|
||||||
| Version | Description |
|
| 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.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.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 |
|
| 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 homeassistant.util.package import is_installed
|
||||||
from importlib.metadata import version
|
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 (
|
from .const import (
|
||||||
CONF_CHAT_MODEL,
|
CONF_CHAT_MODEL,
|
||||||
CONF_MAX_TOKENS,
|
CONF_MAX_TOKENS,
|
||||||
@@ -352,7 +352,9 @@ class ConfigFlow(BaseLlamaConversationConfigFlow, config_entries.ConfigFlow, dom
|
|||||||
local_backend = is_local_backend(user_input[CONF_BACKEND_TYPE])
|
local_backend = is_local_backend(user_input[CONF_BACKEND_TYPE])
|
||||||
self.model_config.update(user_input)
|
self.model_config.update(user_input)
|
||||||
if local_backend:
|
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()
|
return await self.async_step_local_model()
|
||||||
else:
|
else:
|
||||||
return await self.async_step_install_local_wheels()
|
return await self.async_step_install_local_wheels()
|
||||||
|
|||||||
@@ -383,5 +383,5 @@ OPTIONS_OVERRIDES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
INTEGRATION_VERSION = "0.3.6"
|
INTEGRATION_VERSION = "0.3.7"
|
||||||
EMBEDDED_LLAMA_CPP_PYTHON_VERSION = "0.2.88"
|
EMBEDDED_LLAMA_CPP_PYTHON_VERSION = "0.3.5"
|
||||||
@@ -421,8 +421,10 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
|
|||||||
response=intent_response, conversation_id=conversation_id
|
response=intent_response, conversation_id=conversation_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tool_response = None
|
||||||
# parse response
|
# parse response
|
||||||
to_say = service_call_pattern.sub("", response.strip())
|
to_say = service_call_pattern.sub("", response.strip())
|
||||||
|
tool_response = None
|
||||||
for block in service_call_pattern.findall(response.strip()):
|
for block in service_call_pattern.findall(response.strip()):
|
||||||
parsed_tool_call: dict = json.loads(block)
|
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
|
# 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):
|
if self.entry.options.get(CONF_TOOL_MULTI_TURN_CHAT, DEFAULT_TOOL_MULTI_TURN_CHAT) and tool_response is not None:
|
||||||
conversation.append({"role": "tool", "message": json.dumps(tool_response)})
|
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
|
# generate a response based on the tool result
|
||||||
try:
|
try:
|
||||||
@@ -527,6 +532,7 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
|
|||||||
)
|
)
|
||||||
|
|
||||||
conversation.append({"role": "assistant", "message": response})
|
conversation.append({"role": "assistant", "message": response})
|
||||||
|
conversation.append({"role": "assistant", "message": to_say})
|
||||||
|
|
||||||
# generate intent response to Home Assistant
|
# generate intent response to Home Assistant
|
||||||
intent_response = intent.IntentResponse(language=user_input.language)
|
intent_response = intent.IntentResponse(language=user_input.language)
|
||||||
@@ -749,12 +755,14 @@ class LocalLLMAgent(ConversationEntity, AbstractConversationAgent):
|
|||||||
|
|
||||||
value = attributes[attribute_name]
|
value = attributes[attribute_name]
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if attribute_name == "temperature":
|
# try to apply unit if present
|
||||||
value = int(value)
|
unit_suffix = attributes.get(f"{attribute_name}_unit")
|
||||||
if value > 50:
|
if unit_suffix:
|
||||||
value = f"{value}F"
|
value = f"{value} {unit_suffix}"
|
||||||
else:
|
elif attribute_name == "temperature":
|
||||||
value = f"{value}C"
|
# try to get unit or guess otherwise
|
||||||
|
suffix = "F" if value > 50 else "C"
|
||||||
|
value = F"{int(value)} {suffix}"
|
||||||
elif attribute_name == "rgb_color":
|
elif attribute_name == "rgb_color":
|
||||||
value = F"{closest_color(value)} {value}"
|
value = F"{closest_color(value)} {value}"
|
||||||
elif attribute_name == "volume_level":
|
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",
|
"domain": "llama_conversation",
|
||||||
"name": "Local LLM Conversation",
|
"name": "Local LLM Conversation",
|
||||||
"version": "0.3.6",
|
"version": "0.3.7",
|
||||||
"codeowners": ["@acon96"],
|
"codeowners": ["@acon96"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"huggingface-hub==0.23.0",
|
"huggingface-hub>=0.23.0",
|
||||||
"webcolors<=1.13"
|
"webcolors>=24.8.0"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import webcolors
|
import webcolors
|
||||||
|
from webcolors import CSS3
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
@@ -21,6 +23,12 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CSS3_NAME_TO_RGB = {
|
||||||
|
name: webcolors.name_to_rgb(name, CSS3)
|
||||||
|
for name
|
||||||
|
in webcolors.names(CSS3)
|
||||||
|
}
|
||||||
|
|
||||||
class MissingQuantizationException(Exception):
|
class MissingQuantizationException(Exception):
|
||||||
def __init__(self, missing_quant: str, available_quants: list[str]):
|
def __init__(self, missing_quant: str, available_quants: list[str]):
|
||||||
self.missing_quant = missing_quant
|
self.missing_quant = missing_quant
|
||||||
@@ -28,8 +36,9 @@ class MissingQuantizationException(Exception):
|
|||||||
|
|
||||||
def closest_color(requested_color):
|
def closest_color(requested_color):
|
||||||
min_colors = {}
|
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
|
rd = (r_c - requested_color[0]) ** 2
|
||||||
gd = (g_c - requested_color[1]) ** 2
|
gd = (g_c - requested_color[1]) ** 2
|
||||||
bd = (b_c - requested_color[2]) ** 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()
|
fs = HfFileSystem()
|
||||||
potential_files = [ f for f in fs.glob(f"{model_name}/*.gguf") ]
|
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:
|
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)
|
raise MissingQuantizationException(quantization_type, available_quants)
|
||||||
try:
|
try:
|
||||||
os.makedirs(storage_folder, exist_ok=True)
|
os.makedirs(storage_folder, exist_ok=True)
|
||||||
@@ -138,6 +150,11 @@ def validate_llama_cpp_python_installation():
|
|||||||
if process.exitcode != 0:
|
if process.exitcode != 0:
|
||||||
raise Exception(f"Failed to properly initialize llama-cpp-python. (Exit code {process.exitcode}.)")
|
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):
|
def install_llama_cpp_python(config_dir: str):
|
||||||
|
|
||||||
installed_wrong_version = False
|
installed_wrong_version = False
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Local LLM Conversation",
|
"name": "Local LLM Conversation",
|
||||||
"homeassistant": "2024.8.0",
|
"homeassistant": "2024.12.3",
|
||||||
"content_in_root": false,
|
"content_in_root": false,
|
||||||
"render_readme": true
|
"render_readme": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ langcodes
|
|||||||
babel==2.15.0
|
babel==2.15.0
|
||||||
|
|
||||||
# integration requirements
|
# integration requirements
|
||||||
requests>=2.31.0
|
huggingface-hub>=0.23.0
|
||||||
huggingface-hub==0.23.0
|
webcolors>=24.8.0
|
||||||
webcolors==1.13
|
|
||||||
|
|
||||||
# types from Home Assistant
|
# types from Home Assistant
|
||||||
homeassistant>=2024.6.1
|
homeassistant>=2024.6.1
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/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 \
|
# docker run -it --rm \
|
||||||
# --entrypoint bash \
|
# --entrypoint bash \
|
||||||
# -v $(pwd):/tmp/dist \
|
# -v $(pwd):/tmp/dist \
|
||||||
# homeassistant/home-assistant:2023.12.4 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD
|
# homeassistant/home-assistant:2023.12.4 /tmp/dist/make_wheel.sh $VERSION_TO_BUILD
|
||||||
|
|
||||||
# make python 12 wheels
|
# 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 \
|
docker run -it --rm \
|
||||||
--entrypoint bash \
|
--entrypoint bash \
|
||||||
-v $(pwd):/tmp/dist \
|
-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