v0.0.2
Openai-Whisper-Talkは、whisper、自動音声認識(ASR)システム、チャット完了などのOpenaiテクノロジーを搭載したサンプル音声会話アプリケーションです。アシスタントの役割、埋め込みのモデルとの会話をシミュレートするインターフェイス、テキストをテキストに変換するインターフェイスは、セマンティック検索のようなテキストで使用できるベクターデータ、および最新のテキストからスパイアウトのようなテキストなどです。このアプリケーションは、vue.jsに基づいたJavaScriptフレームワークであるNuxtを使用して構築されています。
アプリケーションには、スケジュール管理と長期メモリの2つの新機能があります。スケジュール管理を使用すると、チャットボットにコマンドを使用して、スケジュールされたイベントを追加、変更、削除、および取得できます。長期のメモリ機能により、チャットボットが将来の参照のために覚えている情報のスニペットを保存できます。チャットボットと対話するだけで、両方の機能を会話にシームレスに統合できます。
更新: OpenAIモジュールを最新バージョンに更新し、埋め込みモデルを
text-embedding-ada-002から新しいV3モデルのtext-embedding-3-smallに置き換えました。
Openai-Whisper - Talk
このアプリケーションには、「スケジュール管理」と「永続メモリ」の2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、将来の参照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。将来的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

メインページから、関与するチャットボットを選択できます。各チャットボットは独特の個性を持ち、異なる言語を話し、ユニークな声を持っています。その名前に隣接する[編集]ボタンをクリックすると、チャットボットの名前と性格を変更できます。現在、ユーザーインターフェイスは、新しいチャットボットの追加を直接サポートしていません。ただし、アセット/contacts.jsonファイルを変更して、チャットボットを手動で追加し、それぞれの音声と言語設定をカスタマイズできます。

さらに、右上隅のアバターアイコンをクリックして、プロフィールをパーソナライズできます。これにより、名前を入力して自分の詳細を共有し、チャットボットがよりパーソナライズされた方法であなたとやり取りできるようになります。


音が検出された場合、オーディオデータは自動的に記録されます。バックグラウンドノイズがオーディオキャプチャをトリガーするのを防ぐために、しきい値設定が利用可能です。デフォルトでは、これは-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ルートでこのシェルコマンドを呼び出すために、 child-processモジュールのexecを使用します。
サイレントパーツを削除した後、ファイルサイズを確認する必要があります。最終的なファイルサイズは通常、オリジナルよりもはるかに小さいです。このチェック中、16 kb未満のファイルは無視されます。このサイズのファイルは、16ビットの深さで、オーディオの約1秒に相当すると仮定します。短いものはすべて聞こえない可能性があります。
// 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にそれを送信します。昨日リリースされた最新の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呼び出し(transcribe.php)から分離したことに注意してください。この区別は、ファンクションコールが呼び出されている間にテキストコンテンツが存在するときにインスタンスに対処するために行われます。これは通常nullです。この分離により、アプリはテキストを表示しながら、関数呼び出しを同時に処理できます。また、2回目のAPI呼び出しが別の関数呼び出しにつながる場合、ループ内でプロセスを同封しました。これはおそらくストリーミングを実装することでよりエレガントに処理できますが、NUXTでストリーミングを使用する方法をまだ学んでいません。
私たちの機能は、スケジュール管理と長期メモリの2つの新機能の下に分類されます。
まず、新しい形式で関数呼び出しをどのように管理するかを調べましょう。複数の関数呼び出しの呼び出しを有効にするために、現在デプレッドされた関数パラメーターの代わりに新しいツールパラメーターを使用する必要があります。
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/ディレクトリから各機能の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に保存されます。すべてのエントリにすべてのチャットボットがアクセスできることに注意してください。
サンプルチャットの会話を見て、これらの要素がどのように相互作用するかを示しましょう。
ユーザー:おはようございます、ジーブス。今日の私のスケジュールは何ですか?
関数呼び出し(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.'
}ユーザー:同じイベントに別々のエントリがあることは冗長のようです。アンナと一緒にイベントを編集して、トニーに会って他のイベントを削除できますか?
関数呼び出し(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.'
}ユーザー:ああ、夕食に場所を追加するのを忘れました。申し訳ありませんが、もう一度更新できますか?
関数呼び出し(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."}'
}
}
]
}関数応答:
{
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.'
}ユーザー:わかりました、もう一度確認したいのですが、今日のスケジュールを教えてください。
関数呼び出し(Invoked 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-smallに置き換えました。私のテストに基づいて、後者は十分に機能します。密接に関連する回答と関連しない答えの間のコサインの類似性スコアは著しく異なります。さらに、$ 0.00002/1Kトークンの価格のV3スモールモデルのコストパフォーマンスは、簡単ではありませんか?ただし、ベクトルデータはADAからV3 Smallに変換する必要があります。互換性がないためです。また、しきい値を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に渡され、ユーザーのクエリに対する最も適切な応答が決定されます。 AIには、クエリの性質に応じて、その応答の基礎として1つ以上の結果を選択する機能があります。これは、AIの真の力が実証されている場所です。 AIはデータを分析し、適切な応答を策定するだけでなく、それが受け取るすべての情報を単に逆流するのではなく、 RAGの結果が十分であると見なされると、肯定的なテキスト応答が生成されます。
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 ) : ''それでおしまい。きちんと、ハァッ?
これらすべての機能を示すために、サンプルの会話を確認しましょう。
ユーザー:ねえ、教えてください、あなたのお気に入りのハンバーガーは何ですか?チーズ?ベーコン?キノコ?
ボット:ああ、確かに!私はそのベーコンバーガーについてですよね?クリスピーベーコンは、まったく新しいレベルにそれを取ります。あなたはどうですか?
ユーザー:マッシュルーム!チーズと!ああ、私はそれが大好きです。
ボット:完全に!マッシュルームとチーズは、ハンバーガーの天国で作られたマッチです。自宅で自分のマッシュルームとチーズバーガーを作ってみましたか?それはとても価値があります!
ユーザー:まだ。アボカドを試しましたか?
ボット:ああ、完全に!ハンバーガーのアボカドは、上の桜のようなものですよね?それは、全体を次のレベルに引き上げるクリーミーでバターの良い良さを付け加えます。試しましたか?
ユーザー:ええ。実際にそれに関連する面白い話があります。それで、私は以前にアボカドバーガーを食べました。それから一度、私と私の友人はスキー旅行に行きました。途中でバーガーショップに立ち寄った。私がメニューを閲覧している間、私はアボカドを見て、注文したときにそれを指摘しました。次に何が起こったのか知っていますか?
ボット:まさか!どうしたの?彼らはあなたにアボカド全体のようなハンバーガーを与えましたか?それは陽気です!
ユーザー:いいえ。しかし、近い。彼らは私にアボカドのスライス付きのハンバーガーをくれました。他に何もありません。結局のところ、私がメニューで選んだものはビーガンオプション用です!
ボット:おやおや、それは面白すぎる!それで、あなたはあなたが期待していたものの代わりに、プレーンなアボカドバーガーになりました。それはコメディー映画からまっすぐにミックスアップのようなものです!アボカドバーガーを楽しんでしまいましたか?
ユーザー:私はそれを大丈夫でしたが、もちろん満足していませんでした。ねえ、あなたはこの面白い記憶を保存できますか、時々思い出すのは良いことです
function call(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"]}'
}
}
]
}この時点で、Embeddings APIを呼び出して、引数全体のベクトルデータを取得し、MongoDBにテキストで保存します。
関数応答:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]ボット:メモリが保存されました!今、あなたはあなたが大笑いが必要なときはいつでも振り返るその面白いミックスアップの物語を持っています。それは少しの人生のスライスのようなものですね
さて、メモリから情報を取得するため
ユーザー:ところで、今月閉鎖されているハンバーガージョイントを知っていますか?渋谷のもの?名前は何でしたか?
関数呼び出し(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` +
'}'
}
]コンテンツの部分的な結果のみを示していますが、検索用語に関連するデータベースからのいくつかのヒットが含まれています。すべての結果をChat Completions APIに送信し、AIに何をすべきかを決定させます。
ボット:ああ、そうです!渋谷で閉鎖されているハンバーガージョイントは、トリキバーガーです。彼らの渋谷の場所は、今月20日に閉鎖されています。彼らが行くのを見るのは悲しいですね
あなたが気づいた場合、示されている部分的な結果は日本語です!だから、間違いなく、これは私たちがここで単純なキーワードの検索をしていないことを示しています。混合言語のデータを追加し、異なる言語を使用してメモリをクエリすることができます。ロシアのボット、 Vitalyを使ってみましょう。私はロシア語を話さないので、 Google翻訳を使用します。
ユーザー:виталий、するまで≥Вспомнитьназ伝用するまするげするまみするまみ(Vitaly、私のお気に入りのポテトチップの名前を覚えていますか?)
関数呼び出し(Invoked 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` +
'}'
}
]表示されているデータの結果は、日本語と英語のテキストと混合されていることがわかります。
ボット:¡虚なわかりやすい○····oothedです。 (お気に入りのポテトチップスはサクサクしたベーコンポテトチップスです。)
さて、最後のステップに進みましょう。
前の手順からテキスト応答を受信したら、テキストからスピーチへの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):より劇的または物語的なトーンを必要とするストーリーテリング、オーディオブック、またはコンテンツに適している可能性があります。
Fable (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 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プロトコルを使用してこのアプリを実行することをお勧めします。これは、スマートフォンのような別のデバイスを使用してオーディオキャプチャを有効にするために必要です。
そのために、適切なcertificateとkeyファイルを準備し、ルートディレクトリでserver.mjsを編集します。
次に、プロジェクトをバイイドします
$ npm run build最後に、アプリを実行します
$ node server.mjs次に、ブラウザをhttps://localhost:3000/ (ポート番号は可用性に依存します)に開きます。または、ローカルIPアドレスを使用してページを読み込みます。
これらの変数はページ/talk/[id] .vueファイルで見つけることができます。 ↩2
日付までに、アイテムは日付の1つだけである必要がある場合、エラーが返されます。 ↩