V0.0.2
Openai-whisper-talk adalah aplikasi percakapan suara sampel yang ditenagai oleh teknologi openai seperti Whisper, sistem pengenalan ucapan otomatis (ASR), penyelesaian obrolan, antarmuka yang mensimulasikan percakapan dengan model yang memainkan peran asisten, embeddings, mengonversi teks vektor yang dapat digunakan dalam tugas-tugas seperti auding yang terbaru, dan teks-menempel ke teks yang terkini. Aplikasi ini dibangun menggunakan Nuxt, kerangka kerja JavaScript berdasarkan Vue.js.
Aplikasi ini memiliki dua fitur baru: manajemen jadwal dan memori jangka panjang. Dengan manajemen jadwal, Anda dapat memerintahkan chatbot untuk menambah, memodifikasi, menghapus, dan mengambil acara yang dijadwalkan. Fitur memori jangka panjang memungkinkan Anda untuk menyimpan cuplikan informasi yang akan diingat chatbot untuk referensi di masa mendatang. Anda dapat mengintegrasikan kedua fungsi dengan mulus ke dalam percakapan Anda hanya dengan berinteraksi dengan chatbot.
UPDATE: Memperbarui modul OpenAI ke versi terbaru dan mengganti model embedding dari
text-embedding-ada-002ke V3 Modeltext-embedding-3-smallyang baru.
Openai-whisper-talk は、 Whisper (自動音声認識( asr )システム)、 Penyelesaian obrolan (アシスタントの役割を果たすモデルとの会話をシミュレートするインターフェース)、 embeddings (セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)、そして最新の teks-ke-speech (テキストをリアルな話し言葉のオーディオに変える)など、 openai の技術を駆使したサンプル音声会話アプリケーションです。このアプリケーションは、 vue.js に基づいた javascript フレームワークである nuxt を使用して構築されています。
このアプリケーションには、「スケジュール管理」と「永続メモリ」の 2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、将来の参照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。将来的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

Dari halaman utama, Anda dapat memilih chatbot mana yang akan terlibat. Setiap chatbot memiliki kepribadian yang berbeda, berbicara bahasa yang berbeda, dan memiliki suara yang unik. Anda dapat mengubah nama dan kepribadian chatbot apa pun dengan mengklik tombol edit yang berdekatan dengan namanya. Saat ini, antarmuka pengguna tidak secara langsung mendukung penambahan chatbots baru; Namun, Anda dapat secara manual menambahkan chatbot dan menyesuaikan pengaturan suara dan bahasa untuk masing -masing dengan memodifikasi file aset/contacts.json.

Selain itu, Anda dapat mempersonalisasikan profil Anda dengan mengklik ikon Avatar di sudut kanan atas. Ini memungkinkan Anda untuk memasukkan nama Anda dan berbagi detail tentang diri Anda, memungkinkan chatbot untuk berinteraksi dengan Anda dengan cara yang lebih personal.


Data audio direkam secara otomatis jika suara terdeteksi. Pengaturan ambang batas tersedia untuk mencegah kebisingan latar belakang memicu penangkapan audio. Secara default, ini diatur ke -45dB (dengan -10db mewakili cutoff suara paling keras). Anda dapat menyesuaikan ambang batas ini dengan memodifikasi variabel MIN_DECIBELS 1 sesuai dengan kebutuhan Anda.
Ketika perekaman diaktifkan dan tidak ada suara yang terdeteksi selama 3 detik , data audio diunggah dan dikirim ke backend untuk transkripsi. Perlu dicatat bahwa dalam percakapan yang khas, kesenjangan rata -rata antara setiap belokan adalah sekitar 2 detik. Demikian pula, jeda antara kalimat ketika berbicara kira -kira sama. Karena itu, saya telah memilih durasi yang cukup lama untuk menunggu balasan. Anda dapat menyesuaikan durasi ini dengan mengedit variabel MAX_COUNT 1 .
Sistem dapat terus merekam data audio sampai balasan diterima. Setelah balasan audio dari chatbot diterima dan dimainkan, rekaman audio dinonaktifkan untuk mencegah rekaman yang tidak disengaja dari tanggapan chatbot sendiri.
Input teks juga disediakan jika Anda ingin menulis pesan Anda.
Semua data audio yang direkam diunggah ke direktori public/upload dalam format file webm. Sebelum mengirimkan file audio ke API Whisper, perlu untuk menghapus semua segmen diam. Langkah ini membantu mencegah masalah halusinasi yang terkenal yang dihasilkan oleh Whisper. Untuk alasan yang sama ini, disarankan untuk mengatur nilai MIN_DECIBELS setinggi mungkin, memastikan bahwa hanya pidato yang dicatat.
Untuk menghapus bagian diam dari audio, kami akan menggunakan ffmpeg . Pastikan untuk menginstalnya.
ffmpeg -i sourceFile -af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-50dB outputFileDalam perintah ini:
-1 sourceFile menentukan file input.-af silenceremove menerapkan filter silencerremove .stop_periods=-1 menghilangkan semua periode keheningan.stop_duration=1 menetapkan periode keheningan lebih dari 1 detik sebagai keheningan.stop_threshold=-50dB menetapkan tingkat kebisingan di bawah -50db sebagai keheningan.outputFile file output. Untuk memohon perintah shell ini di rute API kami, kami akan menggunakan exec dari modul child-process .
Setelah menghapus bagian yang sunyi, ukuran file harus diperiksa. Ukuran file akhir biasanya jauh lebih kecil dari aslinya. Selama pemeriksaan ini, file yang lebih kecil dari 16 kb diabaikan, dengan asumsi bahwa file ukuran ini, dengan kedalaman 16-bit, setara dengan sekitar satu detik audio. Apa pun yang lebih pendek kemungkinan tidak terdengar.
// simple file size formula
const audio_file_size = duration_in_seconds * bitrateSeluruh proses, dari menghilangkan bagian diam hingga memeriksa ukuran file, dirancang untuk memastikan bahwa hanya data audio yang layak yang dikirim ke API Whisper. Tujuan kami adalah untuk menghindari halusinasi dan transmisi data yang tidak perlu.
Setelah mengkonfirmasi kelayakan data audio kami, kami kemudian melanjutkan untuk menghubungi Whisper API.
const transcription = await openai . audio . transcriptions . create ( {
file : fs . createReadStream ( filename ) ,
language : lang ,
response_format : 'text' ,
temperature : 0 ,
} ) Di mana lang adalah kode ISO 639-1 dari bahasa yang ditentukan dari chatbot kami. Silakan periksa daftar bahasa yang saat ini didukung.
Kami memilih format text sederhana karena cap waktu tidak diperlukan, dan kami mengatur parameter temperature ke nol untuk mencapai output deterministik.
Setelah menerima transkrip dari Whisper, kami melanjutkan untuk mengirimkannya ke API penyelesaian obrolan dengan panggilan fungsi. Kami menggunakan modul OpenAI node.js terbaru (versi 4) yang dirilis kemarin (2023/11/07), yang mencakup format pemanggilan fungsi yang diperbarui. Iterasi terbaru ini memungkinkan doa beberapa fungsi dalam satu permintaan.
let messages = [ { role : 'system' , content : system_prompt } ]
let all_history = await mongoDb . getMessages ( )
if ( all_history . length > 0 ) {
const history_context = trim_array ( all_history , 20 )
messages = messages . concat ( history_context )
}
messages . push ( { role : 'user' , content : user_query } )
let response = await openai . chat . completions . create ( {
temperature : 0.3 ,
messages ,
tools : [
{ type : 'function' , function : add_calendar_entry } ,
{ type : 'function' , function : get_calendar_entry } ,
{ type : 'function' , function : edit_calendar_entry } ,
{ type : 'function' , function : delete_calendar_entry } ,
{ type : 'function' , function : save_new_memory } ,
{ type : 'function' , function : get_info_from_memory }
]
} )Mari kita bedah berbagai komponen dari panggilan ini di bagian selanjutnya.
Sistem prompt memainkan peran penting dalam memberikan kehidupan pada chatbot kita. Di sinilah kami menetapkan nama dan kepribadiannya, berdasarkan chatbot yang dipilih oleh pengguna. Kami memberikannya dengan instruksi khusus tentang cara merespons, bersama dengan daftar fungsi yang dapat dijalankannya. Kami juga menginformasikannya tentang identitas pengguna dan beberapa detail pribadi. Akhirnya, kami menetapkan tanggal dan waktu saat ini, yang sangat penting untuk mengaktifkan fungsi kalender.
const today = new Date ( )
let system_prompt = `In this session, we will simulate a voice conversation between two friends.nn` +
`# Personan` +
`You will act as ${ selPerson . name } .n` +
` ${ selPerson . prompt } nn` +
`Please ensure that your responses are consistent with this persona.nn` +
`# Instructionsn` +
`The user is talking to you over voice on their phone, and your response will be read out loud with realistic text-to-speech (TTS) technology.n` +
`Use natural, conversational language that are clear and easy to follow (short sentences, simple words).n` +
`Keep the conversation flowing.n` +
`Sometimes the user might just want to chat. Ask them relevant follow-up questions.nn` +
`# Functionsn` +
`You have the following functions that you can call depending on the situation.n` +
`add_calendar_entry, to add a new event.n` +
`get_calendar_entry, to get the event at a particular date.n` +
`edit_calendar_entry, to edit or update existing event.n` +
`delete_calendar_entry, to delete an existing event.n` +
`save_new_memory, to save new information to memory.n` +
`get_info_from_memory, to retrieve information from memory.nn` +
`When you present the result from the function, only mention the relevant details for the user query.n` +
`Omit information that is redundant and not relevant to the query.n` +
`Always be concise and brief in your replies.nn` +
`# Usern` +
`As for me, in this simulation I am known as ${ user_info . name } .n` +
` ${ user_info . prompt } nn` +
`# Todayn` +
`Today is ${ today } .n` Semua pesan akan disimpan di MongoDB .
Untuk mengelola token dan menghindari melampaui batas maksimum model, kami hanya akan mengirim 20 interaksi terakhir. Fungsi trim_array dirancang untuk memangkas riwayat pesan jika melampaui 20 putaran. Ambang ini dapat disesuaikan untuk memenuhi persyaratan spesifik Anda.
// allow less than or equal to 15 turns
const history_context = trim_array ( all_history , 15 ) Dari layar utama, Anda memiliki opsi untuk menghapus riwayat sebelumnya untuk setiap chatbot.

Harap dicatat bahwa kami telah memisahkan penanganan panggilan fungsi (function_call.js) dari panggilan API penyelesaian obrolan utama (transcribe.php). Perbedaan ini dibuat untuk mengatasi contoh ketika konten teks hadir saat panggilan fungsi dipanggil, yang biasanya nol. Pemisahan ini memungkinkan aplikasi untuk menampilkan teks sambil secara bersamaan memproses panggilan fungsi. Saya juga telah melampirkan proses dalam satu loop jika panggilan API kedua menghasilkan panggilan fungsi lain. Ini mungkin bisa ditangani lebih elegan dengan menerapkan streaming , tetapi saya belum belajar cara menggunakan streaming dengan Nuxt.
Fungsi kami dikategorikan di bawah dua fitur baru: manajemen jadwal dan memori jangka panjang .
Mari kita periksa bagaimana kita mengelola panggilan fungsi dengan format baru. Kita harus memanfaatkan parameter alat baru alih-alih parameter fungsi yang sekarang ditentukan untuk mengaktifkan doa beberapa panggilan fungsi.
let response = await openai . chat . completions . create ( {
temperature : 0.3 ,
messages ,
tools : [
{ type : 'function' , function : add_calendar_entry } ,
{ type : 'function' , function : get_calendar_entry } ,
{ type : 'function' , function : edit_calendar_entry } ,
{ type : 'function' , function : delete_calendar_entry } ,
{ type : 'function' , function : save_new_memory } ,
{ type : 'function' , function : get_info_from_memory }
]
} ) Periksa skema JSON dari setiap fungsi dari lib/ Direktori. Ini untuk add_calendar_entry :
{
"name" : "add_calendar_entry" ,
"description" : "Adds a new entry to the calendar" ,
"parameters" : {
"type" : "object" ,
"properties" : {
"event" : {
"type" : "string" ,
"description" : "The name or title of the event"
} ,
"date" : {
"type" : "string" ,
"description" : "The date of the event in 'YYYY-MM-DD' format"
} ,
"time" : {
"type" : "string" ,
"description" : "The time of the event in 'HH:MM' format"
} ,
"additional_detail" : {
"type" : "string" ,
"description" : "Any additional details or notes related to the event"
}
} ,
"required" : [ "event" , "date" , "time" , "additional_detail" ]
}
}Untuk dicus bagaimana semua ini bekerja, mari kita lanjutkan ke bagian selanjutnya.
Untuk manajemen jadwal , kami memiliki fungsi berikut:
Semua entri kalender akan disimpan di MongoDB. Harap dicatat bahwa semua entri akan dapat diakses oleh semua chatbots.
Mari kita lihat percakapan obrolan sampel untuk menunjukkan bagaimana elemen -elemen ini berinteraksi:
Pengguna: Selamat pagi, Jeeves. Apa jadwal saya hari ini?
Panggilan fungsi (Invoke get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [ {
id : 'call_4cCtmNlgYN5o4foVOQM9MIdA' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
} ]
}Tanggapan Fungsi:
[
{
tool_call_id : 'call_4cCtmNlgYN5o4foVOQM9MIdA' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n' +
' "message": "Found 3 entries",n' +
' "items": [n' +
' {n' +
' "_id": "654b2805e51fcd815dea8e2d",n' +
' "event": "Project presentation",n' +
' "date": "2023-11-10",n' +
' "time": "10:00",n' +
' "additional_detail": "Important project presentation",n' +
' "__v": 0n' +
' },n' +
' {n' +
' "_id": "654d7b53ff0bf31de6c2af29",n' +
' "event": "Meet Tony",n' +
' "date": "2023-11-10",n' +
' "time": "19:00",n' +
' "additional_detail": "Electric Sheep Bar",n' +
' "__v": 0n' +
' },n' +
' {n' +
' "_id": "654d7d868751ea8704fc9f11",n' +
' "event": "Dinner with Anna",n' +
' "date": "2023-11-10",n' +
' "time": "19:00",n' +
' "additional_detail": "Electric Sheep Bar",n' +
' "__v": 0n' +
' }n' +
' ]n' +
'}'
}
]Ringkasan obrolan:
{ role : 'assistant' ,
content : 'Good morning, Jerry. Today, you have a project presentation at 10:00 AM and a dinner meeting with Anna at 7:00 PM at the Electric Sheep Bar. You also have a meeting with Tony at 7:00 PM at the same location.'
}Pengguna: Tampaknya berlebihan untuk memiliki entri terpisah untuk acara yang sama. Bisakah Anda mengedit acara dengan Anna untuk menyertakan bertemu Tony dan menghapus acara lainnya?
Fungsi panggilan (dipanggil edit_calendar_entry dan delete_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_ZVAwO5YsXMuWr8876tyCIPIX' ,
type : 'function' ,
function : {
name : 'edit_calendar_entry' ,
arguments : '{"event": "Dinner Meeting with Anna", "date": "2023-11-10", "time": "19:00", "additional_detail": "Meeting with Tony included."}'
}
} ,
{
id : 'call_eMGuI8LxLbXFsLp8FduCrxgp' ,
type : 'function' ,
function : {
name : 'delete_calendar_entry' ,
arguments : '{"delete_type": "event_name", "event": "Meeting with Tony"}'
}
}
]
}Tanggapan Fungsi:
[ {
tool_call_id : 'call_ZVAwO5YsXMuWr8876tyCIPIX' ,
role : 'tool' ,
name : 'edit_calendar_entry' ,
content : '{n "message": "Entry edited",n "name": "Dinner Meeting with Anna"n}'
} ,
{
tool_call_id : 'call_eMGuI8LxLbXFsLp8FduCrxgp' ,
role : 'tool' ,
name : 'delete_calendar_entry' ,
content : '{n "name": "Meeting with Tony",n "message": "Entry deleted"n}'
}
]Ringkasan obrolan:
{ role : 'assistant' ,
content : 'The event "Dinner Meeting with Anna" has been updated to include the meeting with Tony, and the separate event "Meeting with Tony" has been deleted.'
}Pengguna: Oke, hanya untuk memastikan, dapatkah Anda memberi tahu saya jadwal saya lagi untuk hari ini?
Panggilan fungsi (Invoke get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_liqFn24Ds4qp0cy2V5NBg62v' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}Tanggapan Fungsi:
[ {
tool_call_id : 'call_liqFn24Ds4qp0cy2V5NBg62v' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n' +
' "message": "Found 2 entries",n' +
' "items": [n' +
' {n' +
' "_id": "654b2805e51fcd815dea8e2d",n' +
' "event": "Project presentation",n' +
' "date": "2023-11-10",n' +
' "time": "10:00",n' +
' "additional_detail": "Important project presentation",n' +
' "__v": 0n' +
' },n' +
' {n' +
' "_id": "654d7d868751ea8704fc9f11",n' +
' "event": "Dinner with Anna",n' +
' "date": "2023-11-10",n' +
' "time": "19:00",n' +
' "additional_detail": "Meeting with Tony included.",n' +
' "__v": 0n' +
' }n' +
' ]n' +
'}'
}
]Ringkasan obrolan:
{ role : 'assistant' ,
content : 'Your schedule for today is as follows:n' +
'- Project presentation at 10:00 AMn' +
'- Dinner with Anna at 7:00 PM, which includes a meeting with Tony.'
}Pengguna: Ah Anda lupa menambahkan lokasi dalam makan malam. Maaf tapi bisakah Anda memperbaruinya lagi?
Panggilan fungsi (dipanggil edit_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_eRTFMcz2l8qHO7nhp3msI3v8' ,
type : 'function' ,
function : {
name : 'edit_calendar_entry' ,
arguments : '{"event":"Dinner with Anna","date":"2023-11-10","time":"19:00","additional_detail":"Meeting with Tony at the Electric Sheep Bar."}'
}
}
]
}Tanggapan Fungsi:
{
tool_call_id : 'call_eRTFMcz2l8qHO7nhp3msI3v8' ,
role : 'tool' ,
name : 'edit_calendar_entry' ,
content : '{n "message": "Entry edited",n "name": "Dinner with Anna"n}'
}Ringkasan obrolan:
{ role : 'assistant' ,
content : 'The event "Dinner with Anna" has been updated to include the meeting with Tony at the Electric Sheep Bar.'
}Pengguna: Oke, saya ingin mengonfirmasi lagi, dapatkah Anda memberi tahu saya jadwal saya untuk hari ini?
Panggilan fungsi (Dipanggil get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_HYlAj4BTeWcu2CxSp4zBYPuC' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}Tanggapan Fungsi:
[ {
tool_call_id : 'call_HYlAj4BTeWcu2CxSp4zBYPuC' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n' +
' "message": "Found 2 entries",n' +
' "items": [n' +
' {n' +
' "_id": "654b2805e51fcd815dea8e2d",n' +
' "event": "Project presentation",n' +
' "date": "2023-11-10",n' +
' "time": "10:00",n' +
' "additional_detail": "Important project presentation",n' +
' "__v": 0n' +
' },n' +
' {n' +
' "_id": "654d7d868751ea8704fc9f11",n' +
' "event": "Dinner with Anna",n' +
' "date": "2023-11-10",n' +
' "time": "19:00",n' +
' "additional_detail": "Meeting with Tony at the Electric Sheep Bar.",n' +
' "__v": 0n' +
' }n' +
' ]n' +
'}'
}
]Ringkasan obrolan:
{ role : 'assistant' ,
content : 'Your schedule for today is as follows:n' +
'- Project presentation at 10:00 AMn' +
'- Dinner with Anna at 7:00 PM, which includes a meeting with Tony at the Electric Sheep Bar.'
}Untuk memori jangka panjang , kami memiliki fungsi berikut
Semua fungsi sebelumnya terutama berfokus pada pengambilan data yang khas dan mengatur tugas. Tetapi untuk pengambilan memori jangka panjang, kita tidak bisa begitu saja melakukan pencarian kata kunci. Kita perlu mempertimbangkan konteks kueri. Kita perlu melakukan pencarian semantik. Dan dalam hal ini, kami akan menggunakan API Embeddings . Mari kita lanjutkan ke bagian selanjutnya untuk membahas ini lebih lanjut.
UPDATE : Saya telah mengganti
text-embedding-ada-002dengantext-embedding-3-small. Berdasarkan tes saya, yang terakhir berkinerja cukup baik. Skor kesamaan kosinus antara jawaban yang terkait erat dan jawaban yang tidak relevan sangat berbeda. Selain itu, kinerja biaya model kecil V3, dengan harga $ 0,00002/1K token, tidak ada otak? Namun, Anda perlu mengonversi data vektor Anda dari ADA ke V3 Small karena tidak kompatibel. Perhatikan bahwa kami juga menurunkan ambang batas dari 0,72 menjadi 0,3.
Sederhananya, embeddings mengukur keterkaitan string teks. Jika kita memanggil API, itu akan memberi kita data vektor angka mengambang yang terkait dengan teks input.
const embedding = await openai . embeddings . create ( {
model : "text-embedding-3-small" , //"text-embedding-ada-002",
input : "The quick brown fox jumped over the lazy dog" ,
encoding_format : "float" ,
} )Untuk menggunakan ini dalam aplikasi kami, kami akan mengimplementasikan apa yang mereka sebut generasi pengambilan atau kain sederhana.
Awalnya, ketika data baru diterima dari fungsi save_new_memory, kami memanggil API Embeddings untuk menghasilkan representasi vektornya. Data vektor ini kemudian disimpan di MongoDB untuk penggunaan di masa depan.
Selanjutnya, ketika pengguna mengirimkan kueri yang membutuhkan pengambilan memori, fungsi get_info_from_memory dipicu. Kami memanggil API Embeddings untuk parameter pencarian dan membandingkannya dengan data vektor yang disimpan menggunakan kesamaan cosinus sederhana. Perbandingan ini biasanya menghasilkan beberapa pertandingan dengan skor yang bervariasi. Kami telah menetapkan ambang batas kami ke skor 0,3 (0,72 untuk model ADA) atau lebih tinggi, dan kami membatasi hasilnya hingga maksimum 10.
Hasilnya kemudian diteruskan ke API penyelesaian obrolan akhir, yang menentukan respons yang paling cocok untuk kueri pengguna. AI memiliki kemampuan untuk memilih satu atau lebih hasil sebagai dasar untuk responsnya, tergantung pada sifat kueri. Di sinilah kekuatan AI yang sebenarnya ditunjukkan. Daripada sekadar memuntahkan semua informasi yang diterimanya, AI menganalisis data dan merumuskan respons yang tepat. Jika hasil dari kain dianggap cukup, itu akan menghasilkan respons teks yang positif.
Untuk membantu memfasilitasi penyimpanan data vektor dan operasi vektor lainnya di MongoDB , saya menggunakan modul Mongoose. Sejauh ini, ini adalah solusi paling tidak menyakitkan untuk menangani penyimpanan dan pengambilan data vektor.
Pertama, kami mendefinisikan skema kami:
const memoryVectorsSchema = new mongoose . Schema ( {
chunks : [ {
embedding : [ Number ] ,
text : String
} ]
} ) Kemudian kami melampirkan fungsi getScore yang mengeksekusi kesamaan cosinus antara model dokumen dan string kueri tertanam:
memoryVectorsSchema . methods . getScore = function getScore ( query_embedding ) {
return this . chunks . map ( ( chunk ) => {
const dot_product = chunk . embedding . reduce ( ( sum , val , i ) => sum + val * query_embedding [ i ] , 0 )
const magnitude = ( vec ) => Math . sqrt ( vec . reduce ( ( sum , val ) => sum + val * val , 0 ) )
const cosine_similarity = dot_product / ( magnitude ( chunk . embedding ) * magnitude ( query_embedding ) )
return { text : chunk . text , score : cosine_similarity }
} )
}Selanjutnya, kami menyusun skema kami menjadi model:
const MemoryVectors = mongoose . model ( 'MemoryVectors' , memoryVectorsSchema )Untuk menyimpan data vektor:
const newVector = new MemoryVectors ( {
chunks
} )
newVector . save ( )Untuk mencari data vektor yang disimpan dengan embeddings dari kueri pengguna
const cosineSimThreshold = 0.3 // Note: for text-embedding-ada-002, 0.72 but for v3 embed models, use 0.3
const maxResults = 10
const maxFilesLength = 2000 * 3
const vectors = await MemoryVectors . find ( )
const vector_result = vectors . map ( ( v ) => {
return v . getScore ( search_query )
} ) . flat ( ) . sort ( ( a , b ) => b . score - a . score ) . filter ( ( chunk ) => chunk . score > cosineSimThreshold ) . slice ( 0 , maxResults )
const query_result = vector_result . length > 0 ? vector_result . map ( ( v ) => v . text ) . join ( "n" ) . slice ( 0 , maxFilesLength ) : ''Itu saja. Rapi, ya?
Untuk menunjukkan bagaimana semua ini bekerja, mari kita periksa percakapan sampel.
Pengguna: Hei, beri tahu saya, apa burger favorit Anda? keju? Bacon? jamur?
Bot: Oh, pasti! Saya semua tentang burger bacon itu, Anda tahu? Bacon renyah hanya membawanya ke tingkat yang sama sekali baru. Bagaimana denganmu?
Pengguna: Jamur! dengan keju! Oh, saya menyukainya.
Bot: Benar -benar! Jamur dan keju adalah korek api yang dibuat di surga burger. Sudahkah Anda mencoba membuat burger jamur dan keju Anda sendiri di rumah? Itu sangat berharga!
Pengguna: Belum. Sudahkah Anda mencoba alpukat?
Bot: Oh, benar -benar! Alpukat pada burger seperti ceri di atas, Anda tahu? Ini menambahkan kebaikan yang lembut dan mentega yang membawa semuanya ke tingkat berikutnya. Sudahkah kamu mencobanya?
Pengguna: Ya. Sebenarnya ada cerita lucu yang terkait dengannya. Jadi saya pernah mengalami burger alpukat sebelumnya. Lalu suatu kali, saya dan teman -teman saya pergi ke perjalanan ski. Sepanjang jalan kami mampir ke toko burger. Ketika saya menjelajahi menu, saya melihat alpukat dan saya mengarahkannya ketika saya memesan. Apakah Anda tahu apa yang terjadi selanjutnya?
Bot: Tidak mungkin! Apa yang telah terjadi? Apakah mereka memberi Anda burger dengan, seperti, seluruh alpukat di atasnya? Itu akan sangat lucu!
Pengguna: Tidak. tapi dekat. Mereka memberi saya burger dengan sepotong alpukat. tidak ada yang lain. Ternyata, yang saya pilih di menu adalah untuk opsi vegan!
Bot: Ya ampun, itu terlalu lucu! Jadi Anda berakhir dengan burger alpukat polos alih -alih yang Anda harapkan. Itu seperti campuran langsung dari film komedi! Apakah Anda akhirnya menikmati burger alpukat?
Pengguna: Saya memakannya dengan baik tetapi tidak puas, tentu saja. Hei, bisakah kamu menyimpan kenangan lucu ini, kadang -kadang baik untuk diingat
Panggilan fungsi (Dipanggil save_new_memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
type : 'function' ,
function : {
name : 'save_new_memory' ,
arguments : '{"memory_title":"Avocado Burger Mix-Up","memory_detail":"Ordered an avocado burger at a ski trip burger shop, but got a plain avocado burger instead. Turns out, the one I picked on the menu was the vegan option! Hilarious mix-up.","memory_date":"2023-11-10","memory_tags":["funny","food","burger"]}'
}
}
]
}Pada titik ini kami memanggil API Embeddings dan mendapatkan data vektor untuk seluruh argumen dan menyimpannya dengan teks ke MongoDB.
Tanggapan Fungsi:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]Bot: Memori disimpan! Sekarang Anda punya cerita campur aduk yang lucu untuk dilihat kembali kapan pun Anda membutuhkan tawa yang bagus. Ini seperti sepotong kecil kehidupan, Anda tahu?
Sekarang, untuk mengambil info dari memori
Pengguna: Ngomong -ngomong, apakah Anda tahu Burger Joint yang ditutup bulan ini? yang ada di Shibuya? apa namanya?
Panggilan fungsi (dipanggil get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-30"}' }
}
]
}Tanggapan Fungsi:
[ {
tool_call_id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n "message": "No entries found"n}'
}
]Bot: Saya tidak dapat menemukan info tentang penutupan burger bersama di Shibuya bulan ini. Mungkin itu hanya rumor, Anda tahu? Tetapi jika Anda mengetahui namanya, beri tahu saya!
Pengguna: Dapatkah Anda memeriksa ingatan Anda, saya pikir saya mungkin telah memberi tahu Anda sebelumnya
Panggilan fungsi (dipanggil get_info_from_memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_WVBvjR0MKgaaTzik7QBwk3Ei' ,
type : 'function' ,
function : {
name : 'get_info_from_memory' ,
arguments : '{"search":["burger","Shibuya","closing"]}'
}
}
]
} Di sini kami mendapatkan embeddings untuk istilah pencarian alih -alih permintaan pengguna yang sebenarnya. Kemudian menggunakan data vektor yang dihasilkan, kami menghubungi mongoDb.searchEntry untuk mencari data vektor yang disimpan di MongoDB.
Tanggapan Fungsi:
[ {
tool_call_id : 'call_WVBvjR0MKgaaTzik7QBwk3Ei' ,
role : 'tool' ,
name : 'get_info_from_memory' ,
content : '{n' +
' "message": "Retrieved related info for burger,Shibuya,closing",n' +
` "result": "score: 0.8513867910905064\n title: Becker's ハンバーガーショップ閉店 detail: 千葉の人気なバーガーショップ「Becker's」が閉店するらしい。 tags: 閉店,ハンバーガー,千葉\nscore: 0.8506170963709474\n title: トリキバーガーの渋谷店閉店情報 detail: 渋谷のトリキバーガーの店舗が閉店することが決まりました。最後の日は今月の20日です。お店は渋谷の井の頭通りにあります。 date: 2023-11-10 tags: トリキバーガー,渋谷,閉店情報\nscore: 0.8307398914702265\n title: Becker's閉店情報 detail: 千葉の人気なバーガーショップBecker'sが閉店する。最後の日は今月の22日。 date: 2023-11-22 tags: 閉店情報,Becker's...n` +
'}'
}
]Saya hanya menunjukkan hasil parsial dari konten tetapi berisi beberapa hit dari database yang terkait dengan istilah pencarian. Kami mengirimkan semua hasil ke API Penyelesaian Obrolan dan membiarkan AI memutuskan apa yang harus dilakukan.
Bot: Oh, benar! Sendi burger yang ditutup di Shibuya adalah Burger Triki. Lokasi Shibuya mereka ditutup pada tanggal 20 bulan ini. Sedih melihat mereka pergi, Anda tahu?
Jika Anda perhatikan, hasil parsial yang ditunjukkan adalah dalam bahasa Jepang! Jadi, pasti, ini menunjukkan bahwa kami tidak melakukan hanya pencarian kata kunci sederhana di sini. Anda dapat menambahkan data bahasa campuran dan meminta memori menggunakan bahasa yang berbeda dan kemungkinan Anda masih akan mendapatkan jawabannya. Mari kita coba menggunakan bot Rusia kami, Vitaly . Saya akan menggunakan Google Translate karena saya tidak berbicara bahasa Rusia.
Pengguna: Виталий, можеш Вспомнить название оох юбиых ка Agustus (Vitaly, dapatkah Anda mengingat nama keripik kentang favorit saya?)
Panggilan fungsi (dipanggil get_info_from_memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_8fXETNlDMuKEXsitUuqt5PlE' ,
type : 'function' ,
function : {
name : 'get_info_from_memory' ,
arguments : '{"search":["картофельные чипсы"]}'
}
}
]
}Jadi, sekali lagi, kami mendapatkan embeddings untuk istilah pencarian dan menggunakannya terhadap setiap data vektor yang disimpan di MongoDB.
Hasil Fungsi:
[ {
tool_call_id : 'call_8fXETNlDMuKEXsitUuqt5PlE' ,
role : 'tool' ,
name : 'get_info_from_memory' ,
content : '{n' +
' "message": "Retrieved related info for картофельные чипсы",n' +
` "result": "score: 0.7718838658905126\n title: トムさんのお気に入りのパン detail: トムさんのお気に入りのパンはカレーパンとあんドーナツです。カレーパンはスパイシーなカレーとサクサクの生地が絶妙に合い、あんドーナツは甘さともちもち感が魅力です。\nscore: 0.7560670627592779\n title: Crisp Bacon Potato Chips detail: Brand: Dice tags: snack,potato chips,brand\nscore: 0.7354783965092787\n title: あまとうのあんみつ detail: 小樽のあまとうで食べるあんみつは絶品。...n` +
'}'
}
]Anda dapat melihat bahwa hasil data yang ditampilkan dicampur, dengan teks Jepang dan Inggris.
Bot: твои юбиые ка Agustusоельные чипсы - keripik kentang daging renyah. (Keripik kentang favorit Anda adalah keripik kentang bacon renyah.)
Oke, mari kita lanjutkan ke langkah terakhir.
Setelah menerima respons teks dari langkah-langkah sebelumnya, kami sekarang akan memanggil API teks-ke-pidato untuk memungkinkan chatbot kami untuk menyuarakan respons
try {
const mp3 = await openai . audio . speech . create ( {
model : 'tts-1' ,
voice : 'alloy' ,
input : 'The quick brown fox jumps over the lazy dog.' ,
} )
const buffer = Buffer . from ( await mp3 . arrayBuffer ( ) )
await fs . promises . writeFile ( filename , buffer )
} catch ( error ) {
throw error
} Karena dokumen tidak memberikan deskripsi khusus untuk setiap suara, jadi saya meminta chatgpt untuk membuat beberapa tebakan berpendidikan berdasarkan nama mereka:
Paduan (f): Serbaguna dan dapat cocok untuk konten teknis atau instruksional, seperti tutorial, materi pendidikan, atau konten apa pun di mana kejelasan dan ketepatan sangat penting.
Echo (M): Mungkin cocok untuk bercerita, buku audio, atau konten yang membutuhkan nada yang lebih dramatis atau naratif.
Fable (f): Bisa ideal untuk cerita anak -anak, konten fantasi, atau narasi apa pun yang membutuhkan nada yang lebih menyenangkan atau imajinatif.
Onyx (M): Mungkin cocok untuk membaca literatur yang serius, memberikan konten berita, atau materi apa pun yang membutuhkan pengiriman yang lebih serius atau formal.
NOVA (F): Cocok untuk iklan, konten motivasi, atau materi apa pun yang membutuhkan pengiriman yang positif dan antusias.
Shimmer (F): Cocok untuk konten gaya hidup, podcast, atau apa pun yang membutuhkan nada yang menarik, ramah, dan mengundang.
Data audio yang dihasilkan kemudian akan disimpan ke direktori public/upload dalam format file MP3. Kami kemudian akan mengirim tautan ke sisi klien di mana htmlaudioElement dinamis akan memuat dan memainkannya.
const audioDomRef = new Audio ( )
audioDomRef . type = 'audio/mpeg'
audioDomRef . addEventListener ( 'loadedmetadata' , handleAudioLoad )
audioDomRef . addEventListener ( 'ended' , handleAudioEnded )
audioDomRef . addEventListener ( 'error' , handleAudioError )
audioDomRef . src = audio_url
function handleAudioLoad ( ) {
audioDomRef . value . play ( )
}Harap dicatat bahwa perekaman audio akan dinonaktifkan saat respons audio sedang diputar. Ini memastikan bahwa aplikasi tidak mencatat respons bot.
FFMPEG digunakan untuk menghapus bagian diam dalam file audio.
Untuk menginstal alat baris perintah FFMPEG
# on Ubuntu or Debian
sudo apt update && sudo apt install ffmpeg
# on Arch Linux
sudo pacman -S ffmpeg
# on MacOS using Homebrew (https://brew.sh/)
brew install ffmpeg
# on Windows using Chocolatey (https://chocolatey.org/)
choco install ffmpeg
# on Windows using Scoop (https://scoop.sh/)
scoop install ffmpeg
MongoDB akan digunakan untuk menyimpan entri kalender dan data vektor untuk fungsi memori jangka panjang.
Untuk menginstal MongoDB Community Edition, silakan periksa halaman ini. Anda mungkin juga ingin menginstal MongoDB Shell untuk melihat database.
Pertama, pastikan bahwa FFMPEG dan MongoDB dipasang di sistem Anda.
Untuk mengkloning repositori proyek dan menginstal dependensi
$ git clone https://github.com/supershaneski/openai-whisper-talk.git myproject
$ cd myproject
$ npm install Salin file .env.example dan ganti nama ke .env , lalu buka dan edit item di sana dengan nilai aktual. Untuk item MongoDB, Anda mungkin tidak perlu mengeditnya kecuali Anda memiliki pengaturan yang berbeda.
NUXT_OPENAI_API_KEY=your-openai-api-key
NUXT_MONGODB_HOST_NAME=localhost
NUXT_MONGODB_PORT=27017
NUXT_MONGODB_DB_NAME=embeddingvectorsdb
Kemudian untuk menjalankan aplikasi
$ npm run dev Buka browser Anda ke http://localhost:5000/ (nomor port tergantung pada ketersediaan) untuk memuat halaman aplikasi.
Catatan: Saya belum menguji ini dengan pembaruan terbaru
Anda mungkin ingin menjalankan aplikasi ini menggunakan protokol https . Ini diperlukan untuk mengaktifkan pengambilan audio menggunakan perangkat terpisah seperti smartphone.
Untuk melakukannya, siapkan certificate dan file key yang tepat dan edit server.mjs di direktori root.
Lalu kunjungi proyek
$ npm run buildAkhirnya, jalankan aplikasi
$ node server.mjs Sekarang, buka browser Anda ke https://localhost:3000/ (nomor port tergantung pada ketersediaan) atau menggunakan alamat IP lokal Anda untuk memuat halaman.
Anda dapat menemukan variabel -variabel ini di halaman/bicara/[id] .vue file. ↩ ↩ 2
Jika pada tanggal, item hanya perlu satu di bawah tanggal jika tidak kesalahan akan dikembalikan. ↩