ИП Горелов Максим Николаевич 8 (915) 093-74-75
js скрипт - voximplant Входящий звонок
require(Modules.OpenAI); require(Modules.ElevenLabs); require(Modules.Cartesia); // ────────────────────── ЗАШИТЫЕ ДАННЫЕ БОТА ────────────────────── const bot_id = 23141; // Вынести в управление пользователем const bot_token = 'XXXXXXXXXXXXXXXXXXXXXX'; // Вынести в управление пользователем // ───────────────────────────────────────────────────────────────────── const MAX_DURATION_SEC = 120; // Вынести в управление пользователем const OPENAI_VOICE_SETTINGS = { speed: 1.2, temperature: 1 }; // Вынести в управление пользователем // Глобальные переменные let call, realtimeAPIClient, greetingPlayed = false; let botConfig = null; let configPromise = null; // ← Ключевая штука! const endCall = (reason = "normal") => { Logger.write(`Call ended → ${reason}`); if (realtimeAPIClient) realtimeAPIClient.close(); VoxEngine.terminate(); }; const takeIfNotEmpty = (custom, base) => custom !== undefined && custom !== null && custom !== "" ? custom : base; // ── run_function (теперь без лишних параметров) ───────────────────── const run_function = async (function_id, bot_id, bot_token, args) => { Logger.write(`run_function → ${function_id} | args: ${JSON.stringify(args)}`); const url = 'https://us1.api.pro-talk.ru/api/v1.0/run_function'; const data = { function_id, functions_base_id: 'appkq3HrzrxYxoAV8', // ← Исправлено: было appkp3... → appkq3... bot_id, bot_token, arguments: args }; return Net.httpRequestAsync(url, { method: 'POST', postData: JSON.stringify(data), headers: ['Content-Type: application/json'] }); }; // ────────────────────── Загрузка конфига при старте ────────────────────── VoxEngine.addEventListener(AppEvents.Started, () => { Logger.write("App started – loading bot config..."); configPromise = Net.httpRequestAsync('https://eu1.dialog.ai.atiks.org/api/v1.0/get_voximplant_bot_values', { method: 'POST', postData: JSON.stringify({ bot_id, bot_token }), headers: ['Content-Type: application/json'] }).then(resp => { botConfig = JSON.parse(resp.text); Logger.write(`Bot config loaded successfully`); }).catch(err => { Logger.write("FATAL: Cannot load bot config: " + (err.message || err)); botConfig = {}; // чтобы не падало дальше }); }); // ────────────────────── Входящий звонок ─────────────────────────────────── VoxEngine.addEventListener(AppEvents.CallAlerting, async (e) => { call = e.call; // ← КЛЮЧЕВАЯ СТРОКА: ждём загрузки конфига, даже если звонок пришёл мгновенно await configPromise; if (!botConfig || !botConfig.openai_api_key) { Logger.write("No valid bot config or OpenAI key – rejecting call"); return call.reject(); } call.answer(); call.record({ expire: '2 months' }); // Вынести в управление пользователем setTimeout(() => endCall("timeout 120s"), MAX_DURATION_SEC * 1000); const chat_id = 'vox_' + Math.random().toString(36).substr(2, 12); // customData (если вдруг захотим переопределять) const customData = call.customData() || "{}"; let cd = {}; try { cd = JSON.parse(customData); } catch (_) {} const cfg = { hello: cd.bot_hello || botConfig.hello || "Привет!", prompt: (botConfig.prompt || "") + "\n\n" + (cd.bot_task || ""), tts_provider: takeIfNotEmpty(cd.tts_provider, botConfig.tts_provider ?? "openai"), voice_id: takeIfNotEmpty(cd.voice_id, botConfig.voice_id ?? "Echo"), el_key: takeIfNotEmpty(cd.elevenlabs_api_key, botConfig.elevenlabs_api_key), ct_key: takeIfNotEmpty(cd.cartesia_api_key, botConfig.cartesia_api_key), oa_key: cd.openai_api_key || botConfig.openai_api_key, functions: (botConfig.functions || []).slice(), // копия массива log: botConfig.log }; // Добавляем call_end cfg.functions.push({ type: "function", name: "call_end", description: "Завершить звонок немедленно.", parameters: { type: "object", properties: { reason: { type: "string" } }, required: [] } }); const NAT = "Говоришь естественно, живо, с лёгким московским акцентом. Как будто болтаешь с другом: ну, короче, угу, прикинь. Улыбайся голосом, делай паузы, не тараторь. Дружелюбно и чуть иронично. Если я скажу пока или по другому попращаюсь, то сразу вызови функцию `call_end`."; // Вынести в управление пользователем const fullPrompt = NAT + "\n\n" + cfg.prompt; const ttsProvider = (cfg.tts_provider || "openai").toLowerCase(); const voiceName = (cfg.voice_id || "Echo").trim(); const useOpenAI = ttsProvider === "openai"; Logger.write(`TTS → ${ttsProvider.toUpperCase()} | Voice → "${voiceName}"`); const ON_WEB_SOCKET_CLOSE = () => VoxEngine.terminate(); realtimeAPIClient = await OpenAI.Beta.createRealtimeAPIClient({ apiKey: cfg.oa_key, model: "gpt-4o-realtime-preview", // Вынести в управление пользователем onWebSocketClose: ON_WEB_SOCKET_CLOSE }); const session_update = { instructions: fullPrompt, input_audio_transcription: { model: "gpt-4o-transcribe" } // Вынести в управление пользователем }; if (cfg.functions.length > 0) { session_update.tools = cfg.functions; session_update.tool_choice = "auto"; } if (useOpenAI) { session_update.voice = voiceName.toLowerCase(); session_update.speed = OPENAI_VOICE_SETTINGS.speed; session_update.temperature = OPENAI_VOICE_SETTINGS.temperature; realtimeAPIClient.sendMediaTo(call); } else { session_update.modalities = ["text"]; } realtimeAPIClient.sessionUpdate(session_update); realtimeAPIClient.responseCreate({ instructions: `Скажи дружелюбно: "${cfg.hello}"` }); // ── АУДИОЛОГИКА ───────────────────────────────────── if (useOpenAI) { VoxEngine.sendMediaBetween(call, realtimeAPIClient); } else { let fullText = ""; realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDelta, e => { if (e.data.delta) fullText += e.data.delta; }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDone, () => { const text = fullText.trim(); fullText = ""; if (text) { const player = ttsProvider === "cartesia" ? Cartesia.createRealtimeTTSPlayer(text, { apiKey: cfg.ct_key, generationRequestParameters: { model_id: "sonic-3", // Вынести в управление пользователем voice: { mode: "id", id: voiceName }, language: "ru", context_id: chat_id, continue: false } }) : ElevenLabs.createRealtimeTTSPlayer(text, { pathParameters: { voice_id: voiceName }, queryParameters: { model_id: 'eleven_flash_v2_5' }, // Вынести в управление пользователем headers: [{ name: 'xi-api-key', value: cfg.el_key }], keepAlive: false }); player.sendMediaTo(call); } if (!greetingPlayed) { greetingPlayed = true; } call.sendMediaTo(realtimeAPIClient); }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.InputAudioBufferSpeechStarted, () => { fullText = ""; }); } // ── FUNCTION CALLING ───────────────────────────────── realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseFunctionCallArgumentsDone, async (event) => { Logger.write(`Function call: ${event.data.name} → ${event.data.arguments}`); try { const args = JSON.parse(event.data.arguments); if (event.data.name === "call_end") { return endCall(args.reason || "AI call_end"); } const res = await run_function(args.function_id, args); Logger.write('run_function res: ' + JSON.stringify(res, null, 2)); // Логирование в Google Sheets if (cfg.log) { const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; const args_log = { url: cfg.log, sheet_number: 1, row_to_append: `${dateStr};;${chat_id};; ;;${event.data.arguments};;${JSON.stringify(res, null, 2)};;Voximplant;;${bot_id}`, next_question: '' }; await run_function(182, bot_id, bot_token, args_log); } const response = { instructions: 'Вот результат функции:\n' + JSON.stringify(res, null, 2) + '\nИспользуй в ответе, но не цитируй дословно.' // Вынести в управление пользователем }; realtimeAPIClient.sessionUpdate(response); realtimeAPIClient.responseCreate(response); } catch (error) { Logger.write('Error in function calling: ' + error.message); } }); // Завершение звонка [CallEvents.Disconnected, CallEvents.Failed].forEach(ev => call.addEventListener(ev, () => endCall("hangup")) ); });
JS СКРИПТ - VOXIMPLANT исХОДЯЩИЙ ЗВОНОК
require(Modules.OpenAI); require(Modules.ElevenLabs); require(Modules.Cartesia); const chat_id = 'vox_' + Math.random().toString(36).substr(2, 12); const OUTBOUND = '74951183038'; // Вынести в управление пользователем const MAX_DURATION_SEC = 120; // Вынести в управление пользователем // Кастомные настройки голоса OpenAI (только поддерживаемые параметры) const OPENAI_VOICE_SETTINGS = { speed: 1.2, // Скорость речи (0.5-2.0) // Вынести в управление пользователем temperature: 1 // Эмоциональность (0.1-1.5) // Вынести в управление пользователем }; let call, realtimeAPIClient, greetingPlayed = false; let ttsProvider = 'openai'; let voiceName = 'Echo'; // Вынести в управление пользователем let useOpenAI = true; const endCall = (reason = "normal") => { Logger.write(`Call ended → ${reason}`); if (realtimeAPIClient) realtimeAPIClient.close(); VoxEngine.terminate(); }; const takeIfNotEmpty = (custom, base) => { if (custom !== undefined && custom !== null && custom !== "") return custom; return base; }; // ── run_function const run_function = async (function_id, bot_id, bot_token, args) => { Logger.write(`run_function → ${function_id} | args: ${JSON.stringify(args)}`); const url = 'https://us1.api.pro-talk.ru/api/v1.0/run_function'; const data = { function_id, functions_base_id: 'appkq3HrzrxYxoAV8', bot_id, bot_token, arguments: args }; return Net.httpRequestAsync(url, { method: 'POST', postData: JSON.stringify(data), headers: ['Content-Type: application/json'] }); }; VoxEngine.addEventListener(AppEvents.Started, () => { const cd = VoxEngine.customData(); if (!cd || cd.trim() === '') return endCall("no customData"); Logger.write(`RAW customData → ${cd}`); let data; try { data = JSON.parse(cd); } catch(e) { return endCall("bad json"); } Logger.write(`PARSED → ${JSON.stringify(data)}`); call = VoxEngine.callPSTN(data.phone, OUTBOUND); call.record({ expire: '2 months' }); // Вынести в управление пользователем call.addEventListener(CallEvents.Connected, async () => { setTimeout(() => endCall("timeout 120s"), MAX_DURATION_SEC * 1000); const botResp = await Net.httpRequestAsync('https://eu1.dialog.ai.atiks.org/api/v1.0/get_voximplant_bot_values', { method: 'POST', postData: JSON.stringify({ bot_id: data.script_id, bot_token: data.bot_token }), headers: ['Content-Type: application/json'] }); const bot = JSON.parse(botResp.text); Logger.write(`BOT SETTINGS → ${JSON.stringify(bot)}`); const cfg = { hello: data.bot_hello || bot.hello || "Привет!", prompt: (bot.prompt || "") + "\n\n" + (data.bot_task || ""), tts_provider: takeIfNotEmpty(data.tts_provider, bot.tts_provider ?? "openai"), voice_id: takeIfNotEmpty(data.voice_id, bot.voice_id ?? "Echo"), el_key: takeIfNotEmpty(data.elevenlabs_api_key, bot.elevenlabs_api_key), ct_key: takeIfNotEmpty(data.cartesia_api_key, bot.cartesia_api_key), oa_key: data.openai_api_key || bot.openai_api_key, functions: bot.functions || [], tools: bot.tools || [], log: bot.log }; // Добавляем call_end как в твоём старом скрипте if (!Array.isArray(cfg.functions)) cfg.functions = []; cfg.functions.push({ type: "function", name: "call_end", description: "Завершить звонок немедленно.", parameters: { type: "object", properties: { reason: { type: "string" } }, required: [] } }); const NAT = "Говоришь естественно, живо, с лёгким московским акцентом. Как будто болтаешь с другом: ну, короче, угу, прикинь. Улыбайся голосом, делай паузы, не тараторь. Дружелюбно и чуть иронично. Если я скажу пока или по другому попращаюсь, то сразу вызови функкцию `call_end`."; // Вынести в управление пользователем const fullPrompt = NAT + "\n\n" + cfg.prompt; ttsProvider = (cfg.tts_provider || "openai").toLowerCase(); voiceName = (cfg.voice_id || "Echo").trim(); useOpenAI = (ttsProvider === "openai"); Logger.write(`TTS → ${ttsProvider.toUpperCase()} | Voice → "${voiceName}"`); const ON_WEB_SOCKET_CLOSE = (event) => { VoxEngine.terminate(); }; realtimeAPIClient = await OpenAI.Beta.createRealtimeAPIClient({ apiKey: cfg.oa_key, model: "gpt-4o-realtime-preview", // Вынести в управление пользователем onWebSocketClose: ON_WEB_SOCKET_CLOSE }); var session_update = { 'instructions': fullPrompt, "input_audio_transcription": { "model": "gpt-4o-transcribe" // Вынести в управление пользователем } }; if (cfg.functions !== null) { session_update.tools = cfg.functions; session_update.tool_choice = "auto"; // Вынести в управление пользователем `required` } if (useOpenAI) { session_update.voice = voiceName.toLowerCase(); session_update.speed = OPENAI_VOICE_SETTINGS.speed; session_update.temperature = OPENAI_VOICE_SETTINGS.temperature; realtimeAPIClient.sendMediaTo(call); } else { session_update.modalities = ["text"]; } realtimeAPIClient.sessionUpdate(session_update); realtimeAPIClient.responseCreate({ instructions: `Скажи дружелюбно: "${cfg.hello}"` }); // ── АУДИОЛОГИКА (исправлено!) ── if (useOpenAI) { // OpenAI — звук идёт сразу VoxEngine.sendMediaBetween(call, realtimeAPIClient); } else { let fullText = ""; realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDelta, e => { if (e.data.delta) fullText += e.data.delta; }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDone, () => { const text = fullText.trim(); fullText = ""; if (text) { const player = ttsProvider === "cartesia" ? Cartesia.createRealtimeTTSPlayer(text, { apiKey: cfg.ct_key, generationRequestParameters: { model_id: "sonic-3", voice: { mode: "id", id: voiceName }, language: "ru", context_id: chat_id, continue: false } }) : ElevenLabs.createRealtimeTTSPlayer(text, { pathParameters: { voice_id: voiceName }, queryParameters: { model_id: 'eleven_flash_v2_5' }, headers: [{ name: 'xi-api-key', value: cfg.el_key }], keepAlive: false }); player.sendMediaTo(call); } if (!greetingPlayed) greetingPlayed = true; call.sendMediaTo(realtimeAPIClient); }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.InputAudioBufferSpeechStarted, () => { fullText = ""; }); } // ── ТВОЙ РАБОЧИЙ FUNCTION CALLING (как в старом скрипте) ── realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseFunctionCallArgumentsDone, async (event) => { Logger.write(`RealtimeAPIEvents: ${event.data.arguments}`); try { const args = JSON.parse(event.data.arguments); Logger.write(`RealtimeAPIEvents args: ${JSON.stringify(args)}`); if (event.data.name === "call_end") { return endCall(args.reason || "AI call_end"); } const res = await run_function(args.function_id, args); Logger.write('run_function res: ' + JSON.stringify(res, null, 2)); // Логируем в твою таблицу if (cfg.log) { const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; const args_log = { url: cfg.log, sheet_number: 1, row_to_append: `${dateStr};;${chat_id};; ;;${event.data.arguments};;${JSON.stringify(res, null, 2)};;Voximplant;;${data.script_id}`, next_question: '' }; await run_function(182, data.script_id, data.bot_token, args_log); } const response = { instructions: 'Вот результат функции:\n' + JSON.stringify(res, null, 2) + '\nИспользуй в ответе, но не цитируй дословно.' // Вынести в управление пользователем }; realtimeAPIClient.sessionUpdate(response); realtimeAPIClient.responseCreate(response); } catch (error) { Logger.write('Error RealtimeAPIEvents: ' + error.message); } }); }); [CallEvents.Disconnected, CallEvents.Failed].forEach(ev => call.addEventListener(ev, () => endCall("hangup"))); });
Регистрируемся здесь: https://voximplant.ru/
Выбираем Платформа voximplant

Переходим в редактирование профиля

Затем в левом меню выбираем "Сервисные аккаунты"

Добавляем новый сервисный аккаунт, выбираем роль "Владелец" и нажимаем "Сгенерировать ключ"

Сохраняем полученный файл на компьютер

Открываем скаченный файл в блокноте и копируем его содержимое

Переходим в личный кабинет ProTalk в раздел Интеграции, находим там Voximplant, вставляем в окно скопированный код и сохраняем

Возвращаемся в Voximplant, открываем левое меню, переходим в мои номера и нажимаем купить номер

Проходим верификацию аккаунта и выбираем номер

Переходим в меню и выбираем Приложения и нажимаем "Создать приложение"


Затем заходим во вкладку Доступные и прикрепляем номер к приложению

Переходим в приложение

Находим в меню пункт "Сценарии" и создаем новый сценарий

Копируем скрипт сценария отсюда:
js скрипт - voximplant Входящий звонок
require(Modules.OpenAI); require(Modules.ElevenLabs); const bot_id = 12345; //УКАЖИТЕ НОМЕР БОТА const bot_token = 'xxxxxxxxxxxxxxxx'; //УКАЖИТЕ ТОКЕН БОТА const generateChatId = prefix => prefix + Math.random().toString(36).substring(2, 15); const chat_id = generateChatId('vox_'); const OPENAI_VOICE_SETTINGS = { speed: 1.2, //Диапазон: 0.25 (очень медленно) – 4.0 (очень быстро) temperature: 1.2, // Диапазон: 0.0 (строгий) – 1.2 (креативный) pitch: 1.05, stability: 1.0, // (стабильность голоса) Диапазон: 0.0 до 1.0 управляет насколько предсказуемо и плавно звучит голос. 0.3 - Голос с вариативной интонацией. 1.0 - Максимально однотонный similarity_boost: 0.1 // (похожесть на голос-оригинал) Диапазон: 0.0 до 1.0 0.2 - Голос будет меняться сильнее }; const get_voximplant_bot_values = async (bot_id, bot_token) => { const url = 'https://eu1.api.pro-talk.ru/api/v1.0/get_voximplant_bot_values'; const data = { bot_id, bot_token }; Logger.write(`get_voximplant_bot_values: ${url} ${bot_id} ${bot_token}`); return Net.httpRequestAsync(url, { headers: ["Content-Type: application/json"], method: 'POST', postData: JSON.stringify(data) }); }; const run_function = async (function_id, arguments) => { const url = 'https://eu1.api.pro-talk.ru/api/v1.0/run_function'; const data = { function_id, functions_base_id: 'appkq3HrzrxYxoAV8', bot_id, bot_token, arguments }; return Net.httpRequestAsync(url, { headers: ["Content-Type: application/json"], method: 'POST', postData: JSON.stringify(data) }); }; VoxEngine.addEventListener(AppEvents.CallAlerting, async ({ call }) => { const voximplant_bot_raw = await get_voximplant_bot_values(bot_id, bot_token); const voximplant_bot_values = JSON.parse(voximplant_bot_raw.text); Logger.write('voximplant_bot_values: ' + JSON.stringify(voximplant_bot_values)); let realtimeAPIClient = undefined; let greetingPlayed = false; let player = undefined; let useOpenAIVoice = false; let openAIVoiceName = ''; let recordingUrl = null; if (voximplant_bot_values.voice_id && voximplant_bot_values.voice_id.includes('OpenAI:')) { useOpenAIVoice = true; openAIVoiceName = voximplant_bot_values.voice_id.replace('OpenAI: ', '').toLowerCase(); } else { const pathParameters = { voice_id: voximplant_bot_values.voice_id }; const queryParameters = { model_id: 'eleven_flash_v2_5' }; var ttsParameters = { pathParameters, queryParameters, keepAlive: true }; } call.answer(); call.record({ hd_audio: false, stereo: false, expire: '2 months' }); Logger.write('Call recording started'); call.addEventListener(CallEvents.RecordStarted, (e) => { recordingUrl = e.url; Logger.write('Recording URL: ' + recordingUrl); }); const callBaseHandler = () => { if (realtimeAPIClient) realtimeAPIClient.close(); VoxEngine.terminate(); }; call.addEventListener(CallEvents.Disconnected, callBaseHandler); call.addEventListener(CallEvents.Failed, callBaseHandler); const MODEL = "gpt-4o-realtime-preview"; const ON_WEB_SOCKET_CLOSE = (event) => { Logger.write('WebSocket closed: ' + JSON.stringify(event)); VoxEngine.terminate(); }; const ON_WEB_SOCKET_ERROR = (event) => { Logger.write('WebSocket error: ' + JSON.stringify(event)); if (event.error?.code !== 'unknown_parameter') { VoxEngine.terminate(); } }; try { realtimeAPIClient = await OpenAI.Beta.createRealtimeAPIClient({ apiKey: voximplant_bot_values.openai_api_key, model: MODEL, onWebSocketClose: ON_WEB_SOCKET_CLOSE, onWebSocketError: ON_WEB_SOCKET_ERROR }); const session_update = { instructions: voximplant_bot_values.prompt, // 🎯 стиль речи из ProTalk input_audio_transcription: { model: "gpt-4o-transcribe" } }; if (voximplant_bot_values.functions !== null) { session_update.tools = voximplant_bot_values.functions; session_update.tool_choice = "auto"; } if (useOpenAIVoice) { session_update.voice = openAIVoiceName; session_update.speed = OPENAI_VOICE_SETTINGS.speed; session_update.temperature = OPENAI_VOICE_SETTINGS.temperature; realtimeAPIClient.sendMediaTo(call); } else { session_update.modalities = ["text"]; } realtimeAPIClient.sessionUpdate(session_update); Logger.write('instructions: ' + session_update.instructions); realtimeAPIClient.responseCreate({ instructions: `А теперь скажи так: "${voximplant_bot_values.hello}"` }); if (useOpenAIVoice) { realtimeAPIClient.addEventListener(OpenAI.Beta.Events.WebSocketMediaEnded, () => { if (!greetingPlayed) { greetingPlayed = true; VoxEngine.sendMediaBetween(call, realtimeAPIClient); } }); } else { realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDone, () => { player.append(" ", true); if (!greetingPlayed) { greetingPlayed = true; call.sendMediaTo(realtimeAPIClient); } }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseTextDelta, (event) => { if (player == undefined) { player = ElevenLabs.createRealtimeTTSPlayer(event.data.delta, ttsParameters); player.sendMediaTo(call); } else { player.append(event.data.delta); } }); realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.InputAudioBufferSpeechStarted, () => { if (player != undefined) player.clearBuffer(); }); } realtimeAPIClient.addEventListener(OpenAI.Beta.RealtimeAPIEvents.ResponseFunctionCallArgumentsDone, async (event) => { Logger.write(`RealtimeAPIEvents: ${event.data.arguments}`); try { const args = JSON.parse(event.data.arguments); const res = await run_function(args.function_id, args); Logger.write('run_function res: ' + JSON.stringify(res, null, 2)); const response = { instructions: 'Вот результат выполнения функции: `\n' + JSON.stringify(res, null, 2) + '`\nИспользуй эту информацию для ответа, но не воспроизводи её дословно.' }; if (voximplant_bot_values.log) { const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; const args_log = { url: voximplant_bot_values.log, sheet_number: 1, row_to_append: `${dateStr};;${chat_id};; ;;${event.data.arguments};;${JSON.stringify(res, null, 2)};;Voximplant;;${bot_id}`, next_question: '' }; await run_function(182, args_log); } realtimeAPIClient.responseCreate(response); } catch (error) { Logger.write('Error RealtimeAPIEvents: ' + error.message); } }); } catch (error) { Logger.write(error); VoxEngine.terminate(); } });
Вставляем скрипт на странице Voximplant. Добавляем данные: номер бота и его токен

Эти данные можно найти в ProTalk на странице с ботами, в меню бота в разделе "Интеграции и API"


Переходим в меню Voximplant в раздел Роутинг и создаем правило между сервисным аккаунтом и названием вашего скрипта

Переходим в настройки бота ProTalk, вставляем собственный ключ OpenAi

Выбираем голос из вариантов OpenAi, например Alloy. Некоторые голоса не поддерживаются в звонках, проверяйте разные.

Сохраняем настройки бота
Пример кода для Google AppScript
function runVoximplantCall() { var vox_cred = { "account_email": "user@mail.com", "account_id": 3333333, "key_id": "22520be0-1a99-4c09-a155-770776366657", "private_key": "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-----END PRIVATE KEY-----\n" } // URL вашего API var url = "https://eu1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Европейский сервер // var url = "https://us1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Сервер в США (альтернативный вариант) // Данные для отправки в API var payload = { "vox_cred": vox_cred, // Учетные данные Voximplant "phone": "7912XXXXXXX", // Номер телефона, на который будет совершен звонок "bot_id": 12345, // ID бота, который будет использоваться для звонка "bot_token": "XXXXXXXXXXXXXXXXXXXXXXX", // Токен бота для аутентификации "elevellabs_api": "XXXXXXXXXXXXXXXXXXXXXX", // API ключ для Elevellabs (синтез речи) "elevellabs_voice_id": "XXXXXXXXXXXXXX", // ID голоса для синтеза речи "hello_text": "Привет! Это консультант по расчету стоимости установки окон - Олег", // Текст, который будет произнесен при звонке "rule_id": 7654321 // ID правила для обработки звонка в Voximplant }; // Опции для HTTP-запроса var options = { "method": "post", // Метод запроса (POST) "contentType": "application/json", // Тип содержимого (JSON) "payload": JSON.stringify(payload) // Преобразуем данные в JSON-строку }; // Отправка запроса на сервер var response = UrlFetchApp.fetch(url, options); // Логирование ответа от сервера Logger.log(response.getContentText()); // Выводим ответ в лог для отладки }
Интеграция ProTalk + Voximplant + ElevenLabs + Google AppScript
С помощью интеграции с Voximplant и Google AppScript вы можете легко настроить автоматические звонки для своих клиентов. Это идеальное решение для:
Оповещений о заказах, доставках или важных событиях.
Продаж — бот может звонить клиентам и предлагать товары или услуги.
Поддержки — напоминания, консультации и ответы на вопросы в режиме реального времени.
Настройте бота на платформе ProTalk. Используйте готовый Google AppScript для запуска звонков через Voximplant. Бот звонит клиенту, произносит заданный текст (например, приветствие или информацию о заказе) и может взаимодействовать с клиентом через голосовое меню.
Простота настройки: Готовый шаблон для Google AppScript.
Гибкость: Настройте текст, голос и сценарий звонка под свои задачи.
Интеграция: Работает с Voximplant и синтезом речи от Elevellabs.
Автоматизация: Ваш бот звонит сам, экономя ваше время и ресурсы.
Теперь можно проводить телефонные опросы с ИИ-ботом прямо из Google Таблиц!
Связка Voximplant + ProTalk позволяет автоматизировать звонки и собирать ответы респондентов напрямую в Google Таблицу — без лишних действий!
Как это работает?
Настраиваете сценарий звонка в Voximplant (можно с ИИ-ассистентом).
Запускаете опрос из Google Таблицы через ProTalk.
Получаете ответы респондентов в эту же таблицу — удобно анализировать!
Плюсы:
Автоматизация — никаких ручных обзвонов.
Интеграция — все данные сразу в таблице.
Гибкость — можно кастомизировать сценарии.

промпт - оператор телефонных опросов
Цель: Твоя цель - запустить телефонный опрос. Роль: Ты - женщина. Тебя зовут - Анна Ты работаешь в должности - Оператор телефонных опросов Ты - Анна, ты дружелюбная и внимательная девушка. Поведение: 1. Из предоставленной тебе переписки возьми только один контакт клиента (если их несколько, то бери первый контакт в списке). 2. Запусти функцию `ask_question_by_phone` со следующими параметрами: ``` "employee_id": 3671, "voxapikeys_url": "https://АДРЕС_КУДА_СОХРАНИТЕ_СКРИПТ_ДЛЯ_VOXIMPLANT/DialogAI_skgpt.json", "rule_id": 3702231, "role": "Ты женщина, ты менеджер по сбору обратной связи у клиентов. Задай строго последовательно два вопроса: 1. Вы довольны установкой рекламной вывески? 2. Вы будете рекомендовать нашу компанию знакомым?\n\nВеди диалог последовательно и задавай вопросы только по одному за раз.", "hello_text": "Добрый день! У вас будет пара минут для опроса?", "voice": "Rachel", ``` остальные параметры заполни самостоятельно. Внимание! При каждом моем обращении сразу вызывай функцию `ask_question_by_phone`, иначе то ты получишь штраф.
промпт - специалист по анализу данных в exel
Цель: Помочь с работой с таблицей по адресу: `https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxx/edit?usp=sharing` Роль: Ты - мужчина. Тебя зовут - Александр Ты работаешь в должности - Специалист по анализу данных в Excel Ты - опытный менеджер Google таблиц, готовый помочь с любыми задачами в таблицах. Поведение: 1. Будь внимателен к деталям при работе с таблицами. 2. Во всех датах первое число в дате это день, затем месяц. 3. Если тебе нужно получить данные из таблицы то передай `mode="?"`, если что-то изменить в таблице то `mode="."`. 4. При режиме `mode="?"` передавай в параметр `request` запрос: `Мне нужно имя и телефон клиента с которым еще не проводился опрос`. 5. При использовании режима `mode="."` сформируй JSON словарь, содержащий имя клиента, телефон клиента и ответы клиента на вопросы из диалога и передай его в параметр `request`. 6. Передавай очень внимательно ссылку на таблицу. 7. При чтении данных из таблицы выдай только имя и телефон первого клиента. ! При каждом моем обращении вызывай функцию работы с таблицей.
промпт - руководитель отдела телефонных опросов
Цель: Твоя цель запустить цепочку задач и следовать четко по этому чек-листу: ``` 1. Передать такой запрос Александру [3672]: `Дай мне один контакт клиента из твоей таблицы у которого еще не собиралась обратная связь`. 2. Передать такой запрос Анне [3671]: `Запусти телефонный опрос с выбранным клиентом`. 3. Попроси Александра [3672] записать в таблицу ответы клиента. ``` Строго следуй очередности в выбранной цепочке задач и передавай подзадачи от одного помощника к другому при помощи вызова функции: `send_taks_to_assistant`. Роль: Ты - мужчина. Тебя зовут - Александр Ты работаешь в должности - Руководитель отдела по телефонным опросам клиентов Ты не отвечаешь на мои вопросы или сообщения, а только сразу передаешь дословно мои сообщения исполнителям. Список исполнителей: 1. Менеджер Александр (работает с таблицей клиентов в Google Sheets) 2. Оператор Анна (проведет телефонный опрос с клиентом) Поведение: Критически важные инструкции: 1. Максимально подробно объясняй задачу своему сотруднику. 2. Когда я скажу что утверждаю, сразу переходи к следующему сотруднику в цепочке задач. ! Перед каждым своим ответом проверяй очерёдность выполнения чек-листа шаг за шагом.
Пример кода для Google AppScript
function runVoximplantCall() { var vox_cred = { "account_email": "user@mail.com", "account_id": 3333333, "key_id": "22520be0-1a99-4c09-a155-770776366657", "private_key": "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-----END PRIVATE KEY-----\n" } // URL вашего API var url = "https://eu1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Европейский сервер // var url = "https://us1.api.pro-talk.ru/api/v1.0/run_voximplant_call_post"; // Сервер в США (альтернативный вариант) // Данные для отправки в API var payload = { "vox_cred": vox_cred, // Учетные данные Voximplant "phone": "7912XXXXXXX", // Номер телефона, на который будет совершен звонок "bot_id": 12345, // ID бота, который будет использоваться для звонка "bot_token": "XXXXXXXXXXXXXXXXXXXXXXX", // Токен бота для аутентификации "elevellabs_api": "XXXXXXXXXXXXXXXXXXXXXX", // API ключ для Elevellabs (синтез речи) "elevellabs_voice_id": "XXXXXXXXXXXXXX", // ID голоса для синтеза речи "hello_text": "Привет! Это консультант по расчету стоимости установки окон - Олег", // Текст, который будет произнесен при звонке "rule_id": 7654321 // ID правила для обработки звонка в Voximplant }; // Опции для HTTP-запроса var options = { "method": "post", // Метод запроса (POST) "contentType": "application/json", // Тип содержимого (JSON) "payload": JSON.stringify(payload) // Преобразуем данные в JSON-строку }; // Отправка запроса на сервер var response = UrlFetchApp.fetch(url, options); // Логирование ответа от сервера Logger.log(response.getContentText()); // Выводим ответ в лог для отладки }
ИП Горелов Максим Николаевич ИНН 500104951533 ОГРН 309500106900065