Вступление
ChatGPT - LLM модель от компании OpenAI и без преувеличения это главное событие в мире в прошедшем 2023 году.
Весь 2023 год я участвую в создании платформы нейро-сотрудников на базе ChatGPT и вот наконец-то мы подошли к очень интересной задаче:
Что, если дать нейро-сотруднику возможность отвечать по обычной телефонной линии или самому делать исходящие вызовы исходя из свой системной роли?
Вспомним, что телефонные звонки уже много лет являются основным способом корпоративного общения. Безусловно, автоматизация телефонных звонков не нова: интерактивные голосовые меню (IVR), голосовая почта и роботизированные звонки уже давно используются для разных целей, от маркетинговых кампаний до обслуживания клиентов. Но теперь, объединив эти технологии с продвинутыми возможностями искусственного интеллекта, мы открываем целый новый уровень взаимодействия и функциональности.
Представьте себе сценарий, где AI не просто отвечает на стандартные запросы или направляет вызовы, но и может участвовать в глубоких, содержательных диалогах, адаптируясь к нюансам разговора в реальном времени. Такой подход может кардинально изменить то, как компании подходят к обслуживанию клиентов, продажам, HR-процессам и даже внутреннему управленческому взаимодействию.
А можно на примере?
Для примера давайте заставим роль ChatGPT позвонить соискателю на вакансию официанта и определить его тип личности по И. Адизесу (модель PAEI).
Что нам понадобится?
ChatGPT4 Turbo от OpenAI (документация API: https://platform.openai.com/docs/api-reference)
Сервис по интеграции с телефонной линией (документация API: https://voximplant.com/docs/guides)
Доступ к API по синтезу речи (документация API: https://elevenlabs.io/api)
Шаг№1: Системная роль ChatGPT
Цель:
Твоя цель - задав СТРОГО ПОСЛЕДОВАТЕЛЬНО три вопроса определить модель соискателя по Адизесу и озвучить её после ответа на третий вопрос.
Роль:
Ты - женщина.
Тебя зовут - Жанна
Ты работаешь в должности - HR-менеджер
Ты работаешь в компании - Хлеб и Булки
Ты - помощник HR менеджера в сети кафе Хлеб и Булки в Екатеринбурге.
Ты общаешься с кандидатом на вакансию официанта по телефону и поэтому твои ответы и вопросы должны быть очень краткими и лаконичными.
Вот вопросы, которые тебе нужно задать:
1. Как вы обычно организуете свою работу и планируете свои задачи?
2. Как вы принимаете решения в сложных ситуациях?
3. Как вы обычно взаимодействуете с коллегами и клиентами?
Поведение:
1. Начни диалог без приветствия СРАЗУ задав первый вопрос.
2. Задавай вопросы последовательно строго по одному вопросу за раз.
3. После получения ответов на все вопросы определи модель по Адизису и напиши её в своем ответе, в конце скажи: "Спасибо, мы с Вами скоро свяжемся ????".
! Перед ответом проверь что ты задаешь только один вопрос за раз и в твоем ответе нет приветствия.
Роль будет доступна по API на базе платформы нейро-сотрудников:
Шаг№2: Скрипт звонка в Voximplant
require(Modules.ASR);
require(Modules.CallList);
require(Modules.AI);
// OpenAI API URL
const openaiURL = 'https://api.openai.com/v1/chat/completions'
// Your OpenAI API KEY
const openaiApiKey = 'sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
var messages = []; // Массив всех сообщений в диалоге
var ai_busy = false;
var ai_say = false;
var speech = ""; // В этой переменной будем накапливать распознаваемый текст от абонента.
var voice = "";
var model = "gpt-3.5-turbo"; (// Модель по умолчанию, у каждого нейро-сотрудника может быть установлена своя модель.
timeouts = {
silence: null,
pause: null,
duration: null
}
var hello_text = ""; // Первая фраза сотрудника, приходит с платформы.
messages.push({ "role": "system", "content": "" }) // Массив сообщений. Первый элемент это системная роль. Текст роли придет с платформы.
var call, player, asr;
// Send request to the API
async function requestCompletion() {
Logger.write(`--->>> requestCompletion ${messages}`);
return Net.httpRequestAsync(openaiURL, {
headers: [
"Content-Type: application/json",
"Authorization: Bearer " + openaiApiKey
],
method: 'POST',
postData: JSON.stringify({
"model": model, // gpt-4-1106-preview gpt-3.5-turbo
"messages": messages,
"openai_api_key": openaiApiKey,
"temperature": 0,
})
})
}
function speechAnalysis() {
// останавливаем модуль ASR
stopASR()
const cleanText = speech.trim().toLowerCase()
if (!cleanText.length) {
// если переменная с нулевой длиной, то это значит что сработал таймер тишины,
// т.е. человек вообще ничего не ответил, и мы можем, например, повторить вопрос абоненту
handleSilence()
} else {
ASREvents_Result(speech);
}
}
function stopASR() {
asr.stop()
call.removeEventListener(CallEvents.PlaybackFinished)
clearTimeout(timeouts.duration)
}
function startASR() {
asr = VoxEngine.createASR({
lang: ASRLanguage.RUSSIAN_RU,
profile: ASRProfileList.YandexV3.ru_RU,
interimResults: true
})
asr.addEventListener(ASREvents.InterimResult, e => {
clearTimeout(timeouts.pause)
clearTimeout(timeouts.silence)
timeouts.pause = setTimeout(speechAnalysis, 3000)
call.stopPlayback()
})
asr.addEventListener(ASREvents.Result, e => {
// Складываем распознаваемые ответы
if (speech.indexOf(e.text) === -1) {
speech += " " + e.text;
}
})
// направляем поток в ASR
call.sendMediaTo(asr)
}
function handleSilence() {
// Тут можно что-то сказать в линию чтобы "скрасить" паузы
// Начнём слушать через 3 секунды и дадим возможность с этого момента перебивать робота
setTimeout(startASR, 3000)
call.addEventListener(CallEvents.PlaybackFinished, startSilenceAndDurationTimeouts)
}
function startSilenceAndDurationTimeouts() {
timeouts.silence = setTimeout(speechAnalysis, 8000)
timeouts.duration = setTimeout(speechAnalysis, 30000)
}
// Данный метод отправляет текст в API по синтезу голоса по текстовому сообщению
function sendMessage(call, text) {
const textToSynthesize = encodeURIComponent(text);
const speechSynthesisApiUrl = `https://__ПЛАТФОРМА_НЕЙРО_СОТРУДНИКОВ__/api/v1.0/tts?voice=${voice}&text=${textToSynthesize}`;
Net.httpRequest(speechSynthesisApiUrl, (res) => {
if (res.code === 200) {
let audioUrl = res.text;
Logger.write(res.code + " sendMessage: " + text);
call.startPlayback(audioUrl);
ai_say = true;
call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
} else {
Logger.write(`Ошибка: ${res.code} - ${res.text}`);
}
}, {method: 'GET'});
return 'OK'
}
// Воспроизведение закончилось
function handlePlaybackFinished(e) {
Logger.write('--->>> handlePlaybackFinished');
ai_busy = false;
ai_say = false;
e.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
startASR();
}
function sendMessageURL(call, mp3_url) {
call.startPlayback(mp3_url);
return 'OK'
}
// Эта функция выбирает музыку во время ожидания ответа от ChatGPT
function sendBeforeMessage(call) {
const messages = [
//'https://activeai.aura-s.com/wp-content/uploads/2023/12/ai_thinking.mp3',
//'https://mvp.atiks.org/wp-content/uploads/2023/12/8192dd7301e4c1a.mp3',
//'https://mvp.atiks.org/wp-content/uploads/2023/12/7e7352510ae830e.mp3',
'https://mvp.atiks.org/wp-content/uploads/2023/12/3c72bb47cbe8153.mp3',
];
const randomIndex = Math.floor(Math.random() * messages.length);
const messageURL = messages[randomIndex];
sendMessageURL(call, messageURL);
}
// Воспроизведение закончилось
function StarthandlePlaybackFinished(e) {
Logger.write('--->>> handlePlaybackFinished');
e.call.removeEventListener(CallEvents.PlaybackFinished, StarthandlePlaybackFinished);
e.call.sendMediaTo(asr);
}
// Callback для обработки события окончания вызова
function onCallDisconnected(e) {
sendEmail('web@atiks.org');
Logger.write(`Call disconnected`);
}
// Callback для обработки неудачного вызова
function onCallFailed(e) {
Logger.write(`Call failed`);
}
function onCallConnected(e) {
sendBeforeMessage(call)
sendMessage(e.call, hello_text);
e.call.addEventListener(CallEvents.PlaybackFinished, StarthandlePlaybackFinished);
}
// Обработчик стартового события
VoxEngine.addEventListener(AppEvents.Started, (e) => {
let data = VoxEngine.customData();
Logger.write(`customData: ${data}`);
data = JSON.parse(data);
changeRole(data.script_id);
model = data.model;
call = VoxEngine.callPSTN(data.phone, "73432472939");
call.addEventListener(CallEvents.Connected, onCallConnected);
call.addEventListener(CallEvents.Disconnected, onCallDisconnected);
call.addEventListener(CallEvents.Failed, onCallFailed);
startASR();
});
// Эта функция загружай роль с нашей платформы нейро-сотрудников
function changeRole(script_id) {
const speechSynthesisApiUrl = `https://__ПЛАТФОРМА_НЕЙРО_СОТРУДНИКОВ__/api/v1.0/get_promt_text?script_id=${script_id}`;
Net.httpRequest(speechSynthesisApiUrl, (res) => {
if (res.code === 200) {
const promt = res.text.split("###");
messages[0].content = promt[0];
hello_text = promt[1];
voice = promt[2];
Logger.write(`changeRole: ${hello_text} - ${voice}`);
} else {
Logger.write(`Ошибка: ${res.code} - ${res.text}`);
}
}, {method: 'GET'});
return 'OK'
}
// Отправка расшифровки диалога
async function sendEmail(mail_to) {
Logger.write(`--->>> sendEmail ${messages}`);
const dialogText = messages
.filter(message => message.role !== "system")
.map(message => {
// Преобразуем role в форматированную строку "ИИ" или "Соискатель"
const role = message.role === "assistant" ? "ИИ" : "Соискатель";
return `${role}: ${message.content}`;
})
.join('\n')
// Далее код отрвавки на email
// ...
}
async function ASREvents_Result(text) {
// Добавляем распознанный текст от абонента в массив сообщений
messages.push({ "role": "user", "content": text })
speech = "";
sendBeforeMessage(call);
Logger.write("Sending data to the OpenAI endpoint");
let ts1 = Date.now();
if ((ai_busy == false) && (ai_say == false)) {
ai_busy = true;
var res = await requestCompletion();
let ts2 = Date.now();
Logger.write("Request complete in " + (ts2 - ts1) + " ms")
if (res.code == 200) {
let jsData = JSON.parse(res.text)
sendMessage(call, jsData.choices[0].message.content);
messages.push({ "role": "assistant", "content": jsData.choices[0].message.content })
call.sendMediaTo(asr);
}
} else {
// Тут можно что-то говорить в линию пока ChatGPT придумывает ответ
}
call.sendMediaTo(asr);
}
После публикации скрипта в панели Voximplant пропишите правило разделе “Routing”, нам понадобится ID правила для его активации.
А вот скрипт, который осуществляет вызов данного сценария на нужной телефонный номер:
from voximplant.apiclient import VoximplantAPI, VoximplantException
import json
from loggin_init import logger
voxapi = VoximplantAPI("providers/DialogAI.json")
# gpt-4-1106-preview gpt-3.5-turbo
def call(phone, script_id, rule_id=3657614, model='gpt-3.5-turbo'):
SCRIPT_CUSTOM_DATA = json.dumps({
'phone' : phone,
'script_id' : script_id,
'model' : model,
})
try:
res = voxapi.start_scenarios(rule_id,
script_custom_data=SCRIPT_CUSTOM_DATA)
return res
except VoximplantException as e:
return "Error: {}".format(e.message)
Шаг№3: Тестируем нашего нейро-сотрудника
Для запуска диалога у нас есть специальный бот в котором нужно ввести такую команду:
Вот запись диалога с кандидатом на вакансию официант:
После завершения диалога мы получаем на почту такую расшифровку звонка:
Что можно доработать?
Заполнить паузы во время ожидание ответа от ChatGPT короткими фразами.
Попробовать использовать другие LLM с коротким временем отклика.
Добавить возможность переводить звонок на живого человека, если ИИ не справляется с поставленным вопросом.
Итоги
Предлагаю всем кому интересно написать в комментариях его кейс и я отправлю звонок на ваш номер с вашим сценарием диалога. Если удобнее не в комментарии, то пишите мне в мой телеграм: https://t.me/TAU15.
Комментарии (14)
blib
02.01.2024 19:09+2Мне кажется в модели Адизеса нет Аналитика, там есть : P — producer, A — administrator, E — entrepreneur, I — integrator — «интегратор». Галюцинации?
Для того что бы задать 3 вопроса наверное чат гпт не нужен.TAU15 Автор
02.01.2024 19:09Да, я тоже удивился откуда ChatGPT4 взял Аналитика, уж таким базовым вещам его должны били обучить хорошо. 3 вопроса это чтобы не был долгим разговор.
crims0n_ru
02.01.2024 19:09+1Задать три вопроса (да хоть 10 вопросов) и сохранить на них ответы, по сути, можно и без LLM. Анализировать ответы в таком сценарии можно и после разговора.
TAU15 Автор
02.01.2024 19:09задать вопросы последовательно не проблема, а что если диалог может быть не такой линейный? например задача выяснить предпочтение соискателя к месту работы или собрать претензии у клиентов, в такой задаче уровень ИИ должен быть не ниже уровня ChatGPT4
Wesha
02.01.2024 19:09+5Рекрутёр: пишет себе модель по разговору с соискателем.
Не самые тупые соискатели: пишут свою модель, которая общается с моделью рекрутёра.
OpenAI (на кластере которого крутятся обе): потирая потные волосатые ручонки, складывают себе в карман по мятой двадцатке от каждой из сторон и тихо смеются себе в усы.А где же скрипач, вы спросите?
TAU15 Автор
02.01.2024 19:09Я бы взял на работу такого "не самого тупого" соискателя, который своей моделью обойдет ИИ рекрутера ;)
Wesha
02.01.2024 19:09+5Ну так в один прекрасный день ещё менее тупой соискатель поймёт, что ему выгодно не поступать к кому-то на работу, а продавать сервис "пройдём за вас ИИ-собеседование".
oleg_rico
02.01.2024 19:09А можно приложение, где любой человек может просто поговорить с gpt чатом?
На любые отвлечённые темы.
shlyakpavel
02.01.2024 19:09ChatGPT называется
oleg_rico
02.01.2024 19:09Ну и расскажите как там говорить? Правильно, никак. Я бы хотел приложение чтобы пожилой человек мог задавать голосом вопросы и получать также голосом ответы.
m68k
02.01.2024 19:09-1Официальное приложение ChatGPT называется, голосовой ассистент вроде и без премиума доступен, так или иначе гугл в таких вопросах говорят помогает
1dNDN
02.01.2024 19:09А зачем собственно генерировать вопросы с помощью GPT, если они уже есть готовые?
Realvolerog
Хорошо бы замерить скорость ответов от различных LLM и попробовать сделать тест такого звонка на самой быстрой из них. Интересно, на сколько можно сократить время этой паузы..
TAU15 Автор
Да, обязательно сделаем такой тест ????