v0.0.2
OpenAI-Whisper-Talk ist eine Probe-Sprach-Konversationsanwendung, die von OpenAI -Technologien wie Whisper, einem ASR-System (Automatic Speech Recognition), Chat-Vervollständigungen, einer Schnittstelle, die die Konversation mit einem Modell simuliert, das die Rolle des Assistenten, Einbettungsdings spielt, Text-zu-Vektor-Daten, die in Aufgaben wie semantisch gesucht werden können, simuliert. Die Anwendung wird mit Nuxt erstellt, ein JavaScript -Framework basierend auf VUE.JS.
Die Anwendung verfügt über zwei neue Funktionen: Zeitplanverwaltung und Langzeitgedächtnis. Mit dem Zeitplanverwaltung können Sie den Chatbot befehlen, geplante Ereignisse hinzuzufügen, zu ändern, zu löschen und abzurufen. Mit der Langzeitgedächtnisfunktion können Sie Informationen über Informationen speichern, an die sich der Chatbot als zukünftige Referenz erinnern wird. Sie können beide Funktionen nahtlos in Ihre Gespräche integrieren, indem Sie einfach mit dem Chatbot interagieren.
UPDATE: Aktualisierte das OpenAI-Modul auf die neueste Version und ersetzte das Einbettungsmodell von
text-embedding-ada-002durch das neue V3-Modelltext-embedding-3-small.
OpenAI-Whisper -Talk は、 flüstert (自動音声認識( asr )システム)、 Chat-Abschlüsse (アシスタントの役割を果たすモデルとの会話をシミュレートするインターフェース)、 Einbettung (セマンティック検索などのタスクで使用できるベクターデータにテキストを変換する)、そして最新の text-zu-sprach (テキストをリアルな話し言葉のオーディオに変える)など、 openai の技術を駆使したサンプル音声会話アプリケーションです。このアプリケーションは、 vue.js に基づいた JavaScript フレームワークである nuxt を使用して構築されています。
このアプリケーションには、「スケジュール管理」と「永続メモリ」の 2 つの新機能があります。スケジュール管理を使用すると、チャットボットにスケジュールイベントの追加、変更、削除、取得を指示できます。永続メモリ機能を使用すると、将来の参照のためにチャットボットが覚えておく情報のスニペットを保存できます。これらの機能をチャットボットとの対話を通じてシームレスに統合することができます。将来的に、いくつかの機能強化、たとえばメールやメッセージング機能を追加することで、完全な個人アシスタントになるかもしれません。

Auf der Hauptseite können Sie auswählen, mit welchem Chatbot Sie sich beschäftigen sollen. Jeder Chatbot hat eine eigene Persönlichkeit, spricht eine andere Sprache und besitzt eine einzigartige Stimme. Sie können den Namen und die Persönlichkeit eines beliebigen Chatbots ändern, indem Sie auf die Schaltfläche Bearbeiten neben seinem Namen klicken. Derzeit unterstützt die Benutzeroberfläche die Hinzufügung neuer Chatbots nicht direkt. Sie können jedoch manuell einen Chatbot hinzufügen und die Sprach- und Spracheinstellungen für jedes anpassen, indem Sie die Datei assets/contacts.json ändern.

Darüber hinaus können Sie Ihr Profil personalisieren, indem Sie in der oberen rechten Ecke auf das Avatar -Symbol klicken. Auf diese Weise können Sie Ihren Namen eingeben und Details über sich selbst teilen, sodass der Chatbot in personalisierterer Weise mit Ihnen interagieren kann.


Audiodaten werden automatisch aufgezeichnet, wenn der Ton erkannt wird. Es steht eine Schwellenwerteinstellung zur Verfügung, um zu verhindern, dass Hintergrundgeräusche die Audioerfassung auslösen. Standardmäßig ist dies auf -45 dB festgelegt (wobei -10 dB den lautesten Schallschnitt darstellt). Sie können diesen Schwellenwert anpassen, indem Sie die Variable MIN_DECIBELS 1 entsprechend Ihren Anforderungen ändern.
Wenn die Aufzeichnung aktiviert ist und 3 Sekunden lang kein Ton erkannt wird, werden die Audiodaten hochgeladen und zur Transkription an das Backend gesendet. Es ist erwähnenswert, dass in typischen Gesprächen die durchschnittliche Lücke zwischen jeder Runde etwa 2 Sekunden beträgt. In ähnlicher Weise ist die Pause zwischen den Sätzen beim Sprechen ungefähr gleich. Daher habe ich eine Dauer gewählt, die lang genug ist, um auf eine Antwort zu warten. Sie können diese Dauer anpassen, indem Sie die Variable MAX_COUNT 1 bearbeiten.
Das System kann Audiodaten kontinuierlich aufzeichnen, bis eine Antwort eingegangen ist. Sobald die Audio -Antwort aus dem Chatbot empfangen und gespielt wurde, ist die Audioaufzeichnung deaktiviert, um die unbeabsichtigte Aufzeichnung der eigenen Antwort des Chatbot zu verhindern.
Eine Texteingabe wird auch bereitgestellt, wenn Sie Ihre Nachrichten schreiben möchten.
Alle aufgezeichneten Audiodaten werden im Webm -Dateiformat in das public/upload -Verzeichnis hochgeladen. Bevor Sie die Audiodatei an die Whisper -API senden, müssen alle stillen Segmente entfernt werden. Dieser Schritt verhindern das bekannte Problem der von Flüstern erzeugten Halluzinationen. Aus dem gleichen Grund wird empfohlen, den Wert MIN_DECIBELS so hoch wie möglich festzulegen, um sicherzustellen, dass nur Sprache aufgezeichnet wird.
Um stille Teile aus dem Audio zu entfernen, werden wir ffmpeg verwenden. Stellen Sie sicher, dass Sie es installieren.
ffmpeg -i sourceFile -af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-50dB outputFileIn diesem Befehl:
-1 sourceFile gibt die Eingabedatei an.-af silenceremove wendet den Filter silencerremove .stop_periods=-1 entfernt alle Stillesperioden.stop_duration=1 legt jede Zeit der Stille länger als 1 Sekunde als Stille fest.stop_threshold=-50dB legt einen beliebigen Geräuschpegel unter -50 dB als Stille fest.outputFile die Ausgabedatei. Um diesen Shell-Befehl in unserer API-Route aufzurufen, werden wir exec vom child-process -Modul verwenden.
Nach dem Entfernen der stillen Teile sollte die Dateigröße überprüft werden. Die endgültige Dateigröße ist normalerweise viel kleiner als das Original. Während dieser Prüfung werden Dateien, die kleiner als 16 KB sind, ignoriert, unter der Annahme, dass eine Datei dieser Größe mit einer Tiefe von 16 Bit einer Sekunde Audio entspricht. Alles kürzere ist wahrscheinlich unhörbar.
// simple file size formula
const audio_file_size = duration_in_seconds * bitrateDer gesamte Vorgang von der Beseitigung stiller Teile bis zur Überprüfung der Dateigröße soll sicherstellen, dass nur praktikable Audiodaten an die Whisper -API gesendet werden. Unser Ziel ist es, die Halluzination und die unnötige Übertragung von Daten zu vermeiden.
Nachdem wir die Lebensfähigkeit unserer Audiodaten bestätigt haben, rufen wir dann die Whisper -API an.
const transcription = await openai . audio . transcriptions . create ( {
file : fs . createReadStream ( filename ) ,
language : lang ,
response_format : 'text' ,
temperature : 0 ,
} ) Wo lang der ISO 639-1 Code der angegebenen Sprache unseres Chatbots ist. Bitte überprüfen Sie die Liste der aktuell unterstützten Sprache.
Wir entscheiden uns für ein einfaches text , da keine Zeitstempel erforderlich sind, und wir setzen den temperature auf Null ein, um eine deterministische Ausgabe zu erzielen.
Nachdem wir das Transkript von Whisper erhalten haben, senden wir es mit Funktionsaufruf an die API der Chat -Abschlüsse. Wir verwenden das neueste OpenAI Node.js-Modul (Version 4), das gestern (2023/11/07) veröffentlicht wurde, das ein aktualisiertes Funktionsformat enthält. Diese neueste Iteration ermöglicht den Aufruf mehrerer Funktionen in einer einzigen Anforderung.
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 }
]
} )Lassen Sie uns die verschiedenen Komponenten dieses Aufrufs in den folgenden Abschnitten analysieren.
Die System -Eingabeaufforderung spielt eine entscheidende Rolle bei der Verleihung von Leben für unseren Chatbot. Hier legen wir seinen Namen und seine Person an, basierend auf dem vom Benutzer ausgewählten Chatbot. Wir geben es spezifische Anweisungen zur Reaktion, zusammen mit einer Liste von Funktionen, die es ausführen kann. Wir informieren es auch über die Identität des Benutzers und einige persönliche Daten. Schließlich setzen wir das aktuelle Datum und die aktuelle Uhrzeit, was für die Aktivierung von Kalenderfunktionen wesentlich ist.
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` Alle Nachrichten werden in MongoDB gespeichert.
Um Token zu verwalten und die maximale Grenze des Modells zu übertreffen, senden wir nur die letzten 20 Interaktionen. Die trim_array -Funktion ist so konzipiert, dass sie den Nachrichtenverlauf abschneiden, wenn sie 20 Umdrehungen übertrifft. Dieser Schwellenwert kann angepasst werden, um Ihre spezifischen Anforderungen zu erfüllen.
// allow less than or equal to 15 turns
const history_context = trim_array ( all_history , 15 ) Auf dem Hauptbildschirm haben Sie die Möglichkeit, den vorherigen Verlauf für jeden Chatbot zu löschen.

Bitte beachten Sie, dass wir die Behandlung von Funktionsaufruf (function_call.js) vom API -Aufruf der Haupt -Chat -CHAT (transcribe.php) getrennt haben. Diese Unterscheidung wird getroffen, um Instanzen zu adressieren, wenn Textinhalte vorhanden sind, während ein Funktionsaufruf aufgerufen wird, was normalerweise null ist. Diese Trennung ermöglicht es der App, den Text anzuzeigen und gleichzeitig den Funktionsaufruf zu verarbeiten. Ich habe den Vorgang auch innerhalb einer Schleife beigefügt, falls ein zweiter API -Aufruf zu einem anderen Funktionsaufruf führt. Dies könnte wahrscheinlich durch die Implementierung von Streaming eleganter behandelt werden, aber ich muss noch lernen, wie man Streaming mit Nuxt verwendet.
Unsere Funktionen werden unter zwei neue Funktionen kategorisiert: Zeitplanverwaltung und Langzeitgedächtnis .
Lassen Sie uns zunächst untersuchen, wie wir Funktionen mit dem neuen Format verwalten. Wir müssen den Parameter neuer Tools anstelle des Parameters inzwischen vorbereitete Funktionen verwenden, um den Aufruf mehrerer Funktionsaufrufe zu ermöglichen.
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 }
]
} ) Überprüfen Sie das JSON -Schema der einzelnen Funktionen von lib/ Verzeichnis. Hier ist 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" ]
}
}Um Dicuss, wie all diese Funktionen funktionieren, gehen wir mit dem nächsten Abschnitt über.
Für das Zeitplanmanagement haben wir die folgenden Funktionen:
Alle Kalendereinträge werden in MongoDB gespeichert. Bitte beachten Sie, dass alle Einträge für alle Chatbots zugänglich sind.
Schauen wir uns ein Beispiel -Chat -Gespräch an, um zu demonstrieren, wie diese Elemente interagieren:
Benutzer: Guten Morgen, Jeeves. Was ist mein Zeitplan für heute?
Funktionsaufruf (aufrufen get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [ {
id : 'call_4cCtmNlgYN5o4foVOQM9MIdA' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
} ]
}Funktionsantwort:
[
{
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' +
'}'
}
]CHAT -Zusammenfassung:
{ 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.'
}Benutzer: Es scheint überflüssig, einen separaten Eintrag für dasselbe Ereignis zu haben. Können Sie die Veranstaltung mit Anna bearbeiten, um Tony zu treffen und die andere Veranstaltung zu löschen?
Funktionsaufruf (aufgerufen edit_calendar_entry und 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"}'
}
}
]
}Funktionsantwort:
[ {
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}'
}
]CHAT -Zusammenfassung:
{ 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.'
}Benutzer: Okay, nur um sicher zu sein, können Sie mir meinen Zeitplan für heute wieder sagen?
Funktionsaufruf (aufrufen get_calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_liqFn24Ds4qp0cy2V5NBg62v' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}Funktionsantwort:
[ {
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' +
'}'
}
]CHAT -Zusammenfassung:
{ 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.'
}Benutzer: Ah Sie haben vergessen, den Ort im Abendessen hinzuzufügen. Entschuldigung, aber können Sie es noch einmal aktualisieren?
Funktionsaufruf (aufgerufen 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."}'
}
}
]
}Funktionsantwort:
{
tool_call_id : 'call_eRTFMcz2l8qHO7nhp3msI3v8' ,
role : 'tool' ,
name : 'edit_calendar_entry' ,
content : '{n "message": "Entry edited",n "name": "Dinner with Anna"n}'
}CHAT -Zusammenfassung:
{ role : 'assistant' ,
content : 'The event "Dinner with Anna" has been updated to include the meeting with Tony at the Electric Sheep Bar.'
}Benutzer: Okay, ich möchte es noch einmal bestätigen. Können Sie mir meinen Zeitplan für heute sagen?
Funktionsaufruf (aufgerufen Get_Calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_HYlAj4BTeWcu2CxSp4zBYPuC' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-10"}' }
}
]
}Funktionsantwort:
[ {
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' +
'}'
}
]CHAT -Zusammenfassung:
{ 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.'
}Für das Langzeitgedächtnis haben wir die folgenden Funktionen
Alle früheren Funktionen konzentrierten sich in erster Linie auf typische Datenabruf- und Setzen von Aufgaben. Für das Abrufen von Langzeitgedächtnissen können wir jedoch nicht einfach die Suche nach Keywords durchführen. Wir müssen den Kontext der Abfrage berücksichtigen. Wir müssen semantische Suche durchführen. In dieser Hinsicht werden wir die Einbettungs -API verwenden. Gehen wir mit dem nächsten Abschnitt fort, um dies weiter zu besprechen.
UPDATE : Ich habe
text-embedding-ada-002durchtext-embedding-3-smallersetzt. Basierend auf meinen Tests funktioniert letzteres gut genug. Die Kosinus-Ähnlichkeitswerte zwischen eng verwandten Antworten und nicht relevanten Antworten unterscheiden sich deutlich. Darüber hinaus ist die Kosten-Leistungs-Verhältnis des kleinen Modells V3 mit einem Preis von 0,0000002/1K ein Kinderspiel? Sie müssen jedoch Ihre Vektordaten von ADA in V3 um konvertieren, da diese nicht kompatibel sind. Beachten Sie, dass wir auch den Schwellenwert von 0,72 auf 0,3 gesenkt haben.
Einfach ausgedrückt, misst Einbettungen die Verwandtschaft von Textzeichenfolgen. Wenn wir die API aufrufen, erhalten Sie Vektordaten von schwimmenden Zahlen, die dem Eingabetxt zugeordnet sind.
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" ,
} )Um dies in unserer Anwendung zu verwenden, werden wir das implementieren, was sie als Abrufgeneration oder einfacher Lappen bezeichnen.
Wenn wir zunächst neue Daten aus der Funktion SAVE_NEW_MEMORY empfangen haben, rufen wir die API für die Embodendings auf, um seine Vektordarstellung zu generieren. Diese Vektordaten werden dann in MongoDB für die zukünftige Verwendung gespeichert.
Anschließend wird die Funktion get_info_from_Memory ausgelöst, wenn ein Benutzer eine Abfrage einreicht, für die Speicherab Abruf erforderlich ist. Wir nennen die Embettdings -API für die Suchparameter und vergleicht sie mit den gespeicherten Vektordaten unter Verwendung einer einfachen Cosinus -Ähnlichkeit. Dieser Vergleich liefert typischerweise mehrere Übereinstimmungen mit unterschiedlichen Punktzahlen. Wir haben unseren Schwellenwert auf eine Punktzahl von 0,3 (0,72 für ADA -Modell) oder höher eingestellt und die Ergebnisse auf maximal 10 beschränken.
Die Ergebnisse werden dann an die endgültige API des Chat -Abschlusses übergeben, die die am besten geeignete Antwort auf die Abfrage des Benutzers bestimmt. Die KI hat die Möglichkeit, eine oder mehrere Ergebnisse als Grundlage für ihre Antwort auszuwählen, abhängig von der Art der Abfrage. Hier wird die wahre Kraft der KI demonstriert. Anstatt einfach alle Informationen zu erfassen, die sie erhalten, analysiert die AI die Daten und formuliert eine geeignete Antwort. Wenn das Ergebnis aus dem Lappen als ausreichend erachtet wird, erzeugt es eine positive Textantwort.
Um die Speicherung von Vektordaten und anderen Vektoroperationen in MongoDB zu erleichtern, verwende ich das Mongoose -Modul. Dies ist bei weitem die schmerzlosste Lösung, um die Speicherung und das Abrufen von Vektordaten zu bewältigen.
Erstens definieren wir unser Schema:
const memoryVectorsSchema = new mongoose . Schema ( {
chunks : [ {
embedding : [ Number ] ,
text : String
} ]
} ) Anschließend fügen wir die getScore -Funktion bei, die die Ähnlichkeit der Cosinus zwischen dem Dokumentmodell und der eingebetteten Abfrage -Zeichenfolge ausführt:
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 }
} )
}Als nächstes kompilieren wir unser Schema zu einem Modell:
const MemoryVectors = mongoose . model ( 'MemoryVectors' , memoryVectorsSchema )Um eine Vektordaten zu speichern:
const newVector = new MemoryVectors ( {
chunks
} )
newVector . save ( )So durchsuchen Sie die gespeicherten Vektordaten mit den Einbetten der Benutzerabfrage
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 ) : ''Das war's. Ordentlich, oder?
Um zu demonstrieren, wie all diese funktionieren, überprüfen wir ein Beispielgespräch.
Benutzer: Hey, sag mir, was ist dein Lieblingsburger? Käse? Speck? Pilz?
Bot: Oh, sicher! Ich bin alles über diesen Speckburger, weißt du? Der knusprige Speck bringt ihn einfach auf ein ganz neues Niveau. Was ist mit dir?
Benutzer: Pilz! mit Käse! Oh, ich liebe es.
Bot: Total! Pilz und Käse sind ein Match im Burger Heaven. Haben Sie versucht, Ihren eigenen Pilz- und Käse -Burger zu Hause zu machen? Es ist es so wert!
Benutzer: noch nicht. Haben Sie Avocado ausprobiert?
Bot: Oh, total! Avocado auf einem Burger ist wie die Kirsche oben, weißt du? Es fügt diese cremige, butterartige Güte hinzu, die das Ganze auf die nächste Stufe bringt. Hast du es versucht?
Benutzer: Ja. Es gibt tatsächlich eine lustige Geschichte damit. Also hatte ich schon einmal einen Avocado -Burger. Dann gingen ich und meine Freunde einmal zum Skiausflug. Unterwegs kamen wir in einem Burger -Laden an. Während ich auf der Speisekarte stöberte, sah ich Avocado und zeigte darauf, als ich bestellte. Weißt du, was als nächstes passiert ist?
Bot: Auf keinen Fall! Was ist passiert? Haben sie Ihnen einen Burger mit einer ganzen Avocado gegeben? Das wäre lustig!
Benutzer: Nein. aber schließen. Sie gaben mir einen Burger mit einem Stück Avocado. nichts anderes. Es stellt sich heraus, dass die, die ich auf der Speisekarte ausgewählt habe, für vegane Optionen ist!
Bot: Oh mein Gott, das ist zu lustig! Sie haben also einen einfachen Avocado -Burger anstelle des, den Sie erwartet hatten, mit einem einfachen Avocado -Burger gelandet. Das ist wie eine Verwechslung direkt aus einem Comedy-Film! Haben Sie jedoch den Avocado -Burger genossen?
Benutzer: Ich habe es in Ordnung gegessen, war aber natürlich nicht zufrieden. Hey, kannst du diese lustige Erinnerung sparen, es wird gut sein, sich manchmal zu erinnern
Funktionsaufruf (aufgerufen 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"]}'
}
}
]
}Zu diesem Zeitpunkt nennen wir die API für die Einbettung und erhalten die Vektordaten für die gesamten Argumente und speichern sie mit dem Text an MongoDB.
Funktionsantwort:
[ {
tool_call_id : 'call_2TIYJOo9WJcprD7ozQsERoo4' ,
role : 'tool' ,
name : 'save_new_memory' ,
content : '{n "message": "New memory saved"n}'
}
]Bot: Speicher gespeichert! Jetzt haben Sie diese lustige Mischungsgeschichte, auf die Sie zurückblicken müssen, wenn Sie ein gutes Lachen brauchen. Es ist wie ein kleines Stück Leben, weißt du?
Nun zum Abrufen von Informationen aus dem Speicher
Benutzer: Kennen Sie übrigens das Burger -Joint, der diesen Monat schließt? der in Shibuya? Wie war der Name?
Funktionsaufruf (aufgerufen Get_Calendar_entry):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
type : 'function' ,
function : { name : 'get_calendar_entry' , arguments : '{"date":"2023-11-30"}' }
}
]
}Funktionsantwort:
[ {
tool_call_id : 'call_3PNs56LMrmDViZUnrxdBScGz' ,
role : 'tool' ,
name : 'get_calendar_entry' ,
content : '{n "message": "No entries found"n}'
}
]Bot: Ich konnte diesen Monat keine Informationen über einen Burger -Joint -Schluss in Shibuya finden. Vielleicht ist es nur ein Gerücht, weißt du? Aber wenn Sie den Namen herausfinden, lassen Sie es mich wissen!
Benutzer: Können Sie Ihren Speicher überprüfen? Ich denke, ich hätte Ihnen das vorher vielleicht gesagt?
Funktionsaufruf (aufgerufen 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"]}'
}
}
]
} Hier erhalten wir die Einbettungen für die Suchbegriffe anstelle der tatsächlichen Benutzerabfrage. Mit den resultierenden Vektordaten nennen wir mongoDb.searchEntry , um die gespeicherten Vektordaten in MongoDB zu durchsuchen.
Funktionsantwort:
[ {
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` +
'}'
}
]Ich zeige nur ein Teilergebnis des Inhalts an, enthält jedoch mehrere Treffer aus der Datenbank, die sich auf die Suchbegriffe beziehen. Wir senden alle Ergebnisse an die API der Chat -Abschlüsse und lassen die KI entscheiden, was zu tun ist.
Bot: Oh, richtig! Das Burger -Joint, das in Shibuya schließt, ist der Triki -Burger. Ihr Standort Shibuya wird am 20. dieses Monats geschlossen. Es ist traurig zu sehen, wie sie gehen, weißt du?
Wenn Sie es bemerkt haben, ist das gezeigte Teilergebnis in Japanisch! Dies zeigt also definitiv, dass wir hier nicht einfach eine einfache Keyword -Suche durchführen. Sie können Daten der gemischten Sprache hinzufügen und das Gedächtnis mit unterschiedlicher Sprache abfragen. Die Chancen stehen gut, dass Sie immer noch die Antwort erhalten. Versuchen wir, unseren russischen Bot Vitaly zu verwenden. Ich werde Google Translate verwenden, da ich nicht Russisch spreche.
Benutzer: виталий, мжжж в моаить название моих юби– картольных чипо в ыхых картольesse? (Vitaly, können Sie sich an den Namen meiner Lieblingskartoffelchips erinnern?)
Funktionsaufruf (aufgerufen Get_info_From_Memory):
{ role : 'assistant' ,
content : null ,
tool_calls : [
{
id : 'call_8fXETNlDMuKEXsitUuqt5PlE' ,
type : 'function' ,
function : {
name : 'get_info_from_memory' ,
arguments : '{"search":["картофельные чипсы"]}'
}
}
]
}Auch hier erhalten wir die Einbettungen für den Suchbegriff und verwenden sie gegen jede in MongoDB gespeicherte Vektordaten.
Funktionsergebnis:
[ {
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` +
'}'
}
]Sie können sehen, dass das angezeigte Datenergebnis mit japanischen und englischen Texten gemischt ist.
BOT: т тои ююби & ыisches картольные чипы - knusprige Speck -Kartoffelchips. (Ihre Lieblingskartoffelchips sind knusprige Speck -Kartoffelchips.)
Okay, gehen wir mit dem letzten Schritt fort.
Wenn wir die Textantwort aus den vorhergehenden Schritten erhalten haben, werden wir nun die API von Text-to-Speech aufrufen, um unserem Chatbot die Antwort zu vokalisieren
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
} Da das DOC keine spezifischen Beschreibungen für jede Stimme enthält, bat ich Chatgpt, einige ergebnissen sich auf der Grundlage ihrer Namen zu erstellen:
Legierung (f): vielseitig und kann für technische oder Lehrinhalte geeignet sein, wie z. B. Tutorials, Bildungsmaterial oder Inhalt, bei denen Klarheit und Präzision unerlässlich sind.
Echo (M): Möglicherweise passt das Geschichtenerzählen, Hörbücher oder Inhalte, die einen dramatischeren oder narrativen Ton erfordern.
Fabel (F): Könnte ideal für Kindergeschichten, Fantasy -Inhalte oder eine Erzählung, die einen verspielteren oder einfallsreicheren Ton erfordert.
ONYX (M): Möglicherweise ist es geeignet, ernsthafte Literatur zu lesen, Nachrichteninhalte zu liefern oder ein Material, das eine schwerwiegendere oder formelle Lieferung erfordert.
Nova (F): Geeignet für Anzeigen, Motivationsinhalte oder jedes Material, das eine positive und enthusiastische Lieferung erfordert.
SHIMMER (F): Geeignet für Lifestyle -Inhalte, Podcasts oder alles, was einen ansprechenden, freundlichen und einladenden Ton erfordert.
Die generierten Audiodaten werden dann im Verzeichnis für das public/upload im MP3 -Dateiformat gespeichert. Wir senden dann den Link an die Client -Seite, auf der eine dynamische HTMLaudioelement lädt und abspielt.
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 ( )
}Bitte beachten Sie, dass die Aufzeichnung von Audio deaktiviert wird, während die Audio -Antwort abgespielt wird. Dies stellt sicher, dass die Anwendung die Antwort des Bots nicht aufzeichnet.
FFMPEG wird verwendet, um stille Teile in der Audiodatei zu entfernen.
So installieren Sie das FFMPEG-Befehlszeilen-Tool
# 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 wird verwendet, um Kalendereinträge und Vektordaten für die Langzeitspeicherfunktion zu speichern.
Um die MongoDB Community Edition zu installieren, überprüfen Sie bitte diese Seite. Möglicherweise möchten Sie auch MongoDB -Shell installieren, um die Datenbank anzuzeigen.
Stellen Sie zunächst sicher, dass FFMPEG und MongoDB in Ihrem System installiert sind.
So klonen Sie das Projektrepository und installieren Sie die Abhängigkeiten
$ git clone https://github.com/supershaneski/openai-whisper-talk.git myproject
$ cd myproject
$ npm install Kopieren Sie .env.example -Datei und benennen Sie sie in .env um, öffnen Sie sie und bearbeiten Sie die Elemente dort mit tatsächlichen Werten. Für die MongoDB -Elemente müssen Sie sie wahrscheinlich nur bearbeiten, es sei denn, Sie haben ein anderes Setup.
NUXT_OPENAI_API_KEY=your-openai-api-key
NUXT_MONGODB_HOST_NAME=localhost
NUXT_MONGODB_PORT=27017
NUXT_MONGODB_DB_NAME=embeddingvectorsdb
Dann die App ausführen
$ npm run dev Öffnen Sie Ihren Browser in http://localhost:5000/ (Portnummer hängt von der Verfügbarkeit ab) zum Laden der Anwendungsseite.
Hinweis: Ich habe dies noch nicht mit dem neuesten Update getestet
Möglicherweise möchten Sie diese App mithilfe https -Protokolls ausführen. Dies ist erforderlich, um die Audio -Erfassung mithilfe eines separaten Geräts wie einem Smartphone zu aktivieren.
Bereiten Sie dazu das richtige certificate und key vor und bearbeiten Sie server.mjs im Stammverzeichnis.
Dann kauft das Projekt
$ npm run buildSchließlich führen Sie die App aus
$ node server.mjs Öffnen Sie nun Ihren Browser für https://localhost:3000/ (Portnummer hängt von der Verfügbarkeit ab) oder verwenden Sie Ihre lokale IP -Adresse, um die Seite zu laden.
Sie finden diese Variablen in Seiten/Talk/[ID] .vue -Datei. ↩ ↩ 2
Wenn nach Datum, muss der Artikel nur eins unter dem Datum sein, da sonst ein Fehler zurückgegeben wird. ↩