paint-brush
Comment créer votre propre confessionnal d'IA : comment ajouter une voix au LLMpar@slavasobolev
1,140 lectures
1,140 lectures

Comment créer votre propre confessionnal d'IA : comment ajouter une voix au LLM

par Iaroslav Sobolev11m2024/07/14
Read on Terminal Reader

Trop long; Pour lire

Fin février, Bali a accueilli le festival [Lampu](https://lampu.org/), organisé selon les principes du célèbre Burning Man. Nous avons été inspirés par l’idée des confessionnaux catholiques et par les capacités du LLM actuel. Nous avons construit notre propre confessionnal d’IA, où n’importe qui peut parler à une intelligence artificielle.
featured image - Comment créer votre propre confessionnal d'IA : comment ajouter une voix au LLM
Iaroslav Sobolev HackerNoon profile picture
0-item
1-item

Alors qu'OpenAI retarde la sortie des modes vocaux avancés pour ChatGPT, je souhaite partager comment nous avons construit notre application vocale LLM et l'avons intégrée dans un stand interactif.

Parlez à l'IA dans la jungle

Fin février, Bali a accueilli le festival Lampu , organisé selon les principes du célèbre Burning Man. Selon la tradition, les participants créent leurs propres installations et objets d'art.


Mes amis du Camp 19:19 et moi-même, inspirés par l'idée des confessionnaux catholiques et par les capacités des LLM actuels, avons eu l'idée de construire notre propre confessionnal IA, où n'importe qui pourrait parler à une intelligence artificielle.


Voici comment nous l’envisagions au tout début :

  • Lorsque l'utilisateur entre dans un stand, nous déterminons que nous devons démarrer une nouvelle session.


  • L'utilisateur pose une question et l'IA écoute et répond. Nous voulions créer un environnement de confiance et privé où chacun pourrait discuter ouvertement de ses pensées et de ses expériences.


  • Lorsque l'utilisateur quitte la salle, le système met fin à la session et oublie tous les détails de la conversation. Ceci est nécessaire pour garder toutes les boîtes de dialogue privées.

Preuve de concept

Pour tester le concept et commencer à expérimenter une invite pour le LLM, j'ai créé une implémentation naïve en une soirée :

  • Écoutez un microphone.
  • Reconnaissez la parole des utilisateurs à l'aide du modèle Speech-to-Text (STT) .
  • Générez une réponse via LLM .
  • Synthétisez une réponse vocale à l'aide du modèle Text-to-Speech (TTS) .
  • Relisez la réponse à l’utilisateur.



Pour implémenter cette démo, je me suis entièrement appuyé sur les modèles cloud d'OpenAI : Whisper , GPT-4 et TTS . Grâce à l'excellente bibliothèque Speech_recognition , j'ai construit la démo en seulement quelques dizaines de lignes de code.


 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())


Les problèmes que nous avons dû résoudre sont immédiatement apparus après les premiers tests de cette démo :

  • Délai de réponse . Dans une implémentation naïve, le délai entre la question de l'utilisateur et la réponse est de 7 à 8 secondes ou plus. Ce n’est pas bon, mais il existe évidemment de nombreuses façons d’optimiser le temps de réponse.


  • Bruit ambiant . Nous avons découvert que dans les environnements bruyants, nous ne pouvons pas compter sur le microphone pour détecter automatiquement quand un utilisateur a commencé et fini de parler. Reconnaître le début et la fin d'une phrase ( endpointing ) est une tâche non triviale. Ajoutez à cela l’environnement bruyant d’un festival de musique et il est clair qu’une approche conceptuellement différente est nécessaire.


  • Imitez une conversation en direct . Nous voulions donner à l'utilisateur la possibilité d'interrompre l'IA. Pour y parvenir, il faudrait garder le micro allumé. Mais dans ce cas, il faudrait séparer la voix de l'utilisateur non seulement des sons de fond mais aussi de la voix de l'IA.


  • Retour . En raison du délai de réponse, il nous a parfois semblé que le système était gelé. Nous avons réalisé que nous devions informer l'utilisateur de la durée de traitement de la réponse.


Nous avions le choix entre la manière de résoudre ces problèmes : en recherchant une solution d'ingénierie ou de produit adaptée.

Réfléchir à l'UX du stand

Avant même de commencer à coder, nous avons dû décider comment l'utilisateur interagirait avec le stand :

  • Nous devons décider comment détecter un nouvel utilisateur dans la cabine pour réinitialiser l'historique des dialogues passés.


  • Comment reconnaître le début et la fin du discours d'un utilisateur, et que faire s'il souhaite interrompre l'IA.


  • Comment mettre en œuvre un feedback en cas de réponse retardée de l'IA.


Pour détecter un nouvel utilisateur dans la cabine, nous avons envisagé plusieurs options : des capteurs d'ouverture de porte, des capteurs de poids au sol, des capteurs de distance et une caméra + modèle YOLO. Le capteur de distance derrière le dos nous a semblé le plus fiable, car il excluait les déclenchements accidentels, comme lorsque la porte n'est pas suffisamment fermée, et ne nécessitait pas d'installation compliquée, contrairement au capteur de poids.


Pour éviter le défi de reconnaître le début et la fin d'un dialogue, nous avons décidé d'ajouter un gros bouton rouge pour contrôler le microphone. Cette solution permettait également à l'utilisateur d'interrompre l'IA à tout moment.


Nous avons eu de nombreuses idées différentes sur la mise en œuvre du feedback sur le traitement d'une demande. Nous avons opté pour une option avec un écran qui montre ce que fait le système : écouter le microphone, traiter une question ou répondre.


Nous avons également envisagé une option plutôt intelligente avec un ancien téléphone fixe. La session démarre lorsque l'utilisateur décroche le téléphone et le système écoute l'utilisateur jusqu'à ce qu'il raccroche. Cependant, nous avons décidé qu'il est plus authentique lorsque l'utilisateur reçoit une « réponse » par la cabine plutôt que par une voix provenant du téléphone.


Lors de l'installation et au festival


Au final, le flux utilisateur final s'est déroulé comme ceci :

  • Un utilisateur entre dans un stand. Un capteur de distance se déclenche dans son dos, et nous le saluons.


  • L'utilisateur appuie sur un bouton rouge pour démarrer une boîte de dialogue. Nous écoutons le microphone pendant que le bouton est enfoncé. Lorsque l'utilisateur relâche le bouton, nous commençons le traitement de la demande et l'indiquons à l'écran.


  • Si l'utilisateur souhaite poser une nouvelle question pendant que l'IA répond, il peut appuyer à nouveau sur le bouton et l'IA cessera immédiatement de répondre.


  • Lorsque l'utilisateur quitte la cabine, le capteur de distance se déclenche à nouveau et nous effaçons l'historique des dialogues.

Architecture


Arduino surveille l'état du capteur de distance et du bouton rouge. Il envoie toutes les modifications à notre backend via l'API HTTP, ce qui permet au système de déterminer si l'utilisateur est entré ou sorti de la cabine et s'il est nécessaire d'activer l'écoute du microphone ou de commencer à générer une réponse.


L' interface utilisateur Web est simplement une page Web ouverte dans un navigateur qui reçoit en permanence l'état actuel du système du backend et l'affiche à l'utilisateur.


Le backend contrôle le microphone, interagit avec tous les modèles d'IA nécessaires et exprime les réponses LLM. Il contient la logique de base de l'application.

Matériel

Comment coder un croquis pour Arduino, connecter correctement le capteur de distance et le bouton et assembler le tout dans la cabine est le sujet d'un article séparé. Passons brièvement en revue ce que nous avons obtenu sans entrer dans les détails techniques.


Nous avons utilisé un Arduino, plus précisément le modèle ESP32 avec un module Wi-Fi intégré. Le microcontrôleur était connecté au même réseau Wi-Fi que l’ordinateur portable, qui exécutait le backend.



Liste complète du matériel que nous avons utilisé :

Back-end

Les principaux composants du pipeline sont Speech-To-Text (STT), LLM et Text-To-Speech (TTS). Pour chaque tâche, de nombreux modèles différents sont disponibles localement et via le cloud.



Comme nous ne disposions pas d'un GPU puissant, nous avons décidé d'opter pour des versions cloud des modèles. La faiblesse de cette approche est la nécessité d’une bonne connexion Internet. Néanmoins, la vitesse d'interaction après toutes les optimisations était acceptable, même avec l'Internet mobile dont nous disposions au festival.


Examinons maintenant de plus près chaque composant du pipeline.

Reconnaissance de la parole

De nombreux appareils modernes prennent depuis longtemps en charge la reconnaissance vocale. Par exemple, l'API Apple Speech est disponible pour iOS et macOS, et l'API Web Speech est destinée aux navigateurs.


Malheureusement, leur qualité est très inférieure à celle de Whisper ou Deepgram et ne peuvent pas détecter automatiquement la langue.


Pour réduire le temps de traitement, la meilleure option consiste à reconnaître la parole en temps réel pendant que l'utilisateur parle. Voici quelques projets avec des exemples de mise en œuvre : murmure_streaming , murmure.cpp


Avec notre ordinateur portable, la vitesse de reconnaissance vocale utilisant cette approche s'est avérée loin d'être en temps réel. Après plusieurs expériences, nous avons opté pour le modèle Whisper basé sur le cloud d'OpenAI.

LLM et ingénierie rapide

Le résultat du modèle Speech To Text de l'étape précédente est le texte que nous envoyons au LLM avec l'historique des dialogues.


Lors du choix d'un LLM, nous avons comparé GPT-3.5. GPT-4 et Claude. Il s'est avéré que le facteur clé n'était pas tant le modèle spécifique que sa configuration. Finalement, nous avons opté pour GPT-4, dont les réponses nous ont plus plu que les autres.


La personnalisation de l'invite pour les modèles LLM est devenue une forme d'art à part entière. Il existe de nombreux guides sur Internet expliquant comment régler votre modèle selon vos besoins :



Nous avons dû expérimenter de manière approfondie les paramètres d'invite et de température pour que le modèle réponde de manière engageante, concise et humoristique.

Texte pour parler

Nous exprimons la réponse reçue du LLM en utilisant le modèle Text-To-Speech et la lisons à l'utilisateur. Cette étape a été la principale source de retard dans notre démo.


Les LLM mettent beaucoup de temps à répondre. Cependant, ils prennent en charge la génération de réponses en mode streaming – jeton par jeton. Nous pouvons utiliser cette fonctionnalité pour optimiser le temps d'attente en exprimant des phrases individuelles au fur et à mesure de leur réception sans attendre une réponse complète du LLM.


Exprimer des phrases individuelles


  • Faites une requête au LLM.


  • Nous accumulons la réponse dans le tampon jeton par jeton jusqu'à ce que nous ayons une phrase complète de longueur minimale . Le paramètre de longueur minimale est important car il affecte à la fois l'intonation du voicing et le temps de retard initial.


  • Envoyez la phrase générée au modèle TTS et lisez le résultat à l'utilisateur. A cette étape, il est nécessaire de s'assurer qu'il n'y a pas de condition de concurrence critique dans l'ordre de lecture.


  • Répétez l'étape précédente jusqu'à la fin de la réponse LLM


Nous utilisons le temps pendant lequel l'utilisateur écoute le fragment initial pour masquer le retard dans le traitement des parties restantes de la réponse du LLM. Grâce à cette approche, le délai de réponse ne se produit qu'au début et est d'environ 3 secondes.


 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; }


Touches finales

Même avec toutes nos optimisations, un délai de 3 à 4 secondes reste significatif. Nous avons décidé de nous occuper de l'interface utilisateur avec des commentaires pour éviter à l'utilisateur le sentiment que la réponse est bloquée. Nous avons examiné plusieurs approches :


  • Indicateurs LED . Nous devions afficher cinq états : inactif, attente, écoute, réflexion et parole. Mais nous ne parvenions pas à trouver comment le faire d'une manière facile à comprendre avec les LED.


  • Les mots de remplissage , tels que « Laissez-moi réfléchir », « Hmm », etc., imitent un discours réel. Nous avons rejeté cette option car les réponses ne correspondaient souvent pas au ton des réponses du modèle.


  • Mettez un écran dans la cabine. Et affichez différents états avec des animations.


Nous avons opté pour la dernière option avec une simple page Web qui interroge le backend et affiche des animations en fonction de l'état actuel.


Les résultats

Notre salle de confession IA a fonctionné pendant quatre jours et a attiré des centaines de participants. Nous avons dépensé environ 50 $ en API OpenAI. En retour, nous avons reçu des retours positifs substantiels et des impressions précieuses.


Cette petite expérience a montré qu'il est possible d'ajouter une interface vocale intuitive et efficace à un LLM même avec des ressources limitées et des conditions externes difficiles.


Au fait, les sources backend disponibles sur GitHub