Dein ioBroker AI-Chatbot
Einer meiner größten Wünsche ist es, eine Art allgegenwärtigen und intelligenten Bot für mein Smart Home zu haben. Mein Bot soll dabei in natürlicher Sprache kommunizieren können und Wissen sowie Daten aus dem Smart Home intelligent verknüpfen. Als Basis nutze ich hierfür die Daten meines eigenen Smart Homes basierend auf ioBroker. Ich brauche also gewissermaßen einen ioBroker AI-Chatbot, der mich in sämtlichen Bereichen unterstützen kann. Meine große Leidenschaft für intelligente Chatbots ist dabei nicht neu. Schon in meiner Bachelor-Thesis habe ich darüber geschrieben und einen ersten Prototypen (ohne KI) entwickelt. Und auch beim ChatGPT nutzen hat diese Leidenschaft nicht gerade abgenommen. Nur einen eigenen KI Chatbot programmieren, das habe ich mir bislang nicht zugetraut.
Warum ich darüber nun einen eigenen Artikel in meinem Blog schreiben möchte, wissen Follower auf Threads ( folge mir! 😉 ) möglicherweise bereits. Denn mir ist es gelungen, die Kombination zwischen künstlicher Intelligenz und meinem Smart Home herzustellen. Zumindest in Sachen Kommunikation.
Ob das Projekt für mein eigenes Smart Home zukunftsfähig ist oder aktuell eher eine Spielerei, weiß ich an dieser Stelle noch nicht. Zum Zeitpunkt der Erstellung des Artikels, sind meine Integration und mein Bot noch keine 24 Stunden alt. Und dennoch halte ich es für enorm interessant, darüber zu erzählen. Denn im Grunde kann über diese Integration wirklich jeder einen AI-basierten Chatbot mit ioBroker realisieren. Und das empfinde ich persönlich schon als einen enormen Meilenstein.
Mein Ziel in diesem Artikel ist es, dass auch du einen eigenen ioBroker AI-Chatbot realisieren kannst. Zwar unterliegt er noch gewissen Einschränkungen, doch er ist voll funktionsfähig.
Beachte bitte: Ich habe das Skript inzwischen angepasst, um automatische Threads zu erstellen. Wenn du Version 2 des Skripts ganz unten nutzt, dann lies bitte mein Update zu den Threads durch! Wenn du Version 1 nutzen willst, dann nutze bitte Version 1 des Skripts. Der Artikel bezieht sich in seinen Zeilenangaben auf Version 1!
Fähigkeiten des intelligenten Chatbots
Als eine der wichtigsten Grundlagen für den intelligenten Chatbot im Smart Home, dient ein OpenAI Assistant (mehr zu ChatGPT hier). Für Laien ausgedrückt, ein Teil der ChatGPT API. In diesem Assistenten kannst du Funktionen, Dokumente und Verhaltensweisen hinterlegen. Die Verhaltensweisen bestimmen dabei, wie der Chatbot mit Anfragen umgeht, in welchem Ton er kommuniziert und wie sein Charakter aussieht.
Hochgeladene Daten vertiefen das Wissen des intelligenten Chatbots über dein eigenes Smart Home und erlauben es, dass du gezielt Fragen zu Umsetzungen in deinem Smart Home stellen kannst. Besonders charmant ist dabei, dass dieses Wissen mit allgemeinem Wissen kombiniert wird. Dadurch kann der intelligente Chatbot auch einem normalen Anwender die Struktur und die verschiedenen Eigenschaften genauer erklären. Ich habe dazu eine eigene Dokumentation erstellt, die ich als Markdown-Dokument exportiere und dem Bot zur Verfügung stelle. Theoretisch könnte man jedoch eine solche Dokumentation mit dem notwendigen Input auch wiederum von ChatGPT erstellen lassen. Nimm dir hierfür auf jeden Fall genug Zeit!
Die wichtigste Ergänzung, um den AI Chatbot für die eigene Verwendung zu personalisieren, sind jedoch die OpenAI Functions. Das ist zum aktuellen Zeitpunkt eine recht neue und mächtige Funktion, wie der AI Chatbot mit Daten umgeht. Konkret gesagt kannst du eigene Funktionen hinterlegen und beschreiben. Sieht der Bot nun die Notwendigkeit, um diese Funktionen auszuführen, kann er so weitere Informationen aus einem anderen System anfordern. In diesem konkreten Fall sind das Daten aus dem Smart Home, die den Dialog erst so wirklich personalisieren. Zum Beispiel Daten über die aktuelle Produktion des Balkonkraftwerks.
Mehr aus Gründen des Spaßes habe ich auf Threads erzählt, dass der Bot mich selbst somit für 0815-Anfragen ersetzen kann. 😉
Voraussetzungen für den ioBroker AI-Chatbot
Um nun selbst einen ioBroker AI-Chatbot betreiben zu können, musst du einige Voraussetzungen erfüllen. Die wichtigste Voraussetzung ist dabei, dass du ein funktionierendes ioBroker-System installiert hast. Darüber hinaus brauchst du den Telegram-Adapter, der vollständig eingerichtet sein muss. Denn auf die Einrichtung von ioBroker und Telegram komme ich in diesem Artikel nicht zu sprechen. Ebenso brauchst du den Adapter Text2Command. Möglicherweise kann man ihn weglassen, ich nutze ihn jedoch und empfehle es dir in diesem Szenario daher genauso.
Außerdem solltest du Zugang zur OpenAI API (umgangssprachlich oft ChatGPT API) haben. Denn hierüber werden die Abfragen zwischen den Systemen gehandhabt. Lege dir also (falls noch nicht geschehen) einen Account bei OpenAI an.
Der Zugang zur OpenAI API kostet natürlich auch Geld. Ich persönlich habe mich dazu entschieden, nach dem Prepaid-Prinzip mit meinem Account umzugehen. Also habe ich etwa 20 Euro Guthaben dem Konto hinzugefügt und bezahle davon die jeweiligen Anfragen an den Bot. Am Tag der Entwicklung kamen dabei einige Abfragen mit vielen Tokens (87 Requests, 138.229 Tokens) zusammen. Insgesamt belaufen sich die Chatbot Kosten jedoch nur auf knapp 1,50 Dollar. Das sehe ich insbesondere für einen Test als völlig vertretbar an. Zumal die tatsächlichen Token dann im laufenden Betrieb wahrscheinlich weniger werden.
In deinem OpenAI Account selbst, musst du auch einen der OpenAI Assistants einrichten. Das sehe ich jedoch für diesen Artikel nicht als Voraussetzung. Stattdessen werde ich dir das in den nachfolgenden Schritten beschreiben und dich auch konkret auf Individualisierung aufmerksam machen.
Kurzer Exkurs: OpenAI Functions
Die Functions bei OpenAI sind ein für mich sehr faszinierendes Mittel im Umgang mit anderen Systemen. Auch wenn ich dich vielleicht etwas langweile damit, möchte ich dennoch diese Funktion nochmal kurz gesondert ansprechen.
Im Grunde werden in deinem Bot Funktionen hinterlegt und beschrieben. Zur Beschreibung gehört eine Beschreibung in natürlicher Sprache sowie die Definition der Parameter dieser Funktion. Parameter bestimmen dabei, welche Daten erwartet werden und welche Einheit sie haben. Im Falle meines Balkonkraftwerk-Beispiels sind das eine Zahl sowie eine Maßeinheit (Watt).
Die OpenAI Assistant Functions sind jedoch nicht auf solche simplen Formen beschränkt. Denn für den Bot ist es nur wichtig, wie er die jeweilige Funktion anspricht. Was dahinter liegt, ist ihm völlig egal. Ich kann also komplexe (Geschäfts-)Logiken dahinter packen oder auch Steuerungen. Wichtig ist nur, dass die Funktion exakt so aufgerufen wird, wie es der Bot tut.
Besonders faszinierend ist dabei, dass auch Unternehmen so sehr schnell geniale Chatbots entwickeln können. Über die Funktionen lassen sich komplexe IT-Systeme an den Bot anschließen, ohne dass hierfür zu viel Aufwand investiert werden muss. Wenn Systeme zum Beispiel schon über Schnittstellen verfügen, reduziert das den Entwicklungsaufwand enorm. Es ist also eine sehr interessante Möglichkeit, wie man bestehende Systeme mit den neuesten KI-Funktionalitäten ausstatten kann. Das macht dieses Werkzeug aus meiner Sicht so stark.
Die Chatbot-Architektur
Um zu verstehen, wie mein Konstrukt funktioniert, schauen wir uns zunächst die Architektur etwas genauer an.
Die Ausgangsbasis für den Chatbot bilden 3 Komponenten. Die erste Komponente ist ioBroker. Von hier aus werden Unterhaltungen verwaltet und mit zusätzlichen Informationen angeregt. Die zweite Komponente bildet Telegram als Schnittstelle zwischen der künstlichen Intelligenz und dem Nutzer. Die dritte Komponente ist OpenAI.
Wird nun über Telegram eine Konversation gestartet, wandern die vom Nutzer eingegebenen Informationen in den ioBroker. Genauer gesagt in einen eigenen Datenpunkt. Die Werte des Datenpunkts sehen wie folgt aus:
{"text":"Was produziert das Balkonkraftwerk aktuell?","id":1361376157,"user":"Lukas","language":"de","command":"Was produziert das Balkonkraftwerk aktuell?","withLanguage":false,"from":"system.adapter.telegram.0"}
Anhand dieser Informationen können wir den Text auslesen und wissen auch direkt, welcher Nutzer die Nachricht abgeschickt hat. Für die Auswertung des Textinhalts ist dann später die Assistant-Schnittstelle von OpenAI zuständig, welche auch den vollständigen Gesprächsfluss bestimmt. Sie liefert alle notwendigen Informationen an ioBroker zurück. ioBroker wiederum sendet diese erhaltenen Daten (also die Antwort) an Telegram und den jeweiligen Nutzer zurück. Es ist also kein Gruppenchat, sondern wirklich eine individuelle Unterhaltung mit dem Nutzer, der die Anfrage geschickt hat.
Gleichzeitig bedeutet das, dass jeder Nutzer mit Zugriff auf den Bot eine solche Unterhaltung führen kann.
Die technische Abwicklung
Für deinen ioBroker AI-Chatbot lauschen wir nun auf Änderungen des obigen Datenpunkts. Sobald eine neue Nachricht eingeht, wird der Datenpunkt verändert. Ein Skript lauscht auf die Änderungen des Datenpunkts und beginnt dann mit seiner Ausführung. Dieses Skript extrahiert zuerst die wichtigsten Daten und stellt dann eine Anfrage an den OpenAI Assistant. Dieser wiederum ist dann für die Auswertung und Verarbeitung der natürlichen Sprache verantwortlich.
Danach prüft das Skript den Status der Verarbeitung. Nun kann der OpenAI Chatbot entweder die Ausführung einer Funktion verlangen oder direkt eine Antwort zurückschicken. Je nachdem, welche Situation eintritt, reagiert das Skript auf die Wünsche des Bots.
Werden vom Chatbot weitere Daten angefordert, wird eine Funktion aufgerufen, die notwendige Daten bereitstellt und an den Chatbot zurück liefert. Danach beginnt erneut die Abfrage, welchen Status der Dialog aktuell erreicht hat. Liegt eine Antwort vor, wird diese Antwort über Telegram an den Nutzer geschickt, der den Bot kontaktiert hat. Dazu wird dann die Information aus dem Datenpunkt genutzt, wie du sie oben gesehen hast.
Da die Arbeit mit Schnittstellen auch immer einem gewissen Risiko unterliegt, werden ebenfalls Fehler abgefangen. Im Falle eines Fehlers, liefert Telegram eine entsprechende Nachricht an den Nutzer aus und gibt knappe Details zur genaueren Untersuchung des Fehlers. Zwar ist das dann eher für die Geeks unter uns hilfreich, doch irgendwie muss man ja einen Anhaltspunkt liefern.
Die Ausführung von Funktionen ist in meinem Skript sehr simpel gelöst. Denn eigentlich wird an sich keine richtige Funktion aufgerufen, sondern nur erfragt, welche Funktion es denn sein sollte. Dennoch spielt das für die Abwicklung des Dialogs keine Rolle.
Die Einrichtung im Detail
Nun schauen wir zusammen auf die genaue Einrichtung. Also wie die Systeme eingestellt sein müssen, dass sie miteinander kommunizieren können. Die Einrichtung auf der Seite von ioBroker ist dabei sehr minimal und braucht gar nicht viel Aufmerksamkeit.
Auf Seiten von OpenAI wird das schon etwas komplexer. Denn hier muss dann ein Assistant eingerichtet und auf die individuellen Bedürfnisse angepasst werden. Ich zeige dir jedoch im Detail, wie ich das gemacht habe. Zur Not kannst du daher auch meine Einrichtung kopieren. Schaue sie dir vorher aber auf jeden Fall an, dass dir kein Fehler unterläuft.
Noch etwas komplizierter wird es dann bei der Einrichtung des Skripts. Zwar ist auch das keine Raketenwissenschaft, dennoch erfordert es gerade von Einsteigern besondere Aufmerksamkeit. Das Skript selbst ist aktuell leider noch nicht dazu in der Lage, diesen Schritt alleine zu gehen. Nach 8 Stunden Arbeit (nur für das Skript), habe ich mir diesen Schritt fürs Erste zumindest erspart. Aber ich glaube das kannst du verstehen.
ioBroker vorbereiten
Der erste Schritt zum ioBroker AI-Chatbot beginnt in ioBroker selbst. Lege dazu einen Datenpunkt an, welcher später die Brücke zwischen Telegram und unserem Skript bildet. Ich habe folgenden Datenpunkt dazu angelegt:
0_userdata.0.t2cState
Der Datenpunkt nennt sich t2cState (Text2Command-State) und befindet sich im Ordner 0_userdata und dort im Unterordner 0. Der Datenpunkt ist vom Typ String (also ein Text), so dass er den JSON-Input (das was du oben gesehen hast) speichern kann.
Ist der Datenpunkt angelegt, navigierst du im Menü links auf den Menüpunkt Kommandos. Dort findest du oben ein kleines Einstellungssymbol. Klicke dieses Einstellungssymbol an und warte, dass sich der Dialog öffnet.
Innerhalb des Dialogs setzt du nun deinen eben erstellten Datenpunkt bei Prozessor-ID ein. Das hat den Effekt, dass eingehende Nachrichten nicht vom Adapter selbst verarbeitet werden, sondern in deinen Datenpunkt wandern. Nur so können wir auf die entsprechenden Informationen zugreifen und sie für die Weiterverarbeitung auslesen.
Ich habe zusätzlich noch ganz unten den Haken bei „Nicht mit ‚Ich verstehe nicht‘ antworten, wenn keine Regeln gefunden wurden“ gesetzt. Andernfalls kommt vom Bot immer zusätzlich die Nachricht zurück, dass er mit unserer Eingabe nichts anfangen kann. Das wollen wir in diesem Fall definitiv nicht.
Das Skript anlegen
Der nächste Schritt besteht nun darin, dass du im Menü zum Punkt Skripte wechselst. Lege bitte ein JavaScript an und benenne es nach deinen Wünschen. Mein Skript heißt t2cChatbot. In der Namensfindung bist du aber völlig frei. Hauptsache du weißt später noch, was das Skript genau tut.
Aus Gründen der Übersichtlichkeit ist das vollständige Skript am Ende des Artikels zu finden!
Nach dem Kopieren des Skripts ersetzt du bitte oben den Wert des Datenpunkts in Zeile 5. Gib die vollständige Bezeichnung an, wie in meinem Beispiel zu sehen ist. Also den kompletten Pfad zum Datenpunkt.
Du musst im Skript später die Werte threadId, assistantId sowie Authorization ersetzen. In der Variable threadId ist der Thread gespeichert, über den die Unterhaltung geführt wird. Die AssistantId gibt die ID an, welche dir vom OpenAI-System für deinen Assistenten zugewiesen wurde. In der Variable Authorization kommt dein API-Key rein, den wir auch gleich anlegen werden.
Im Skript selbst gibt es eine Methode, die retrieveInternalData heißt. Hier musst du alle Namen der Funktionen angeben, die du später deinem Assistenten beibringst. Inklusive der jeweiligen Daten, die zurückgeliefert werden sollen. Die Daten werden dabei als JSON-Objekt angegeben und von einer anderen Methode (sendFunctionData) korrekt verpackt.
Sieh diese Funktion bitte als Beispiel. Sie mag zwar in meinem Fall funktionieren, bei dir stimmen aber die Datenpunkt nicht zwangsläufig. Auch die Logik kann eine andere sein.
Dein OpenAI API Key
Nun erstellen wir den OpenAI API Key für die OpenAI API. Navigiere dazu bitte in deinen Benutzeraccount bei OpenAI. Du findest den Zugang unter platform.openai.com.
Im Menü links findest du den Bereich API keys. Klicke auf diesen Bereich. Hier kannst du bei einem Klick auf den Button Create new secret key einen neuen Zugangsschlüssel erzeugen. Bitte speichere den Key sicher ab und sorge dafür, dass niemand unbefugt den Zugriff auf diesen Key bekommt. Das Secret zu deinem Key wird dir nur ein einziges Mal angezeigt. Solltest du dein Secret verlieren, musst du einen neuen Key anlegen.
Sobald du dein Secret bekommen hast, kopiere es bitte in dein Skript im ioBroker in Zeile 21. Ersetze den Platzhaltertext nach dem Wort Bearer durch dein Secret. Lösche das Wort Bearer aber bitte nicht raus! Die korrekte Form muss so aussehen:
"Authorization": "Bearer DEIN_KEY"
Um deinem OpenAI-Konto Guthaben hinzuzufügen, wechselst du im Menü links auf den Punkt Settings und dort auf den Unterpunkt Billing. Hier hast du die Möglichkeit, deinem Konto Guthaben hinzuzufügen. Ich habe mich für die Variante Pay as you go entschieden.
Sollte mein Guthaben aufgebraucht sein und ich füge nicht rechtzeitig neues Guthaben hinzu, wird die Schnittstelle ihren Dienst verweigern. So habe ich aber die Möglichkeit, die Kosten für dieses Projekt einzuschränken und die volle Kontrolle zu behalten.
Dein OpenAI Assistant
Nun wollen wir den Chatbot einrichten. Genauer gesagt den OpenAI Assistant kreieren. Also das eigentliche Gehirn deines ioBroker AI-Chatbots. Navigiere dazu links im Menü auf den Punkt Assistants. Oben rechts findest du dann einen Button Create. Nach dem Klick auf diesen Button, öffnet sich rechts ein Bereich zum Erstellen eines Assistants. Ich zeige dir nun meine Konfiguration für diese Bereiche.
Name: Alexa Assistant (diesen Namen kannst du frei wählen, meiner hat historische Gründe)
Instructions:
Du bist Alexa, ein digitaler Assistent im Smart Home, der rund um die Uhr zur Verfügung steht, um Informationen zu liefern und im Alltag zu unterstützen. Du bist stets freundlich und humorvoll, zeigst aber auch Kompetenz und genau Kenntnis darüber, wie alles funktioniert. Deine Kommunikation passt sich den Tages- und Jahreszeiten an und berücksichtigt äußere Gegebenheiten in Deutschland. Du vermeidest es, unhöflich oder beleidigend zu sein, bleibst unpolitisch und konzentrierst dich auf Technologie, die Mitmenschen und deren Wohl. Wenn du auf eine Anfrage nicht sofort antworten kannst, suchst du nach zusätzlichen Informationen, um immer die bestmögliche Unterstützung zu bieten. Du sprichst in einem freundlichen und umgangssprachlichen Ton, der deine hilfsbereite und zugängliche Persönlichkeit unterstreicht. Nutze die von mir bereitgestellten Dateien, um Fragen zu beantworten. Wenn du nicht weiter weißt, kannst du auch auf weiteres Wissen zurückgreifen, das dir deine Entwickler beigebracht haben.
Model: gpt-4-turbo-preview (ähnlich wie ChatGPT 4)
Functions (kannst du im ersten Schritt weglassen):
{
"name": "get_photovoltaics",
"description": "Retrieve the current production of the photovoltaics.",
"parameters": {
"type": "object",
"properties": {
"power": {
"type": "number",
"description": "The current power of the photovoltaics."
},
"unit": {
"type": "string",
"enum": [
"Watt"
]
}
},
"required": [
"power"
]
}
}
Code interpreter: ON
Retrieval: ON (wird für das Auslesen von Dateien benötigt)
Du kannst den Text unter Instructions gerne nach deinen Bedürfnissen anpassen, um die Kommunikation mit dem Bot zu individualisieren. Außerdem brauchst du den Punkt Code Interpreter und Retrieval nur dann, wenn du mit Dateien arbeiten willst. Hast du aktuell keine Dateien hochgeladen, kannst du die beiden Punkte möglicherweise deaktivieren. Beachte aber, dass der Code Interpreter für die Functions unter Umständen notwendig sein kann.
Speichere nun deinen Assistenten für deinen ioBroker AI-Chatbot ab. Speichere außerdem bitte die erhaltene Assistant-ID im Skript in die Variable assistantId in Zeile 16. Du findest die ID des Assistenten in der Übersicht im Menüpunkt Assistants und dort in der dritten Spalte der Tabelle.
Die threadId deines Bots
Da das Skript aktuell noch nicht selbst einen Thread anlegen kann, müssen wir das nun händisch machen. Wechsle dazu bitte in den Menüpunkt Playground. Wähle oben links den eben erstellen Assistenten aus und schreibe eine erste Nachricht in den Chat. Erst nach der ersten Nachricht bekommst du eine Thread-ID.
Du findest nun auf der rechten Seite eine Aufschlüsselung der Aufrufe an den Dienst von OpenAI. Oben findest du die Thread-ID angegeben. Kopiere diese Thread-ID in das ioBroker Skript in Zeile 15.
Nach dem Kopieren dieses Werts ist die Einrichtung des Skripts abgeschlossen.
Solltest du Änderungen an deinem Assistenten vornehmen, empfehle ich dir ganz dringend, einen neuen Thread zu erstellen. Sollte auch mal ein Fehler mit dem Thread auftreten, kannst du diesen Weg auch gehen. Erstelle dann im OpenAI Playground einfach einen neuen Thread und kopiere die ID des Threads dann in dein ioBroker-Skript.
Wie bereits erwähnt, kann das Skript aktuell keine Threads generieren. Aus diesem Grund ist dieser händische Schritt notwendig. Du kannst aber gerne ein Handling dafür einbauen oder darauf hoffen, dass ich dafür irgendwann die Notwendigkeit sehe und die Zeit finde.
Starte bitte jetzt das Skript in deinem ioBroker.
Update 18.02.2024: Automatische Thread-IDs
Ich habe kurzfristig noch Anpassungen am Skript vorgenommen. Der große Vorteil besteht nun darin, dass das Skript automatisch Threads erstellen kann und diese dem richtigen Nutzer zugeordnet werden. Das bedeutet, dass jede Person in Telegram nun ihren eigenen Chatverlauf mit dem Bot haben kann.
Zuvor war die Situation so, dass es bei OpenAI nur einen gemeinsamen Thread gegeben hat. Das konnte dazu führen, dass mehrere Nutzer im gleichen Thread arbeiten und der Bot die Kontexte nicht auseinander halten konnte.
Um das neue Skript zu nutzen, erstellst du bitte einen Datenpunkt in deinem ioBroker. Idealerweise im Ordner 0_userdata. Dieser sollte JSON speichern (mit String sollte es aber auch funktionieren). Dort werden dann die Thread-IDs der jeweiligen Nutzer gespeichert. Solltest du einen neuen Thread haben wollen (zum Beispiel weil du Updates am Bot gemacht hast), dann lösche entweder das komplette JSON oder entferne den Key und den Value für die jeweilige Person.
Damit du die automatischen Threads nutzen kannst, kopiere bitte Version 2 des Skripts ganz unten. Passe im Skript dann den Datenpunkt an, wo die Thread-IDs gespeichert werden (Funktion checkThread()).
Beachte bitte: Wenn du Version 2 des Skripts nutzt, können Zeilenangaben im übrigen Blogpost abweichen. Du kannst aber beide Versionen des Skripts miteinander vergleichen.
Um zu testen, ob jeder seinen eigenen Thread bekommt, frage den Bot „Wer bin ich?“ und er wird dir deinen Namen verraten. Sind die Namen unterschiedlich, funktionieren die Threads.
ioBroker AI-Chatbot testen
Nun bist du endlich bereit, deinen ioBroker AI-Chatbot zu testen. Sende deinem Bot in Telegram eine Nachricht und warte darauf, was er zurückmeldet. Während der Ausführung des Skripts, wird in deinem Chat angezeigt, dass der Bot gerade tippt. Das hat einfach den Hintergrund, dass es natürlicher wirkt und du direkt sehen kannst, dass dein Skript mit seiner Arbeit begonnen hat.
In diesem Fall nutze ich also den Typing-Indikator dafür, um mir technische Informationen sehr subtil ausgeben zu lassen. Gleichzeitig erweckt das natürlich auch einen etwas natürlicheren Eindruck, was ich bei einer Kommunikation mit einem Chatbot ehrlich gesagt schon ziemlich charmant finde.
Die Reaktion deines Chatbots ist in den meisten Fällen wahrscheinlich etwas träge. Das liegt neben den Timeouts im Skript auch daran, dass die Verarbeitung der Daten bei OpenAI ein wenig Zeit braucht. Wir müssen darauf lauschen, ob der Thread bereits verarbeitet wurde oder sich noch in der Verarbeitung befindet. Um die Schnittstelle nicht mehrfach in der Sekunde anzusprechen und damit die Kosten in die Höhe zu treiben, habe ich die Anfragen durch das Timeout künstlich reduziert. Wenn dir das nicht gefällt, kannst du die Timeouts entweder entfernen oder verkürzen. Solltest du sie jedoch entfernen, musst du den darin enthaltenen Code dennoch aufrufen.
Ich kann nun mit einer Konversation starten und den Bot zu den verschiedensten Dingen befragen. Der Gesprächsfluss wirkt wie schon von ChatGPT bekannt sehr natürlich und angenehm. Ich habe jedenfalls nicht immer den Eindruck, mit einer Maschine zu kommunizieren. Eben deshalb habe ich meinem Assistenten auch einen Namen gegeben, der sich mit dem meines Sprachassistenten deckt. Das erweckt den Eindruck, dass es im gesamten Haus einen sehr intelligenten und allgegenwärtigen Bot gibt, der über alle Informationen verfügt.
Tipp für die Erstellung deines Assistenten
Ein ebenfalls ganz charmanter Weg, um den Assistenten für deinen ioBroker AI-Chatbot vorher ohne große Kosten zu testen, sind die GPTs. Hier kannst du die gleichen Instruktionen hinterlegen, wie du eben bei der Schnittstelle gesehen hast. Logge dich dazu bei ChatGPT ein und wechsle links im Menü auf den Punkt Explore GPTs. Klicke dann oben rechts auf Create.
Im Tab Configure kannst du die eben gezeigte Konfiguration für deinen Bot hinterlegen und seine Kommunikation testen. Wechselst du hingegen auf das Tab Create, so kannst du in natürlicher Sprache und mit Unterstützung durch ChatGPT einen eigenen Bot erschaffen. Wenn dir der Bot am Ende gefällt, kannst du im Tab Configure die Konfiguration einsehen und die darin enthaltenen Werte einfach in deinen Assistenten kopieren.
Beachte dabei jedoch, dass du für die Nutzung der GPTs die Pro-Version von ChatGPT brauchst, die dich pro Monat auch ungefähr 20 Dollar kostet. Wenn du sie jedoch schon nutzt, kannst du so deinen Assistenten vorab ohne zusätzliche Kosten ausprobieren und die Feinheiten nochmal nachjustieren.
Alternative zu OpenAI
Wer sich nun bei der Umsetzung des Projekts denkt, dass es doch auch anders gehen kann, liegt möglicherweise richtig. Zwar habe ich es selbst nicht ausprobiert, doch es gibt ein Open Source Projekt, das in meinen Augen sehr vielversprechend ist.
Die Rede ist hierbei von LocalAI. Einer Art alternativen Umsetzung von OpenAIs Produkten, welche lokal bei dir im Netzwerk läuft und über die gleichen Schnittstellen verfügen soll. Das Projekt kann von dir kostenlos auf GitHub heruntergeladen und ausprobiert werden. Inwiefern sich jedoch Assistenten damit umsetzen lassen und wie stark die Anpassung des Skripts sein muss (ich vermute sehr stark), kann ich an dieser Stelle nicht beurteilen. Möglicherweise lässt sich ein ioBroker AI-Chatbot jedoch auch damit umsetzen.
Dennoch ist auch das ein interessanter Ansatz, um künstliche Intelligenz (insbesondere im Smart Home) vollständig lokal zu betreiben. Damit verlassen deine Daten nicht dein eigenes Hoheitsgebiet und du musst dir weniger Gedanken um den Datenschutz machen. Allerdings muss man dazu sagen, dass das auf Kosten der Leistung geht. Denn künstliche Intelligenz braucht ordentlich Power. Du zwar die Daten nicht aus der Hand geben und kannst vollständig lokal arbeiten, musst jedoch auch die Rechenleistung zur Verfügung stellen.
Inwiefern das dann noch effizienter ist, muss jeder für sich selbst beurteilen. In diese Diskussion will ich mich gar nicht einmischen. In meinem Homelab setze ich das aktuell nicht ein.
Fazit zum ioBroker AI-Chatbot
Ich persönlich bin von den Möglichkeiten von OpenAI und einem OpenAI Chatbot grundsätzlich beeindruckt. Das merkt man sicherlich auch ein wenig im Artikel zum ioBroker AI-Chatbot, soll aber keinesfalls als Werbung verstanden werden. Ich experimentiere gerne mit Technologien und konnte mir mit diesem Projekt einen kleinen Traum erfüllen. Und zwar eine tiefergehende Integration einer künstlichen Intelligenz in mein Smart Home.
Gleichzeitig weiß ich aber auch, dass das im Grunde nur ein Meilenstein auf einer langen Reise ist. Und unter Umständen einiges an Geld kosten kann. Ebenfalls ist es für Laien oft schwer nachzuvollziehen und noch nicht die finale Lösung für die Integration in ein Smart Home System.
Viele Unternehmen werben ja aktuell mit dem Thema der künstlichen Intelligenz und packen eigentlich nur irgendwelche besonderen Funktionen in ihr Produkt. In meinem Smart Home werkelt jedoch nun wirklich eine KI. Zumindest für Dialoge. Und im Grunde kann man auf diese Integration sehr gut aufbauen. Die Kombination von Daten aus dem Smart Home, Wissen durch Dokumente und der natürliche Gesprächsfluss sorgen dafür, dass viele deiner Haushaltsmitglieder auch Zugang zu Wissen bekommen, das sonst vielleicht nicht so greifbar ist.
Die KI kann jedem individuell und mit der notwendigen Geduld erklären, wie was funktioniert. Das macht es dir als Betreiber des Smart Homes auch etwas einfacher und schafft dir mehr Freiraum für andere Dinge. Was ich persönlich sehr charmant finde.
Ob mein ioBroker AI-Chatbot jedoch Bestand haben wird, weiß ich noch nicht. Sicherlich ist es ein sehr geniales Gimmick. Doch wie viel kann man da noch rausholen?
Lassen wir uns überraschen und experimentieren weiter. Vielleicht hast du ja auch coole Ideen oder Inspirationen, die du gerne mit mir und den anderen teilen möchtest. Dann hinterlasse gerne einen Kommentar und lass uns darüber sprechen. Ich freue mich darauf.
Das vollständige ioBroker Skript
Version 1 des Skripts (hier musst du die Thread-ID wie im Blogpost beschrieben händisch hinterlegen)
var request = require('request');
var userText = null;
var user = null;
on({id: '0_userdata.0.t2cState', change: "ne"}, async function (obj) {
var task = JSON.parse(obj.state.val);
userText = task.text;
user = task.user;
sendToTelegram("typing");
askChatGPT();
});
const configuration = {
baseUrl: "https://api.openai.com/v1/threads/",
threadId: "DEINE_THREAD_ID",
assistantId: "DEINE_ASSISTENTEN_ID"
};
const header = {
"Content-Type": "application/json",
"Authorization": "Bearer DEIN_SECRET",
"OpenAI-Beta": "assistants=v1"
};
const config1 = {
method: 'post',
url: configuration.baseUrl + configuration.threadId + '/messages',
headers: header,
body: {
"role": "user",
"content": userText
},
json: true
};
const config2 = {
method: 'post',
url: configuration.baseUrl + configuration.threadId + '/runs',
headers: header,
body: {
"assistant_id": configuration.assistantId,
"instructions": "Sprich den Nutzer gerne mit seinem Namen an."
},
json: true
};
const config3 = {
method: 'get',
url: configuration.baseUrl + configuration.threadId + '/runs/',
headers: header
};
const config4 = {
method: 'get',
url: configuration.baseUrl + configuration.threadId + '/messages',
headers: header
};
const config5 = {
method: 'post',
url: configuration.baseUrl + configuration.threadId + '/runs/',
headers: header,
body: {
"tool_outputs": [{
"tool_call_id": null,
"output": null
}]
},
json: true
};
function askChatGPT() {
sendToTelegram("typing");
try {
config1.body.content = userText;
request.post(config1, function(error, response, body) {
if (!error && response.statusCode == 200) {
config2.body.instructions = "Der Benutzer heißt " + user;
runThread(config2);
} else {
sendToTelegram("Fehler in der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode + " (Debug: Ask CGPT, Outer-Else)");
}
});
} catch (error) {
sendToTelegram("Fehler bei der Herstellung der Verbindung zum Chatbot. (Debug: Ask CGPT, Catch)");
}
}
function runThread(config) {
sendToTelegram("typing");
try {
request.post(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
setTimeout(function() {
var localConfig = {...config3};
localConfig.url = config3.url + body.id;
retrieveState(localConfig);
}, 2000);
} else {
sendToTelegram("Fehler in der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode + " (Debug: Ask CGPT, Inner-Else)");
}
});
} catch(error) {
sendToTelegram("Fehler beim Starten des Threads.");
}
}
function retrieveState(config, retry = false) {
sendToTelegram("typing");
try {
request.get(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
var parsedBody = JSON.parse(body);
if (parsedBody.status == 'queued' || parsedBody.status == 'in_progress') {
setTimeout(function() {
retrieveState(config, true);
}, 3000);
} else if (parsedBody.status == 'completed') {
retrieveMessage(config4);
} else if (parsedBody.status == 'requires_action') {
retrieveInternalData(parsedBody.required_action.submit_tool_outputs.tool_calls[0].function.name, parsedBody.required_action.submit_tool_outputs.tool_calls[0].id, parsedBody.id);
} else {
sendToTelegram("Unbekannter Zustand. Status-Code: " + response.statusCode + " (Debug: Retrieve-State, Else)");
}
} else if (response.statusCode == 404 && retry == false) {
setTimeout(function() {
retrieveState(config, true);
}, 3000);
} else {
sendToTelegram("Fehler bei der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Abrufen des Status.");
}
}
function retrieveMessage(config) {
sendToTelegram("typing");
try {
request.get(config, function(error, response, body) {
if (!error && response.statusCode == 200) {
sendToTelegram(JSON.parse(body).data[0].content[0].text.value);
} else {
sendToTelegram("Fehler beim Abrufen der Nachricht. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Abrufen der Nachricht.");
}
}
function retrieveInternalData(functionName, callId, runId) {
switch (functionName) {
case "get_photovoltaics":
var pvData = (getState("shelly.0.shellypro4pm#083af27d01b4#1.Relay0.Power").val * (-1));
sendFunctionData({ power: pvData, unit: "Watt" }, callId, runId);
break;
default:
sendFunctionData("Not found", callId, runId);
break;
}
}
function sendFunctionData(input, callId, runId) {
sendToTelegram("typing");
try {
var config = JSON.parse(JSON.stringify(config5));
config.body.tool_outputs[0].tool_call_id = callId;
config.body.tool_outputs[0].output = JSON.stringify(input);
config.url = config5.url + runId + '/submit_tool_outputs';
request.post(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
var localConfig = {...config3};
localConfig.url = config3.url + runId;
retrieveState(localConfig);
} else {
sendToTelegram("Fehler bei der Übergabe der Daten. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Übergeben der Daten an den Chatbot.");
}
}
function sendToTelegram(text) {
sendTo("telegram.0", {
text: text,
user: user
});
}
Version 2 des Skripts
Die Version 2 des Skripts erstellt automatisch Threads und hinterlegt die IDs in einem Datenpunkt. Beachte bitte mein Update weiter oben im Artikel (im Abschnitt zur Thread-ID)!
var request = require('request');
var userText = null;
var user = null;
var threadIdOfUser = null;
on({id: '0_userdata.0.t2cState', change: "ne"}, async function (obj) {
var task = JSON.parse(obj.state.val);
userText = task.text;
user = task.user;
sendToTelegram("typing");
checkThread();
});
const configuration = {
baseUrl: "https://api.openai.com/v1/threads",
assistantId: "DEINE_ASSISTENTEN_ID"
};
const header = {
"Content-Type": "application/json",
"Authorization": "Bearer DEIN_SECRET",
"OpenAI-Beta": "assistants=v1"
};
const config0 = {
method: 'post',
url: configuration.baseUrl,
headers: header
};
const config1 = {
method: 'post',
url: configuration.baseUrl + '/' + threadIdOfUser + '/messages',
headers: header,
body: {
"role": "user",
"content": userText
},
json: true
};
const config2 = {
method: 'post',
url: configuration.baseUrl + '/' + threadIdOfUser + '/runs',
headers: header,
body: {
"assistant_id": configuration.assistantId,
"instructions": "Sprich den Nutzer gerne mit seinem Namen an."
},
json: true
};
const config3 = {
method: 'get',
url: configuration.baseUrl + '/' + threadIdOfUser + '/runs/',
headers: header
};
const config4 = {
method: 'get',
url: configuration.baseUrl + '/' + threadIdOfUser + '/messages',
headers: header
};
const config5 = {
method: 'post',
url: configuration.baseUrl + '/' + threadIdOfUser + '/runs/',
headers: header,
body: {
"tool_outputs": [{
"tool_call_id": null,
"output": null
}]
},
json: true
};
function updateConfig() {
config1.url = configuration.baseUrl + '/' + threadIdOfUser + '/messages';
config2.url = configuration.baseUrl + '/' + threadIdOfUser + '/runs';
config3.url = configuration.baseUrl + '/' + threadIdOfUser + '/runs/';
config4.url = configuration.baseUrl + '/' + threadIdOfUser + '/messages';
config5.url = configuration.baseUrl + '/' + threadIdOfUser + '/runs/';
}
function checkThread() {
sendToTelegram("typing");
try {
var userThreadIds = getState("0_userdata.0.openai-chatbot-threads").val;
if (userThreadIds == null || userThreadIds == "") {
userThreadIds = {};
setState("0_userdata.0.openai-chatbot-threads", JSON.stringify(userThreadIds));
} else {
userThreadIds = JSON.parse(userThreadIds);
}
if (userThreadIds.hasOwnProperty(user)) {
threadIdOfUser = userThreadIds[user]; // TODO: What if user thread id is empty !?
updateConfig();
askChatGPT();
} else {
createThread();
}
} catch (error) {
sendToTelegram("Fehler beim Prüfen des Threads.");
}
}
function createThread() {
sendToTelegram("typing");
try {
var userThreadIds = JSON.parse(getState("0_userdata.0.openai-chatbot-threads").val);
if (userThreadIds == null || userThreadIds == "") {
userThreadIds = {};
}
request.post(config0, function(error, response, body) {
if (!error && response.statusCode == 200) {
userThreadIds[user] = (JSON.parse(body)).id;
threadIdOfUser = (JSON.parse(body)).id;
updateConfig();
setState("0_userdata.0.openai-chatbot-threads", JSON.stringify(userThreadIds), true);
askChatGPT();
} else {
sendToTelegram("Fehler beim Erstellen eines Threads. Status-Code: " + response.statusCode);
}
});
} catch (error) {
sendToTelegram("Fehler beim Anlegen des Threads.");
}
}
function askChatGPT() {
sendToTelegram("typing");
try {
config1.body.content = userText;
request.post(config1, function(error, response, body) {
if (!error && response.statusCode == 200) {
config2.body.instructions = "Der Benutzer heißt " + user;
runThread(config2);
} else {
console.log(response);
sendToTelegram("Fehler in der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode + " (Debug: Ask CGPT, Outer-Else)");
}
});
} catch (error) {
sendToTelegram("Fehler bei der Herstellung der Verbindung zum Chatbot. (Debug: Ask CGPT, Catch)");
}
}
function runThread(config) {
sendToTelegram("typing");
try {
request.post(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
setTimeout(function() {
var localConfig = {...config3};
localConfig.url = config3.url + body.id;
retrieveState(localConfig);
}, 2000);
} else {
sendToTelegram("Fehler in der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode + " (Debug: Ask CGPT, Inner-Else)");
}
});
} catch(error) {
sendToTelegram("Fehler beim Starten des Threads.");
}
}
function retrieveState(config, retry = false) {
sendToTelegram("typing");
try {
request.get(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
var parsedBody = JSON.parse(body);
if (parsedBody.status == 'queued' || parsedBody.status == 'in_progress') {
setTimeout(function() {
retrieveState(config, true);
}, 3000);
} else if (parsedBody.status == 'completed') {
retrieveMessage(config4);
} else if (parsedBody.status == 'requires_action') {
retrieveInternalData(parsedBody.required_action.submit_tool_outputs.tool_calls[0].function.name, parsedBody.required_action.submit_tool_outputs.tool_calls[0].id, parsedBody.id);
} else {
sendToTelegram("Unbekannter Zustand. Status-Code: " + response.statusCode + " (Debug: Retrieve-State, Else)");
}
} else if (response.statusCode == 404 && retry == false) {
setTimeout(function() {
retrieveState(config, true);
}, 3000);
} else {
sendToTelegram("Fehler bei der Kommunikation mit dem Chatbot. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Abrufen des Status.");
}
}
function retrieveMessage(config) {
sendToTelegram("typing");
try {
request.get(config, function(error, response, body) {
if (!error && response.statusCode == 200) {
sendToTelegram(JSON.parse(body).data[0].content[0].text.value);
} else {
sendToTelegram("Fehler beim Abrufen der Nachricht. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Abrufen der Nachricht.");
}
}
function retrieveInternalData(functionName, callId, runId) {
switch (functionName) {
case "get_photovoltaics":
var pvData = (getState("shelly.0.shellypro4pm#083af27d01b4#1.Relay0.Power").val * (-1));
sendFunctionData({ power: pvData, unit: "Watt" }, callId, runId);
break;
default:
sendFunctionData("Not found", callId, runId);
break;
}
}
function sendFunctionData(input, callId, runId) {
sendToTelegram("typing");
try {
var config = JSON.parse(JSON.stringify(config5));
config.body.tool_outputs[0].tool_call_id = callId;
config.body.tool_outputs[0].output = JSON.stringify(input);
config.url = config5.url + runId + '/submit_tool_outputs';
request.post(config, function (error, response, body) {
if (!error && response.statusCode == 200) {
var localConfig = {...config3};
localConfig.url = config3.url + runId;
retrieveState(localConfig);
} else {
sendToTelegram("Fehler bei der Übergabe der Daten. Status-Code: " + response.statusCode);
}
});
} catch(error) {
sendToTelegram("Fehler beim Übergeben der Daten an den Chatbot.");
}
}
function sendToTelegram(text) {
sendTo("telegram.0", {
text: text,
user: user
});
}
4 Kommentare
Udo · 22. April 2024 um 13:21
Interessanter Artikel.
Wenn ich nichts überlesen habe, fehlen hier aber reale Anwendungsbeispiele?
Was genau bringt mir das und was genau macht die KI jetzt im Smarthome?
Lukas · 22. April 2024 um 17:51
Hallo Udo,
die KI macht an sich mit deinem Smart Home nichts. Sie bezieht jedoch durch die Functions ihre Informationen direkt aus dem Smart Home und kann so auf Basis deiner eigenen Daten Fragen beantworten.
Recht am Anfang des Artikels habe ich dazu ein Beispiel. Der Chatbot kann dir deine eigene Struktur besser erklären und auch zusätzliche Infos liefern.
Wie das natürlich in der Praxis nutzbar ist, das ist ein anderes Thema. Hier in diesem Artikel habe ich mich vor allem auf die technische Umsetzung konzentriert. Ich wollte damit zeigen, was alles möglich ist.
Smarte Grüße
DarkWolf · 18. Februar 2024 um 20:14
Moin,
wie bei Threads angedroht, habe ich mir deinen Artikel jetzt endlich durchgelesen.
Wirklich verständlich beschrieben, wie bei deinen anderen Artikeln hier auch, zumindest die ich bisher gelesen habe 😉
Aktuell läuft bei mir selbst leider kein iobroker mehr. Aber nach diesem Artikel möchte ich das natürlich auch mal „nachbauen“ – bin ja neugierig und mag solche technischen „Spielereien“!
Brauche dann nur noch die Zeit, alles wieder einzurichten. Aber noch mal: richtig guter Artikel! Bin mir ziemlich sicher, dass ich öfter vorbeischauen werde. 🙂
Schönen Abend noch!
lg
DarkWolf
Lukas · 19. Februar 2024 um 18:20
Hey,
danke für dein positives Feedback, das freut mich sehr! 😊
Diese technischen Spielereien sind absolut interessant. Zumal sie manchmal sogar noch Inspiration für viel mehr Ideen liefern können.
Oder auch als Ausgangspunkt für größere Projekte dienen. Das macht es so besonders in meinen Augen.
Smarte Grüße