जबकि ओपनएआई चैटजीपीटी के लिए उन्नत वॉयस मोड्स को जारी करने में देरी कर रहा है, मैं यह साझा करना चाहता हूं कि हमने अपने एलएलएम वॉयस एप्लिकेशन को कैसे बनाया और इसे एक इंटरैक्टिव बूथ में एकीकृत किया।
फरवरी के अंत में, बाली ने लाम्पू उत्सव की मेजबानी की, जिसे प्रसिद्ध बर्निंग मैन के सिद्धांतों के अनुसार आयोजित किया गया था। इसकी परंपरा के अनुसार, प्रतिभागी अपनी खुद की स्थापनाएँ और कला वस्तुएँ बनाते हैं।
कैंप 19:19 के मेरे मित्र और मैं, कैथोलिक कन्फेशनल के विचार और वर्तमान एलएलएम की क्षमताओं से प्रेरित होकर, अपना स्वयं का एआई कन्फेशनल बनाने का विचार लेकर आए, जहां कोई भी कृत्रिम बुद्धिमत्ता से बात कर सकता था।
हमने शुरू में इसकी कल्पना इस प्रकार की थी:
अवधारणा का परीक्षण करने और एलएलएम के लिए एक संकेत के साथ प्रयोग शुरू करने के लिए, मैंने एक शाम में एक सरल कार्यान्वयन तैयार किया:
इस डेमो को लागू करने के लिए, मैंने पूरी तरह से OpenAI के क्लाउड मॉडल पर भरोसा किया: व्हिस्पर , GPT-4 और TTS । बेहतरीन लाइब्रेरी स्पीच_रिकग्निशन की बदौलत, मैंने कोड की कुछ दर्जन लाइनों में ही डेमो बना लिया।
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())
इस डेमो के प्रथम परीक्षण के बाद हमें जिन समस्याओं का समाधान करना था, वे तुरंत स्पष्ट हो गईं:
हमारे पास इन समस्याओं को हल करने का विकल्प था: उपयुक्त इंजीनियरिंग या उत्पाद समाधान की तलाश करके।
कोड लिखने से पहले ही हमें यह तय करना था कि उपयोगकर्ता बूथ के साथ किस प्रकार बातचीत करेगा:
बूथ में किसी नए उपयोगकर्ता का पता लगाने के लिए, हमने कई विकल्पों पर विचार किया: दरवाज़ा खोलने वाले सेंसर, फ़्लोर वेट सेंसर, डिस्टेंस सेंसर और एक कैमरा + YOLO मॉडल। पीछे की तरफ़ लगा डिस्टेंस सेंसर हमें सबसे ज़्यादा विश्वसनीय लगा, क्योंकि यह आकस्मिक ट्रिगर को रोकता है, जैसे कि जब दरवाज़ा पर्याप्त रूप से बंद न हो, और वज़न सेंसर के विपरीत, इसे जटिल इंस्टॉलेशन की आवश्यकता नहीं होती।
संवाद की शुरुआत और अंत को पहचानने की चुनौती से बचने के लिए, हमने माइक्रोफ़ोन को नियंत्रित करने के लिए एक बड़ा लाल बटन जोड़ने का फैसला किया। इस समाधान ने उपयोगकर्ता को किसी भी समय AI को बाधित करने की अनुमति दी।
किसी अनुरोध को संसाधित करने पर फ़ीडबैक लागू करने के बारे में हमारे पास कई अलग-अलग विचार थे। हमने एक स्क्रीन के साथ एक विकल्प पर फैसला किया जो दिखाता है कि सिस्टम क्या कर रहा है: माइक्रोफ़ोन सुनना, किसी प्रश्न को संसाधित करना, या उत्तर देना।
हमने पुराने लैंडलाइन फोन के साथ एक स्मार्ट विकल्प पर भी विचार किया। जब उपयोगकर्ता फोन उठाता है तो सत्र शुरू हो जाता है, और सिस्टम तब तक उपयोगकर्ता की बात सुनता है जब तक वह फोन नहीं काट देता। हालांकि, हमने फैसला किया कि यह अधिक प्रामाणिक है जब उपयोगकर्ता को फोन से आवाज के बजाय बूथ द्वारा "उत्तर" दिया जाता है।
अंत में, अंतिम उपयोगकर्ता प्रवाह इस प्रकार सामने आया:
Arduino दूरी सेंसर और लाल बटन की स्थिति पर नज़र रखता है। यह HTTP API के ज़रिए हमारे बैकएंड पर सभी बदलाव भेजता है, जो सिस्टम को यह निर्धारित करने की अनुमति देता है कि उपयोगकर्ता बूथ में प्रवेश कर चुका है या छोड़ चुका है और क्या माइक्रोफ़ोन को सुनना सक्रिय करना या प्रतिक्रिया उत्पन्न करना शुरू करना आवश्यक है।
वेब यूआई ब्राउज़र में खोला गया एक वेब पेज मात्र है जो बैकएंड से सिस्टम की वर्तमान स्थिति को निरंतर प्राप्त करता है और उसे उपयोगकर्ता को प्रदर्शित करता है।
बैकएंड माइक्रोफ़ोन को नियंत्रित करता है, सभी आवश्यक AI मॉडल के साथ बातचीत करता है, और LLM प्रतिक्रियाओं को आवाज़ देता है। इसमें ऐप का मुख्य तर्क शामिल है।
Arduino के लिए स्केच को कैसे कोड करें, डिस्टेंस सेंसर और बटन को सही तरीके से कैसे कनेक्ट करें, और बूथ में यह सब कैसे असेंबल करें, यह एक अलग लेख का विषय है। आइए तकनीकी विवरण में जाए बिना संक्षेप में समीक्षा करें कि हमें क्या मिला।
हमने एक Arduino, अधिक सटीक रूप से, मॉडल ESP32 का उपयोग किया जिसमें एक अंतर्निहित वाई-फाई मॉड्यूल था। माइक्रोकंट्रोलर लैपटॉप के समान वाई-फाई नेटवर्क से जुड़ा था, जो बैकएंड चला रहा था।
हमारे द्वारा उपयोग किये गये हार्डवेयर की पूरी सूची:
पाइपलाइन के मुख्य घटक स्पीच-टू-टेक्स्ट (एसटीटी), एलएलएम और टेक्स्ट-टू-स्पीच (टीटीएस) हैं। प्रत्येक कार्य के लिए, स्थानीय और क्लाउड दोनों के माध्यम से कई अलग-अलग मॉडल उपलब्ध हैं।
चूँकि हमारे पास शक्तिशाली GPU नहीं था, इसलिए हमने मॉडल के क्लाउड-आधारित संस्करण चुनने का फैसला किया। इस दृष्टिकोण की कमज़ोरी एक अच्छे इंटरनेट कनेक्शन की आवश्यकता है। फिर भी, सभी अनुकूलन के बाद भी इंटरेक्शन की गति स्वीकार्य थी, यहाँ तक कि त्यौहार में हमारे पास मौजूद मोबाइल इंटरनेट के साथ भी।
अब, आइए पाइपलाइन के प्रत्येक घटक पर करीब से नज़र डालें।
कई आधुनिक डिवाइस लंबे समय से स्पीच रिकग्निशन का समर्थन करते हैं। उदाहरण के लिए, Apple स्पीच API iOS और macOS के लिए उपलब्ध है, और वेब स्पीच API ब्राउज़र के लिए है।
दुर्भाग्यवश, वे व्हिस्पर या डीपग्राम की तुलना में गुणवत्ता में बहुत निम्न हैं और स्वचालित रूप से भाषा का पता नहीं लगा सकते हैं।
प्रोसेसिंग समय को कम करने के लिए, सबसे अच्छा विकल्प उपयोगकर्ता के बोलते समय वास्तविक समय में भाषण को पहचानना है। यहाँ कुछ प्रोजेक्ट दिए गए हैं, जिनमें उन्हें लागू करने के तरीके के उदाहरण दिए गए हैं:
हमारे लैपटॉप के साथ, इस दृष्टिकोण का उपयोग करके भाषण पहचान की गति वास्तविक समय से बहुत दूर निकली। कई प्रयोगों के बाद, हमने OpenAI के क्लाउड-आधारित व्हिस्पर मॉडल पर निर्णय लिया।
पिछले चरण के स्पीच टू टेक्स्ट मॉडल का परिणाम वह पाठ है जिसे हम संवाद इतिहास के साथ LLM को भेजते हैं।
एलएलएम चुनते समय, हमने GPT-3.5. GPT-4 और क्लाउड की तुलना की। यह पता चला कि मुख्य कारक विशिष्ट मॉडल नहीं बल्कि उसका कॉन्फ़िगरेशन था। अंततः, हमने GPT-4 पर सहमति जताई, जिसके उत्तर हमें दूसरों की तुलना में अधिक पसंद आए।
एलएलएम मॉडल के लिए प्रॉम्प्ट का अनुकूलन एक अलग कला रूप बन गया है। इंटरनेट पर आपके मॉडल को अपनी ज़रूरत के हिसाब से ट्यून करने के तरीके के बारे में कई गाइड मौजूद हैं:
हमें मॉडल को आकर्षक, संक्षिप्त और विनोदपूर्ण ढंग से प्रतिक्रिया देने के लिए प्रॉम्प्ट और तापमान सेटिंग्स के साथ बड़े पैमाने पर प्रयोग करना पड़ा।
हम टेक्स्ट-टू-स्पीच मॉडल का उपयोग करके एलएलएम से प्राप्त प्रतिक्रिया को आवाज़ देते हैं और इसे उपयोगकर्ता को वापस सुनाते हैं। यह कदम हमारे डेमो में देरी का प्राथमिक स्रोत था।
एलएलएम को प्रतिक्रिया देने में काफी समय लगता है। हालांकि, वे स्ट्रीमिंग मोड में प्रतिक्रिया निर्माण का समर्थन करते हैं - टोकन दर टोकन। हम इस सुविधा का उपयोग एलएलएम से पूर्ण प्रतिक्रिया की प्रतीक्षा किए बिना प्राप्त होने वाले अलग-अलग वाक्यांशों को आवाज़ देकर प्रतीक्षा समय को अनुकूलित करने के लिए कर सकते हैं।
हम उस समय का उपयोग करते हैं जब उपयोगकर्ता प्रारंभिक खंड को सुनता है ताकि LLM से प्रतिक्रिया के शेष भागों को संसाधित करने में होने वाली देरी को छिपाया जा सके। इस दृष्टिकोण के कारण, प्रतिक्रिया में देरी केवल शुरुआत में होती है और ~ 3 सेकंड होती है।
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; }
हमारे सभी अनुकूलन के बावजूद, 3-4 सेकंड की देरी अभी भी महत्वपूर्ण है। हमने उपयोगकर्ता को यह महसूस होने से बचाने के लिए फीडबैक के साथ UI का ख्याल रखने का फैसला किया कि प्रतिक्रिया रुकी हुई है। हमने कई तरीकों पर गौर किया:
हमने अंतिम विकल्प को चुना, जिसमें एक सरल वेब पेज था जो बैकएंड को पोल करता था तथा वर्तमान स्थिति के अनुसार एनिमेशन दिखाता था।
हमारा AI कन्फेशन रूम चार दिनों तक चला और इसमें सैकड़ों लोग शामिल हुए। हमने OpenAI API पर सिर्फ़ $50 खर्च किए। बदले में, हमें काफ़ी सकारात्मक प्रतिक्रियाएँ और मूल्यवान इंप्रेशन मिले।
इस छोटे से प्रयोग से पता चला कि सीमित संसाधनों और चुनौतीपूर्ण बाह्य परिस्थितियों के बावजूद भी एलएलएम में सहज और कुशल वॉयस इंटरफेस जोड़ना संभव है।
वैसे, बैकएंड स्रोत GitHub पर उपलब्ध हैं