OpenAI, ChatGPT için gelişmiş Ses Modlarının yayınlanmasını geciktirirken , LLM ses uygulamamızı nasıl oluşturduğumuzu ve onu etkileşimli bir kabine nasıl entegre ettiğimizi paylaşmak istiyorum.
Şubat ayının sonunda Bali, ünlü Burning Man ilkelerine göre düzenlenen Lampu festivaline ev sahipliği yaptı. Geleneğe göre katılımcılar kendi enstalasyonlarını ve sanat objelerini yaratıyorlar.
Camp 19:19'daki arkadaşlarım ve ben, Katolik günah çıkarma salonları fikrinden ve mevcut yüksek lisansların yeteneklerinden esinlenerek, herkesin bir yapay zeka ile konuşabileceği kendi yapay zeka günah çıkarma odamızı inşa etme fikrini ortaya attık.
Bunu en başında şöyle hayal etmiştik:
Konsepti test etmek ve LLM'ye yönelik bir istemle denemeler yapmaya başlamak için bir akşam saf bir uygulama oluşturdum:
Bu demoyu uygulamak için tamamen OpenAI'nin bulut modellerine güvendim: Whisper , GPT-4 ve TTS . Mükemmel konuşma_recognition kütüphanesi sayesinde demoyu yalnızca birkaç düzine kod satırıyla oluşturdum.
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())
Bu demodaki ilk testlerden sonra çözmemiz gereken sorunlar hemen ortaya çıktı:
Bu sorunları nasıl çözeceğimize dair bir seçeneğimiz vardı: uygun bir mühendislik veya ürün çözümü arayarak.
Kodlamaya başlamadan önce kullanıcının kabinle nasıl etkileşim kuracağına karar vermemiz gerekiyordu:
Kabinde yeni bir kullanıcıyı tespit etmek için çeşitli seçenekleri değerlendirdik: kapı açma sensörleri, zemin ağırlığı sensörleri, mesafe sensörleri ve kamera + YOLO modeli. Arkadaki mesafe sensörü, kapının yeterince sıkı kapatılmaması gibi kazara tetiklenenleri hariç tuttuğu ve ağırlık sensörünün aksine karmaşık kurulum gerektirmediği için bize en güvenilir göründü.
Bir diyaloğun başlangıcını ve sonunu tanıma zorluğunu ortadan kaldırmak için mikrofonu kontrol edecek büyük bir kırmızı düğme eklemeye karar verdik. Bu çözüm aynı zamanda kullanıcının yapay zekayı istediği zaman kesmesine de olanak tanıdı.
Bir isteğin işlenmesinde geri bildirimin uygulanması konusunda birçok farklı fikrimiz vardı. Sistemin ne yaptığını gösteren ekranlı bir seçeneğe karar verdik: mikrofonu dinlemek, bir soruyu işlemek veya yanıtlamak.
Ayrıca eski bir sabit hatlı telefonla oldukça akıllı bir seçenek de düşündük. Oturum, kullanıcı telefonu açtığında başlayacak ve sistem, kullanıcı telefonu kapatana kadar onu dinleyecektir. Ancak, kullanıcıya telefondan gelen bir ses yerine kabin tarafından "cevap verilmesinin" daha gerçekçi olacağına karar verdik.
Sonunda son kullanıcı akışı şu şekilde ortaya çıktı:
Arduino, mesafe sensörünün ve kırmızı düğmenin durumunu izler. Tüm değişiklikleri HTTP API aracılığıyla arka uçumuza gönderir; bu, sistemin kullanıcının kabine girip girmediğini ve mikrofonu dinlemeyi etkinleştirmenin veya bir yanıt oluşturmaya başlamanın gerekli olup olmadığını belirlemesine olanak tanır.
Web kullanıcı arayüzü , sistemin mevcut durumunu sürekli olarak arka uçtan alan ve kullanıcıya görüntüleyen, bir tarayıcıda açılan bir web sayfasıdır.
Arka uç mikrofonu kontrol eder, gerekli tüm yapay zeka modelleriyle etkileşime girer ve LLM yanıtlarını seslendirir. Uygulamanın temel mantığını içerir.
Arduino için bir taslağın nasıl kodlanacağı, mesafe sensörünün ve düğmenin doğru şekilde nasıl bağlanacağı ve hepsinin kabinde nasıl monte edileceği ayrı bir makalenin konusudur. Teknik detaylara girmeden elimizdekileri kısaca gözden geçirelim.
Arduino'yu, daha doğrusu yerleşik Wi-Fi modülüne sahip ESP32 modelini kullandık. Mikrodenetleyici, arka ucu çalıştıran dizüstü bilgisayarla aynı Wi-Fi ağına bağlıydı.
Kullandığımız donanımların tam listesi:
Boru hattının ana bileşenleri Konuşmadan Metne (STT), LLM ve Metinden Konuşmaya (TTS)'dir. Her görev için hem yerel olarak hem de bulut üzerinden birçok farklı model mevcuttur.
Elimizde güçlü bir GPU bulunmadığından modellerin bulut tabanlı versiyonlarını tercih etmeye karar verdik. Bu yaklaşımın zayıflığı iyi bir internet bağlantısına duyulan ihtiyaçtır. Yine de tüm optimizasyonlardan sonraki etkileşim hızı, festivalde sahip olduğumuz mobil internete rağmen kabul edilebilir düzeydeydi.
Şimdi boru hattının her bir bileşenine daha yakından bakalım.
Birçok modern cihaz uzun süredir konuşma tanımayı desteklemektedir. Örneğin, Apple Speech API, iOS ve macOS için mevcuttur ve Web Speech API, tarayıcılar içindir.
Maalesef kalite açısından Whisper veya Deepgram'a göre çok düşüktürler ve dili otomatik olarak algılayamazlar.
İşlem süresini azaltmak için en iyi seçenek, kullanıcı konuşurken konuşmayı gerçek zamanlı olarak tanımaktır. İşte bunların nasıl uygulanacağına dair örnekler içeren bazı projeler:
Dizüstü bilgisayarımızda bu yaklaşımı kullanan konuşma tanıma hızının gerçek zamanlı olmaktan çok uzak olduğu ortaya çıktı. Birkaç denemeden sonra OpenAI'nin bulut tabanlı Whisper modeline karar verdik.
Önceki adımdaki Speech To Text modelinin sonucu, diyalog geçmişiyle birlikte LLM'ye gönderdiğimiz metindir.
Yüksek Lisans seçerken GPT-3.5'i karşılaştırdık. GPT-4 ve Claude. Anahtar faktörün spesifik modelden ziyade konfigürasyonu olduğu ortaya çıktı. Sonunda cevaplarını diğerlerinden daha çok beğendiğimiz GPT-4'te karar kıldık.
LLM modelleri için istemin özelleştirilmesi ayrı bir sanat formu haline geldi. İnternette modelinizi ihtiyacınıza göre nasıl ayarlayacağınızla ilgili birçok kılavuz vardır:
Modelin ilgi çekici, kısa ve esprili bir şekilde yanıt vermesini sağlamak için istem ve sıcaklık ayarlarıyla kapsamlı denemeler yapmak zorunda kaldık.
LLM'den alınan yanıtı Text-To-Speech modelini kullanarak seslendiriyoruz ve kullanıcıya dinletiyoruz. Bu adım, demomuzdaki gecikmelerin ana kaynağıydı.
LLM'lerin yanıt vermesi oldukça uzun zaman alıyor. Bununla birlikte, akış modunda jeton bazında yanıt oluşturmayı desteklerler. Bu özelliği, LLM'den tam bir yanıt beklemeden, tek tek ifadeleri alındıkça seslendirerek bekleme süresini optimize etmek için kullanabiliriz.
Kullanıcının ilk parçayı dinlediği süreyi, LLM'den gelen yanıtın geri kalan bölümlerinin işlenmesindeki gecikmeyi gizlemek için kullanırız. Bu yaklaşım sayesinde yanıt gecikmesi yalnızca başlangıçta meydana gelir ve ~3 saniyedir.
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; }
Tüm optimizasyonlarımıza rağmen 3-4 saniyelik bir gecikme hala önemli. Kullanıcıyı yanıtın askıda kaldığı hissinden kurtarmak için kullanıcı arayüzüne geri bildirimle dikkat etmeye karar verdik. Birkaç yaklaşıma baktık:
Arka ucu yoklayan ve mevcut duruma göre animasyonlar gösteren basit bir web sayfasıyla son seçeneğe karar verdik.
Yapay zeka itiraf odamız dört gün boyunca faaliyet gösterdi ve yüzlerce katılımcının ilgisini çekti. OpenAI API'lerine yaklaşık 50$ harcadık. Karşılığında önemli ölçüde olumlu geri bildirimler ve değerli izlenimler aldık.
Bu küçük deney, sınırlı kaynaklara ve zorlu dış koşullara rağmen bir LLM'ye sezgisel ve etkili bir ses arayüzü eklemenin mümkün olduğunu gösterdi.
Bu arada GitHub'da bulunan arka uç kaynakları