Привет, Хабр! Продуктовая аналитика хорошо работает с событиями и метриками, но ломается на живых коммуникациях. Звонки зачастую остаются неохваченными анализом, хотя именно там слышно как клиент злится или сомневается, но эти сигналы доходят до менеджера продукта хаотично, а не в системном виде.
В этом гайде разберём, как превратить записи звонков в продуктовые инсайты без ручного прослушивания — с помощью Python, звонков от МТС Exolve, интерфейса на Streamlit и нейронкой MWS GPT от МТС.
Архитектура решения
Инструмент построен как простой и прозрачный пайплайн обработки данных. Он не требует отдельного бэкенда, очередей или фронтенда — вся логика укладывается в несколько Python-модулей и управляется из интерфейса Streamlit.
С точки зрения потока данных система состоит из последовательных шагов, каждый из которых решает одну конкретную задачу и не знает о деталях остальных.
Сбор данных. Забираем историю звонков и расшифровки по API с разделением ролей «клиент» и «оператор»
Анализ. Передаём текст диалога в нейронку и получаем структурированный JSON: краткую суть обращения, настроение клиента, риск ухода и продуктовые запросы
В��зуализация. Показываем результат в дашборде и сохраняем выбранные инсайты в таблицу
Компоненты
МТС Exolve API — источник данных со звонками и транскрипциями
MWS GPT — анализ диалога и генерация структурированных данных с помощью модели mws-gpt-alpha
Streamlit — интерфейс, фильтры, метрики и кэширование. Позволяет быстро собрать рабочий интерфейс прямо на Python без отдельного фронтенда и API-слоя
Google Sheets — простой способ сохранить инсайты из интерфейса и передать их команде
Шаг 1. Забираем диалоги из МТС Exolve
Анализ запускается вручную из интерфейса. Это сделано это для контроля нагрузки и экономии затрат на нейронку.
По кнопке запрашиваем список последних звонков через метод GetList и получаем 10 последних звонков. Лимит можно легко увеличить, например до 100.
Полученный список отображается в интерфейсе. Вы выбираете конкретный звонок и вручную запускаете анализ — только в этот момент начинается дальнейший пайплайн.
Для выбранного звонка по его call_uid запрашивается транскрипция. Она приходит в виде набора фрагментов, привязанных к аудиоканалам. Эти каналы соответствуют ролям участников разговора — клиенту и оператору — и передаются в поле channel_tag.
Эту структуру важно сохранить. Если склеить текст без указания ролей, модель теряет контекст разговора и хуже интерпретирует эмоции, претензии и запросы клиента. Поэтому на этом этапе маппятся каналы на роли Client и Operator и собирается последовательный диалог.
На выходе получается структурирова��ный текст разговора, готовый к анализу нейронкой.
# exolve_provider.py import requests from config import Config class ExolveProvider: def __init__(self): self.headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"} def get_last_calls(self, limit=10): """Запрашивает список последних звонков.""" url = "https://api.exolve.ru/statistics/call-history/v2/GetList" try: resp = requests.post(url, headers=self.headers, json={"limit": limit}, timeout=10) return resp.json().get('calls', []) except Exception as e: return [] def get_transcript(self, call_uid: str) -> str | None: """Скачивает и форматирует расшифровку.""" url = f"{Config.EXOLVE_BASE_URL}/GetTranscribation" try: resp = requests.post(url, headers=self.headers, json={"uid": int(call_uid)}, timeout=10) if resp.status_code != 200: return None data = resp.json() chunks = data.get('transcribation', [{}])[0].get('chunks', []) # Склеиваем диалог: [Role]: Text lines = [] for chunk in chunks: role = "Operator" if chunk.get('channel_tag') == 2 else "Client" text = chunk.get('text', '') lines.append(f"[{role}]: {text}") return "\n".join(lines) except Exception: return None
Шаг 2. Анализируем как продакт менеджер
На этом этапе получаем продуктовые инсайты, с которыми можно работать дальше. Для этого используем LLM в роли Senior Product Manager. Передаём модели полный диалог вместе с промптом, который явно задаёт роль и ожидаемый формат ответа.
Ключевой момент — строгая структура результата. Просим модель вернуть JSON с заранее определёнными полями:
краткая суть обращения
настроение клиента
риск ухода
продуктовые запросы
цитаты из речи клиента
Такой формат позволяет показывать в интерфейсе, фильтровать, сохранять и передавать в бэклог без дополнительной обработки.
# prompts.py def get_product_analysis_prompt(transcript: str) -> str: return f""" Ты — Senior Product Manager. Твоя цель — найти точки роста продукта на основе звонка в техподдержку. **Текст звонка:** {transcript} **Задача:** Проанализируй диалог и верни JSON со следующей структурой: {{ "summary": "Краткая суть проблемы (1 предложение)", "customer_mood": "Настроение клиента (Angry/Neutral/Happy/Frustrated)", "key_fear": "Глубинный страх клиента (потеря денег/времени/контроля)", "feature_request": "Какую функцию явно или неявно хочет клиент? (или null)", "churn_risk": "Риск ухода (Low/Medium/High)", "original_phrases": ["Цитата 1", "Цитата 2"], "tags": ["Tag1", "Tag2"] }} **Важно:** 1. Цитаты должны быть точными выдержками из текста клиента. 2. Отвечай только валидным JSON. """
Чтобы ответы были стабильными и воспроизводимыми:
Снижаем степень случайности через температуру до 0.1
Ограничиваем количество на ответ до 1500 для контроля расходов на нейронку
Ограничиваем выборку слов наиболее вероятными, задав top_p 0.1
Чтобы UI не падал с ошибками, используем try/except и возвращаем fallback-результат при неуспехе.
На выходе получаем структурированный результат анализа диалога, готовый к визуализации и сохранению.
# ai_engine.py def analyze_transcript(self, transcript: str, prompt_func) -> dict: # Заглушка на случай сбоя fallback_result = { "summary": "Ошибка анализа (LLM Error)", "customer_mood": "Unknown", "churn_risk": "Unknown", "tags": ["Error"] } payload = { "model": "mws-gpt-alpha", "messages": [...], "temperature": 0.1, "top_p": 0.1, # Nucleus Sampling для максимальной строгости "max_tokens": 1500 } try: resp = requests.post(Config.MTS_AI_URL, headers=self.headers, json=payload, timeout=30) resp.raise_for_status() content = resp.json()['choices'][0]['message']['content'] # Очистка от markdown-оберток (AI любит добавлять ```json) clean_content = content.replace("```json", "").replace("```", "").strip() return json.loads(clean_content) except Exception as e: logger.error(f"AI Error: {e}") return fallback_result
Шаг 3. Собираем дашборд
Streamlit позволяет делать интерфейс прямо на Python без отдельного фронтенда. В нашем случае он показывает список звонков, запускает обработку и отображает результат.
Чтобы интерфейс работал быстро и не дёргал API лишний раз, кэшируем через @st.cache_data. Тяжёлые операции — например, загрузка списка звонков — выполняются один раз и переиспользуются при следующих действиях пользователя.
# app.py import streamlit as st import pandas as pd from exolve_provider import ExolveProvider from ai_engine import AIAnalyst from prompts import get_product_analysis_prompt # Инициализация сервисов (кэшируем, чтобы не пересоздавать объекты) @st.cache_resource def get_services(): return ExolveProvider(), AIAnalyst() exolve, ai = get_services() @st.cache_data(ttl=300) # Кэш данных живет 5 минут def load_calls(limit): return exolve.get_last_calls(limit) # Основной интерфейс st.title("📞 Анализ звонков: Поиск инсайтов") # Сайдбар с настройками with st.sidebar: limit = st.slider("Загружать звонков", 5, 50, 10) if st.button("🔄 Сброс кэша"): st.cache_data.clear() # Получаем список звонков calls = load_calls(limit) df = pd.DataFrame(calls) # Выводим таблицу st.dataframe(df[['date', 'calling_number', 'duration', 'status']], use_container_width=True) # Выбираем звонок для анализа selected_uid = st.selectbox("Выберите звонок:", df['uid'].unique()) if st.button("🚀 Запустить анализ"): with st.spinner("Магия AI..."): text = exolve.get_transcript(selected_uid) analysis = ai.analyze_transcript(text, get_product_analysis_prompt) # Визуализация метрик (KPI) c1, c2, c3 = st.columns(3) c1.metric("Настроение", analysis.get("customer_mood")) # Подсветка риска risk = analysis.get("churn_risk") c2.metric("Риск ухода", risk, delta_color="inverse" if risk == "High" else "normal") # Карточки с инсайтами (используем HTML/CSS внутри Markdown для красоты) st.markdown(f""" <div class="insight-box"> <b>Суть:</b> {analysis.get("summary")}<br> <b>Глубинный страх:</b> {analysis.get("key_fear")} </div> """, unsafe_allow_html=True)
Полный код app.py занимает менее 100 строк, но дает полноценный UI с табами, выпадающими списками и красивыми метриками.

Шаг 4. Сохранение результатов
Если найденный инсайт кажется полезным, его можно сохранить одной кнопкой «Сохранить в Google Sheets». В этом случае результат анализа записывается в Google Sheets — как отдельная строка со структурой JSON от нейросети.
Таблица используется как простой бэклог: сюда попадают продуктовые проблемы, риски ухода и запросы на фичи, которые дальше можно разобрать командой.
expand_less if st.button("💾 Сохранить в Google Sheets"): if sheets.save_analysis(selected_uid, analysis): st.success("Инсайт сохранен в бэклог!") else: st.error("Ошибка сохранения")
Пример использования
Продакт-менеджер в нашем интерфейсе видит звонок длительностью 15 минут, что аномально долго для нашей поддержки.
Кликает «Анализировать»
Система показывает:
Настроение Frustrated (Раздражен)
Риск ухода High
Суть: «Клиент не смог выгрузить отчет в PDF, кнопка выдает 404»
Инсайт: «Клиент боится, что не успеет сдать отчетность налоговой из-за нашего бага»
Продакт нажимает «Сохранить в Google Sheets». Проблема улетает в общий реестр с тегом #critical.
Без этого инструмента звонок мог потеряться, а клиент мог бы молча уйти к конкурентам.
Заключение
Мы разобрали, как превратить записи звонков службы поддержки и отдела продаж в источник продуктовых инсайтов. Связка Python, МТС Exolve, Streamlit и MWS GPT даёт управляемый процесс: анализ запускается по требованию, нагрузка контролируется, а результат готов к дальнейшей работе.
Кстати, если вы не хотите писать код, у MWS GPT есть и No-code платформа, где похожие пайплайны можно собирать визуально. Но для инженера гибкость кода всегда дает преимущество.
Идеи для развития
Вместо ручного запуска пайплайн повесить на вебхуки об окончании звонка в МТС Exolve и анализировать диалоги автоматически
Собирать проблематику по всем звонкам и раз в сутки агрегировать результаты: формировать краткую выжимку по болям, рискам и запросам на фичи и отправлять её менеджерам продуктов, например, утром перед началом рабочего дня.
Отдельное направление — работа с промптами. При повышенной креативности модели можно просить нейронку предлагать 3–5 идей потенциальных фич на основе проблем и страхов клиентов, передавая ей текущий продуктовый контекст
Репозиторий с кодом.
