v0.0.2
Openai-Wisper-Talk -это приложение для голосового разговора, основанное на технологиях OpenAI , таких как Whisper, автоматическое распознавание речи (ASR), завершение чата, интерфейс, который имитирует разговор с моделью, которая играет роль помощника, встраивания, преобразует текст в векторные данные, которые можно использовать в таких задачах, как Semantic Searching, и в последнем текстовом списке. Приложение строится с использованием Nuxt, JavaScript Framework, основанной на Vue.js.
Приложение имеет две новые функции: управление расписанием и долговременная память. С помощью управления графиками вы можете командовать чат -ботом для добавления, изменения, удаления и получения запланированных событий. Функция долговременной памяти позволяет хранить фрагменты информации, которую чат-бот запомнит для будущей ссылки. Вы можете плавно интегрировать обе функции в свои разговоры, просто взаимодействуя с чатботом.
ОБНОВЛЕНИЕ: Обновлено модуль OpenAI до последней версии и заменил модель встраивания от
text-embedding-ada-002до новой модели V3-моделиtext-embedding-3-small.
Openai-Wisper-Talk は、 Whisper (自動音声認識 ((ASR) システム) 、 、 Завершение чата (アシスタントの役割を果たすモデルとの会話をシミュレートするインターフェース アシスタントの役割を果たすモデルとの会話をシミュレートするインターフェース) 、 、 、 、 セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)
このアプリケーションには、「スケジュール管理」と「永続メモリ」の 2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、将来の参照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。将来的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

На главной странице вы можете выбрать, с каким чат -ботом взаимодействовать. Каждый чат -бот имеет особую личность, говорит на разных языках и обладает уникальным голосом. Вы можете изменить имя и личность любого чата, нажав кнопку «Редактировать» , прилегающую к его имени. В настоящее время пользовательский интерфейс не поддерживает добавление новых чат -ботов; Тем не менее, вы можете вручную добавить чат -бот и настроить настройки голоса и языка для каждого, изменяя файл Assets/Contacts.json.

Кроме того, вы можете персонализировать свой профиль, нажав на значок аватара в правом верхнем углу. Это позволяет вам ввести свое имя и поделиться подробностями о себе, позволяя чат -боту взаимодействовать с вами более персонализированным образом.


Аудиоданные автоматически записываются, если обнаружен звук. Пороговая настройка доступна для предотвращения запуска фонового шума. По умолчанию это установлено на -45 дБ (с -10 дБ, представляющее самое громкое отсечение звука). Вы можете настроить этот порог, изменяя переменную MIN_DECIBELS 1 в соответствии с вашими потребностями.
Когда запись включена, и звук не обнаружен в течение 3 секунд , аудиоданные загружаются и отправляются на бэкэнд для транскрипции. Стоит отметить, что в типичных разговорах средний разрыв между каждым ходом составляет около 2 секунд. Точно так же пауза между предложениями при разговоре примерно одинакова. Поэтому я выбрал продолжительность, которая достаточно длинна, чтобы означать ждать ответа. Вы можете настроить эту продолжительность, редактируя переменную MAX_COUNT 1 .
Система может постоянно записывать аудиоданные, пока не будет получен ответ. После того, как аудио -ответ от чат -бота будет получен и воспроизводится, аудиозапись отключена, чтобы предотвратить непреднамеренную запись собственного ответа Chatbot.
Текстовый ввод также предоставляется, если вы хотите написать свои сообщения.
Все записанные аудиоданные загружаются в каталог public/upload в формате файла WebM. Перед тем, как отправить аудиофайл в API Whisper, необходимо удалить все молчаливые сегменты. Этот шаг помогает предотвратить хорошо известную проблему галлюцинаций, полученных Whisper. По этой же причине рекомендуется установить значение 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 Устанавливает любой уровень шума ниже -50 дБ в виде тишины.outputFile файл. Чтобы вызвать эту команду Shell в нашем маршруте API, мы будем использовать exec из модуля child-process .
После удаления молчаливых частей размер файла должен быть проверен. Окончательный размер файла обычно намного меньше, чем оригинал. Во время этой проверки файлы не менее 16 КБ игнорируются, предполагая, что файл такого размера с 16-разрядной глубиной примерно одна секунда звука. Все короче, вероятно, неразборчиво.
// simple file size formula
const audio_file_size = duration_in_seconds * bitrateВесь процесс, от устранения молчаливых частей до проверки размера файла, предназначен для обеспечения того, чтобы на Whisper API отправлялись только жизнеспособные аудиоданные. Наша цель - избежать галлюцинации и ненужной передачи данных.
Подтвердив жизнеспособность наших аудиоданных, мы затем переживаем API Whisper.
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 завершения чата с вызовом функций. Мы используем последний модуль Openai Node.js (версия 4), который был выпущен вчера (2023/11/07), который включает обновленный формат обновления функций. Эта новейшая итерация позволяет вызов нескольких функций в одном запросе.
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 CHAT API (Transcribe.php). Это различие проводится для решения экземпляров, когда присутствует текстовое содержимое, когда вызывается функциональный вызов, который обычно является нулевым. Это разделение позволяет приложению отображать текст при одновременной обработке функционального вызова. Я также заключил процесс в цикле в том случае, если второй вызов API приводит к другому вызову функции. Это, вероятно, может быть обработано более элегантно, внедряя потоковую передачу , но мне еще предстоит научиться использовать потоковую передачу с Nuxt.
Наши функции классифицируются по двум новым функциям: управление графиками и долговременная память .
Давайте сначала рассмотрим, как мы управляем функцией вызова с новым форматом. Мы должны использовать новый параметр «Инструменты» вместо параметра Defrecated Functions , чтобы включить вызов нескольких вызовов функций.
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 }
]
} ) Проверьте схему JSON каждой функции из lib/ Directory. Вот для 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. Обратите внимание, что все записи будут доступны для всех чат -ботов.
Давайте посмотрим на образец разговора в чате, чтобы продемонстрировать, как эти элементы взаимодействуют:
Пользователь: Доброе утро, Дживс. Какое мое расписание на сегодня?
Функциональный вызов (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"}' }
} ]
}Ответ функции:
[
{
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, чтобы включить встречу с Тони и удалить другое мероприятие?
Функция вызова (призванная 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.'
}Пользователь: Хорошо, просто чтобы быть уверенным, вы можете рассказать мне снова мое расписание на сегодня?
Функциональный вызов (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"}' }
}
]
}Ответ функции:
[ {
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 Enterdings . Давайте перейдем к следующему разделу, чтобы обсудить это дальше.
ОБНОВЛЕНИЕ : Я заменил
text-embedding-ada-002наtext-embedding-3-small. Основываясь на моих тестах, последний работает достаточно хорошо. Косинутные оценки сходства между тесно связанными ответами и необратированными ответами заметно различны. Кроме того, затрат на небольшую модель V3 по цене $ 0,00002/1K токенов, это не сложно? Тем не менее, вам необходимо преобразовать ваши векторные данные из ADA в маленький V3, поскольку они не совместимы. Обратите внимание, что мы также снизили порог с 0,72 до 0,3.
Проще говоря, Enterdings измеряет связанность текстовых строк. Если мы позвоним в 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 Entgdings для генерации его векторного представления. Эти векторные данные затем хранятся в MongoDB для будущего использования.
Впоследствии, когда пользователь подает запрос, который требует поиска памяти, запускается функция GET_INFO_FROM_MEMORY. Мы называем API Entgeddings для параметров поиска и сравниваем их с хранимыми векторными данными, используя простое сходство косинуса. Это сравнение обычно дает несколько совпадений с различными оценками. Мы установили наш порог на оценку 0,3 (0,72 для модели ADA) или выше, и мы ограничиваем результаты максимум 10.
Затем результаты передаются в окончательный API завершения чата, который определяет наиболее подходящий ответ на запрос пользователя. ИИ имеет возможность выбрать один или несколько результатов в качестве основы для его ответа, в зависимости от природы запроса. Здесь демонстрируется истинная сила ИИ. Вместо того, чтобы просто отвергать всю информацию, которую он получает, ИИ анализирует данные и формулирует соответствующий ответ. Если результат из тряпки считается достаточным, он генерирует положительный текстовый ответ.
Чтобы облегчить хранение векторных данных и других векторных операций в 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 ) : ''Вот и все. Аккуратно, а?
Чтобы продемонстрировать, как все это работает, давайте проверим образец разговора.
Пользователь: Эй, скажи мне, какой у тебя любимый бургер? сыр? бекон? гриб?
Бот: О, точно! Вы знаете, я все в этом бургере с беконом? Хрустящий бекон просто выводит его на совершенно новый уровень. А вы?
Пользователь: гриб! С сыром! О, мне это нравится.
Бот: Полностью! Гриб и сыр - это совпадение в районе бургера. Вы пробовали сделать свой собственный бургер с грибами и сыром дома? Это того стоит!
Пользователь: пока нет. Вы пробовали авокадо?
Бот: О, полностью! Авокадо на бургере похож на вишню на вершине, понимаете? Это добавляет кремовому маслянистую доброту, которая поднимает все это на следующий уровень. Вы пробовали это?
Пользователь: Да. На самом деле есть забавная история, связанная с этим. Так что у меня был авокадо бургер раньше. Затем однажды я и мои друзья отправились в лыжную поездку. По пути мы остановились в магазине гамбургеров. Пока я просматривал меню, я увидел авокадо и указал на это, когда заказал. Вы знаете, что произошло дальше?
Бот: ни за что! Что случилось? Они дали вам гамбургер с целым авокадо? Это было бы весело!
Пользователь: нет. но близко. Они дали мне гамбургер с кусочком авокадо. ничего больше. Оказывается, тот, который я выбрал в меню, для вегана!
Бот: О, черт возьми, это слишком смешно! Таким образом, вы получили простой бургер авокадо, а не тот, который вы ожидали. Это похоже на смешивание прямо из комедийного фильма! Вы в конечном итоге наслаждались бургером из авокадо?
Пользователь: я съел это хорошо, но, конечно, не был удовлетворен. Эй, ты можешь сохранить эту забавную память, иногда будет хорошо вспомнить
Функциональный вызов (призванное сохранение_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 Enterdings и получаем векторные данные для всех аргументов и сохраняем его с текстом в MongoDB.
Ответ функции:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]Бот: Память сохранила! Теперь у вас есть эта забавная история микширования, чтобы оглянуться назад, когда вам нужно хорошо смеяться. Знаешь, это как маленький кусочек жизни?
Теперь для получения информации из памяти
Пользователь: Кстати, знаете ли вы, что Burger Maint, который закрывается в этом месяце? Тот в Сибуя? Как было имя?
Функциональный вызов (призванный 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 завершения чата и позволяем ИИ решать, что делать.
Бот: О, верно! Бургерский сустав, который закрывается в Сибуя, является Triki Burger. Их расположение шибуя закрывается 20 -го числа этого месяца. Знаешь, грустно видеть, как они уходят?
Если вы заметили, показан частичный результат на японском языке! Так что, безусловно, это показывает, что мы не делаем простой поиск ключевых слов здесь. Вы можете добавить данные смешанного языка и запрашивать память, используя другой язык, и, скорее всего, вы все равно получите ответ. Давайте попробуем использовать нашего русского бота, Виталия . Я буду использовать Google Translate, так как не говорю по -русски.
Пользователь: Витали, то, что -то, что -то, что -то, что -то, как, а? (Виталия, ты помнишь название моих любимых картофельных чипсов?)
Функция вызова (призванная 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` +
'}'
}
]Вы можете видеть, что показанный результат данных является смешанным, с японскими и английскими текстами.
Бот: Тво. (Ваши любимые картофельные чипсы - хрустящие картофельные чипсы из бекона.)
Хорошо, давайте перейдем к последнему шагу.
Получив текстовый ответ из предыдущих шагов, мы теперь позвоним в 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
} Поскольку DOC не дает конкретных описаний для каждого голоса, поэтому я попросил Chatgpt сделать некоторые образованные предположения на основе их имен:
Сплав (F): универсальный и может быть подходит для технического или учебного контента, такого как учебные пособия, образовательные материалы или любой контент, где важны ясность и точность.
Echo (M): может подходить для рассказывания историй, аудиокниг или контента, который требует более драматического или повествовательного тона.
Fable (F): может быть идеальной для детских историй, фантастического содержания или любого повествования, который требует более игривого или творческого тона.
Onyx (M): может быть подходит для чтения серьезной литературы, предоставления новостей или любого материала, который требует более серьезной или формальной доставки.
Nova (F): подходит для рекламы, мотивационного контента или любого материала, который требует позитивной и восторженной доставки.
Shimmer (F): подходит для содержания образа жизни, подкастов или чего -либо, что требует привлекательного, дружелюбного и привлекательного тона.
Затем сгенерированные аудиоданные будут сохранены в каталоге public/upload в формате файла mp3. Затем мы отправим ссылку на сторону клиента, где динамическое 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 Community Edition, пожалуйста, проверьте эту страницу. Вы также можете установить оболочку MongoDB для просмотра базы данных.
Во -первых, убедитесь, что в вашей системе установлены 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 Protocol. Это необходимо, чтобы включить захват аудио с использованием отдельного устройства, такого как смартфон.
Для этого подготовьте надлежащий certificate и key файлы и редактировать server.mjs в корневом каталоге.
Затем купите проект
$ npm run buildНаконец, запустите приложение
$ node server.mjs Теперь откройте свой браузер на https://localhost:3000/ (номер порта зависит от доступности) или используйте свой локальный IP -адрес для загрузки страницы.
Вы можете найти эти переменные в файле страниц/talk/[id] .vue. ↩ ↩ 2
Если по дате, пункт должен быть только одним в соответствии с датой, иначе будет возвращена ошибка. ↩