v0.0.2
Openi-whisper-Talk é um aplicativo de conversa de voz de amostra alimentado por tecnologias OpenAI , como Whisper, um sistema de reconhecimento automático de fala (ASR), conclusões de bate-papo, uma interface que simula a conversa com um modelo que desempenha a função de assistência, incorporação, converte o texto em texto vetorial que pode ser usado em textos como a pesquisa semântica, e a audição. O aplicativo foi construído usando o Nuxt, uma estrutura JavaScript baseada em vue.js.
O aplicativo possui dois novos recursos: gerenciamento de programação e memória de longo prazo. Com o gerenciamento de programas, você pode comandar o chatbot para adicionar, modificar, excluir e recuperar eventos agendados. O recurso de memória de longo prazo permite armazenar trechos de informações que o chatbot se lembrará de referência futura. Você pode integrar perfeitamente as duas funções em suas conversas simplesmente interagindo com o chatbot.
ATUALIZAÇÃO: Atualizou o módulo OpenAI para a versão mais recente e substituiu o modelo de incorporação do
text-embedding-ada-002para o novo modelo V3-Modeltext-embedding-3-small.
OpenAi-Whisper-Talk は、 Whisper (自動音声認識 (ASR) システム) 、 Concluções de bate-papo (アシスタントの役割を果たすモデルとの会話をシミュレートするインターフェース) 、 、 incorporação (セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)) 、そして最新の Text-to-fala (テキストをリアルな話し言葉のオーディオに変える) など、 OpenAi の技術を駆使したサンプル音声会話アプリケーションです。このアプリケーションは、 Vue.js に基づいた Javaxcript フレームワークである Nuxt を使用して構築されています。 を使用して構築されています。 など、
このアプリケーションには、「スケジュール管理」と「永続メモリ」の 2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、将来の参照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。将来的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

Na página principal, você pode escolher com qual chatbot se envolver. Cada chatbot tem uma personalidade distinta, fala um idioma diferente e possui uma voz única. Você pode alterar o nome e a personalidade de qualquer chatbot clicando no botão Editar adjacente ao seu nome. Atualmente, a interface do usuário não suporta diretamente a adição de novos chatbots; No entanto, você pode adicionar manualmente um chatbot e personalizar as configurações de voz e linguagem para cada um modificando o arquivo ativo/contacts.json.

Além disso, você pode personalizar seu perfil clicando no ícone Avatar no canto superior direito. Isso permite que você insira seu nome e compartilhe detalhes sobre si mesmo, permitindo que o chatbot interaja com você de uma maneira mais personalizada.


Os dados de áudio são gravados automaticamente se o som for detectado. Uma configuração de limite está disponível para impedir que o ruído de fundo acione a captura de áudio. Por padrão, isso é definido como -45db (com -10db representando o ponto de corte de som mais alto). Você pode ajustar esse limite modificando a variável MIN_DECIBELS 1 de acordo com suas necessidades.
Quando a gravação é ativada e nenhum som é detectado por 3 segundos , os dados de áudio são enviados e enviados para o back -end para transcrição. Vale a pena notar que, em conversas típicas, a diferença média entre cada turno é de cerca de 2 segundos. Da mesma forma, a pausa entre as frases quando fala é aproximadamente a mesma. Portanto, escolhi uma duração que é longa o suficiente para esperar esperar por uma resposta. Você pode ajustar essa duração editando a variável MAX_COUNT 1 .
O sistema pode registrar continuamente os dados de áudio até que uma resposta seja recebida. Depois que a resposta de áudio do chatbot é recebida e reproduzida, a gravação de áudio está desativada para impedir a gravação inadvertida da própria resposta do chatbot.
Uma entrada de texto também é fornecida se você deseja escrever suas mensagens.
Todos os dados de áudio gravados são carregados no diretório public/upload no formato de arquivo do WebM. Antes de enviar o arquivo de áudio para a API Whisper, é necessário remover todos os segmentos silenciosos. Esta etapa ajuda a evitar a questão bem conhecida das alucinações geradas pelo Whisper. Por esse mesmo motivo, é recomendável definir o valor MIN_DECIBELS o mais alto possível, garantindo que apenas a fala seja registrada.
Para remover peças silenciosas do áudio, usaremos ffmpeg . Certifique -se de instalá -lo.
ffmpeg -i sourceFile -af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-50dB outputFileNeste comando:
-1 sourceFile especifica o arquivo de entrada.-af silenceremove aplica o filtro silencerremove .stop_periods=-1 remove todos os períodos de silêncio.stop_duration=1 define qualquer período de silêncio por mais de 1 segundo como silêncio.stop_threshold=-50dB define qualquer nível de ruído abaixo de -50db como silêncio.outputFile o arquivo de saída. Para invocar este comando shell em nossa rota da API, usaremos exec do módulo child-process .
Depois de remover as peças silenciosas, o tamanho do arquivo deve ser verificado. O tamanho final do arquivo é geralmente muito menor que o original. Durante essa verificação, os arquivos menores que 16 kb são ignorados, assumindo que um arquivo desse tamanho, com uma profundidade de 16 bits, equivale a aproximadamente um segundo de áudio. Qualquer coisa mais curta é provavelmente inaudível.
// simple file size formula
const audio_file_size = duration_in_seconds * bitrateTodo o processo, desde a eliminação de peças silenciosas até a verificação do tamanho do arquivo, foi projetado para garantir que apenas dados viáveis de áudio sejam enviados para a API Whisper. Nosso objetivo é evitar a alucinação e a transmissão desnecessária de dados.
Tendo confirmado a viabilidade de nossos dados de áudio, passamos a chamar a API Whisper.
const transcription = await openai . audio . transcriptions . create ( {
file : fs . createReadStream ( filename ) ,
language : lang ,
response_format : 'text' ,
temperature : 0 ,
} ) onde lang é o código ISO 639-1 da linguagem especificada do nosso chatbot. Por favor, verifique a lista do idioma atualmente suportado.
Optamos por um formato text simples, pois os registros de data e hora não são necessários e definimos o parâmetro temperature como zero para obter uma saída determinística.
Depois de receber a transcrição do Whisper, passamos a enviá -la para a API de conclusão de bate -papo com a chamada de função. Utilizamos o mais recente módulo OpenAI Node.js (versão 4) lançado ontem (2023/11/07), que inclui um formato atualizado de cambalhamento de funções. Essa iteração mais recente permite a invocação de várias funções em uma única solicitação.
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 }
]
} )Vamos dissecar os vários componentes desta chamada nas seções subsequentes.
O prompt do sistema desempenha um papel crucial em dar vida ao nosso chatbot. É aqui onde estabelecemos seu nome e persona, com base no chatbot escolhido pelo usuário. Fornecemos instruções específicas sobre como responder, juntamente com uma lista de funções que ela pode executar. Também informamos sobre a identidade do usuário e alguns detalhes pessoais. Finalmente, definimos a data e a hora atuais, essenciais para ativar as funções do calendário.
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` Todas as mensagens serão armazenadas no MongoDB .
Para gerenciar tokens e evitar superar o limite máximo do modelo, enviaremos apenas as últimas 20 interações. A função trim_array foi projetada para aparar o histórico da mensagem se ultrapassar 20 voltas. Esse limite pode ser ajustado para atender aos seus requisitos específicos.
// allow less than or equal to 15 turns
const history_context = trim_array ( all_history , 15 ) Na tela principal, você tem a opção de apagar o histórico anterior para cada chatbot.

Observe que separamos o manuseio de chamadas de função (function_call.js) da chamada principal da API de conclusão de bate -papo (transcribe.php). Essa distinção é feita para abordar as instâncias em que o conteúdo do texto está presente enquanto uma chamada de função é invocada, que normalmente é nula. Essa separação permite que o aplicativo exiba o texto enquanto processa simultaneamente a chamada de função. Também envolvi o processo em um loop no caso de uma segunda chamada da API resultar em outra chamada de função. Provavelmente, isso poderia ser tratado com mais elegância, implementando o streaming , mas ainda não aprendi a usar o streaming com o NUXT.
Nossas funções são categorizadas sob dois novos recursos: gerenciamento de programação e memória de longo prazo .
Vamos primeiro examinar como gerenciamos a função chamando com o novo formato. Devemos utilizar o novo parâmetro de ferramentas em vez do parâmetro de funções agora depreciado para permitir a invocação de várias chamadas de função.
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 }
]
} ) Verifique o esquema JSON de cada funções do lib/ Diretório. Aqui está para 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" ]
}
}Para DiCuss como tudo isso funciona, vamos para a próxima seção.
Para gerenciamento de programas , temos as seguintes funções:
Todas as entradas do calendário serão armazenadas em MongoDB. Observe que todas as entradas estarão acessíveis a todos os chatbots.
Vamos dar uma olhada em uma conversa de bate -papo para demonstrar como esses elementos interagem:
Usuário: Bom dia, Jeeves. Qual é a minha agenda para hoje?
Chamada de função (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"}' }
} ]
}Resposta da função:
[
{
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' +
'}'
}
]Resumo do chat:
{ 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.'
}Usuário: parece redundante ter uma entrada separada para o mesmo evento. Você pode editar o evento com Anna para incluir o encontro de Tony e excluir o outro evento?
Chamada de funções (Invoked Edit_Calendar_Entry e 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"}'
}
}
]
}Resposta da função:
[ {
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}'
}
]Resumo do chat:
{ 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.'
}Usuário: Ok, só para ter certeza, você pode me dizer minha programação novamente para hoje?
Chamada de função (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"}' }
}
]
}Resposta da função:
[ {
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' +
'}'
}
]Resumo do chat:
{ 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.'
}Usuário: Ah, você esqueceu de adicionar o local no jantar. Desculpe, mas você pode atualizá -lo novamente?
Chamada de função (invoked 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."}'
}
}
]
}Resposta da função:
{
tool_call_id : 'call_eRTFMcz2l8qHO7nhp3msI3v8' ,
role : 'tool' ,
name : 'edit_calendar_entry' ,
content : '{n "message": "Entry edited",n "name": "Dinner with Anna"n}'
}Resumo do chat:
{ role : 'assistant' ,
content : 'The event "Dinner with Anna" has been updated to include the meeting with Tony at the Electric Sheep Bar.'
}Usuário: Ok, quero confirmar novamente, você pode me dizer minha programação para hoje?
Chamada de função (invocou get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_HYlAj4BTeWcu2CxSp4zBYPuC' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}Resposta da função:
[ {
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' +
'}'
}
]Resumo do chat:
{ 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.'
}Para memória de longo prazo , temos as seguintes funções
Todas as funções anteriores se concentraram principalmente na recuperação típica de dados e nas tarefas de definição. Mas, para recuperação de memória de longo prazo, não podemos simplesmente fazer a pesquisa de palavras -chave. Precisamos considerar o contexto da consulta. Precisamos fazer pesquisa semântica. E nesse sentido, usaremos a API de incorporação . Vamos prosseguir para a próxima seção para discutir isso ainda mais.
ATUALIZAÇÃO : Eu substituí
text-embedding-ada-002portext-embedding-3-small. Com base nos meus testes, o último tem um bom desempenho. As pontuações de similaridade de cosseno entre respostas intimamente relacionadas e respostas não relevantes são marcadamente distintas. Além disso, o desempenho de custo do modelo pequeno V3, ao preço de US $ 0,00002/1K, é um acéfalo? No entanto, você precisa converter seus dados vetoriais de ADA para V3 Small, pois eles não são compatíveis. Observe que também reduzimos o limite de 0,72 para 0,3.
Para simplificar, a incorporação mede a relação das seqüências de texto. Se chamarmos a API, ela nos dará dados vetoriais de números flutuantes associados ao texto de entrada.
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" ,
} )Para usar isso em nosso aplicativo, implementaremos o que eles chamam de geração de recuperação de recuperação ou pano simples.
Inicialmente, quando novos dados são recebidos da função Save_New_Memory, chamamos a API de incorporação para gerar sua representação vetorial. Esses dados do vetor são então armazenados no MongoDB para uso futuro.
Posteriormente, quando um usuário envia uma consulta que requer recuperação de memória, a função get_info_from_memory é acionada. Chamamos a API de incorporação para os parâmetros de pesquisa e os compara com os dados do vetor armazenado usando uma similaridade simples de cosseno. Essa comparação normalmente gera várias correspondências com pontuações variadas. Definimos nosso limite para uma pontuação de 0,3 (0,72 para o modelo ADA) ou superior, e limitamos os resultados a um máximo de 10.
Os resultados são passados para a API final de conclusão de bate -papo, que determina a resposta mais adequada à consulta do usuário. A IA tem a capacidade de selecionar um ou mais resultados como base para sua resposta, dependendo da natureza da consulta. É aqui que o verdadeiro poder da IA é demonstrado. Em vez de simplesmente regurgitar todas as informações que recebe, a IA analisa os dados e formula uma resposta apropriada. Se o resultado do pano for considerado suficiente, ele gerará uma resposta positiva de texto.
Para ajudar a facilitar o armazenamento de dados vetoriais e outras operações vetoriais no MongoDB , estou usando o módulo Mongoose. De longe, esta é a solução mais indolor para lidar com armazenamento e recuperação de dados vetoriais.
Primeiro, definimos nosso esquema:
const memoryVectorsSchema = new mongoose . Schema ( {
chunks : [ {
embedding : [ Number ] ,
text : String
} ]
} ) Em seguida, anexamos a função getScore , que executa a similaridade do cosseno entre o modelo de documento e a sequência de consulta incorporada:
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 }
} )
}Em seguida, compilamos nosso esquema em um modelo:
const MemoryVectors = mongoose . model ( 'MemoryVectors' , memoryVectorsSchema )Para salvar um vetor dados:
const newVector = new MemoryVectors ( {
chunks
} )
newVector . save ( )Para pesquisar os dados do vetor armazenado com as incorporações da consulta do usuário
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 ) : ''É isso. Legal, hein?
Para demonstrar como tudo isso funciona, vamos verificar uma amostra de conversa.
Usuário: Ei, diga -me, qual é o seu hambúrguer favorito? queijo? bacon? cogumelo?
BOT: Oh, com certeza! Eu sou tudo sobre aquele hambúrguer de bacon, sabe? O bacon crocante leva a um nível totalmente novo. E você?
Usuário: cogumelo! com queijo! Oh, eu amo isso.
Bot: Totalmente! Cogumelos e queijo são uma partida feita no paraíso do hambúrguer. Você já tentou fazer seu próprio hambúrguer de cogumelos e queijo em casa? Vale a pena!
Usuário: ainda não. Você já experimentou abacate?
BOT: Oh, totalmente! O abacate em um hambúrguer é como a cereja no topo, sabe? Acrescenta a bondade cremosa e amanteigada que leva tudo ao próximo nível. Você já tentou?
Usuário: Sim. Na verdade, há uma história engraçada relacionada a ela. Então, eu já tive hambúrguer de abacate antes. Então, uma vez, eu e meus amigos fomos para a viagem de esqui. Ao longo do caminho, paramos em uma loja de hambúrgueres. Enquanto eu estava navegando no menu, vi abacate e apontei quando pedi. Você sabe o que aconteceu a seguir?
BOT: De jeito nenhum! O que aconteceu? Eles lhe deram um hambúrguer com, tipo, um abacate inteiro? Isso seria hilário!
Usuário: Não. mas feche. Eles me deram um hambúrguer com fatia de abacate. nada mais. Acontece que o que escolhi no menu é para opção vegana!
BOT: Oh meu Deus, isso é muito engraçado! Então você acabou com um hambúrguer de abacate simples em vez do que você esperava. É como uma confusão direta de um filme de comédia! Você acabou gostando do hambúrguer de abacate?
Usuário: Eu comi tudo bem, mas não fiquei satisfeito, é claro. Ei, você pode salvar esta memória engraçada, será bom lembrar às vezes
Chamada de função (invocou 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"]}'
}
}
]
}Nesse ponto, chamamos a API de incorporação e obtemos os dados do vetor para todos os argumentos e salvamos com o texto no MongoDB.
Resposta da função:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]Bot: Memória salva! Agora você tem aquela história engraçada para olhar para trás sempre que precisar de uma boa risada. É como uma pequena fatia da vida, sabe?
Agora, para recuperar informações da memória
Usuário: A propósito, você conhece a junta de hambúrguer que está fechando este mês? aquele em Shibuya? Qual era o nome?
Chamada de função (invocou get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-30"}' }
}
]
}Resposta da função:
[ {
tool_call_id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n "message": "No entries found"n}'
}
]BOT: Não consegui encontrar nenhuma informação sobre um fechamento de um hambúrguer em Shibuya este mês. Talvez seja apenas um boato, sabe? Mas se você descobrir o nome, me avise!
Usuário: Você pode verificar sua memória, acho que poderia ter dito isso antes
Chamada de função (invocou 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"]}'
}
}
]
} Aqui, obtemos as incorporações para os termos de pesquisa em vez da consulta real do usuário. Em seguida, usando os dados vetoriais resultantes, chamamos mongoDb.searchEntry para pesquisar os dados do vetor armazenado no MongoDB.
Resposta da função:
[ {
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` +
'}'
}
]Estou apenas mostrando resultado parcial do conteúdo, mas ele contém vários hits do banco de dados relacionado aos termos de pesquisa. Enviamos todos os resultados para a API de conclusão de bate -papo e deixamos a IA decidir o que fazer.
BOT: Oh, certo! A articulação do hambúrguer que está fechando em Shibuya é o hambúrguer triki. A localização de Shibuya está fechando no dia 20 deste mês. É triste vê -los ir, sabe?
Se você notou, o resultado parcial mostrado está em japonês! Então, definitivamente, isso mostra que não estamos fazendo apenas uma pesquisa de palavras -chave simples aqui. Você pode adicionar dados de linguagem mista e consultar a memória usando idiomas e chances diferentes de que você ainda receberá a resposta. Vamos tentar usar nosso bot russo, Vitaly . Vou usar o Google Tradutor, pois não falo russo.
user: Виталий, можешь вспомнить название моих любимых картофельных чипсов? (Vitaly, você consegue se lembrar do nome das minhas batatas fritas favoritas?)
Chamada de função (invocou get_info_from_memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_8fXETNlDMuKEXsitUuqt5PlE' ,
type : 'function' ,
function : {
name : 'get_info_from_memory' ,
arguments : '{"search":["картофельные чипсы"]}'
}
}
]
}Então, novamente, obtemos as incorporações para o termo de pesquisa e o usamos em cada dados do vetor armazenados no MongoDB.
Resultado da função:
[ {
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` +
'}'
}
]Você pode ver que o resultado dos dados mostrado é misto, com textos japoneses e ingleses.
BOT: то ююииыыы кт março de março de março ччипы - batatas fritas de bacon crocantes. (Suas batatas fritas favoritas são batatas fritas de bacon.)
Ok, vamos prosseguir para a etapa final.
Ao receber a resposta do texto das etapas anteriores, agora chamaremos a API de texto em fala para permitir que nosso chatbot vocalize a resposta
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
} Como o médico não fornece descrições específicas para cada voz, pedi que o ChatGPT fizesse algumas suposições educadas com base em seus nomes:
Alloy (F): Versátil e pode ser adequado para conteúdo técnico ou instrucional, como tutoriais, material educacional ou qualquer conteúdo em que clareza e precisão sejam essenciais.
ECHO (M): Pode ser adequado para contar histórias, audiolivros ou conteúdo que requer um tom mais dramático ou narrativo.
Fable (F): Pode ser ideal para histórias infantis, conteúdo de fantasia ou qualquer narrativa que exija um tom mais divertido ou imaginativo.
Onyx (M): Pode ser adequado para ler literatura séria, fornecer conteúdo de notícias ou qualquer material que exija uma entrega mais séria ou formal.
NOVA (F): Adequado para anúncios, conteúdo motivacional ou qualquer material que exija uma entrega positiva e entusiasta.
Shimmer (F): Adequado para conteúdo de estilo de vida, podcasts ou qualquer coisa que exija um tom envolvente, amigável e convidativo.
Os dados de áudio gerados serão salvos no diretório public/upload no formato de arquivo MP3. Em seguida, enviaremos o link para o lado do cliente, onde um htmlaudioElement dinâmico carregará e reproduzirá.
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 ( )
}Observe que a gravação do áudio será desativada enquanto a resposta de áudio estiver sendo exibida. Isso garante que o aplicativo não registre a resposta do bot.
O FFMPEG é usado para remover peças silenciosas no arquivo de áudio.
Para instalar a ferramenta de linha de comando 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
O MongoDB será usado para armazenar entradas de calendário e dados vetoriais para função de memória de longo prazo.
Para instalar o MongoDB Community Edition, verifique esta página. Você também pode querer instalar o MongoDB Shell para visualizar o banco de dados.
Primeiro, verifique se o FFMPEG e o MongoDB estão instalados no seu sistema.
Para clonar o repositório do projeto e instalar as dependências
$ git clone https://github.com/supershaneski/openai-whisper-talk.git myproject
$ cd myproject
$ npm install Copy .env.example arquivo e renomeie para .env , depois abra -o e edite os itens lá com valores reais. Para os itens do MongoDB, você provavelmente não precisa editá -los, a menos que tenha uma configuração diferente.
NUXT_OPENAI_API_KEY=your-openai-api-key
NUXT_MONGODB_HOST_NAME=localhost
NUXT_MONGODB_PORT=27017
NUXT_MONGODB_DB_NAME=embeddingvectorsdb
Então para executar o aplicativo
$ npm run dev Abra seu navegador para http://localhost:5000/ (o número da porta depende da disponibilidade) para carregar a página de aplicativo.
Nota: ainda não testei isso com a atualização mais recente
Você pode executar este aplicativo usando o protocolo https . Isso é necessário para ativar a captura de áudio usando um dispositivo separado como um smartphone.
Para fazer isso, prepare o certificate e os arquivos key adequados e edite server.mjs no diretório raiz.
Em seguida, compre o projeto
$ npm run buildFinalmente, execute o aplicativo
$ node server.mjs Agora, abra seu navegador para https://localhost:3000/ (o número da porta depende da disponibilidade) ou use seu endereço IP local para carregar a página.
Você pode encontrar essas variáveis nas páginas/talk/[id] .vue arquivo. ↩ ↩ 2
Se a data, o item precisará ser apenas um na data, caso contrário, um erro será retornado. ↩