paint-brush
So bauen Sie Ihren eigenen KI-Beichtstuhl: So fügen Sie dem LLM eine Stimme hinzuvon@slavasobolev
1,140 Lesungen
1,140 Lesungen

So bauen Sie Ihren eigenen KI-Beichtstuhl: So fügen Sie dem LLM eine Stimme hinzu

von Iaroslav Sobolev11m2024/07/14
Read on Terminal Reader

Zu lang; Lesen

Ende Februar fand auf Bali das [Lampu](https://lampu.org/)-Festival statt, das nach den Prinzipien des berühmten Burning Man organisiert wurde. Wir waren von der Idee katholischer Beichtstühle und den Möglichkeiten des aktuellen LLM inspiriert. Wir bauten unseren eigenen KI-Beichtstuhl, in dem jeder mit einer künstlichen Intelligenz sprechen konnte.
featured image - So bauen Sie Ihren eigenen KI-Beichtstuhl: So fügen Sie dem LLM eine Stimme hinzu
Iaroslav Sobolev HackerNoon profile picture
0-item
1-item

Während OpenAI die Veröffentlichung der erweiterten Sprachmodi für ChatGPT verzögert , möchte ich mitteilen, wie wir unsere LLM-Sprachanwendung erstellt und in einen interaktiven Stand integriert haben.

Sprich mit der KI im Dschungel

Ende Februar fand auf Bali das Lampu- Festival statt, das nach den Prinzipien des berühmten Burning Man veranstaltet wurde. Der Tradition entsprechend erstellen die Teilnehmer ihre eigenen Installationen und Kunstobjekte.


Inspiriert von der Idee katholischer Beichtstühle und den Möglichkeiten der aktuellen LLMs kamen meine Freunde vom Camp 19:19 und ich auf die Idee, unseren eigenen KI-Beichtstuhl zu bauen, in dem jeder mit einer künstlichen Intelligenz sprechen kann.


So haben wir uns das am Anfang vorgestellt:

  • Wenn der Benutzer eine Kabine betritt, stellen wir fest, dass wir eine neue Sitzung starten müssen.


  • Der Benutzer stellt eine Frage und die KI hört zu und antwortet. Wir wollten eine vertrauensvolle und private Umgebung schaffen, in der jeder offen über seine Gedanken und Erfahrungen sprechen kann.


  • Wenn der Benutzer den Raum verlässt, beendet das System die Sitzung und vergisst alle Gesprächsdetails. Dies ist notwendig, um alle Dialoge vertraulich zu halten.

Konzeptioneller Beweiß

Um das Konzept zu testen und mit einer Eingabeaufforderung für das LLM zu experimentieren, habe ich an einem Abend eine naive Implementierung erstellt:

  • Hören Sie einem Mikrofon zu.
  • Erkennen Sie Benutzersprache mithilfe des Speech-to-Text-Modells (STT) .
  • Generieren Sie eine Antwort über LLM .
  • Synthetisieren Sie eine Sprachantwort mithilfe des Text-to-Speech-Modells (TTS) .
  • Spielen Sie dem Benutzer die Antwort vor.



Um diese Demo zu implementieren, habe ich mich vollständig auf die Cloud-Modelle von OpenAI verlassen: Whisper , GPT-4 und TTS . Dank der hervorragenden Bibliothek speech_recognition konnte ich die Demo in nur wenigen Dutzend Codezeilen erstellen.


 import os import asyncio from dotenv import load_dotenv from io import BytesIO from openai import AsyncOpenAI from soundfile import SoundFile import sounddevice as sd import speech_recognition as sr load_dotenv() aiclient = AsyncOpenAI( api_key=os.environ.get("OPENAI_API_KEY") ) SYSTEM_PROMPT = """ You are helpfull assistant. """ async def listen_mic(recognizer: sr.Recognizer, microphone: sr.Microphone): audio_data = recognizer.listen(microphone) wav_data = BytesIO(audio_data.get_wav_data()) wav_data.name = "SpeechRecognition_audio.wav" return wav_data async def say(text: str): res = await aiclient.audio.speech.create( model="tts-1", voice="alloy", response_format="opus", input=text ) buffer = BytesIO() for chunk in res.iter_bytes(chunk_size=4096): buffer.write(chunk) buffer.seek(0) with SoundFile(buffer, 'r') as sound_file: data = sound_file.read(dtype='int16') sd.play(data, sound_file.samplerate) sd.wait() async def respond(text: str, history): history.append({"role": "user", "content": text}) completion = await aiclient.chat.completions.create( model="gpt-4", temperature=0.5, messages=history, ) response = completion.choices[0].message.content await say(response) history.append({"role": "assistant", "content": response}) async def main() -> None: m = sr.Microphone() r = sr.Recognizer() messages = [{"role": "system", "content": SYSTEM_PROMPT}] with m as source: r.adjust_for_ambient_noise(source) while True: wav_data = await listen_mic(r, source) transcript = await aiclient.audio.transcriptions.create( model="whisper-1", temperature=0.5, file=wav_data, response_format="verbose_json", ) if transcript.text == '' or transcript.text is None: continue await respond(transcript.text, messages) if __name__ == '__main__': asyncio.run(main())


Die Probleme, die wir lösen mussten, wurden bereits nach den ersten Tests dieser Demo deutlich:

  • Antwortverzögerung . Bei einer naiven Implementierung beträgt die Verzögerung zwischen Benutzerfrage und Antwort 7-8 Sekunden oder länger. Das ist nicht gut, aber es gibt offensichtlich viele Möglichkeiten, die Antwortzeit zu optimieren.


  • Umgebungsgeräusche . Wir haben festgestellt, dass wir uns in lauten Umgebungen nicht darauf verlassen können, dass das Mikrofon automatisch erkennt, wann ein Benutzer mit dem Sprechen begonnen und aufgehört hat. Das Erkennen des Anfangs und des Endes einer Phrase ( Endpunktbildung ) ist keine triviale Aufgabe. Kombiniert man dies mit der lauten Umgebung eines Musikfestivals, ist klar, dass ein konzeptionell anderer Ansatz erforderlich ist.


  • Live-Gespräche nachahmen . Wir wollten dem Benutzer die Möglichkeit geben, die KI zu unterbrechen. Dazu müssten wir das Mikrofon eingeschaltet lassen. In diesem Fall müssten wir die Stimme des Benutzers jedoch nicht nur von den Hintergrundgeräuschen, sondern auch von der Stimme der KI trennen.


  • Feedback . Aufgrund der Antwortverzögerung schien es uns manchmal, als sei das System eingefroren. Wir erkannten, dass wir den Benutzer darüber informieren müssen, wie lange die Antwort verarbeitet wird


Wir hatten die Wahl, diese Probleme zu lösen: durch die Suche nach einer geeigneten technischen oder Produktlösung.

Die UX des Messestandes durchdenken

Bevor wir überhaupt mit dem Code begannen, mussten wir entscheiden, wie der Benutzer mit dem Stand interagieren würde:

  • Wir sollten entscheiden, wie ein neuer Benutzer in der Kabine erkannt werden kann, um den bisherigen Dialogverlauf zurückzusetzen.


  • Wie erkennt man den Anfang und das Ende der Rede eines Benutzers und was ist zu tun, wenn dieser die KI unterbrechen möchte?


  • So implementieren Sie Feedback, wenn die KI verzögert reagiert.


Um einen neuen Benutzer in der Kabine zu erkennen, haben wir mehrere Optionen in Betracht gezogen: Türöffnungssensoren, Bodengewichtssensoren, Abstandssensoren und ein Kamera-+YOLO-Modell. Der Abstandssensor hinter der Rückseite erschien uns am zuverlässigsten, da er versehentliche Auslöser ausschloss, etwa wenn die Tür nicht fest genug geschlossen ist, und im Gegensatz zum Gewichtssensor keine komplizierte Installation erforderte.


Um die Herausforderung zu vermeiden, den Anfang und das Ende eines Dialogs zu erkennen, haben wir beschlossen, einen großen roten Knopf zur Steuerung des Mikrofons hinzuzufügen. Diese Lösung ermöglicht es dem Benutzer auch, die KI jederzeit zu unterbrechen.


Zur Implementierung einer Rückmeldung bei der Bearbeitung einer Anfrage hatten wir viele verschiedene Ideen. Wir entschieden uns für eine Variante mit einem Bildschirm, der anzeigt, was das System gerade macht: das Abhören des Mikrofons, die Bearbeitung einer Frage oder eine Antwort.


Wir haben auch eine ziemlich clevere Option mit einem alten Festnetztelefon in Betracht gezogen. Die Sitzung würde beginnen, wenn der Benutzer den Hörer abnimmt, und das System würde dem Benutzer zuhören, bis er auflegt. Wir haben jedoch entschieden, dass es authentischer ist, wenn der Benutzer von der Kabine „beantwortet“ wird, als von einer Stimme aus dem Telefon.


Beim Aufbau und auf dem Festivalgelände


Am Ende sah der endgültige Benutzerfluss wie folgt aus:

  • Ein Benutzer betritt eine Kabine. Hinter seinem Rücken wird ein Abstandssensor ausgelöst und wir begrüßen ihn.


  • Der Benutzer drückt einen roten Knopf, um einen Dialog zu starten. Während der Knopf gedrückt wird, hören wir das Mikrofon. Wenn der Benutzer den Knopf loslässt, beginnen wir mit der Verarbeitung der Anfrage und zeigen sie auf dem Bildschirm an.


  • Wenn der Benutzer eine neue Frage stellen möchte, während die KI antwortet, kann er die Taste erneut drücken und die KI beendet die Antwort sofort.


  • Wenn der Benutzer die Kabine verlässt, löst der Abstandssensor erneut aus und wir löschen den Dialogverlauf.

Die Architektur


Arduino überwacht den Zustand des Abstandssensors und des roten Knopfs. Es sendet alle Änderungen über die HTTP-API an unser Backend, wodurch das System feststellen kann, ob der Benutzer die Kabine betreten oder verlassen hat und ob es erforderlich ist, das Abhören des Mikrofons zu aktivieren oder mit der Generierung einer Antwort zu beginnen.


Die Web-Benutzeroberfläche ist lediglich eine in einem Browser geöffnete Webseite, die kontinuierlich den aktuellen Status des Systems vom Backend empfängt und dem Benutzer anzeigt.


Das Backend steuert das Mikrofon, interagiert mit allen erforderlichen KI-Modellen und spricht die LLM-Antworten aus. Es enthält die Kernlogik der App.

Hardware

Wie man einen Sketch für Arduino codiert, den Abstandssensor und den Knopf richtig anschließt und alles in der Kabine zusammenbaut, ist ein Thema für einen separaten Artikel. Lassen Sie uns kurz zusammenfassen, was wir bekommen haben, ohne auf technische Details einzugehen.


Wir verwendeten einen Arduino, genauer gesagt das Modell ESP32 mit eingebautem WLAN-Modul. Der Mikrocontroller war mit demselben WLAN-Netzwerk verbunden wie der Laptop, auf dem das Backend lief.



Vollständige Liste der von uns verwendeten Hardware:

Backend

Die Hauptkomponenten der Pipeline sind Speech-To-Text (STT), LLM und Text-To-Speech (TTS). Für jede Aufgabe stehen viele verschiedene Modelle sowohl lokal als auch über die Cloud zur Verfügung.



Da wir keinen leistungsstarken Grafikprozessor zur Verfügung hatten, entschieden wir uns für Cloud-basierte Versionen der Modelle. Die Schwäche dieses Ansatzes ist die Notwendigkeit einer guten Internetverbindung. Dennoch war die Interaktionsgeschwindigkeit nach allen Optimierungen akzeptabel, selbst mit dem mobilen Internet, das wir auf dem Festival hatten.


Sehen wir uns nun die einzelnen Komponenten der Pipeline genauer an.

Spracherkennung

Viele moderne Geräte unterstützen die Spracherkennung schon seit langem. Beispielsweise ist die Apple Speech API für iOS und macOS verfügbar, die Web Speech API für Browser.


Leider sind sie qualitativ viel schlechter als Whisper oder Deepgram und können die Sprache nicht automatisch erkennen.


Um die Verarbeitungszeit zu verkürzen, ist die beste Option die Spracherkennung in Echtzeit, während der Benutzer spricht. Hier sind einige Projekte mit Beispielen für die Implementierung: flüstern_streaming , flüstern.cpp


Bei unserem Laptop erwies sich die Geschwindigkeit der Spracherkennung mit diesem Ansatz als weit von Echtzeit entfernt. Nach mehreren Experimenten entschieden wir uns für das cloudbasierte Whisper-Modell von OpenAI.

LLM und Prompt Engineering

Das Ergebnis des Speech-to-Text-Modells aus dem vorherigen Schritt ist der Text, den wir mit dem Dialogverlauf an das LLM senden.


Bei der Auswahl eines LLM verglichen wir GPT-3.5, GPT-4 und Claude. Es stellte sich heraus, dass der entscheidende Faktor nicht so sehr das spezifische Modell war, sondern vielmehr seine Konfiguration. Letztendlich entschieden wir uns für GPT-4, dessen Antworten uns besser gefielen als die der anderen.


Die Anpassung der Eingabeaufforderung für LLM-Modelle ist zu einer eigenen Kunstform geworden. Im Internet gibt es viele Anleitungen, wie Sie Ihr Modell nach Bedarf anpassen können:



Wir mussten ausgiebig mit den Eingabeaufforderungs- und Temperatureinstellungen experimentieren, um eine ansprechende, präzise und humorvolle Reaktion des Modells zu erzielen.

Text zu Sprache

Wir sprechen die vom LLM erhaltene Antwort mithilfe des Text-to-Speech-Modells aus und spielen sie dem Benutzer vor. Dieser Schritt war die Hauptursache für Verzögerungen in unserer Demo.


LLMs brauchen ziemlich lange, um zu antworten. Sie unterstützen jedoch die Antwortgenerierung im Streaming-Modus – Token für Token. Wir können diese Funktion nutzen, um die Wartezeit zu optimieren, indem wir einzelne Phrasen aussprechen, sobald sie empfangen werden, ohne auf eine vollständige Antwort vom LLM zu warten.


Einzelne Sätze vertonen


  • Stellen Sie eine Anfrage an den LLM.


  • Wir sammeln die Antwort Token für Token im Puffer, bis wir einen vollständigen Satz mit Mindestlänge haben. Der Parameter Mindestlänge ist wichtig, da er sowohl die Intonation der Stimme als auch die anfängliche Verzögerungszeit beeinflusst.


  • Senden Sie den generierten Satz an das TTS-Modell und spielen Sie dem Benutzer das Ergebnis vor. Bei diesem Schritt muss sichergestellt werden, dass es in der Wiedergabereihenfolge keine Race Condition gibt.


  • Wiederholen Sie den vorherigen Schritt bis zum Ende der LLM-Antwort


Wir nutzen die Zeit, während der Benutzer das erste Fragment abhört, um die Verzögerung bei der Verarbeitung der restlichen Teile der Antwort vor dem LLM zu verbergen. Dank dieses Ansatzes tritt die Antwortverzögerung nur am Anfang auf und beträgt ~3 Sekunden.


 async generateResponse(history) { const completion = await this.ai.completion(history); const chunks = new DialogChunks(); for await (const chunk of completion) { const delta = chunk.choices[0]?.delta?.content; if (delta) { chunks.push(delta); if (chunks.hasCompleteSentence()) { const sentence = chunks.popSentence(); this.voice.ttsAndPlay(sentence); } } } const sentence = chunks.popSentence(); if (sentence) { this.voice.say(sentence); } return chunks.text; }


Letzter Schliff

Selbst mit all unseren Optimierungen ist eine Verzögerung von 3-4 Sekunden immer noch erheblich. Wir haben uns entschieden, die Benutzeroberfläche mit Feedback zu versehen, um dem Benutzer das Gefühl zu ersparen, dass die Antwort hängen bleibt. Wir haben uns mehrere Ansätze angesehen:


  • LED-Anzeigen . Wir mussten fünf Zustände anzeigen: Leerlauf, Warten, Zuhören, Denken und Sprechen. Aber wir konnten nicht herausfinden, wie wir das auf eine Weise machen konnten, die mit LEDs leicht verständlich war.


  • Füllwörter wie „Lass mich nachdenken“, „Hmm“ usw. ahmen die Sprache im wirklichen Leben nach. Wir haben diese Option verworfen, da Füllwörter oft nicht zum Ton der Antworten des Modells passten.


  • Platzieren Sie einen Bildschirm in der Kabine. Und zeigen Sie verschiedene Zustände mit Animationen an.


Wir haben uns für die letzte Option mit einer einfachen Webseite entschieden, die das Backend abfragt und entsprechend dem aktuellen Status Animationen anzeigt.


Die Ergebnisse

Unser KI-Beichtraum lief vier Tage lang und zog Hunderte von Teilnehmern an. Wir haben nur etwa 50 US-Dollar für OpenAI-APIs ausgegeben. Im Gegenzug erhielten wir viel positives Feedback und wertvolle Eindrücke.


Dieses kleine Experiment hat gezeigt, dass es auch mit begrenzten Ressourcen und herausfordernden äußeren Bedingungen möglich ist, einem LLM eine intuitive und effiziente Sprachschnittstelle hinzuzufügen.


Übrigens, die Backend-Quellen sind auf GitHub verfügbar