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
如果按日期按日期仅在日期下为一个,否则将返回错误。 ↩