V0.0.2
openai-whisper-talk is a sample voice conversation application powered by OpenAI technologies such as Whisper, an automatic speech recognition (ASR) system, Chat Completions, an interface that simulates conversation with a model that plays the role of assistant, Embeddings, converts text to vector data that can be used in tasks like semantic searching, and the latest Text-to-Speech, that turn text ito lifelike spoken audio.該應用程序是使用NUXT構建的,NUXT是基於vue.js的JavaScript框架。
該應用程序具有兩個新功能:計劃管理和長期內存。使用計劃管理,您可以命令聊天機器人添加,修改,刪除和檢索計劃的事件。長期內存功能使您可以存儲聊天機器人記住的信息段,以供將來參考。您可以通過與聊天機器人進行交互,將兩個功能無縫集成到對話中。
更新:將OpenAI模塊更新為最新版本,並將嵌入模型從
text-embedding-ada-002更換為新的V3型號text-embedding-3-small。
openai- whisper -talk(whisper)(ASR)システム)(聊天完成(アシスタントの役割を果たすモデルとの會話をシミュレートするインターフェース)(嵌入(セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)(嵌入(セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)、そして最新の(文本到語音(テキストをリアルな話し言葉のオーディオに変える)など、)openai openai nuxt
このアプリケーションには、「スケジュール管理」と「永続メモリ」の 2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、將來の參照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。將來的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

在主頁上,您可以選擇要與哪個聊天機器人互動。每個聊天機器人都有獨特的個性,講不同的語言,並且具有獨特的聲音。您可以通過單擊與其名稱相鄰的編輯按鈕來更改任何聊天機器人的名稱和個性。當前,用戶界面不直接支持新聊天機器人的添加;但是,您可以手動添加聊天機器人,並通過修改資產/聯繫人文件來自定義每個語音和語言設置。

此外,您可以通過單擊右上角的頭像圖標來個性化個人資料。這使您可以輸入自己的姓名並分享有關自己的詳細信息,從而使聊天機器人能夠以更個性化的方式與您互動。


如果檢測到聲音,則會自動記錄音頻數據。可以使用閾值設置,以防止背景噪聲觸發音頻捕獲。默認情況下,將其設置為-45DB (-10dB代表最大聲音的截止值)。您可以根據需要修改MIN_DECIBELS 1變量來調整此閾值。
啟用錄製並且在3秒內未檢測到聲音時,音頻數據將上傳並發送到後端進行轉錄。值得注意的是,在典型的對話中,每個回合之間的平均差距約為2秒。同樣,說話時句子之間的暫停大致相同。因此,我選擇了足夠長的時間來等待回复的時間。您可以通過編輯MAX_COUNT 1變量來調整此持續時間。
系統可以連續記錄音頻數據,直到收到答復為止。一旦收到和播放了聊天機器人的音頻回复,就會禁用錄音,以防止聊天機器人自己的響應無意。
如果要寫消息,也提供文本輸入。
所有記錄的音頻數據均以WebM文件格式上傳到public/upload目錄。在將音頻文件提交給Whisper API之前,有必要刪除所有無聲段。此步驟有助於防止低語產生的知名問題。出於同樣的原因,建議將MIN_DECIBELS設置為盡可能高的值,以確保僅記錄語音。
要刪除音頻中的無聲零件,我們將使用ffmpeg 。確保安裝它。
ffmpeg -i sourceFile -af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-50dB outputFile在此命令中:
-1 sourceFile指定輸入文件。-af silenceremove使用過濾器silencerremove 。stop_periods=-1消除了所有沉默。stop_duration=1設置超過1秒鐘的沉默的任何沉默。stop_threshold=-50dB將任何噪聲水平設置為-50db以下。outputFile文件。為了在我們的API路線中調用此shell命令,我們將使用child-process模塊中的exec 。
刪除無聲零件後,應檢查文件大小。最終文件大小通常比原始文件小得多。在此檢查中,假設一個具有16位深度的文件相當於音頻的大約一秒鐘,則忽略了小於16 KB的文件。任何較短的東西都可能聽不見。
// simple file size formula
const audio_file_size = duration_in_seconds * bitrate從消除無聲零件到檢查文件大小的整個過程旨在確保僅將可行的音頻數據發送到Whisper API。我們的目的是避免幻覺和數據的不必要傳輸。
確認了我們的音頻數據的可行性後,我們開始稱呼Whisper API。
const transcription = await openai . audio . transcriptions . create ( {
file : fs . createReadStream ( filename ) ,
language : lang ,
response_format : 'text' ,
temperature : 0 ,
} )其中lang是我們聊天機器人指定語言的ISO 639-1代碼。請檢查當前支持的語言的列表。
由於不需要時間戳,因此我們選擇簡單的text格式,並且將temperature參數設置為零以實現確定性輸出。
從Whisper收到筆錄後,我們隨時將其提交給聊天完成API,並使用功能調用。我們利用昨天(2023/11/07)發布的最新的OpenAI Node.js模塊(版本4),其中包括更新的函數calling格式。此最新迭代可以在單個請求中調用多個功能。
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 }
]
} )讓我們在隨後的各節中剖析此調用的各個組件。
系統提示在為我們的聊天機器人賦予生命方面起著至關重要的作用。在這裡,我們基於用戶選擇的聊天機器人來建立其名稱和角色。我們為它提供有關如何響應的特定說明,以及可以執行的功能列表。我們還將其告知用戶的身份和一些個人詳細信息。最後,我們設置了當前日期和時間,這對於激活日曆功能至關重要。
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` 所有消息將存儲在MongoDB中。
為了管理令牌並避免超過模型的最大限制,我們將僅發送最後20個交互。如果trim_array函數旨在修剪消息歷史記錄,則該函數超過20圈。可以調整此閾值以滿足您的特定要求。
// allow less than or equal to 15 turns
const history_context = trim_array ( all_history , 15 ) 在主屏幕上,您可以選擇刪除每個聊天機器人的先前歷史記錄。

請注意,我們已經將函數調用(function_call.js)的處理方法與主聊天完成API呼叫(trescribe.php)分開。進行這種區別是為了解決在調用函數調用時存在文本內容時的實例(通常為null)。此分隔使應用程序可以同時處理函數調用時顯示文本。如果第二個API調用導致另一個函數調用,我還將該過程包含在循環中。通過實現流媒體,這可能可以更優雅地處理,但是我還沒有學習如何與NUXT一起使用流媒體。
我們的功能在兩個新功能下分類:計劃管理和長期內存。
讓我們首先檢查如何使用新格式管理函數調用。我們必須利用新工具參數,而不是現在被剝奪的函數參數來啟用多個函數調用的調用。
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 }
]
} )檢查lib/ Directory的每個功能的JSON模式。這是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" ]
}
}為了使所有這些工作原理,讓我們繼續下一個部分。
對於計劃管理,我們具有以下功能:
所有日曆條目將存儲在MongoDB中。請注意,所有聊天機器人都可以訪問所有條目。
讓我們看一下示例聊天對話,以說明這些元素如何相互作用:
用戶:早上好,jeeves。我今天的時間表是什麼?
函數調用(Indoke get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [ {
id : 'call_4cCtmNlgYN5o4foVOQM9MIdA' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
} ]
}功能響應:
[
{
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' +
'}'
}
]聊天摘要:
{ 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.'
}用戶:對於同一事件,單獨的條目似乎是多餘的。您可以與Anna編輯活動以包括與Tony錄製並刪除另一個活動嗎?
函數調用(調用edit_calendar_entry和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"}'
}
}
]
}功能響應:
[ {
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}'
}
]聊天摘要:
{ 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.'
}用戶:好吧,可以肯定的是,您能再次告訴我我的日程安排嗎?
函數調用(Indoke get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_liqFn24Ds4qp0cy2V5NBg62v' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}功能響應:
[ {
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' +
'}'
}
]聊天摘要:
{ 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.'
}用戶:啊,您忘了在晚餐中添加位置。抱歉,您可以再次更新嗎?
函數調用(調用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."}'
}
}
]
}功能響應:
{
tool_call_id : 'call_eRTFMcz2l8qHO7nhp3msI3v8' ,
role : 'tool' ,
name : 'edit_calendar_entry' ,
content : '{n "message": "Entry edited",n "name": "Dinner with Anna"n}'
}聊天摘要:
{ role : 'assistant' ,
content : 'The event "Dinner with Anna" has been updated to include the meeting with Tony at the Electric Sheep Bar.'
}用戶:好的,我想再次確認,您能告訴我我今天的時間表嗎?
函數調用(調用get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_HYlAj4BTeWcu2CxSp4zBYPuC' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}功能響應:
[ {
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' +
'}'
}
]聊天摘要:
{ 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.'
}對於長期記憶,我們具有以下功能
所有以前的功能主要集中在典型的數據檢索和設置任務上。但是對於長期內存檢索,我們不能僅僅進行關鍵字查找。我們需要考慮查詢的上下文。我們需要進行語義搜索。在這方面,我們將使用嵌入式API 。讓我們繼續下一節進行進一步討論。
更新:我已經用
text-embedding-ada-002text-embedding-3-smallADA-002。根據我的測試,後者的表現足夠好。餘弦相似性在緊密相關的答案和非相關答案之間的分數明顯不同。此外,V3小型型號的成本效果(價格為$ 0.00002/1k代幣)是不介意的嗎?但是,由於它們不兼容,因此您需要將矢量數據從ADA轉換為V3小。請注意,我們還將閾值從0.72降低到0.3。
簡而言之,嵌入衡量文本字符串的相關性。如果我們調用API,它將為我們提供與輸入文本關聯的浮數量的向量數據。
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" ,
} )要在我們的應用程序中使用此功能,我們將實施他們所謂的檢索增強生成或簡單的抹布。
最初,當從save_new_memory函數中收到新數據時,我們調用嵌入式API以生成其向量表示。然後將此向量數據存儲在MongoDB中以供將來使用。
隨後,當用戶提交需要內存檢索的查詢時,觸發get_info_from_memory函數。我們將嵌入式API稱為搜索參數,並使用簡單的餘弦相似性將其與存儲的向量數據進行比較。該比較通常會產生多種分數的匹配。我們將閾值設置為0.3(ADA模型為0.72)或更高的得分,我們將結果限制在最多10。
然後將結果傳遞給最終聊天完成API,該API確定了對用戶查詢的最合適響應。 AI可以根據查詢的性質選擇一個或多個結果作為其響應的基礎。這是AI的真正力量的地方。 AI不簡單地反思其收到的所有信息,而是分析數據並製定適當的響應。如果抹布的結果被認為足夠,它將產生正面的文本響應。
為了幫助促進MongoDB中矢量數據和其他向量操作的存儲,我正在使用Mongoose模塊。到目前為止,這是處理矢量數據存儲和檢索的最無痛解決方案。
首先,我們定義了我們的模式:
const memoryVectorsSchema = new mongoose . Schema ( {
chunks : [ {
embedding : [ Number ] ,
text : String
} ]
} )然後,我們附加getScore函數,該功能在文檔模型和嵌入式查詢字符串之間執行餘弦相似性:
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 }
} )
}接下來,我們將模式編譯為一個模型:
const MemoryVectors = mongoose . model ( 'MemoryVectors' , memoryVectorsSchema )為了保存向量數據:
const newVector = new MemoryVectors ( {
chunks
} )
newVector . save ( )搜索用戶查詢的嵌入的存儲的矢量數據
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 ) : ''就是這樣。整潔,是嗎?
為了證明所有這些工作方式,讓我們檢查一下示例對話。
用戶:嘿,告訴我,您最喜歡的漢堡是什麼?起司?培根?蘑菇?
bot:哦,肯定!我全都是那個培根漢堡,你知道嗎?酥脆的培根將其提升到一個全新的水平。你呢?
用戶:蘑菇!和奶酪!哦,我喜歡它。
bot:完全!蘑菇和奶酪是在漢堡天堂製作的匹配。您是否嘗試過在家製作自己的蘑菇和奶酪漢堡?值得!
用戶:還沒有。您嘗試過鱷梨嗎?
bot:哦,完全!漢堡上的鱷梨就像上面的櫻桃,你知道嗎?它添加了奶油般的黃油美味,使整個東西都達到了新的水平。你嘗試過嗎?
用戶:是的。實際上,有一個有趣的故事。所以我以前有鱷梨漢堡。然後有一次,我和我的朋友去滑雪旅行。一路走來,我們在一家漢堡店停下來。當我瀏覽菜單時,我看到了鱷梨,並在訂購時指出了它。你知道接下來發生了什麼嗎?
機器人:沒辦法!發生了什麼?他們有沒有給您一個漢堡,就像上面有整個鱷梨嗎?那會很有趣!
用戶:不。但是關閉。他們給了我一個帶有鱷梨切片的漢堡。別無其他。事實證明,我在菜單上選擇的是素食選擇!
博特:哦,天哪,太有趣了!因此,您最終得到了一個普通的鱷梨漢堡,而不是預期的。這就像一部喜劇電影中的混音!但是,您最終喜歡鱷梨漢堡嗎?
用戶:我吃了好吃,但當然不滿意。嘿,你能節省這個有趣的記憶嗎,有時會很高興
函數調用(調用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"]}'
}
}
]
}在這一點上,我們調用嵌入式API並獲取整個參數的向量數據,並將其保存到MongoDB。
功能響應:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]bot:保存內存!現在,您有一個有趣的混音故事可以回顧一下,只要您需要一個好笑。這就像生活的一小部分,你知道嗎?
現在,要從內存中檢索信息
用戶:順便說一句,您知道本月關閉的漢堡關節嗎? shibuya的那個?叫什麼名字?
函數調用(調用get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-30"}' }
}
]
}功能響應:
[ {
tool_call_id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n "message": "No entries found"n}'
}
]博特:本月我找不到有關漢堡關閉關閉的任何信息。也許這只是一個謠言,你知道嗎?但是,如果您發現這個名字,請告訴我!
用戶:您可以檢查您的記憶嗎?我想我可能已經告訴過您
函數調用(調用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"]}'
}
}
]
}在這裡,我們獲取搜索詞而不是實際用戶查詢的嵌入。然後使用結果矢量數據,我們調用mongoDb.searchEntry搜索MongoDB中存儲的向量數據。
功能響應:
[ {
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` +
'}'
}
]我僅顯示內容的部分結果,但它包含與搜索詞相關的數據庫中的幾個命中。我們將所有結果發送到聊天完成API,並讓AI決定該怎麼做。
bot:哦,對!北野的漢堡關節是Triki Burger。他們的Shibuya所在地將於本月20日關閉。看到他們走了很可悲,你知道嗎?
如果您注意到,所顯示的部分結果是日語!因此,當然,這表明我們在這裡不只是簡單的關鍵字查找。您可以添加混合語言的數據,並使用不同的語言查詢內存,並且可能仍然會得到答案。讓我們嘗試使用我們的俄羅斯機器人Vitaly 。由於我不會說俄語,因此我會使用Google翻譯。
用戶:Виталий,截取,можеш -thoumш -limin。 (Vitaly,您還記得我最喜歡的薯片的名字嗎?)
函數調用(調用get_info_from_memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_8fXETNlDMuKEXsitUuqt5PlE' ,
type : 'function' ,
function : {
name : 'get_info_from_memory' ,
arguments : '{"search":["картофельные чипсы"]}'
}
}
]
}因此,再次,我們獲得了搜索詞的嵌入式,並將其用於MongoDB中存儲的每個向量數據。
功能結果:
[ {
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` +
'}'
}
]您可以看到所顯示的數據結果混合在一起,日語和英語文本混合在一起。
bot:юlipomouthюlu是醃製的培根薯片。 (您最喜歡的薯片是酥脆的培根薯片。)
好吧,讓我們繼續前進。
從前一個步驟中收到文本響應後,我們現在將調用文本到語音API,以使我們的聊天機器人發聲響應
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
} 由於文檔沒有為每個聲音提供特定的描述,因此我要求Chatgpt根據其名稱做出一些有根據的猜測:
合金(F):多功能,可能適用於技術或教學內容,例如教程,教育材料或任何清晰度和精度至關重要的內容。
Echo (M):可能適合講故事,有聲讀物或需要更具戲劇性或敘事語氣的內容。
寓言(f):可能是兒童故事,幻想內容或任何需要更有趣或富有想像力的敘述的理想選擇。
Onyx (M):可能適合閱讀認真的文學作品,提供新聞內容或任何需要更嚴重或正式交付的材料。
Nova (F):適用於廣告,動機內容或任何需要積極和熱情的交付的材料。
Shimmer (F):適合生活方式內容,播客或任何需要引人入勝,友好和誘人的語氣的東西。
然後,生成的音頻數據將以MP3文件格式保存到public/upload目錄。然後,我們將將鏈接發送到客戶端,在該客戶端,動態的HTMLAudioElement將加載並播放它。
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 ( )
}請注意,在播放音頻響應時,音頻的錄製將被禁用。這樣可以確保應用程序不會記錄機器人的響應。
FFMPEG用於刪除音頻文件中的無聲零件。
安裝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將用於存儲長期內存功能的日曆條目和矢量數據。
要安裝MongoDB社區版,請檢查此頁面。您可能還需要安裝MongoDB Shell以查看數據庫。
首先,確保系統中安裝了FFMPEG和MongoDB。
克隆項目存儲庫並安裝依賴項
$ git clone https://github.com/supershaneski/openai-whisper-talk.git myproject
$ cd myproject
$ npm install複製.env.example文件並將其重命名為.env ,然後將其打開並用實際值編輯其中的項目。對於MongoDB項目,除非有不同的設置,否則您可能不需要編輯它們。
NUXT_OPENAI_API_KEY=your-openai-api-key
NUXT_MONGODB_HOST_NAME=localhost
NUXT_MONGODB_PORT=27017
NUXT_MONGODB_DB_NAME=embeddingvectorsdb
然後運行應用程序
$ npm run dev將瀏覽器打開至http://localhost:5000/ (端口號取決於可用性)以加載應用程序頁面。
注意:我尚未通過最新更新對此進行測試
您可能需要使用https協議運行此應用。這是使用智能手機(例如智能手機)啟用音頻捕獲的。
為了這樣做,請準備正確的certificate和key文件,然後在根目錄處編輯server.mjs 。
然後建立項目
$ npm run build最後,運行應用程序
$ node server.mjs現在,將瀏覽器打開到https://localhost:3000/ (端口號取決於可用性)或使用您的本地IP地址加載頁面。
您可以在頁面/talk/[id] .vue文件中找到這些變量。 ↩2
如果按日期按日期僅在日期下為一個,否則將返回錯誤。 ↩