Home Assistant mit lokalem LLM: Sprachsteuerung ohne Cloud
Wie Home Assistant mit einem lokalen LLM über Ollama freie Befehle versteht, die nie vorprogrammiert wurden. Aufbau, Sackgassen und ehrliche Grenzen.
Die eingebaute Sprachsteuerung von Home Assistant funktioniert nach dem Prinzip Volltext-Muster: Für jeden Befehl existiert ein Satz Schablonen, und was nicht in die Schablone passt, wird nicht verstanden. „Schalte das Licht im Büro ein” geht. „Im Büro ist es zu dunkel zum Arbeiten” geht nicht, es sei denn, man hat genau diese Formulierung vorher als eigenen Intent hinterlegt.
Die Alternative kennen die meisten aus dem Regal: Google Assistant oder Alexa verstehen freie Formulierungen, dafür verlässt jeder Befehl das eigene Netz, und die Geräteliste liegt beim Konzern. Genau das wollte ich nicht. Zum Glück ist die Conversation-Schnittstelle von Home Assistant inzwischen eine echte Pipeline: STT rein, Konversations-Agent in der Mitte, TTS raus, und jede Stufe lässt sich austauschen. Der Agent in der Mitte muss nicht OpenAI heissen, er muss nur deren API sprechen.
Mein Ziel: freie deutsche Befehle und Rückfragen gegen mein Home Assistant, komplett lokal. „Ist noch ein Fenster offen?”, „Mach es im Büro hell” oder „Fahr die Heizung im Schlafzimmer runter, wenn niemand da ist”, ohne dass eine Silbe das Haus verlässt.
Der Aufbau
Die Kette sieht so aus: Home Assistant läuft wie gehabt. Als Konversations-Agent ist die OpenAI-Integration eingebunden, zeigt aber nicht auf OpenAI, sondern auf die OpenAI-kompatible Schnittstelle meines Ollama-Servers. Das ist derselbe Server, der auch die Dokumentenklassifikation für Paperless übernimmt. Für die Sprache hängen Whisper (Spracherkennung) und Piper (Sprachausgabe) über das Wyoming-Protokoll an Home Assistant, ein ESP32-S3 mit Micro Wake Word dient als Satellit am Schreibtisch.
Der wichtige Punkt: Der Textweg braucht keine dieser Sprachkomponenten. Wer nur über die Assist-Chatbox oder die App schreibt, ist mit Home Assistant plus Ollama fertig. Die Sprachpipeline ist ein optionales zweites Projekt, und wie sich zeigen wird, das deutlich aufwendigere.
Der Conversation-Agent an Ollama
Die Einrichtung besteht aus drei Feldern in der OpenAI-Integration:
Basis-URL: http://<ollama-server>:11434/v1
API-Key: ollama # beliebiger Platzhalter, Ollama prüft ihn nicht
Modell: qwen2.5:7b-instruct
Ollama bringt seit geraumer Zeit einen /v1-Endpunkt mit, der die OpenAI-Chat-Completions-API nachbildet. Home Assistant merkt keinen Unterschied. Der API-Key ist Pflichtfeld in der Integration, Ollama ignoriert ihn: ein Platzhalter reicht.
Entscheidend ist die Einstellung „Control Home Assistant” beim Agenten. Damit bekommt das Modell bei jeder Anfrage die Liste der freigegebenen Entitäten mit ihren aktuellen Zuständen in den Kontext und darf die entsprechenden Werkzeuge aufrufen: Geräte schalten, Zustände abfragen, Szenen aktivieren. Ohne diese Option ist es ein Chatbot, der über mein Haus plaudert, aber nichts tut.
Damit steht auch die Modellanforderung fest: Das Modell muss Function Calling beherrschen. Das ist die erste Sackgasse, zu der ich gleich komme. Bei mir arbeitet ein 7B-Modell aus der Qwen2.5-Familie; Llama-3.1-Modelle mit Tool-Support tun es ebenfalls. Das ist keine Benchmark-Empfehlung, sondern der Stand nach einigem Probieren. Wer es nachbaut, sollte zuerst sein vorhandenes Modell testen und nur wechseln, wenn die Werkzeugaufrufe nicht sauber kommen.
Was das LLM kann, was Intents nicht konnten
Der klassische Weg für eigene Befehle in Home Assistant ist intent_script. Für jeden Satz, den das Haus verstehen soll, schreibt man den Intent und die Aktion selbst:
intent_script:
BueroHell:
action: light.turn_on
target:
entity_id: light.buero
speech:
text: "Büro ist jetzt hell."
Das funktioniert zuverlässig und deterministisch. Es skaliert nur nicht mit der Kreativität dessen, was Menschen tatsächlich sagen. Jede Formulierung, jede Kombination, jede Rückfrage ist ein eigener Eintrag.
Mit dem LLM fallen drei Dinge weg. Erstens die Formulierungsstrenge: „Mach es im Büro hell”, „Bürolicht an” und „Ich kann nichts sehen” landen alle bei derselben Aktion, weil das Modell die Absicht und nicht den Wortlaut matcht. Zweitens Kontext über mehrere Sätze: Auf „Ist noch ein Fenster offen?” kann ein „Und die Tür?” folgen, und das Modell weiss, dass jetzt die Türkontakte gemeint sind. Drittens, und das ist der eigentliche Gewinn, Abfragen über Zustände hinweg, für die es gar keinen Intent gibt. „Ist noch ein Fenster offen?” ist kein Schaltbefehl, sondern eine Aggregation über alle Fensterkontakte. Die habe ich nie programmiert; das Modell liest die Zustandsliste und antwortet.
In Automationen lässt sich der Agent auch direkt ansprechen, etwa um eine Ansage zu generieren:
action: conversation.process
data:
agent_id: <conversation-agent-id>
text: "Fasse zusammen, welche Fenster offen sind, in einem Satz."
response_variable: ansage
Die Antwort steht in ansage.response.speech.plain.speech und kann an Piper oder einen Mediaplayer gehen. Damit werden Ansagen formuliert, statt vorgefertigte Strings abzuspielen.
Drei Sackgassen auf dem Weg
Die erste Sackgasse war ein Modell ohne Function Calling. Es klang überzeugend, sagte Dinge wie „Ich habe das Licht eingeschaltet”, hatte aber nichts geschaltet. Manche Modelle schrieben den Werkzeugaufruf als JSON in den Antworttext, was Home Assistant dann dem Benutzer vorlas. Ohne sauberen Tool-Support im Modell ist die ganze Integration Dekoration. Das lässt sich in einer Minute testen: Agent einrichten, „Schalte die Wohnzimmerlampe ein” schicken, schauen, ob sich im Protokoll ein Werkzeugaufruf findet. Wenn nein, Modell wechseln, nicht am Prompt drehen.
Die zweite Sackgasse war mein eigener Ehrgeiz: Ich hatte zunächst alles exponiert, knapp dreihundert Entitäten. Die Folge war ein voller Kontext bei jeder Anfrage, spürbare Wartezeit und ein Modell, das zwischen „Wohnzimmer Decke”, „Wohnzimmer Stehlampe” und „Wohnzimmer LED-Leiste” schlecht wählte und gelegentlich das falsche Gerät schaltete. Die Lösung ist eine kurze Freigabeliste: nur die Geräte, die der Assistent wirklich bedienen soll, mit sprechenden Namen und Aliassen.
Die dritte Sackgasse steckte in der Spracherkennung. Kurze deutsche Befehle sind für kleine Whisper-Modelle ein ungnädiger Fall: wenig Kontext, viele Eigennamen, und ein Modell, das im Zweifel lieber englisch halluziniert. „Flurlicht aus” wurde schon mal zu „Floor light house” und danach zu einer Frage über Häuser. Zwei Massnahmen haben geholfen: die Sprache fest auf Deutsch pinnen, damit das Modell nicht bei jeder Stille neu rät, und eine Modellgrösse wählen, die der Hardware gewachsen ist, statt der kleinsten Variante.
Sprache ist der aufwendige Teil
Nach dem Aufbau ist meine Bilanz eindeutig: Der LLM-Agent war der schnelle Teil. Integration einrichten, Freigabeliste kuratieren, fertig. Der Text-Chat funktionierte am selben Abend.
Die Sprachpipeline hat die Abende danach gebraucht. Micro Wake Word auf dem ESP32 funktioniert, reagiert aber empfindlich auf Raumakustik und die Position des Satelliten. Die deutschen Piper-Stimmen klingen nach Bahnhofsdurchsage: verständlich, aber niemand verwechselt sie mit einem Cloud-Assistenten. Und jede Stufe der Kette addiert Latenz: Aufwachen, Aufnehmen, Transkribieren, Denken, Antworten, Sprechen. Es dauert spürbar länger als ein stummer Schalter. Das ist meine subjektive Einschätzung, keine Messung. Wer erwartet, dass sich das wie ein kommerzielles Produkt anfühlt, wird enttäuscht.
Fairerweise gehört dazu: Diese Latenz und dieser Klang sind der Preis dafür, dass kein Mikrofon im Haus auf einen fremden Server zeigt.
Bilanz
Was am Ende läuft: freie deutsche Befehle gegen eine kuratierte Geräteliste, Zustandsabfragen ohne vorher definierte Intents, formulierte Ansagen aus Automationen. Alles lokal, alles ohne Konto bei einem Anbieter, der die API nächstes Quartal ändern könnte. Letzteres unterschätze ich nicht mehr: Ein selbst gehostetes Open-Weight-Modell ändert sein Verhalten nicht über Nacht, weil ein Anbieter ein Update ausrollt oder ein Modell abkündigt. Was heute funktioniert, funktioniert morgen noch genauso. Das ist die gleiche Grundhaltung wie bei den Automationen, die ohne Internet laufen.
Was es kostet: einen Rechner, der das Modell stemmt (bei mir derselbe Ollama-Server wie für Paperless), spürbare Latenz bei Sprache, und die Ehrlichkeit, dass ein 7B-Modell schlechter schaltet als ein Cloud-Assistent mit jahrelangem Intent-Training. Fehlschaltungen kommen vor. Deshalb die kurze Freigabeliste, und deshalb bleibt alles Sicherheitsrelevante ausserhalb der Reichweite des Modells.
Für mich trägt sich das trotzdem: Die Schablonenpflege ist weg, die Abfragen sind echt nützlich, und die Hoheit über die Pipeline liegt bei mir. Wer keinen Sprachsatelliten braucht, bekommt den eigentlichen Gewinn, den verstehenden Agenten, mit einem Bruchteil des Aufwands: Home Assistant, ein Ollama-Endpunkt, eine kurze Freigabeliste. Der Rest ist Luxus und Bastelei.